Use computed props to determine select all state, handle radio value

This commit is contained in:
guerler
2023-05-03 14:23:28 -04:00
committed by Assunta DeSanto
parent ca7cad1d89
commit 7a82a7873e
5 changed files with 72 additions and 64 deletions
@@ -1,11 +1,11 @@
<script setup lang="ts">
import { computed, ref, watch, type ComputedRef } from "vue";
import { computed, type ComputedRef } from "vue";
import FormDrilldownList from "./FormDrilldownList.vue";
import type { Option } from "./types.js";
import { getAllValues, type Option, type Value } from "./utilities";
export interface FormDrilldownProps {
id: string;
value?: string | string[];
value?: Value;
options: Array<Option>;
multiple: boolean;
}
@@ -16,55 +16,44 @@ const props = withDefaults(defineProps<FormDrilldownProps>(), {
});
const emit = defineEmits<{
(e: "input", value: string | string[] | null): void;
(e: "input", value: Value): void;
}>();
const selectAllIndeterminate = ref(false);
const selectAll = ref(false);
const hasOptions = computed(() => {
return props.options.length > 0;
});
// Determine all available values
const allValues: ComputedRef<string[]> = computed(() => {
let options = null;
const values: string[] = [];
const stack: Array<Array<Option>> = [props.options];
while ((options = stack.pop())) {
options.forEach((option) => {
if (option.value) {
values.push(option.value);
}
if (option.options.length > 0) {
stack.push(option.options);
}
});
}
return values;
return getAllValues(props.options);
});
// Determine current value and set select all state
const currentValue = computed({
get: (): string[] => {
if (props.value === null || props.value === "") {
return [];
} else if (Array.isArray(props.value)) {
return props.value;
} else {
return [props.value];
}
},
set: (newValue: string[]): void => {
emit("input", newValue);
},
// Determine current value
const currentValue: ComputedRef<string[]> = computed(() => {
if (props.value === null || props.value === "") {
return [];
} else if (Array.isArray(props.value)) {
return props.value;
} else {
return [props.value];
}
});
// Determine if select all is checked
const selectAllChecked: ComputedRef<boolean> = computed(() => {
return allValues.value.length === currentValue.value.length;
});
// Determine if select all state undetermined
const selectAllIndeterminate: ComputedRef<boolean> = computed(() => {
return ![0, allValues.value.length].includes(currentValue.value.length);
});
// Handle click on individual check/radio element
function handleClick(value: string): void {
if (props.multiple) {
const newValue = currentValue.value.slice();
const index = newValue.indexOf(value);
const newValue: string[] = currentValue.value.slice();
const index: number = newValue.indexOf(value);
if (index !== -1) {
newValue.splice(index, 1);
} else {
@@ -84,24 +73,14 @@ function handleClick(value: string): void {
function onSelectAll(selected: boolean): void {
emit("input", selected ? allValues.value : null);
}
// Watch value and adjust select all state accordingly
watch(
() => props.value,
() => {
const length = currentValue.value.length;
selectAll.value = allValues.value.length === length;
selectAllIndeterminate.value = ![0, allValues.value.length].includes(length);
}
);
</script>
<template>
<div v-if="hasOptions">
<b-form-checkbox
v-if="props.multiple"
v-model="selectAll"
v-localize
:checked="selectAllChecked"
:indeterminate="selectAllIndeterminate"
class="d-inline select-all-checkbox"
@change="onSelectAll">
@@ -1,6 +1,6 @@
<script setup lang="ts">
import FormDrilldownOption from "./FormDrilldownOption.vue";
import type { Option } from "./types.js";
import type { Option } from "./utilities";
export interface Props {
currentValue: string[];
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import { computed, ref, type ComputedRef } from "vue";
import FormDrilldownList from "./FormDrilldownList.vue";
import type { Option } from "./types.js";
import type { Option } from "./utilities";
export interface Props {
currentValue: string[];
@@ -14,16 +14,26 @@ const props = defineProps<Props>();
const showChildren = ref(false);
const hasOptions = computed(() => {
const hasOptions: ComputedRef<boolean> = computed(() => {
return props.option.options.length > 0;
});
const isChecked = computed(() => {
// indicates if the value has been selected or not (required for checkbox display)
const isChecked: ComputedRef<boolean> = computed(() => {
const optionValue = props.option.value;
return props.currentValue.indexOf(optionValue) !== -1;
return props.currentValue.includes(optionValue);
});
function toggleChildren() {
// provides a single value, which is either null or a non-empty string (required for radio display)
const singleValue = computed({
get(): string | null {
const value: string[] = props.currentValue;
return value.length > 0 ? value[0] || null : null;
},
set() {},
});
function toggleChildren(): void {
showChildren.value = !showChildren.value;
}
</script>
@@ -36,16 +46,16 @@ function toggleChildren() {
</span>
<b-form-checkbox
v-if="multiple"
class="d-inline drilldown-option"
class="drilldown-option d-inline"
:checked="isChecked"
@change="handleClick(option.value)">
{{ option.name }}
</b-form-checkbox>
<b-form-radio
v-else
class="d-inline"
:selected="isChecked"
name="selectedOption"
v-model="singleValue"
class="drilldown-option d-inline"
:value="option.value"
@change="handleClick(option.value)">
{{ option.name }}
</b-form-radio>
@@ -1,5 +0,0 @@
export interface Option {
name: string;
value: string;
options: Array<Option>;
}
@@ -0,0 +1,24 @@
export interface Option {
name: string;
value: string;
options: Array<Option>;
}
export type Value = string[] | string | null;
export function getAllValues(headOptions: Array<Option>): string[] {
let options = null;
const values: string[] = [];
const stack: Array<Array<Option>> = [headOptions];
while ((options = stack.pop())) {
options.forEach((option) => {
if (option.value) {
values.push(option.value);
}
if (option.options.length > 0) {
stack.push(option.options);
}
});
}
return values;
}