@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
JavaScript
/**-----------------------------------------------------------------------------------------
* 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: `
(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">
(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>
}
(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: `
(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">
(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>
}
(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]
}] } });