1
votes

Briefly: Is there any way to implement a custom comparator for the number column filters?

Long story:

I use ag-grid in Angular (2). I provide my own components for cells in my ag-grid:

let column = {
  headerName: column.header,
  field: column.id,
  cellRendererFramework: DynamicComponent,
  filter: this.convertFormatToFilterType(column.format) // returns "text", "number" or "date"
}

In order to make the filters get values from my cells properly I provide custom comparators (this one is for the text column filters):

if (c.filter === "text")
  c['filterParams'] = {
    textCustomComparator: (filter, cell, filterText): boolean => {
    var filterTextLowerCase = filterText.toLowerCase();
    var valueLowerCase = cell.value.toString().toLowerCase();
    switch (filter) {
      case 'contains':
        return valueLowerCase.indexOf(filterTextLowerCase) >= 0;
      case 'notContains':
        return valueLowerCase.indexOf(filterTextLowerCase) === -1;
      case 'equals':
        return valueLowerCase === filterTextLowerCase;
      case 'notEqual':
        return valueLowerCase != filterTextLowerCase;
      case 'startsWith':
        return valueLowerCase.indexOf(filterTextLowerCase) === 0;
      case 'endsWith':
        var index = valueLowerCase.lastIndexOf(filterTextLowerCase);
        return index >= 0 && index === (valueLowerCase.length - filterTextLowerCase.length);
      default:
        // should never happen
        console.warn('invalid filter type ' + filter);
          return false;
        }
      }
    };

You can see I need to access the value of the cell by using "cell.value". The code above works fine.

What I have troubles with is providing similar functionality for the number column filters - they don't seem to use any custom comparator. Therefore, what is happening, the filter tries to access the cell's value directly instead of using "cell.value".

So, is there any way to implement a custom comparator for the number column filters? Or, if not, any other way I can get the value from my cells correctly in this case?

1

1 Answers

1
votes

What I ended up doing is implementing a ag-grid custom filter component

import { Component, ViewChild, ViewContainerRef } from '@angular/core';

import { IFilterParams, IDoesFilterPassParams, RowNode, IAfterGuiAttachedParams } from 'ag-grid/main';
import { IFilterAngularComp } from 'ag-grid-angular/main';

// https://www.ag-grid.com/javascript-grid-filter-component/#gsc.tab=0 / Angular Filtering
// create your filter as a Angular component
@Component({
    selector: 'filter-cell',
    template: `
    <select #select (ngModelChange)="onSelectChange($event)" [ngModel]="operator">
      <option value="eq">Equals</option>
      <option value="neq">Not equal</option>
      <option value="lt">Less than</option>
      <option value="lte">Less than or equals</option>
      <option value="gt">Greater than</option>
      <option value="gte">Greater than or equals</option>
      <option value="inrange">In range</option>
    </select>

    <br>

    <input #input (ngModelChange)="onChange($event)" [ngModel]="text">
    <br>
    <div *ngIf='operator === "inrange"'>
      <input #input2 (ngModelChange)="onChange2($event)" [ngModel]="text2">
    </div>
    `,
    styles: ['select { margin: 2px 4px; }', 'input { height: 26px; margin: 2px 4px; }']
})
export class GridNumberFilterComponent implements IFilterAngularComp {
    private params: IFilterParams;
    private valueGetter: (rowNode: RowNode) => any;
    public operator: string = 'eq';
    public text: string = '';
    public text2: string = '';

    @ViewChild('select', { read: ViewContainerRef }) public select;
    @ViewChild('input', { read: ViewContainerRef }) public input;
    @ViewChild('input2', { read: ViewContainerRef }) public input2;

    agInit(params: IFilterParams): void {
        this.params = params;
        this.valueGetter = params.valueGetter;
    }

    isFilterActive(): boolean {
        return this.text !== null && this.text !== undefined && this.text !== '';
    }

    doesFilterPass(params: IDoesFilterPassParams): boolean {
        let cellNumber = Number(this.valueGetter(params.node).value);
        let filterNumber = this.text ? Number(this.text) : -Infinity;
        let filterNumber2 = this.text2 ? Number(this.text2) : Infinity;

        switch (this.operator) {
            case 'eq': return cellNumber === filterNumber;
            case 'neq': return cellNumber !== filterNumber;
            case 'lt': return cellNumber < filterNumber;
            case 'lte': return cellNumber <= filterNumber;
            case 'gt': return cellNumber > filterNumber;
            case 'gte': return cellNumber >= filterNumber;
            case 'inrange': return cellNumber >= filterNumber && cellNumber <= filterNumber2;

            default: return true;
        }
    }

    getModel(): any {
        return { value: this.text };
    }

    setModel(model: any): void {
        this.text = model ? model.value : '';
    }

    afterGuiAttached(params: IAfterGuiAttachedParams): void {
        this.input.element.nativeElement.focus();
    }

    componentMethod(message: string): void {
        alert(`Alert from PartialMatchFilterComponent ${message}`);
    }

    onSelectChange(newValue): void {
        if (this.operator !== newValue) {
            this.operator = newValue;
            this.params.filterChangedCallback();
        }
    }

    onChange(newValue): void {
        if (this.text !== newValue) {
            this.text = newValue;
            this.params.filterChangedCallback();
        }
    }

    onChange2(newValue): void {
        if (this.text2 !== newValue) {
            this.text2 = newValue;
            this.params.filterChangedCallback();
        }
    }
}

which I add to my column like this:

let column = {
  headerName: column.header,
  field: column.id,
  cellRendererFramework: DynamicComponent,
  filterFramework: GridNumberFilterComponent
}