How to create custom range calendar filter for Angular PrimeNg data turbo table

Let’s look at how to implement Angular selection range for the newest Angular PrimeNg version. This tutorial was developed on v10.0.0-rc-1 version.

I had difficulty to find a proper solution for range selection in PrimeNg turbo data table for the newest version. Therefore I have decided to create a workaround. For the time selection, each click event on the calendar panel fire only one piece of data – currently selected date. So the calendar range is stateless. You need to remember somehow keep in memory what date was fired before and implement this logic in a custom filter.

I observed that there are several rules according to which p-calendar component behave when the selection mode "range" is selected. Here are my findings according to which I programmed the logic:

  • At first, the calendar component has an empty, undefined selection.
  • If one date is selected, only one date is fired on click event.
  • If the same date as date previously selected is selected, new date (which is the same as previously selected date) is fired on click event.
  • If a date before the already selected date is selected, the selection is cancelled and earlier date is fired on click event.
  • If a date after the already selected date is selected, the selection range is visible.
  • If any new date is selected after the selection range is visible, a new date is selected, fired upon click event and selection range is cancelled.

When all this wrote down here is implementation code for component and custom filter:

record-list.component.html

<p-table #dt [value]="records" sortMode="multiple" [paginator]="true" [rowsPerPageOptions]="rowsPerPageTable" [rows]="rowsPerTable" [showCurrentPageReport]="true" [(first)]="page" styleClass="p-datatable-striped" currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries">
    <ng-template pTemplate="header">
        <tr class="ui-table-thead">
            <th [ngStyle]="{'width':'50%'}" pSortableColumn="createdDate">Created <p-sortIcon field="createdDate"></p-sortIcon></th>
            <th [ngStyle]="{'width':'50%'}" pSortableColumn="info">Info <p-sortIcon field="info"></p-sortIcon></th>
        </tr>
        <tr class="ui-table-thead">
            <th>
                <p-calendar (onSelect)="onDateSelect($event)" [style]="{'width':'100%'}" selectionMode="range" [readonlyInput]="true" (onClearClick)="onDateClear($event)" [showButtonBar]="true" placeholder="Created Date" class="p-column-filter" dateFormat="yy-mm-dd"></p-calendar>
            </th>
            <th>
                <input pInputText type="text" [style]="{'width':'100%'}" (input)="dt.filter($event.target.value, 'info', 'contains')" placeholder="Info">
            </th>
        </tr>
    </ng-template>

    <ng-template pTemplate="body" let-rowData>
        <tr ng-repeat="rowData in let-rowData" ng-click="showClient(rowData)">
            <td>{{ rowData.createdDate }}</td>
            <td>{{ rowData.info }}</td>
        </tr>
    </ng-template>

</p-table>

record-list.component.ts

export class RecordListComponent implements OnInit {

  public records: IssueShortInfo[];

  @ViewChild('dt') table: Table;

  cols: any[];
  dateRangeStart: string;
  dateRangeEnd: string;

  rowsPerPageTable: number[] = [25, 50, 100, 200];

  page: number = 0;
  rowsPerTable: number = 25;

  constructor(private recordService: RecordService) {}

  ngOnInit() {

    this.recordService.getAllRecords().subscribe(page => {
      this.getRecords(page);
    }, (error => {
      console.log(error)
    }));

    this.cols = [
      {field: 'id', header: 'Id'},
      {field: 'createdDate', header: 'Created Date'},
      {field: 'info', header: 'Info'}
    ];

    FilterUtils['customCreatedDateFilter'] = (value: string, filter) => {

      if (this.dateRangeStart === value && this.dateRangeEnd === undefined) {
        return true;
      }

      if (this.dateRangeStart === value || this.dateRangeEnd === value) {
        return true;
      }

      if (
        this.dateRangeStart !== undefined &&
        this.dateRangeEnd !== undefined &&
        moment(this.dateRangeStart).isBefore(value) &&
        moment(this.dateRangeEnd).isAfter(value)) {
        return true;
      }

      return false;
    };
  }

  getRecords(page: Page) {
    this.records = page.content.map(incomingRecordDTO => new Record(incomingRecordDTO));
  }

  onDateSelect($event) {

    const eventDate = this.formatDate($event);

    if (this.dateRangeStart === undefined) {
      this.dateRangeStart = eventDate;
    } else if (moment($event).isBefore(this.dateRangeStart)) {
      this.dateRangeStart = eventDate;
      this.dateRangeEnd = undefined;
    } else if (moment($event).isSame(this.dateRangeStart) && this.dateRangeStart !== undefined && this.dateRangeEnd === undefined) {
      this.dateRangeEnd = eventDate;
    } else if (moment($event).isSame(this.dateRangeStart) && this.dateRangeStart !== undefined && this.dateRangeEnd !== undefined) {
      this.dateRangeStart = eventDate;
      this.dateRangeEnd = undefined;
    } else if (moment($event).isAfter(this.dateRangeStart) && this.dateRangeStart !== undefined && this.dateRangeEnd !== undefined) {
      this.dateRangeStart = eventDate;
      this.dateRangeEnd = undefined;
    } else {
      this.dateRangeEnd = eventDate;
    }

    this.table.filter(eventDate, 'createdDate', 'customCreatedDateFilter');
  }

  onDateClear($event) {
    this.dateRangeStart = undefined;
    this.dateRangeEnd = undefined;
    this.table.filter('', 'createdDate', 'equals');
  }

  formatDate(date) {
    let month = date.getMonth() + 1;
    let day = date.getDate();

    if (month < 10) {
      month = '0' + month;
    }

    if (day < 10) {
      day = '0' + day;
    }
    return date.getFullYear() + '-' + month + '-' + day;
  }

  customCreatedDateArrayFilter(event) {
    this.table.filter(event, 'createdDate', 'customCreatedDateFilter');
  }

}
This entry was posted in Angular and tagged , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.