@progress/kendo-angular-filter
Version:
Kendo UI Angular Filter
330 lines (325 loc) • 19 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 { ChangeDetectorRef, Component, ElementRef, forwardRef, Input, isDevMode, Renderer2 } from '@angular/core';
import { LocalizationService } from '@progress/kendo-angular-l10n';
import { xIcon } from '@progress/kendo-svg-icons';
import { BaseFilterRowComponent } from './base-filter-row.component';
import { FilterErrorMessages } from './error-messages';
import { FilterService } from './filter.service';
import { NavigationService } from './navigation.service';
import { defaultDateOperators, defaultNumericOperators, defaultOperators, defaultStringOperators, FilterItem, getKeyByValue, isFilterEditor, localizeOperators, nullOperators } from './util';
import { ButtonComponent } from '@progress/kendo-angular-buttons';
import { FilterDateEditorComponent } from './editors/date-editor.component';
import { FilterBooleanEditorComponent } from './editors/boolean-editor.component';
import { FilterNumericEditorComponent } from './editors/numeric-editor.component';
import { FilterTextEditorComponent } from './editors/text-editor.component';
import { FilterExpressionOperatorsComponent } from './filter-expression-operators.component';
import { NgIf, NgSwitch, NgSwitchCase } from '@angular/common';
import { AriaLabelValueDirective } from './aria-label.directive';
import { DropDownListComponent } from '@progress/kendo-angular-dropdowns';
import { TemplateContextDirective } from '@progress/kendo-angular-common';
import * as i0 from "@angular/core";
import * as i1 from "./filter.service";
import * as i2 from "./navigation.service";
import * as i3 from "@progress/kendo-angular-l10n";
/**
* @hidden
*/
export class FilterExpressionComponent extends BaseFilterRowComponent {
filterService;
cdr;
/**
* @hidden
*/
xIcon = xIcon;
static ngAcceptInputType_currentItem;
currentItem;
operators = [];
isBoolean = false;
editorType;
isEditorDisabled = false;
editorTemplate;
editorFormat;
get currentFilterExpression() {
return this.getFilterExpressionByField(this.currentItem.field);
}
get numericEditorFormat() {
const isSupportedFormat = typeof this.editorFormat !== 'string' && !this.isNumberFormat(this.editorFormat);
if (this.editorFormat && isSupportedFormat) {
console.warn(FilterErrorMessages.improperNumericEditorValue(this.currentFilterExpression.title));
}
return this.editorFormat;
}
get dateEditorFormat() {
const isSupportedFormat = typeof this.editorFormat !== 'string' && !this.isDateFormat(this.editorFormat);
if (this.editorFormat && isSupportedFormat) {
console.warn(FilterErrorMessages.improperDateEditorValue(this.currentFilterExpression.title));
}
return this.editorFormat;
}
isNumberFormat(obj) {
if (isDevMode() && obj &&
(obj['currency'] ||
obj['currencyDisplay'] ||
obj['maximumFractionDigits'] ||
obj['minimumIntegerDigits'] ||
obj['style'] ||
obj['useGrouping'])) {
return true;
}
else {
return false;
}
}
isDateFormat(obj) {
if (isDevMode() && obj && obj['displayFormat'] && obj['inputFormat']) {
return true;
}
else {
return false;
}
}
localizationSubscription;
constructor(filterService, cdr, element, navigationService, localization, renderer) {
super(element, navigationService, localization, renderer);
this.filterService = filterService;
this.cdr = cdr;
}
ngOnInit() {
this.isEditorDisabled = nullOperators.indexOf(this.currentItem.operator) >= 0;
const foundFilter = this.getFilterExpressionByField(this.currentItem.field);
if (this.currentItem.field) {
this.setOperators(foundFilter);
}
if (foundFilter?.editorFormat) {
this.editorFormat = foundFilter.editorFormat;
}
const defaultFilter = this.getFilterExpressionByField(this.filterService.filters[0].field);
if (!this.currentItem.field) {
this.currentItem.field = this.filterService.filters[0].field;
if (defaultFilter.editorFormat) {
this.editorFormat = defaultFilter.editorFormat;
}
this.setOperators(defaultFilter);
}
this.setEditorTemplate();
this.localizationSubscription = this.localization.changes.subscribe(() => {
this.setOperators(foundFilter || defaultFilter);
this.cdr.detectChanges();
});
}
ngOnDestroy() {
if (this.localizationSubscription) {
this.localizationSubscription.unsubscribe();
}
}
normalizeOperators(filterEditor, operators) {
const result = [];
for (let j = 0; j < operators.length; j++) {
if (isFilterEditor(filterEditor)) {
result.push({
value: operators[j],
text: this.localization.get(getKeyByValue(defaultOperators[filterEditor], operators[j]))
});
}
}
return result;
}
getFilterExpressionByField(name) {
const foundFilter = this.filterService.filters.find(filter => filter.field === name);
if (foundFilter) {
return foundFilter;
}
return null;
}
filterValueChange(value) {
this.navigationService.currentToolbarItemIndex = this.itemNumber;
this.navigationService.currentToolbarItemChildrenIndex = 0;
this.currentItem.value = null;
this.currentItem.field = value;
this.setEditorTemplate();
const foundFilter = this.getFilterExpressionByField(this.currentItem.field);
this.setOperators(foundFilter);
this.editorFormat = foundFilter.editorFormat;
this.valueChange.emit();
}
getDefaultOperators(operatorsType) {
switch (operatorsType) {
case 'string':
return localizeOperators(defaultStringOperators)(this.localization);
case 'number':
return localizeOperators(defaultNumericOperators)(this.localization);
case 'date':
return localizeOperators(defaultDateOperators)(this.localization);
default:
break;
}
}
getEditorType() {
const filterExpression = this.filterService.filters.find((filterExpression) => filterExpression.field === this.currentItem.field);
return filterExpression?.editor;
}
getFilters() {
return this.filterService.filters || [];
}
removeFilterExpression() {
this.filterService.remove(this.currentItem, this.index);
this.valueChange.emit(true);
}
setOperators(filter) {
this.isBoolean = filter.editor === 'boolean';
if (this.isBoolean) {
return;
}
if (filter.operators) {
const localizedOperators = this.normalizeOperators(filter.editor, filter.operators);
this.operators = localizedOperators;
}
else {
this.operators = this.getDefaultOperators(filter.editor);
}
if (!this.currentItem.operator) {
this.currentItem.operator = this.operators[0].value;
}
const operatorDoesNotExist = !this.operators.find(operator => operator.value === this.currentItem.operator);
if (this.currentItem.operator && operatorDoesNotExist) {
this.currentItem.operator = this.operators[0].value;
}
}
onOperatorChange(value) {
this.navigationService.currentToolbarItemIndex = this.itemNumber;
this.navigationService.currentToolbarItemChildrenIndex = 1;
this.valueChange.emit();
if (nullOperators.includes(value)) {
this.currentItem.value = null;
this.isEditorDisabled = true;
}
else {
this.isEditorDisabled = false;
}
}
setEditorTemplate() {
const filterExpression = this.filterService.filters.find((filter) => filter.field === this.currentItem.field);
this.editorTemplate = filterExpression?.editorTemplate;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FilterExpressionComponent, deps: [{ token: i1.FilterService }, { token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: i2.NavigationService }, { token: i3.LocalizationService }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: FilterExpressionComponent, isStandalone: true, selector: "kendo-filter-expression", inputs: { currentItem: "currentItem" }, providers: [{
provide: FilterItem,
useExisting: forwardRef(() => FilterExpressionComponent)
}], usesInheritance: true, ngImport: i0, template: `
<div class="k-filter-toolbar">
<div class="k-toolbar k-toolbar-md k-toolbar-solid" role="toolbar" [attr.aria-label]="messageFor('filterToolbarAriaLabel')" (mousedown)="onMouseDown($event)">
<div class="k-filter-field k-toolbar-item" >
<kendo-dropdownlist
[tabindex]="-1"
[kendoAriaLabelValue]="messageFor('filterFieldAriaLabel')"
[title]="messageFor('filterExpressionFilters')"
[data]="getFilters()"
textField="title"
valueField="field"
[value]="currentItem.field"
[valuePrimitive]="true"
(valueChange)="filterValueChange($event)">
</kendo-dropdownlist>
</div>
<div *ngIf="!isBoolean" class="k-filter-operator k-toolbar-item" >
<kendo-filter-expression-operators
[currentItem]="currentItem"
[operators]="operators"
[editorType]="getEditorType()"
(valueChange)="onOperatorChange($event);">
</kendo-filter-expression-operators>
</div>
<div class="k-filter-value k-toolbar-item">
<ng-container *ngIf="!editorTemplate" [ngSwitch]="getEditorType()">
<kendo-filter-text-editor *ngSwitchCase="'string'" [currentItem]="currentItem" [isDisabled]="isEditorDisabled" (valueChange)="valueChange.emit()"></kendo-filter-text-editor>
<kendo-filter-numeric-editor *ngSwitchCase="'number'" [currentItem]="currentItem" [isDisabled]="isEditorDisabled" [format]="numericEditorFormat" (valueChange)="valueChange.emit()"></kendo-filter-numeric-editor>
<kendo-filter-boolean-editor *ngSwitchCase="'boolean'" [currentItem]="currentItem" (valueChange)="valueChange.emit()"></kendo-filter-boolean-editor>
<kendo-filter-date-editor *ngSwitchCase="'date'" [currentItem]="currentItem" [isDisabled]="isEditorDisabled" [format]="dateEditorFormat" (valueChange)="valueChange.emit()"></kendo-filter-date-editor>
</ng-container>
<ng-container *ngIf="editorTemplate">
<ng-template
[templateContext]="{templateRef: editorTemplate, $implicit: currentItem}">
</ng-template>
</ng-container>
</div>
<button
kendoButton
class="k-toolbar-button"
tabindex="-1"
icon="x"
[svgIcon]="xIcon"
fillMode="flat"
[title]="messageFor('remove')"
(click)="removeFilterExpression()">
</button>
</div>
</div>
`, isInline: true, dependencies: [{ kind: "component", type: DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "directive", type: AriaLabelValueDirective, selector: "[kendoAriaLabelValue]", inputs: ["kendoAriaLabelValue"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: FilterExpressionOperatorsComponent, selector: "kendo-filter-expression-operators", inputs: ["currentItem", "editorType", "operators"], outputs: ["valueChange"] }, { kind: "directive", type: NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "component", type: FilterTextEditorComponent, selector: "kendo-filter-text-editor", inputs: ["currentItem", "isDisabled"], outputs: ["valueChange"] }, { kind: "component", type: FilterNumericEditorComponent, selector: "kendo-filter-numeric-editor", inputs: ["currentItem", "isDisabled", "format"], outputs: ["valueChange"] }, { kind: "component", type: FilterBooleanEditorComponent, selector: "kendo-filter-boolean-editor", inputs: ["currentItem"], outputs: ["valueChange"] }, { kind: "component", type: FilterDateEditorComponent, selector: "kendo-filter-date-editor", inputs: ["currentItem", "isDisabled", "format"], outputs: ["valueChange"] }, { kind: "directive", type: TemplateContextDirective, selector: "[templateContext]", inputs: ["templateContext"] }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FilterExpressionComponent, decorators: [{
type: Component,
args: [{
providers: [{
provide: FilterItem,
useExisting: forwardRef(() => FilterExpressionComponent)
}],
selector: 'kendo-filter-expression',
template: `
<div class="k-filter-toolbar">
<div class="k-toolbar k-toolbar-md k-toolbar-solid" role="toolbar" [attr.aria-label]="messageFor('filterToolbarAriaLabel')" (mousedown)="onMouseDown($event)">
<div class="k-filter-field k-toolbar-item" >
<kendo-dropdownlist
[tabindex]="-1"
[kendoAriaLabelValue]="messageFor('filterFieldAriaLabel')"
[title]="messageFor('filterExpressionFilters')"
[data]="getFilters()"
textField="title"
valueField="field"
[value]="currentItem.field"
[valuePrimitive]="true"
(valueChange)="filterValueChange($event)">
</kendo-dropdownlist>
</div>
<div *ngIf="!isBoolean" class="k-filter-operator k-toolbar-item" >
<kendo-filter-expression-operators
[currentItem]="currentItem"
[operators]="operators"
[editorType]="getEditorType()"
(valueChange)="onOperatorChange($event);">
</kendo-filter-expression-operators>
</div>
<div class="k-filter-value k-toolbar-item">
<ng-container *ngIf="!editorTemplate" [ngSwitch]="getEditorType()">
<kendo-filter-text-editor *ngSwitchCase="'string'" [currentItem]="currentItem" [isDisabled]="isEditorDisabled" (valueChange)="valueChange.emit()"></kendo-filter-text-editor>
<kendo-filter-numeric-editor *ngSwitchCase="'number'" [currentItem]="currentItem" [isDisabled]="isEditorDisabled" [format]="numericEditorFormat" (valueChange)="valueChange.emit()"></kendo-filter-numeric-editor>
<kendo-filter-boolean-editor *ngSwitchCase="'boolean'" [currentItem]="currentItem" (valueChange)="valueChange.emit()"></kendo-filter-boolean-editor>
<kendo-filter-date-editor *ngSwitchCase="'date'" [currentItem]="currentItem" [isDisabled]="isEditorDisabled" [format]="dateEditorFormat" (valueChange)="valueChange.emit()"></kendo-filter-date-editor>
</ng-container>
<ng-container *ngIf="editorTemplate">
<ng-template
[templateContext]="{templateRef: editorTemplate, $implicit: currentItem}">
</ng-template>
</ng-container>
</div>
<button
kendoButton
class="k-toolbar-button"
tabindex="-1"
icon="x"
[svgIcon]="xIcon"
fillMode="flat"
[title]="messageFor('remove')"
(click)="removeFilterExpression()">
</button>
</div>
</div>
`,
standalone: true,
imports: [DropDownListComponent, AriaLabelValueDirective, NgIf, FilterExpressionOperatorsComponent, NgSwitch, NgSwitchCase, FilterTextEditorComponent, FilterNumericEditorComponent, FilterBooleanEditorComponent, FilterDateEditorComponent, TemplateContextDirective, ButtonComponent]
}]
}], ctorParameters: function () { return [{ type: i1.FilterService }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i2.NavigationService }, { type: i3.LocalizationService }, { type: i0.Renderer2 }]; }, propDecorators: { currentItem: [{
type: Input
}] } });