[FIXED] primeng p-calendar in p-table filter header, allow use to enter date manually and clear it

Issue

I am using primeng’s p-calendar as a filter in the header of a table. I need to select a date but also enter one by hand, so had to implement an injectable service with a handleSelection method. The ugly problem is that I currently have to return the current selectedValue and assign it to the previous one.

Is there a way to pass the previousStartDate(which is a Date type) as a reference, so I can update it in the handleSelection function instead of return it?

<p-calendar #startDate appendTo="body" dateFormat="dd M yy" 
    (onSelect)="previousStartDate = calendarService.handleSelection(dt, startDate, 'startDate', 'equals', previousStartDate)" 
    (onBlur)="previousStartDate = calendarService.handleSelection(dt, startDate, 'startDate', 'equals', previousStartDate)"
    styleClass="sym-column-filter">
</p-calendar>

My handleSelection function looks like this

@Injectable({
    providedIn: 'root'
})
export class CalendarService {

    constructor(private dateService: DateService) {}

    /*
    To be used for the p-table filters where the filter is a p-calendar
    This allows us to also enter the date manually in the text box
    */
    handleSelection(dt: Table, calendar: Calendar, field: string, comparisonMethod: string, previousDate: Date | null): Date | null {
        // only continue if the value has changed from the previous one
        if (calendar.value === previousDate) {
            return;
        }
        // If there is a date then filter by that
        if (calendar.value) {
            dt.filter(this.dateService.formatDate(calendar.value), field, comparisonMethod);
        } else {
            // Otherwise we may have removed some text so need to refresh the p-table
            dt.filter('', field, comparisonMethod);
        }
        return calendar.value;
    }
}

Just a pic of what it looks like

enter image description here

Solution

I did a bit more playing and found another approach, simpler actually. Instead of using the onSelect and onBlur I found you could use the ngModelChange call.

There do seem to be some limitations when playing with a p-calendar in a p-table header, especially when you want to type a date in and select date(existing most common approach). For me I wanted to enter text, but also for it to make a filter request when it was cleared(no value). This approach does that, it ignores invalid dates, but will make a request when it is empty or has a valid date.

The markup is as follows:

<p-calendar
    #endDate 
    [(ngModel)]="endDateFilter" 
    (ngModelChange)="calendarService.calendarDateChanged($event, endDate, dt, 'endDate', 'equals')" 
    appendTo="body" 
    dateFormat="dd M yy" 
    placeholder="Select"                        
    styleClass="sym-column-filter">
</p-calendar>

Now my helper service looked like this:

import { Injectable } from '@angular/core';
import { Calendar } from 'primeng/calendar';
import { Table } from 'primeng/table';
import { DateService } from './date.service';

/**
 * The calendar helper service is a common injectable service that can handle entering a date in the p-calendar when the p-calendar
 * is used as a filter header
 */
@Injectable({
    providedIn: 'root'
})
export class CalendarService {

    constructor(private dateService: DateService) {}

    /*
    To be used for the p-table filters where the filter is a p-calendar
    This allows us to also enter the date manually in the text box
    */
    calendarDateChanged(calendarDate: Date | null, calendar: Calendar, table: Table, field: string, comparisonMethod: string) {
        // We do a setTimeout to give it one tick of time to make sure the selected date is populated
        // If we don't do this then the date will be an empty string even though they may have selected a date
        setTimeout(() => {
            // Get the DOM input field to check the text, this is primarily to check of the value has been cleared
            // so we need to reset the filter
            const calendarInput = calendar?.el.nativeElement.querySelector('input');
            if (calendarInput) {
                const enteredValue = calendarInput.value;
                // check it is a valid date or is empty for clearing the filter
                if (calendarDate || enteredValue === '') {
                    table.filter(enteredValue === '' ? null : this.dateService.formatDate(calendar.value), field, comparisonMethod);
                }
            }
        }, 1);
      }
}

I can then just use it in my component by injecting it as a service, had to make it public in order for the markup to call it.

endDateFilter: Date | null;

constructor(
    public calendarService: CalendarService
) { }

Answered By – Andrew

Answer Checked By – Pedro (Easybugfix Volunteer)

Leave a Reply

(*) Required, Your email will not be published