Our blazing fast Grid component built with pure JavaScript


Post by jintech »

Hi,
I have been investigating the grid filters in Bryntum. We can define the store, field, and filtering functionality based on our requirements. However, I don't see any property or function to customize what we show in those filters.

For example, if I have an assignee/project manager column, I can make a separate store for those members and set that in the filterable field so that when I click on the filter icon, I see the dropdown containing all the assignee names. However, I cannot customize that dropdown to show assignee avatar and name for each row and then be able to filter based on user's selection

The same goes for the date filter. If the column type is set to date, I can see the date filter I want i.e.

date_filter.png
date_filter.png (120.93 KiB) Viewed 422 times

However, in my case I have set the date column type to template as I am doing some custom rendering and formatting which cannot be achieved if I use type: date.

Main Objective.

  1. Be able to customize filters so that I can display what I want in those filters. For example for assignee I should be able to show the avatar and name in dropdown menu.
  2. Be able to view the date filter as shown in the above screenshot if column type is set to template
  3. Customize what options I want to show in the first field of the date filter
    date_filter_options.png
    date_filter_options.png (133.75 KiB) Viewed 422 times

Post by mats »

However, in my case I have set the date column type to template as I am doing some custom rendering and formatting which cannot be achieved if I use type: date.

Let's start with this one, could you please share your objective and what didn't work regarding custom rendering / format?


Post by jintech »

The below code is a reference from bryntum of how I am defining the date folumn

{    
text: "Deadline", field: 'start', type: 'template', editor: false, minWidth: 80, width: 80, align: 'left', hideable: true, // Set to false to prevent the user from hiding the column resizable: false, filterable: true, groupable: true, headerRenderer: ({ column, headerElement }) => { headerElement.setAttribute('data-btip', column.text); return column.text; }, template: ({value}) => `${value}`, filterable: { filterField : { type : 'date' }
} },

As a result, when I click on the filter icon on this column I get the following filter view

current_date_filter.png
current_date_filter.png (131.65 KiB) Viewed 381 times

My desired date filter for this column while keeping the column type as template and keeping other properties as is should be like this. Also I want to modify the options that are visible in the first dropdown of the below filter

desired_date_filter.png
desired_date_filter.png (133.92 KiB) Viewed 381 times

Post by emil »

Hi jintech,

Have you explored the following configuration options?

  1. To customize each filter input's value field : https://www.bryntum.com/products/gantt/docs/api/Core/widget/FieldFilterPicker#config-getValueFieldConfig
  2. To override a column's filter data type (e.g. to "date") : https://www.bryntum.com/products/gantt/docs/api/Grid/column/Column#config-filterType
  3. To customize operator drop-down options :
    https://www.bryntum.com/products/gantt/docs/api/Core/widget/FieldFilterPicker#config-operators

To use the new filter UI you cannot have filterField in your column config.

You can provide FieldFilterPicker configuration to the Filter feature by passing a getFieldFilterPickerConfig function as shown in the code for the "Field Filters" example app: https://bryntum.com/products/grid/examples/fieldfilters/

In this case your getFieldFilterPickerConfig function would return a config object that includes, e.g., getValueFieldConfig or operators.


Post by jintech »

I was able to show the date filter I wanted by simply removing the filterFn and setting filterType to date. However, when I select a date from the filter and apply that filter, no records are shown even though I have records with the same dates present.

In the custom template function for the dates, I am performing custom date rendering which also involves changing the date format.
On the frontend the HTML rendered is something like this

<span class="badge badge-default" style=" background-color:#d9534f20;; color:#d9534f;display:inline-block; padding:.2em .6em .3em;">17 Nov 23 </span>

For this do I need to write a custom function which is able to filter the dates correctly?


Post by emil »

The filtering should be using the data values from the underlying Model, using the field defined for your column (in your code snippet above it's called start), so the custom renderer should not be involved at all. Are the values in your start field JavaScript Dates? Could you provide an example of your data?

From what you wrote, it sounds like the filter gets applied and then rows disappear (get filtered out), so something is happening, we just need to understand why the filter is not matching. Is that correct?


Post by jintech »

This is what the data looks like for my grid.

grid data.png
grid data.png (77.07 KiB) Viewed 300 times

The start field name was just a reference from the Bryntum grid examples. On my grid, I have two date columns where I want to apply the desired date filter

  • due_date

  • required_by_date


Post by jintech »

For columns containing avatar, we can show the list of users in the filter dropdown like this

assignee_column.png
assignee_column.png (236.79 KiB) Viewed 287 times

is there a way we can show the avatar as well along with the name in the dropdown. Something like this

avatar_dropdown.png
avatar_dropdown.png (2.65 KiB) Viewed 287 times

Also when any option is selected, show the avatar instead of the name in the selected options for the filter


Post by emil »

jintech wrote: Tue Apr 02, 2024 10:15 am

This is what the data looks like for my grid.

Thanks. When I take the fieldfilters example app and change the start column to type template and filterType to date the date filtering seems to work, so I'm not sure what might be happening in your app. Are you able to create a reduced test case with a row or two of data that you can share so I can look into it?

jintech wrote: Tue Apr 02, 2024 1:10 pm

is there a way we can show the avatar as well along with the name in the dropdown

For this you would need to use filterable : { filterField : { type : 'combo', ... }} in your column definition. This will use the legacy filter UI as shown in your screenshot. You would provide the necessary config for the combo field. Here's an example:

filterable : {
    filterField : {
        type : 'combo',
        items : [{ value: 'stockholm', text: 'Stockholm' }, { value: 'helsinki', text: 'Helsinki' }],
        listItemTpl : record => `
            <div class="b-resource-item">
                <span class="avatar"><img src="/cities/${record.value}" /></span>
                <span>${record.text}</span>
            </div>
        `,
    }
}

Instead of static data you'd want to configure the combo with a store, possibly your ResourceStore.


Post by jintech »

Thanks, emil. I was able to implement the avatar as I wanted and filter the records as expected.

For the date filter issue, I believe it has to do with the date format. On the bryntum example, the format of the start field is as follows
"2019-01-25T19:00:00.000Z"
While I am getting the date value from my database in the following format
"2024-02-08 05:58:00+00"

When I add filterFn inside the filterable config for the date column, the date type changes.
Morover, I am not able to limit the options I have to show the first dropdown for the date. You did share the following link https://www.bryntum.com/products/gantt/docs/api/Core/widget/FieldFilterPicker#config-operators but I cant see any practical implementation for a grid column. Can you provide some example reference code which I can modify according to my requirements?

For reference you can try to view the below code which was taken from https://bryntum.com/products/grid/examples/project-summary/

import { AjaxStore, Grid, DateHelper, Tooltip } from '../../build/grid.module.js?474871';
import shared from '../_shared/shared.module.js?474871';

const
    // Currency formatter
    formatUSD    = new Intl.NumberFormat('en-US', {
        style                 : 'currency',
        currency              : 'USD',
        maximumFractionDigits : 0
    }).format,
    memberStore  = new AjaxStore({
        sorters : ['name'],
        readUrl : 'data/members.json'
    }),
    statuses     = ['Not started', 'In progress', 'Paused', 'Reviewing', 'Completed'],
    statusColors = ['gray', 'blue', 'yellow', 'light-blue', 'green'],
    getAvatars   = id => {
        // Creates the member image icons
        const member = typeof id === 'number' ? memberStore.find(m => m.id === id) : id;

    return member ? {
        class       : 'avatar',
        elementData : member,
        style       : `--background: url(../../_shared/images/users/${member.photo})`
    } : '';
},
grid         = new Grid({
    appendTo : 'container',

    selectionMode : {
        cell         : false,
        checkbox     : true,
        showCheckAll : true
    },

    features : {
        cellEdit  : true,
        filter : true
    },

    rowHeight   : 60,
    columnLines : false,

    columns : [
        {
            text  : 'Project',
            field : 'project',
            ref   : 'projectFilter',
            flex  : '1 1 18em',
            renderer({ record }) {
                return [
                    {
                        tag       : 'i',
                        className : `b-fa b-fa-fw b-fa-${record.icon || 'question'} category-icon`
                    },
                    record.project,
                    {
                        className : 'location',
                        text      : record.location
                    }
                ];
            }
        },
        {
            text  : 'Status',
            field : 'status',
            ref   : 'statusFilter',
            flex  : '1 1 8em',
            renderer({ value, row }) {
                const clsAction = value === 'Completed' ? 'addCls' : 'removeCls';
                row[clsAction]('b-completed');
                return {
                    class : 'badge',
                    style : {
                        backgroundColor : `var(--${statusColors[statuses.indexOf(value)]})`
                    },
                    text : value
                };
            },
            editor : {
                type     : 'combo',
                editable : false,
                items    : statuses
            },
            // Changing the header filter field to a combo
            filterable : {
                filterField : {
                    type     : 'combo',
                    editable : false,
                    items    : statuses
                }
            }
        },
        {
            text   : 'Start',
            ref    : 'startFilter',
            field  : 'startDate',
            type   : 'date',
            format : 'MMM DD',
            flex   : '1 1 6em',
            filterable : {
              filterFn: ({ record, value }) => {
		debugger;  			
	   }
            }
        },
        {
            text     : 'Est. cost',
            ref      : 'costFilter',
            field    : 'estimatedCost',
            type     : 'number',
            flex     : '1 1 5em',
            renderer : ({ value }) => value ? formatUSD(value) : ''
        },
        {
            text       : 'Total cost',
            ref        : 'totalFilter',
            field      : 'cost',
            type       : 'number',
            flex       : '1 1 5em',
            htmlEncode : false, // Allows HTML in renderer
            renderer({ value, record : { estimatedCost } }) {
                return value && value !== estimatedCost
                    ? `<i class="change-indicator b-fa b-fa-caret-${value > estimatedCost ? 'up' : 'down'}"></i> ${formatUSD(value)}`
                    : '';
            }
        },
        {
            text  : 'Progress, %',
            ref   : 'progressFilter',
            field : 'progress',
            flex  : '1 1 4em',
            type  : 'percent'
        },
        {
            text   : 'Team members',
            field  : 'members',
            flex   : '1 1 10em',
            editor : {
                type         : 'combo',
                multiSelect  : true,
                editable     : false,
                store        : memberStore,
                displayField : 'name'
            },
            renderer({ value }) {
                return value?.length ? {
                    class    : 'avatar-container',
                    children : value.map(getAvatars)
                } : '';
            },
            // Changing head filter field to combobox
            filterable : {
                filterField : {
                    type         : 'combo',
                    editable     : false,
                    store        : memberStore.chain(), // Chain to allow this store to be filtered separately
                    valueField   : 'id',
                    displayField : 'name'
                },

                // Need to change how the filter is applied as well
                filterFn : ({ record, value }) => record.members?.includes(value * 1)
            }
        }
    ],

    store : {
        readUrl   : 'data/projects.json',
        autoLoad  : true,
        listeners : {
            async load({ source }) {
                // Calculates start months with the base of current month
                source.forEach(record => {
                    record.startDate = DateHelper.add(DateHelper.startOf(new Date(), 'month'), record.start, 'month');
                });

                // Load members to store
                await memberStore.load();
                grid.refreshRows?.();
            }
        }
    }
});

Tooltip.new({
    forElement  : grid.element,
    forSelector : '.avatar',
    listeners   : {
        beforeShow({ source }) {
            source.html = source.activeTarget.elementData.name;
        }
    }
});

Adding even filterFn inside the filterable field changes the filter from

date_filter_before.png
date_filter_before.png (242.03 KiB) Viewed 226 times

to this

date_filter_after.png
date_filter_after.png (242.51 KiB) Viewed 226 times

Post Reply