2

Consider a scenario where I am displaying a START and END YEARs in a p-dataTable column.

Here is the column code snippet:

<p-column field="startYear" header="Active Period" filterPlaceholder="Search" [filter]="true">
  <ng-template let-col let-manual="rowData" pTemplate="body">
    {{record.startYear}} - {{record.endYear}}
  </ng-template>
</p-column>

Which displays as:

|--------------|
| ActivePeriod |
|--------------|
|2015 - 2017   |
|--------------|
|2012 - 2016   |
|--------------|
|2023 - 2025   |
|--------------|
|2024 - 2030   |
|--------------|
|2011 - 2013   |
|--------------|

I want to apply a custom filter so that when user types in an YEAR the table should only show the records which falls in that Active period.

Which means I have to write a logic where I have check if the user searched year is in between each active period.

How can I achieve this?

Kishor Prakash
  • 8,011
  • 12
  • 61
  • 92

2 Answers2

5

First, you can add hidden assistant field years which will contain an array with years within given range, so for range 2012 - 2015, we will get an array:

[
  2012,
  2013,
  2014,
  2015
]

You can do it using map function, something like:

const yearsRange = row.endYear - row.startYear + 1;
YOUR_DATA_ARRAY.map(row => ({
  ...row,
  years: [...Array(yearsRange).keys()].map(y => y + row.startYear)
}))

Out of the box PRIME NG does not have between filter. Filters available currently are:

  • startsWith
  • contains
  • endsWith
  • equals
  • notEquals
  • in
  • lt
  • gt

You can extend it writing your own filter:

// import Table class
import { Table } from 'primeng/components/table/table';

.....

// get reference to your table
@ViewChild('dt') tableRef: Table;

.....

// extend filters
this.tableRef.filterConstraints.between = (value, filter: any): boolean => {
  if (filter === undefined || filter === null || filter === '') {
    return true;
  }

  if (value === undefined || value === null || !value.length) {
    return false;
  }

  return value.some(val => +val === +filter);
};

now you can use it in your HTML:

<p-table #dt [value]="cars">

  .....

  <input size="4"
         (input)="dt.filter($event.target.value, 'years', 'between')">

  .....

STACKBLITZ: https://stackblitz.com/edit/angular-sfoh3j?file=app%2Fapp.component.ts

UPDATE

I noticed you use old (and deprecated) DataTable, the solution should be very similar, filters are almost the same missing lt and gt. Just change names in imports and type names in my example. HTML syntax is also different. Let me know if you need help with old DataTable

Andriy
  • 14,781
  • 4
  • 46
  • 50
  • hi @Andriy, I'm using the old version DataTable and I'm getting an error on the .filterConstraints could you please help me? I have no time to migrate to the newer version. thanks in advance. – Ivan Lopez Apr 12 '22 at 17:10
  • please create a STACKBLITZ repro of your issue, explain what is the error and what you want to achieve and I will try to help – Andriy Apr 13 '22 at 05:58
2

The answer by Andriy helped me out on a particular problem I was having with the DataTable. We needed a couple of custom filters for >= and <= for floats. I took Andriy's solution and applied it to our problem. I'm posting the code here in case anyone else can benefit from the combined responses.

1 - Built a custom component to handle the input from the user

Typescript:

import { Component, Output, EventEmitter  } from '@angular/core';
import { FormBuilder, FormGroup, AbstractControl, FormControl } from '@angular/forms';
import { SelectItem } from 'primeng/components/common/selectitem';
import { ICustomGtLsFilter } from '../../Models/ICustomGtLsFilter';

@Component({
  selector: 'app-custom-filter',
  templateUrl: './custom-filter.component.html',
  styleUrls: ['./custom-filter.component.css']
})
export class CustomFilterComponent {
    @Output() onCriteriaSelectedChanged = new EventEmitter<ICustomGtLsFilter>();

    _myForm: FormGroup;
    _criteria: SelectItem[] = [{label: 'None', value: 'none'}, { label: '<=', value: 'lte' }, { label: '>=', value: 'gte' }];

    private _filterModel: ICustomGtLsFilter = new ICustomGtLsFilter();

    constructor(private _fb: FormBuilder) {
        this._myForm = _fb.group({
            _inputField: [null],
            _criteriaSel:[]
        });
    }

    fieldChanged(event) {
        // clear the input field
        if (event === "none") {
            this._myForm.controls['_inputField'].setValue('');
        }

        this._filterModel.Criteria = event;
        this._filterModel.FilterVal = this._myForm.controls['_inputField'].value;

        this.onCriteriaSelectedChanged.emit(this._filterModel);
    }
}

Template

<form [formGroup]="_myForm">
    <div class="row">
        <div class="col-sm-12">
            <input type="number" name="_inputField" id="_inputField" [formControl]="_myForm.controls['_inputField']" autocomplete="off" />
            <p-dropdown [options]="_criteria"
                        [autoWidth]="false"
                        id="_criteriaSel"
                        name="_criteriaSel"
                        placeholder="None"
                        formControlName="_criteriaSel"
                        (onChange)="fieldChanged($event.value)">
            </p-dropdown>
        </div>
    </div>
</form>  

Interface:

export class ICustomGtLsFilter {
    FilterVal: number;
    Criteria: string;
}

Here is the custom component defined as an ng-template for the column we want to apply the filter to:

<p-column field='TimeRem'
                  header='Time Remaining'
                  [sortable]='true'
                  [filter]='true'>
            <ng-template let-col
                         pTemplate='filter'>
                <app-custom-filter appendTo="body" 
                         (onCriteriaSelectedChanged)="dt.filter($event.FilterVal,col.field,$event.Criteria)">
                </app-custom-filter>
            </ng-template>
            <ng-template let-col
                         let-rd='rowData'
                         pTemplate='body'>
                <span appAlignRightStyle>{{rd[col.field] | format: '0.0'}}</span>
            </ng-template>
        </p-column>

And finally the relevant typescript code in the component that uses the DataTable:

...

import { Component, OnInit, OnDestroy, ViewChild, AfterViewInit } from '@angular/core';
import { CustomFilterComponent } from '../../../../shared/custom-filter/custom-filter.component';
import { ICustomGtLsFilter } from '../../../../Models/ICustomGtLsFilter';

export class SomeComponent implements OnInit {
    @ViewChild('dt') dt: DataTable;

 ngAfterViewInit() { 
        // custom filter implementations
        this.dt.filterConstraints['gte'] = function gte(value: any, filter: any): boolean {

            if (filter === undefined || filter === null) {
                return false;
            }

            if (value === undefined || value === null || value.length === 0) {
                return false;
            }

            if (value >= filter) {
                return true;
            }

            return false;
        };

        this.dt.filterConstraints['lte'] = function lte(value: any, filter: any): boolean {

            if (filter === undefined || filter === null) {
                return false;
            }

            if (value === undefined || value === null || value.length === 0) {
                return false;
            }

            if (value <= filter) {
                return true;
            }

            return false;
        };

        this.dt.filterConstraints['none'] = function none(value: any, filter: any): boolean {

            return true;
        };

    }
Enoch_Root
  • 21
  • 2