UNPKG

ag-grid

Version:

Advanced Data Grid / Data Table supporting Javascript / React / AngularJS / Web Components

421 lines (346 loc) 14.2 kB
import {Component} from "../widgets/component"; import {IFilterComp, IDoesFilterPassParams, IFilterParams} from "../interfaces/iFilter"; import {QuerySelector} from "../widgets/componentAnnotations"; import {Autowired, Context} from "../context/context"; import {GridOptionsWrapper} from "../gridOptionsWrapper"; import {_} from "../utils"; import { IFloatingFilterParams, InputTextFloatingFilterComp, FloatingFilterChange, BaseFloatingFilterChange } from "./floatingFilter"; import {INumberFilterParams, ITextFilterParams} from "./textFilter"; export interface Comparator<T> { (left: T, right: T): number; } const DEFAULT_TRANSLATIONS: {[name: string]: string}= { loadingOoo:'Loading...', equals:'Equals', notEqual:'Not equal', lessThan:'Less than', greaterThan:'Greater than', inRange:'In range', lessThanOrEqual:'Less than or equals', greaterThanOrEqual:'Greater than or equals', filterOoo:'Filter...', contains:'Contains', notContains:'Not contains', startsWith: 'Starts with', endsWith: 'Ends with', searchOoo: 'Search...', selectAll: 'Select All', applyFilter: 'Apply Filter', clearFilter: 'Clear Filter' }; /** * T(ype) The type of this filter. ie in DateFilter T=Date * P(arams) The params that this filter can take * M(model getModel/setModel) The object that this filter serializes to * F Floating filter params * * Contains common logic to ALL filters.. Translation, apply and clear button * get/setModel context wiring.... */ export abstract class BaseFilter<T, P extends IFilterParams, M> extends Component implements IFilterComp { public static EQUALS = 'equals'; public static NOT_EQUAL = 'notEqual'; public static LESS_THAN = 'lessThan'; public static LESS_THAN_OR_EQUAL = 'lessThanOrEqual'; public static GREATER_THAN = 'greaterThan'; public static GREATER_THAN_OR_EQUAL = 'greaterThanOrEqual'; public static IN_RANGE = 'inRange'; public static CONTAINS = 'contains';//1; public static NOT_CONTAINS = 'notContains';//1; public static STARTS_WITH = 'startsWith';//4; public static ENDS_WITH = 'endsWith';//5; private newRowsActionKeep: boolean; filterParams: P; clearActive: boolean; applyActive: boolean; defaultFilter: string; filter: string; @QuerySelector('#applyPanel') private eButtonsPanel: HTMLElement; @QuerySelector('#applyButton') private eApplyButton: HTMLElement; @QuerySelector('#clearButton') private eClearButton: HTMLElement; @Autowired('context') public context: Context; @Autowired('gridOptionsWrapper') gridOptionsWrapper: GridOptionsWrapper; public init(params: P): void { this.filterParams = params; this.defaultFilter = this.filterParams.defaultOption; if (this.filterParams.filterOptions && !this.defaultFilter) { if (this.filterParams.filterOptions.lastIndexOf(BaseFilter.EQUALS) < 0) { this.defaultFilter = this.filterParams.filterOptions[0]; } } this.customInit(); this.filter = this.defaultFilter; this.clearActive = params.clearButton === true; //Allowing for old param property apply, even though is not advertised through the interface this.applyActive = ((params.applyButton === true) || ((<any>params).apply === true)); this.newRowsActionKeep = params.newRowsAction === 'keep'; this.setTemplate(this.generateTemplate()); _.setVisible(this.eApplyButton, this.applyActive); if (this.applyActive) { this.addDestroyableEventListener(this.eApplyButton, "click", this.filterParams.filterChangedCallback); } _.setVisible(this.eClearButton, this.clearActive); if (this.clearActive) { this.addDestroyableEventListener(this.eClearButton, "click", this.onClearButton.bind(this)); } let anyButtonVisible: boolean = this.applyActive || this.clearActive; _.setVisible(this.eButtonsPanel, anyButtonVisible); this.instantiate(this.context); this.initialiseFilterBodyUi(); this.refreshFilterBodyUi(); } public onClearButton() { this.setModel(null); this.onFilterChanged(); } public abstract customInit(): void; public abstract isFilterActive(): boolean; public abstract modelFromFloatingFilter(from: string): M; public abstract doesFilterPass(params: IDoesFilterPassParams): boolean; public abstract bodyTemplate(): string; public abstract resetState(): void; public abstract serialize(): M; public abstract parse(toParse: M): void; public abstract refreshFilterBodyUi(): void; public abstract initialiseFilterBodyUi(): void; public floatingFilter(from: string): void { if (from !== '') { let model: M = this.modelFromFloatingFilter(from); this.setModel(model); } else { this.resetState(); } this.onFilterChanged(); } public onNewRowsLoaded() { if (!this.newRowsActionKeep) { this.resetState (); } } public getModel(): M { if (this.isFilterActive()) { return this.serialize(); } else { return null; } } public getNullableModel(): M { return this.serialize(); } public setModel(model: M): void { if (model) { this.parse (model); } else { this.resetState(); } this.refreshFilterBodyUi(); } private doOnFilterChanged(applyNow: boolean = false): boolean { this.filterParams.filterModifiedCallback(); let requiresApplyAndIsApplying: boolean = this.applyActive && applyNow; let notRequiresApply: boolean = !this.applyActive; let shouldFilter: boolean = notRequiresApply || requiresApplyAndIsApplying; if (shouldFilter) { this.filterParams.filterChangedCallback(); } this.refreshFilterBodyUi(); return shouldFilter; } public onFilterChanged(): void { this.doOnFilterChanged(); } public onFloatingFilterChanged(change: FloatingFilterChange): boolean { //It has to be of the type FloatingFilterWithApplyChange if it gets here let casted: BaseFloatingFilterChange<M> = <BaseFloatingFilterChange<M>>change; this.setModel(casted ? casted.model : null); return this.doOnFilterChanged(casted ? casted.apply : false); } public generateFilterHeader(): string { return ''; } private generateTemplate(): string { let translate = this.translate.bind(this); let body: string = this.bodyTemplate(); return `<div> ${this.generateFilterHeader()} ${body} <div class="ag-filter-apply-panel" id="applyPanel"> <button type="button" id="clearButton">${translate('clearFilter')}</button> <button type="button" id="applyButton">${translate('applyFilter')}</button> </div> </div>`; } public translate(toTranslate: string): string { let translate = this.gridOptionsWrapper.getLocaleTextFunc(); return translate(toTranslate, DEFAULT_TRANSLATIONS[toTranslate]); } public getDebounceMs(filterParams: ITextFilterParams | INumberFilterParams): number { if (filterParams.applyButton && filterParams.debounceMs) { console.warn('ag-Grid: debounceMs is ignored when applyButton = true'); return 0; } return filterParams.debounceMs != null ? filterParams.debounceMs : 500; } } /** * Every filter with a dropdown where the user can specify a comparing type against the filter values */ export abstract class ComparableBaseFilter<T, P extends IFilterParams, M> extends BaseFilter<T, P, M> { @QuerySelector('#filterType') private eTypeSelector: HTMLSelectElement; public abstract getApplicableFilterTypes(): string[]; public abstract filterValues(): T | T[]; public init(params: P) { super.init(params); this.addDestroyableEventListener(this.eTypeSelector, "change", this.onFilterTypeChanged.bind(this)); } public customInit() { if (!this.defaultFilter) { this.defaultFilter = this.getDefaultType(); } } public generateFilterHeader(): string { let defaultFilterTypes = this.getApplicableFilterTypes(); let restrictedFilterTypes = this.filterParams.filterOptions; let actualFilterTypes = restrictedFilterTypes ? restrictedFilterTypes : defaultFilterTypes; let optionsHtml: string[] = actualFilterTypes.map(filterType => { let localeFilterName = this.translate(filterType); return `<option value="${filterType}">${localeFilterName}</option>`; }); let readOnly = optionsHtml.length == 1 ? 'disabled' : ''; return optionsHtml.length <= 0 ? '' : `<div> <select class="ag-filter-select" id="filterType" ${readOnly}> ${optionsHtml.join('')} </select> </div>`; } public initialiseFilterBodyUi() { this.setFilterType(this.filter); } public abstract getDefaultType(): string; private onFilterTypeChanged(): void { this.filter = this.eTypeSelector.value; this.refreshFilterBodyUi(); this.onFilterChanged(); } public isFilterActive(): boolean { let rawFilterValues = this.filterValues(); if (this.filter === BaseFilter.IN_RANGE) { let filterValueArray = (<T[]>rawFilterValues); return filterValueArray[0] != null && filterValueArray[1] != null; } else { return rawFilterValues != null; } } public setFilterType(filterType: string): void { this.filter = filterType; this.eTypeSelector.value = filterType; } } export interface NullComparator { equals?: boolean; lessThan?: boolean; greaterThan?: boolean; } export interface IScalarFilterParams extends IFilterParams { inRangeInclusive?: boolean; nullComparator?: NullComparator; } /** * Comparable filter with scalar underlying values (ie numbers and dates. Strings are not scalar so have to extend * ComparableBaseFilter) */ export abstract class ScalarBaseFilter<T, P extends IScalarFilterParams, M> extends ComparableBaseFilter<T, P, M> { static readonly DEFAULT_NULL_COMPARATOR: NullComparator = { equals: false, lessThan: false, greaterThan: false }; public abstract comparator(): Comparator<T>; private nullComparator(type: string): Comparator<T> { return (filterValue: T, gridValue: T): number => { if (gridValue == null) { let nullValue = this.translateNull (type); if (this.filter === BaseFilter.EQUALS) { return nullValue? 0 : 1; } if (this.filter === BaseFilter.GREATER_THAN) { return nullValue? 1 : -1; } if (this.filter === BaseFilter.GREATER_THAN_OR_EQUAL) { return nullValue? 1 : -1; } if (this.filter === BaseFilter.LESS_THAN_OR_EQUAL) { return nullValue? -1 : 1; } if (this.filter === BaseFilter.LESS_THAN) { return nullValue? -1 : 1; } if (this.filter === BaseFilter.NOT_EQUAL) { return nullValue? 1 : 0; } } let actualComparator: Comparator<T> = this.comparator(); return actualComparator (filterValue, gridValue); }; } public getDefaultType(): string { return BaseFilter.EQUALS; } private translateNull(type: string): boolean { let reducedType: string = type.indexOf('greater') > -1 ? 'greaterThan': type.indexOf('lessThan') > -1 ? 'lessThan': 'equals'; if (this.filterParams.nullComparator && (<any>this.filterParams.nullComparator)[reducedType]) { return (<any>this.filterParams.nullComparator)[reducedType]; } return (<any>ScalarBaseFilter.DEFAULT_NULL_COMPARATOR)[reducedType]; } public doesFilterPass(params: IDoesFilterPassParams): boolean { let value: any = this.filterParams.valueGetter(params.node); let comparator: Comparator<T> = this.nullComparator (this.filter); let rawFilterValues: T[] | T= this.filterValues(); let from: T= Array.isArray(rawFilterValues) ? rawFilterValues[0]: rawFilterValues; if (from == null) { return true; } let compareResult = comparator(from, value); if (this.filter === BaseFilter.EQUALS) { return compareResult === 0; } if (this.filter === BaseFilter.GREATER_THAN) { return compareResult > 0; } if (this.filter === BaseFilter.GREATER_THAN_OR_EQUAL) { return compareResult >= 0; } if (this.filter === BaseFilter.LESS_THAN_OR_EQUAL) { return compareResult <= 0; } if (this.filter === BaseFilter.LESS_THAN) { return compareResult < 0; } if (this.filter === BaseFilter.NOT_EQUAL) { return compareResult != 0; } //From now on the type is a range and rawFilterValues must be an array! let compareToResult: number = comparator((<T[]>rawFilterValues)[1], value); if (this.filter === BaseFilter.IN_RANGE) { if (!this.filterParams.inRangeInclusive) { return compareResult > 0 && compareToResult < 0; } else { return compareResult >= 0 && compareToResult <= 0; } } throw new Error('Unexpected type of date filter!: ' + this.filter); } }