6
votes

Using Ext.ux.grid.FiltersFeature, I have remote filters and I am trying to write a function to apply a date filter on a grid column programmatically (rather than clicking on the filter drop down menu in the column header). The first time I run the function the grid store gets reloaded without the filter. When I run the function a second time (and every time thereafter) it works totally fine, the store reloads with the filters. Here is the gist of the function I have:

// a filter object for testing
aFilter = {type: 'date', field: 'a_date_field', comparison: 'gt', value: '2012-03-08 00:00:00'}

var grid = Ext.create('Ext.grid.Panel', {
    store: store,
    features: [{
        ftype: 'filters',
    }],
    columns[{
        header: 'ID',
        dataIndex: 'id',
        itemId: 'id',            
        width: 40,
    }, {
        xtype: 'datecolumn',
        header: 'Date',
        dataIndex: 'a_date_field',
        itemId: 'a_date_field',
        width: 75,
        format:'j-M-Y',
        filterable: true
    }],
    listeners: {
        'afterrender': function() {

            // Need to create the filters as soon as the grid renders
            // rather than waiting for the user to click on the header
            grid.filters.createFilters();
        }
    },
    bbar: [{
        text: 'Do a filter',
        handler: function() {

            // get the filter that is attached to the grid
            var gridFilter = grid.filters.getFilter(aFilter.field);

            // have to do this to create a menu for this filter
            gridFilter.init({dataIndex: aFilter.field, type: aFilter.type, active: true});

            // if this column is a date filter column
            if (gridFilter.type == 'date') {
                var dateValue = Ext.Date.parse(aFilter.value, 'Y-m-d H:i:s');
                if (filter.comparison == 'gt') {
                    gridFilter.setValue({after: dateValue});
                } else {
                    gridFilter.setValue({before: dateValue});
                }
            }
        }
    }
});

I also found that this function works the first time if I click on any grid header menu before I run the function.

I've been trying to find out what changes are made to the grid which make the filter work after the first attempt fails or what clicking on any grid header does to make it work. But nothing I add seems to fix it so it will run the first time. Has anyone implemented this successfully?

7
Have you tried calling createFilter() on the FiltersFeature object?Dave Cole

7 Answers

9
votes

I have workaround:

bbar: [{
    text: 'Do a filter',
    handler: function() {
        var grid = this.up('grid');
        var dateValue = Ext.Date.parse(aFilter.value, 'Y-m-d H:i:s');
        var value = aFilter.comparison == 'gt' ? {after: dateValue} : {before: dateValue};

        var gridFilter = grid.filters.getFilter(aFilter.field);

        if (!gridFilter) {
            gridFilter = grid.filters.addFilter({
                active: true,
                type: aFilter.type,
                dataIndex: aFilter.dataIndex,
            });

            gridFilter.menu.show();
            gridFilter.setValue(value);
            gridFilter.menu.hide();
        } else {
            gridFilter.setActive(true);
        }

        Ext.Function.defer(function(){
            gridFilter = grid.filters.getFilter(aFilter.field);
            gridFilter.setValue(value);
        }, 10);
    }
}]

As you can see I actually apply filter 2 times.

6
votes

As an update, I expanded this function and modified it to work with ExtJS 4.1.1

Here is an example of the function to set grid filters dynamically (without the user needing to click on the menu items). Afterwards, the filtered items will be visible to the user in the grid column header menus as if he clicked on them and set them manually.

The "grid" argument is a grid with FiltersFeature that you want to filter. The other argument is an array of "filter" objects (I'll show an example below), the function simply applies all the passed "filter" objects to the grid.

doGridFilter: function(grid, filters) {

    // for each filter object in the array
    Ext.each(filters, function(filter) {
        var gridFilter = grid.filters.getFilter(filter.field);

        gridFilter.setActive(true);
        switch(filter.data.type) {

            case 'date':
                var dateValue = Ext.Date.parse(filter.data.value, 'm/d/Y'),
                    value;

                switch (filter.data.comparison) {

                    case 'gt' :
                        value = {after: dateValue};
                        break;
                    case 'lt' :
                        value = {before: dateValue};
                        break;
                    case 'eq' :
                        value = {on: dateValue};
                        break;
                }
                gridFilter = log.filters.getFilter(filter.field);
                gridFilter.setValue(value);
                gridFilter.setActive(true);
                break;

            case 'numeric':
                var value;

                switch (filter.data.comparison) {

                    case 'gt' :
                        value = {gt: filter.data.value};
                        break;
                    case 'lt' :
                        value = {lt: filter.data.value};
                        break;
                    case 'eq' :
                        value = {eq: filter.data.value};
                        break;
                }
                gridFilter = log.filters.getFilter(filter.field);
                gridFilter.setValue(value);
                gridFilter.setActive(true);
                break;

            case 'list':
                gridFilter = log.filters.getFilter(filter.field);
                gridFilter.menu.setSelected(gridFilter.menu.selected, false);
                gridFilter.menu.setSelected(filter.data.value.split(','), true);
                break;

            default :
                gridFilter = log.filters.getFilter(filter.field);
                gridFilter.setValue(filter.data.value);
                break;
        }
    });
}

Here's an example of a "filter" object array.

// an example of a "filters" argument
[{
    field: 'some_list_column_data_index',
    data: {
        type: 'list',
        value: 'item1,item2,item3,item4,item5,item6,item7'
    }
}, {
    field: 'some_date_column_data_index',
    data: {
        type: 'date',
        comparison: 'gt',
        value: '07/07/2007'
    }
}]

One caveat, you need to "create" the filters manually before using this function. Normally FiltersFeature grid filters are "created" the first time a user clicks on one of them, that may not happen if the user just wants to apply one of these predefined filters.

That can be handled easily by including this afterrender listener in the gridpanel.

listeners: {

    // must create the filters after grid is rendered
    afterrender: function(grid) {
        grid.filters.createFilters();
    }
}
3
votes

Just add

filter: true

to grid columns description like this:

me.columns = [
        {header:"Name", dataIndex:"name", editor:"textfield", filter: true},
    ];

if you want to get the filter work after the first attempt, first instance create.

1
votes

Here is something that may be worth looking into. It seems that the filters plugin is listening for menucreate event to initialize the filters. I wonder if menu create event is deferred until necessary and hence the filters don't get initialized?

/**
 * @private Handle creation of the grid's header menu. Initializes the filters and listens
 * for the menu being shown.
 */
onMenuCreate: function(headerCt, menu) {
    var me = this;
    me.createFilters(); //<------
    menu.on('beforeshow', me.onMenuBeforeShow, me);
},
0
votes

Do you want to apply grid filter or may be store.filter() capability would suit you better? In this case just filter the store, and grid will display filtered records.

0
votes

I discovered another way to implement this. It appears that grid features are only bound to the grid after the grid is rendered. This means that any setup of the filter will not take effect until after the grid is rendered. The initial load of the store appears to be initiated before the grid is rendered.

I solved my problem by creating my store with a memory proxy containing no data.

me.store = Ext.create('Ext.data.Store', {
    model: 'SummaryData',
    data: [],
    proxy: {
        type: 'memory',
        reader: 'array'
    },
    remoteSort: true,
    remoteFilter: true
});

Then set up an afterrender handler on the grid to poke in the correct proxy and initiate a load of the store.

afterrender: function () {
    var me = this;

    me.store.setProxy({
        type: 'ajax',
        url : '/print_unallocated/change_site__data',
        reader: {
            type: 'json',
            root: 'rows'
        },
        listeners: {
            exception: function (proxy, response) {
                Max.reportException(response);
            }
        }
    });

    me.filters.createFilters();
    me.store.load();
},
0
votes

In the source, you can see a comment related to this.

// Call getMenu() to ensure the menu is created, and so, also are the filters. We cannot call
// createFilters() withouth having a menu because it will cause in a recursion to applyState()
// that ends up to clear all the filter values. This is likely to happen when we reorder a column
// and then add a new filter before the menu is recreated.
me.view.headerCt.getMenu();

You can test whether the menu has been created before applying your filter. If it hasn't, do it yourself.

if(!grid.getView().headerCt.menu){
    grid.getView().headerCt.getMenu();
}