mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merged PR 1902: feat(shared-filter-inputs-checkbox-input): add bulk toggle functionality for...
feat(shared-filter-inputs-checkbox-input): add bulk toggle functionality for checkbox options Replace individual option iteration with new toggleAllCheckboxOptions method in FilterService. This improves performance and provides cleaner API for selecting/deselecting all checkbox options at once. Updates component logic to use the new bulk operation and fixes test expectations accordingly. Ref: #5231
This commit is contained in:
committed by
Andreas Schickinger
parent
1e84223076
commit
ad00899b6e
@@ -163,48 +163,19 @@ export class FilterService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selection state of a specific checkbox option within a hierarchical structure.
|
||||
* Sets the selection state of a specific checkbox option within a checkbox input.
|
||||
*
|
||||
* This method handles the selection/deselection of checkbox options with automatic
|
||||
* parent-child relationship management and maximum options enforcement:
|
||||
* - When selecting a parent option, all child options are implicitly selected
|
||||
* - When deselecting a parent option, all child options are also deselected
|
||||
* - Child options can be individually selected/deselected
|
||||
* - If `maxOptions` is configured and selection would exceed the limit, oldest selections are removed (FIFO)
|
||||
* This method allows you to select or deselect a specific checkbox option by its
|
||||
* group, groupKey, and option keys. It handles the selection logic, including
|
||||
* enforcing maximum options limits and managing parent-child relationships.
|
||||
*
|
||||
* @param checkboxOption - The checkbox option object containing the hierarchical path and metadata
|
||||
* @param selected - Whether to select (true) or deselect (false) the option
|
||||
* @param checkboxOption - The checkbox option to set the selection for
|
||||
* @param selected - If true, selects the option; if false, deselects it
|
||||
* @param options - Optional parameters
|
||||
* @param options.commit - If true, commits the changes immediately
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Select a specific option
|
||||
* filterService.setInputCheckboxOptionSelected(
|
||||
* checkboxOption,
|
||||
* true
|
||||
* );
|
||||
*
|
||||
* // Deselect a parent option (also deselects all children)
|
||||
* filterService.setInputCheckboxOptionSelected(
|
||||
* parentCheckboxOption,
|
||||
* false,
|
||||
* { commit: true }
|
||||
* );
|
||||
*
|
||||
* // When maxOptions is set to 3 and selecting would exceed the limit:
|
||||
* // If currently selected: ['option1', 'option2', 'option3']
|
||||
* // Selecting 'option4' results in: ['option2', 'option3', 'option4']
|
||||
* // (oldest selection 'option1' is automatically removed)
|
||||
* ```
|
||||
*
|
||||
* @remarks
|
||||
* The maximum options enforcement only applies when selecting options. Deselection is not affected by the limit.
|
||||
* The FIFO (First In, First Out) strategy ensures the most recently selected options are preserved when the limit is exceeded.
|
||||
* @param options.commit - If true, commits the changes immediately after setting the selection
|
||||
*/
|
||||
setInputCheckboxOptionSelected(
|
||||
checkboxOption: CheckboxFilterInputOption,
|
||||
// [group, groupKey, ...optionKeys]: string[],
|
||||
selected: boolean,
|
||||
options?: { commit: boolean },
|
||||
): void {
|
||||
@@ -272,28 +243,88 @@ export class FilterService {
|
||||
}
|
||||
}
|
||||
|
||||
// getInputCheckboxOptionSelected([group, groupKey, ...optionKeys]: string[]):
|
||||
// | boolean
|
||||
// | undefined {
|
||||
// const input = this.#state.inputs().find((input) => {
|
||||
// return input.group === group && input.key === groupKey;
|
||||
// });
|
||||
/**
|
||||
* Toggles the selection state of all checkbox options within a specific group and key.
|
||||
*
|
||||
* This method allows you to select or deselect all options in a checkbox input by
|
||||
* providing the group and groupKey. It updates the selected state of the input
|
||||
* accordingly, either selecting all options or clearing the selection.
|
||||
*
|
||||
* @param group - The group name of the checkbox input
|
||||
* @param groupKey - The key of the checkbox input within the group
|
||||
* @param checkboxOptions - The list of checkbox options to toggle
|
||||
* @param selected - If true, selects all options; if false, clears all selections
|
||||
* @param options - Optional parameters
|
||||
* @param options.commit - If true, commits the changes immediately after toggling
|
||||
*/
|
||||
toggleAllCheckboxOptions({
|
||||
group,
|
||||
groupKey,
|
||||
checkboxOptions,
|
||||
selected,
|
||||
options,
|
||||
}: {
|
||||
group: string;
|
||||
groupKey: string;
|
||||
checkboxOptions: CheckboxFilterInputOption[];
|
||||
selected: boolean;
|
||||
options?: { commit: boolean };
|
||||
}): void {
|
||||
// Get the group and groupKey from the first option
|
||||
if (checkboxOptions.length === 0) {
|
||||
this.#logger.warn('No checkbox options provided');
|
||||
return;
|
||||
}
|
||||
|
||||
// if (!input) {
|
||||
// this.#logger.warn(`Input not found`, () => ({
|
||||
// inputGroup: group,
|
||||
// inputKey: groupKey,
|
||||
// }));
|
||||
// return undefined;
|
||||
// }
|
||||
const inputs = this.#state.inputs().map((input) => {
|
||||
const target = input.group === group && input.key === groupKey;
|
||||
|
||||
// if (input.type !== InputType.Checkbox) {
|
||||
// this.logUnsupportedInputType(input, 'getInputCheckboxValue');
|
||||
// return undefined;
|
||||
// }
|
||||
if (!target) {
|
||||
return input;
|
||||
}
|
||||
|
||||
// return input.selected.includes(optionKeys.join('.'));
|
||||
// }
|
||||
if (input.type !== InputType.Checkbox) {
|
||||
this.logUnsupportedInputType(
|
||||
input,
|
||||
'setAllInputCheckboxOptionsSelected',
|
||||
);
|
||||
return input;
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
// Get all option keys recursively from all provided options
|
||||
const getAllOptionKeys = (
|
||||
options: CheckboxFilterInputOption[],
|
||||
): string[] =>
|
||||
options.flatMap((option) => {
|
||||
const isParent =
|
||||
Array.isArray(option.values) && option.values.length > 0;
|
||||
|
||||
if (isParent) {
|
||||
// If the option has children, only include child keys
|
||||
return getAllOptionKeys(option.values!);
|
||||
} else {
|
||||
// If no children, include the option itself
|
||||
return [checkboxOptionKeysHelper(option).join('.')];
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...input,
|
||||
selected: getAllOptionKeys(checkboxOptions),
|
||||
};
|
||||
} else {
|
||||
// Unselect all options
|
||||
return { ...input, selected: [] };
|
||||
}
|
||||
});
|
||||
|
||||
patchState(this.#state, { inputs });
|
||||
|
||||
if (options?.commit) {
|
||||
this.commit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the date range values for an input with the specified key.
|
||||
|
||||
@@ -38,6 +38,7 @@ describe('CheckboxInputComponent', () => {
|
||||
const mockFilterService = {
|
||||
inputs: mockInputsSignal,
|
||||
setInputCheckboxOptionSelected: jest.fn(),
|
||||
toggleAllCheckboxOptions: jest.fn(),
|
||||
};
|
||||
|
||||
const createComponent = createComponentFactory({
|
||||
@@ -64,7 +65,7 @@ describe('CheckboxInputComponent', () => {
|
||||
label: 'label',
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
spectator = createComponent({ props: { inputKey: 'key' } });
|
||||
filterService = spectator.inject(FilterService);
|
||||
jest.clearAllMocks();
|
||||
@@ -81,12 +82,17 @@ describe('CheckboxInputComponent', () => {
|
||||
});
|
||||
|
||||
it('should call setInputCheckboxOptionSelected for each option on toggleSelection', () => {
|
||||
const group = 'group';
|
||||
const key = 'key';
|
||||
const options = [option];
|
||||
spectator.detectChanges();
|
||||
spectator.component.toggleSelection();
|
||||
expect(filterService.setInputCheckboxOptionSelected).toHaveBeenCalledWith(
|
||||
option,
|
||||
true,
|
||||
);
|
||||
expect(filterService.toggleAllCheckboxOptions).toHaveBeenCalledWith({
|
||||
group,
|
||||
groupKey: key,
|
||||
checkboxOptions: options,
|
||||
selected: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should compute allChecked as true when all options are checked', () => {
|
||||
|
||||
@@ -100,7 +100,16 @@ export class CheckboxInputComponent {
|
||||
filterQuery = signal<string>('');
|
||||
|
||||
allChecked = computed(() => {
|
||||
return this.input().options.every(
|
||||
const input = this.input();
|
||||
const allOptions = getAllCheckboxOptionsHelper(input);
|
||||
|
||||
// Only check leaf options (options without children)
|
||||
// Parent options are automatically considered checked if all their children are checked
|
||||
const leafOptions = allOptions.filter(
|
||||
(option) => !option.values || option.values.length === 0,
|
||||
);
|
||||
|
||||
return leafOptions.every(
|
||||
(option) =>
|
||||
checkboxSelectedHelper(this.filterService.inputs(), option) ===
|
||||
'checked',
|
||||
@@ -108,10 +117,15 @@ export class CheckboxInputComponent {
|
||||
});
|
||||
|
||||
toggleSelection() {
|
||||
const filterGroup = this.input().group;
|
||||
const filterGroupKey = this.inputKey();
|
||||
const options = this.input()?.options || [];
|
||||
const allChecked = this.allChecked();
|
||||
options.forEach((option) => {
|
||||
this.filterService.setInputCheckboxOptionSelected(option, !allChecked);
|
||||
this.filterService.toggleAllCheckboxOptions({
|
||||
group: filterGroup,
|
||||
groupKey: filterGroupKey,
|
||||
checkboxOptions: options,
|
||||
selected: !allChecked,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user