ag-grid
Version:
Advanced Javascript Datagrid. Supports raw Javascript, AngularJS 1.x, AngularJS 2.0 and Web Components
428 lines (374 loc) • 18.2 kB
text/typescript
/// <reference path="../utils.ts" />
/// <reference path="textFilter.ts" />
/// <reference path="numberFilter.ts" />
/// <reference path="setFilter.ts" />
/// <reference path="../widgets/agPopupService.ts" />
/// <reference path="../widgets/agPopupService.ts" />
/// <reference path="../grid.ts" />
/// <reference path="../entities/rowNode.ts" />
module ag.grid {
var _ = Utils;
export class FilterManager {
private $compile: any;
private $scope: any;
private gridOptionsWrapper: GridOptionsWrapper;
private grid: any;
private allFilters: any;
private rowModel: any;
private popupService: PopupService;
private valueService: ValueService;
private columnController: ColumnController;
private quickFilter: string;
private advancedFilterPresent: boolean;
private externalFilterPresent: boolean;
public init(grid: Grid, gridOptionsWrapper: GridOptionsWrapper, $compile: any, $scope: any,
columnController: ColumnController, popupService: PopupService, valueService: ValueService) {
this.$compile = $compile;
this.$scope = $scope;
this.gridOptionsWrapper = gridOptionsWrapper;
this.grid = grid;
this.allFilters = {};
this.columnController = columnController;
this.popupService = popupService;
this.valueService = valueService;
this.columnController = columnController;
this.quickFilter = null;
}
public setFilterModel(model: any) {
if (model) {
// mark the filters as we set them, so any active filters left over we stop
var modelKeys = Object.keys(model);
_.iterateObject(this.allFilters, (colId, filterWrapper) => {
_.removeFromArray(modelKeys, colId);
var newModel = model[colId];
this.setModelOnFilterWrapper(filterWrapper.filter, newModel);
});
// at this point, processedFields contains data for which we don't have a filter working yet
_.iterateArray(modelKeys, (colId) => {
var column = this.columnController.getColumn(colId);
if (!column) {
console.warn('Warning ag-grid setFilterModel - no column found for colId ' + colId);
return;
}
var filterWrapper = this.getOrCreateFilterWrapper(column);
this.setModelOnFilterWrapper(filterWrapper.filter, model[colId]);
});
} else {
_.iterateObject(this.allFilters, (key, filterWrapper) => {
this.setModelOnFilterWrapper(filterWrapper.filter, null);
});
}
this.grid.onFilterChanged();
}
private setModelOnFilterWrapper(filter: { getApi: () => { setModel: Function }}, newModel: any) {
// because user can provide filters, we provide useful error checking and messages
if (typeof filter.getApi !== 'function') {
console.warn('Warning ag-grid - filter missing getApi method, which is needed for getFilterModel');
return;
}
var filterApi = filter.getApi();
if (typeof filterApi.setModel !== 'function') {
console.warn('Warning ag-grid - filter API missing setModel method, which is needed for setFilterModel');
return;
}
filterApi.setModel(newModel);
}
public getFilterModel() {
var result = <any>{};
_.iterateObject(this.allFilters, function (key: any, filterWrapper: any) {
// because user can provide filters, we provide useful error checking and messages
if (typeof filterWrapper.filter.getApi !== 'function') {
console.warn('Warning ag-grid - filter missing getApi method, which is needed for getFilterModel');
return;
}
var filterApi = filterWrapper.filter.getApi();
if (typeof filterApi.getModel !== 'function') {
console.warn('Warning ag-grid - filter API missing getModel method, which is needed for getFilterModel');
return;
}
var model = filterApi.getModel();
if (model) {
result[key] = model;
}
});
return result;
}
public setRowModel(rowModel: any) {
this.rowModel = rowModel;
}
// returns true if any advanced filter (ie not quick filter) active
public isAdvancedFilterPresent() {
var atLeastOneActive = false;
_.iterateObject(this.allFilters, function (key, filterWrapper) {
if (!filterWrapper.filter.isFilterActive) { // because users can do custom filters, give nice error message
console.error('Filter is missing method isFilterActive');
}
if (filterWrapper.filter.isFilterActive()) {
atLeastOneActive = true;
}
});
return atLeastOneActive;
}
// returns true if quickFilter or advancedFilter
public isAnyFilterPresent(): boolean {
return this.isQuickFilterPresent() || this.advancedFilterPresent || this.externalFilterPresent;
}
// returns true if given col has a filter active
public isFilterPresentForCol(colId: any) {
var filterWrapper = this.allFilters[colId];
if (!filterWrapper) {
return false;
}
if (!filterWrapper.filter.isFilterActive) { // because users can do custom filters, give nice error message
console.error('Filter is missing method isFilterActive');
}
var filterPresent = filterWrapper.filter.isFilterActive();
return filterPresent;
}
private doesFilterPass(node: RowNode, filterToSkip?: any) {
var data = node.data;
var colKeys = Object.keys(this.allFilters);
for (var i = 0, l = colKeys.length; i < l; i++) { // critical code, don't use functional programming
var colId = colKeys[i];
var filterWrapper = this.allFilters[colId];
// if no filter, always pass
if (filterWrapper === undefined) {
continue;
}
if (filterWrapper.filter === filterToSkip) {
continue
}
if (!filterWrapper.filter.doesFilterPass) { // because users can do custom filters, give nice error message
console.error('Filter is missing method doesFilterPass');
}
var params = {
node: node,
data: data
};
if (!filterWrapper.filter.doesFilterPass(params)) {
return false;
}
}
// all filters passed
return true;
}
// returns true if it has changed (not just same value again)
public setQuickFilter(newFilter: any): boolean {
if (newFilter === undefined || newFilter === "") {
newFilter = null;
}
if (this.quickFilter !== newFilter) {
if (this.gridOptionsWrapper.isVirtualPaging()) {
console.warn('ag-grid: cannot do quick filtering when doing virtual paging');
return;
}
//want 'null' to mean to filter, so remove undefined and empty string
if (newFilter === undefined || newFilter === "") {
newFilter = null;
}
if (newFilter !== null) {
newFilter = newFilter.toUpperCase();
}
this.quickFilter = newFilter;
return true;
} else {
return false;
}
}
public onFilterChanged(): void {
this.advancedFilterPresent = this.isAdvancedFilterPresent();
this.externalFilterPresent = this.gridOptionsWrapper.isExternalFilterPresent();
_.iterateObject(this.allFilters, function (key, filterWrapper) {
if (filterWrapper.filter.onAnyFilterChanged) {
filterWrapper.filter.onAnyFilterChanged();
}
});
}
public isQuickFilterPresent(): boolean {
return this.quickFilter !== null;
}
public doesRowPassOtherFilters(filterToSkip: any, node: any): boolean {
return this.doesRowPassFilter(node, filterToSkip);
}
public doesRowPassFilter(node: any, filterToSkip?: any): boolean {
//first up, check quick filter
if (this.isQuickFilterPresent()) {
if (!node.quickFilterAggregateText) {
this.aggregateRowForQuickFilter(node);
}
if (node.quickFilterAggregateText.indexOf(this.quickFilter) < 0) {
//quick filter fails, so skip item
return false;
}
}
//secondly, give the client a chance to reject this row
if (this.externalFilterPresent) {
if (!this.gridOptionsWrapper.doesExternalFilterPass(node)) {
return false;
}
}
//lastly, check our internal advanced filter
if (this.advancedFilterPresent) {
if (!this.doesFilterPass(node, filterToSkip)) {
return false;
}
}
//got this far, all filters pass
return true;
}
private aggregateRowForQuickFilter(node: RowNode) {
var aggregatedText = '';
var that = this;
this.columnController.getAllColumns().forEach(function (column: Column) {
var data = node.data;
var value = that.valueService.getValue(column.colDef, data, node);
if (value && value !== '') {
aggregatedText = aggregatedText + value.toString().toUpperCase() + "_";
}
});
node.quickFilterAggregateText = aggregatedText;
}
public refreshDisplayedValues() {
if (!this.rowModel.getTopLevelNodes) {
console.error('ag-Grid: could not find getTopLevelNodes on rowModel. you cannot use setFilter when' +
'doing virtualScrolling as the filter has no way of getting the full set of values to display. ' +
'Either stop using this filter type, or provide the filter with a set of values (see the docs' +
'on configuring the setFilter).')
}
var rows = this.rowModel.getTopLevelNodes();
var colKeys = Object.keys(this.allFilters);
for (var i = 0, l = colKeys.length; i < l; i++) {
var colId = colKeys[i];
var filterWrapper = this.allFilters[colId];
// if no filter, always pass
if (filterWrapper === undefined || (typeof filterWrapper.filter.setFilteredDisplayValues !== 'function')) {
continue;
}
var displayedFilterValues = new Array();
for (var j = 0; j < rows.length; j++) {
if (this.doesFilterPass(rows[j], i)) {
displayedFilterValues.push(rows[j])
}
}
filterWrapper.filter.setFilteredDisplayValues(displayedFilterValues)
}
}
public onNewRowsLoaded() {
var that = this;
Object.keys(this.allFilters).forEach(function (field) {
var filter = that.allFilters[field].filter;
if (filter.onNewRowsLoaded) {
filter.onNewRowsLoaded();
}
});
}
private createValueGetter(column: Column) {
var that = this;
return function valueGetter(node: any) {
return that.valueService.getValue(column.colDef, node.data, node);
};
}
public getFilterApi(column: Column) {
var filterWrapper = this.getOrCreateFilterWrapper(column);
if (filterWrapper) {
if (typeof filterWrapper.filter.getApi === 'function') {
return filterWrapper.filter.getApi();
}
}
}
private getOrCreateFilterWrapper(column: Column) {
var filterWrapper = this.allFilters[column.colId];
if (!filterWrapper) {
filterWrapper = this.createFilterWrapper(column);
this.allFilters[column.colId] = filterWrapper;
this.refreshDisplayedValues();
}
return filterWrapper;
}
private createFilterWrapper(column: Column) {
var colDef = column.colDef;
var filterWrapper = {
column: column,
filter: <any> null,
scope: <any> null,
gui: <any> null
};
if (typeof colDef.filter === 'function') {
// if user provided a filter, just use it
// first up, create child scope if needed
if (this.gridOptionsWrapper.isAngularCompileFilters()) {
filterWrapper.scope = this.$scope.$new();;
}
// now create filter (had to cast to any to get 'new' working)
this.assertMethodHasNoParameters(colDef.filter);
filterWrapper.filter = new (<any>colDef.filter)();
} else if (colDef.filter === 'text') {
filterWrapper.filter = new TextFilter();
} else if (colDef.filter === 'number') {
filterWrapper.filter = new NumberFilter();
} else {
filterWrapper.filter = new SetFilter();
}
var filterChangedCallback = this.grid.onFilterChanged.bind(this.grid);
var filterModifiedCallback = this.grid.onFilterModified.bind(this.grid);
var doesRowPassOtherFilters = this.doesRowPassOtherFilters.bind(this, filterWrapper.filter);
var filterParams = colDef.filterParams;
var params = {
colDef: colDef,
rowModel: this.rowModel,
filterChangedCallback: filterChangedCallback,
filterModifiedCallback: filterModifiedCallback,
filterParams: filterParams,
localeTextFunc: this.gridOptionsWrapper.getLocaleTextFunc(),
valueGetter: this.createValueGetter(column),
doesRowPassOtherFilter: doesRowPassOtherFilters,
context: this.gridOptionsWrapper.getContext,
$scope: filterWrapper.scope
};
if (!filterWrapper.filter.init) { // because users can do custom filters, give nice error message
throw 'Filter is missing method init';
}
filterWrapper.filter.init(params);
if (!filterWrapper.filter.getGui) { // because users can do custom filters, give nice error message
throw 'Filter is missing method getGui';
}
var eFilterGui = document.createElement('div');
eFilterGui.className = 'ag-filter';
var guiFromFilter = filterWrapper.filter.getGui();
if (_.isNodeOrElement(guiFromFilter)) {
//a dom node or element was returned, so add child
eFilterGui.appendChild(guiFromFilter);
} else {
//otherwise assume it was html, so just insert
var eTextSpan = document.createElement('span');
eTextSpan.innerHTML = guiFromFilter;
eFilterGui.appendChild(eTextSpan);
}
if (filterWrapper.scope) {
filterWrapper.gui = this.$compile(eFilterGui)(filterWrapper.scope)[0];
} else {
filterWrapper.gui = eFilterGui;
}
return filterWrapper;
}
private assertMethodHasNoParameters(theMethod: any) {
var getRowsParams = _.getFunctionParameters(theMethod);
if (getRowsParams.length > 0) {
console.warn('ag-grid: It looks like your filter is of the old type and expecting parameters in the constructor.');
console.warn('ag-grid: From ag-grid 1.14, the constructor should take no parameters and init() used instead.');
}
}
public showFilter(column: Column, eventSource: any) {
var filterWrapper = this.getOrCreateFilterWrapper(column);
this.popupService.positionPopup(eventSource, filterWrapper.gui, 200);
var hidePopup = this.popupService.addAsModalPopup(filterWrapper.gui, true);
if (filterWrapper.filter.afterGuiAttached) {
var params = {
hidePopup: hidePopup,
eventSource: eventSource
};
filterWrapper.filter.afterGuiAttached(params);
}
}
}
}