UNPKG

@progress/kendo-angular-grid

Version:

Kendo UI Grid for Angular - high performance data grid with paging, filtering, virtualization, CRUD, and more.

325 lines (324 loc) 14.4 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { Component, Input, EventEmitter, Output, ViewChild } from '@angular/core'; import { filterBy } from '@progress/kendo-data-query'; import { CheckBoxComponent, TextBoxComponent, TextBoxPrefixTemplateDirective } from '@progress/kendo-angular-inputs'; import { IconWrapperComponent } from '@progress/kendo-angular-icons'; import { searchIcon } from '@progress/kendo-svg-icons'; import { ContextService } from '../common/provider.service'; import { LocalDataChangesService } from '../editing/local-data-changes.service'; import { FormatPipe } from '../rendering/common/format.pipe'; import { isPresent, replaceMessagePlaceholder } from '@progress/kendo-angular-common'; import { FilterInputDirective } from './filter-input.directive'; import * as i0 from "@angular/core"; import * as i1 from "../common/provider.service"; import * as i2 from "../editing/local-data-changes.service"; /** * @hidden */ export class MultiCheckboxFilterComponent { ctx; dataChangesService; column; filterChange = new EventEmitter(); filterInput; constructor(ctx, dataChangesService) { this.ctx = ctx; this.dataChangesService = dataChangesService; } listData = []; searchIcon = searchIcon; showSelectAll = true; currentlySelected = new Set(); isSearched = false; currentFilter; filterChangeSub; baseListData = []; ngOnInit() { this.initializeData(); this.currentFilter = this.ctx.grid?.filter ?? this.emptyFilter(); this.updateSelectionFromFilter(); if (this.currentFilter) { this.filterChange.emit(this.currentFilter); } } ngAfterViewInit() { if (this.filterInput) { this.filterChangeSub = this.filterInput.change.subscribe(this.onSearch.bind(this)); } } ngOnDestroy() { if (this.filterChangeSub) { this.filterChangeSub.unsubscribe(); this.filterChangeSub = null; } } isItemSelected(item) { return this.currentlySelected.has(item); } onSearch(value) { const searchValue = value; this.isSearched = searchValue.length > 0; this.showSelectAll = !this.isSearched; if (!searchValue) { this.listData = [...this.baseListData]; } else { this.listData = filterBy(this.baseListData, { operator: 'contains', value: searchValue }); } } handleCheckBoxChange(checkedState, value, selectAllChecked) { const field = this.column?.field; if (!field) { return; } if (!this.currentFilter) { this.currentFilter = this.emptyFilter(); } const newFilter = this.currentFilter; const filters = [...newFilter.filters]; const compositeIndex = this.getCompositeFilterIndex(newFilter); let fieldFilters = []; if (compositeIndex !== -1 && filters[compositeIndex].filters && !selectAllChecked) { fieldFilters = [...filters[compositeIndex].filters]; } if (checkedState && selectAllChecked) { this.listData.forEach(item => { if (!fieldFilters.some(f => f.value === item)) { fieldFilters.push({ field, operator: 'eq', value: item }); } }); } else if (!checkedState && selectAllChecked) { fieldFilters = []; } else if (checkedState) { if (!fieldFilters.some(f => f.value === value)) { fieldFilters.push({ field, operator: 'eq', value }); } } else { const idx = fieldFilters.findIndex(f => f.value === value); if (idx > -1) { fieldFilters.splice(idx, 1); } } if (fieldFilters.length === 0) { if (compositeIndex !== -1) { filters.splice(compositeIndex, 1); } } else { const block = { logic: 'or', filters: fieldFilters }; if (compositeIndex !== -1) { filters[compositeIndex] = block; } else { filters.push(block); } } newFilter.logic = 'and'; newFilter.filters = filters; this.currentFilter = newFilter; this.updateSelectionFromFilter(); this.filterChange.emit(this.currentFilter); } get filteredGridData() { return filterBy(this.gridData, this.ctx.grid?.filter); } get selectAllChecked() { if (!this.listData || this.listData.length === 0) { return false; } const total = this.listData.length; const selectedInView = this.listData.filter(i => this.currentlySelected.has(i)).length; if (selectedInView === 0) { return false; } if (selectedInView === total) { return true; } return 'indeterminate'; } get gridData() { let data = []; const isLocalData = isPresent(this.ctx?.dataBindingDirective); if (isPresent(this.normalizedFilterVariant.data)) { data = this.normalizedFilterVariant.data; } else if (isLocalData) { data = this.dataChangesService.data; } else { data = this.ctx.grid?.flatData; } return data || []; } get normalizedFilterVariant() { const defaultMultiCheckboxSettings = { variant: 'multiCheckbox', search: true }; if (typeof this.column?.filterVariant === 'string') { return { variant: this.column.filterVariant, search: true }; } return Object.assign(defaultMultiCheckboxSettings, this.column?.filterVariant); } get selectedItemsMessage() { const localizationMsg = this.messageFor('multiCheckboxFilterSelectedItemsCount') || ''; return replaceMessagePlaceholder(localizationMsg, 'selectedItemsCount', this.currentlySelected.size.toString()); } messageFor(key) { return this.ctx.localization.get(key); } getUniqueDateValues(data) { return data.reduce((acc, current) => { const value = current[this.column.field]; if (value instanceof Date && acc.findIndex(d => d.getTime() === value.getTime()) === -1) { acc.push(value); } return acc; }, []); } initializeData() { if (!this.column) { this.baseListData = []; return; } const isDate = this.column.filter === 'date'; if (isDate) { const dates = this.getUniqueDateValues(this.gridData); const sortedDates = [...dates].sort((a, b) => a - b); this.baseListData = sortedDates; } else { const field = this.column.field; const mapped = this.gridData.map(item => item?.[field]); this.baseListData = [...new Set(mapped)].sort((a, b) => { if (a > b) { return 1; } if (a < b) { return -1; } return 0; }); } this.listData = [...this.baseListData]; } emptyFilter() { return { filters: [], logic: 'and' }; } getCompositeFilterIndex(filter) { const field = this.column?.field; return filter.filters.findIndex((f) => f.filters?.length && f.filters[0].field === field); } updateSelectionFromFilter() { this.currentlySelected.clear(); if (!this.currentFilter || !this.column?.field) { return; } const idx = this.getCompositeFilterIndex(this.currentFilter); if (idx === -1) { return; } const block = this.currentFilter.filters[idx]; if (block && Array.isArray(block.filters)) { block.filters.forEach((f) => { if (f.field === this.column.field && f.operator === 'eq') { this.currentlySelected.add(f.value); } }); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MultiCheckboxFilterComponent, deps: [{ token: i1.ContextService }, { token: i2.LocalDataChangesService }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: MultiCheckboxFilterComponent, isStandalone: true, selector: "kendo-grid-multicheckbox-filter", inputs: { column: "column" }, outputs: { filterChange: "filterChange" }, viewQueries: [{ propertyName: "filterInput", first: true, predicate: FilterInputDirective, descendants: true }], ngImport: i0, template: ` @if (normalizedFilterVariant.search) { <kendo-textbox kendoFilterInput class="k-searchbox" [placeholder]="messageFor('multiCheckboxFilterSearchPlaceholder')"> <ng-template kendoTextBoxPrefixTemplate> <kendo-icon-wrapper innerCssClass="k-input-icon" name="search" [svgIcon]="searchIcon"></kendo-icon-wrapper> </ng-template> </kendo-textbox> } <ul class="k-reset k-multicheck-wrap"> @if (showSelectAll) { <li class="k-item k-check-all-wrap"> <label class="k-label k-checkbox-label" role="option"> <kendo-checkbox [checkedState]="selectAllChecked" (checkedStateChange)="handleCheckBoxChange($event, null, true)"> </kendo-checkbox> <span>{{ messageFor('multiCheckboxFilterSelectAllLabel') }}</span> </label> </li> } @for (item of listData; track item) { <li class="k-item"> <label class="k-label k-checkbox-label" role="option"> <kendo-checkbox [checkedState]="isItemSelected(item)" (checkedStateChange)="handleCheckBoxChange($event, item)"> </kendo-checkbox> <span>{{ item | format: column.format }}</span> </label> </li> } </ul> <div class="k-filter-selected-items">{{selectedItemsMessage}}</div> `, isInline: true, dependencies: [{ kind: "component", type: CheckBoxComponent, selector: "kendo-checkbox", inputs: ["checkedState", "rounded"], outputs: ["checkedStateChange"], exportAs: ["kendoCheckBox"] }, { kind: "component", type: TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "directive", type: TextBoxPrefixTemplateDirective, selector: "[kendoTextBoxPrefixTemplate]", inputs: ["showSeparator"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "pipe", type: FormatPipe, name: "format" }, { kind: "directive", type: FilterInputDirective, selector: "[kendoFilterInput]", inputs: ["filterDelay", "columnLabel", "value"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MultiCheckboxFilterComponent, decorators: [{ type: Component, args: [{ selector: 'kendo-grid-multicheckbox-filter', template: ` @if (normalizedFilterVariant.search) { <kendo-textbox kendoFilterInput class="k-searchbox" [placeholder]="messageFor('multiCheckboxFilterSearchPlaceholder')"> <ng-template kendoTextBoxPrefixTemplate> <kendo-icon-wrapper innerCssClass="k-input-icon" name="search" [svgIcon]="searchIcon"></kendo-icon-wrapper> </ng-template> </kendo-textbox> } <ul class="k-reset k-multicheck-wrap"> @if (showSelectAll) { <li class="k-item k-check-all-wrap"> <label class="k-label k-checkbox-label" role="option"> <kendo-checkbox [checkedState]="selectAllChecked" (checkedStateChange)="handleCheckBoxChange($event, null, true)"> </kendo-checkbox> <span>{{ messageFor('multiCheckboxFilterSelectAllLabel') }}</span> </label> </li> } @for (item of listData; track item) { <li class="k-item"> <label class="k-label k-checkbox-label" role="option"> <kendo-checkbox [checkedState]="isItemSelected(item)" (checkedStateChange)="handleCheckBoxChange($event, item)"> </kendo-checkbox> <span>{{ item | format: column.format }}</span> </label> </li> } </ul> <div class="k-filter-selected-items">{{selectedItemsMessage}}</div> `, standalone: true, imports: [CheckBoxComponent, TextBoxComponent, TextBoxPrefixTemplateDirective, IconWrapperComponent, FormatPipe, FilterInputDirective] }] }], ctorParameters: () => [{ type: i1.ContextService }, { type: i2.LocalDataChangesService }], propDecorators: { column: [{ type: Input }], filterChange: [{ type: Output }], filterInput: [{ type: ViewChild, args: [FilterInputDirective] }] } });