Our pure JavaScript Scheduler component


Post by henrikbackman »

Hi,

We use the filterBar with a customized "store filtering" since the standard filter doesn't work with our data. When we enter text into the field the filtering happens, but right after that the field is emptied but the filtering remains. We don't have any code that does the clearing of the value.

The question is: how do we prevent that from happening?

Thanks!

Post by pmiklashevich »

Please provide a small test case based on this demo: https://www.bryntum.com/examples/scheduler/filtering/

Pavlo Miklashevych
Sr. Frontend Developer


Post by henrikbackman »

I use https://www.bryntum.com/docs/scheduler/ ... ieldChange for the input fields in the filterBar. When I do that (and the script detects that it is a `userAction`) I loop trough the scheduler.store.filter and return true/false if there is a match or not. When that is done the field value is emptied (but the filter remains).

Am I using the wrong function to detect changes to the input field or is there some way of preventing the field from being reset?

Post by henrikbackman »

Now I've tried it in the filtering example and the error occurs there as well. Here is the code:
import { DateHelper,WidgetHelper,Scheduler,DomClassList } from '../../build/scheduler.module.js';
import shared from '../_shared/shared.module.js';

let scheduler;

var _filterSpecificColumn = function(filterValue, column, field) {
    var i, dataText, dataColumn;

    scheduler.store.filter(function(storeData) {
        // return true;

        dataColumn = storeData[column];

        if (Array.isArray(dataColumn)) {
            for (i = 0; i < dataColumn.length; i++) {
                dataText = dataColumn[i].data.text || '';

                if (_compareFilterString(dataText, filterValue)) {
                    return true;
                }
            }
        } else if (typeof dataColumn === 'string') {
            return _compareFilterString(dataColumn, filterValue);
        } else {
            dataText = dataColumn.text || '';

            return _compareFilterString(dataText, filterValue);
        }

        return false;
    });
};

var _compareFilterString = function(dataText, filterValue) {
    dataText    = dataText.toLowerCase();
    filterValue = filterValue.toLowerCase();

    if (dataText.indexOf(filterValue) !==  -1) {
        return true;
    }

    return false;
};

//region Widgets

WidgetHelper.append([{
    type                 : 'textfield',
    id                   : 'filterByName',
    cls                  : 'b-bright',
    placeholder          : 'Find tasks by name',
    clearable            : true,
    keyStrokeChangeDelay : 100,
    onChange             : ({ value }) => {
        value = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

        scheduler.eventStore.filter(event => event.name.match(new RegExp(value, 'i')));
    }
}, {
    type                 : 'textfield',
    id                   : 'highlight',
    cls                  : 'b-bright',
    placeholder          : 'Highlight tasks',
    clearable            : true,
    keyStrokeChangeDelay : 100,
    onChange             : ({ value }) => {
        scheduler.eventStore.forEach(task => {
            const taskClassList = new DomClassList(task.cls),
                matched = taskClassList['b-match'];

            if (task.name.toLowerCase().indexOf(value) >= 0) {
                if (!matched) {
                    taskClassList.add('b-match');
                }
            }
            else if (matched) {
                taskClassList.remove('b-match');
            }
            task.cls = taskClassList.value;
        });
        scheduler.element.classList[value.length > 0 ? 'add' : 'remove']('b-highlighting');
    }
}], { insertFirst : document.getElementById('tools') || document.body });

//endregion

scheduler = new Scheduler({

    appendTo   : 'container',
    minHeight  : '20em',
    eventStyle : 'colored',
    eventColor : null,

    features : {
        filterBar: {
            onColumnFilterFieldChange: function(eventData) {
                if (eventData.userAction) {
                    var value   = eventData.value,
                        column  = eventData.source.name,
                        field   = eventData.event.target;

                    _filterSpecificColumn(value, column, field);
                }
            },
        },
        stripe     : true,
        timeRanges : true,
        eventEdit  : {
            extraWidgets : [
                { type : 'text', name : 'location', label : 'Location', dataset : { eventType : 'Meeting' } }
            ]
        }
    },

    columns : [{
        type      : 'resourceInfo',
        imagePath : '../_shared/images/users/',
        text      : 'Staff',
        width     : 170
    }, {
        text   : 'Role',
        field  : 'role',
        width  : 140,
        editor : {
            type        : 'combo',
            items       : ['Sales', 'Developer', 'Marketing', 'Product manager'],
            editable    : false,
            pickerWidth : 140
        }
    }],

    crudManager : {
        autoLoad  : true,
        transport : {
            load : {
                url : 'data/data.json'
            }
        }
    },

    barMargin : 5,
    rowHeight : 55,

    startDate  : new Date(2017, 1, 7, 8),
    endDate    : new Date(2017, 1, 7, 18),
    viewPreset : 'hourAndDay',

    // Specialized body template with header and footer
    eventBodyTemplate : data => `
        <div class="b-sch-event-header">${data.headerText}</div>
        <div class="b-sch-event-footer">${data.footerText}</div>
    `,

    eventRenderer({ eventRecord, resourceRecord, tplData }) {
        tplData.style = 'background-color:' + resourceRecord.color;

        return {
            headerText : DateHelper.format(eventRecord.startDate, this.displayDateFormat),
            footerText : eventRecord.name || ''
        };
    }
});
It has nothing to do with our code. Uncomment the "return true;" in the "scheduler.store.filter" function to verify that.

Post by pmiklashevich »

Hello,

Thanks for applying the test case. It helps a lot to understand what is going on. I've checked the test case and here is what I'd like to say:

First of all onColumnFilterFieldChange is a private function and it's not supposed to be overridden.

If you still wish to override it, please follow the override concept.

If you look at the original onColumnFilterFieldChange function code you'll see that we suspend store tracking while we are filtering.
    onColumnFilterFieldChange({ source : field, value }) {
        const me   = this,
            grid = me.grid;

        // we don't want to hear back store "filter" event
        // so we suspend store tracking
        me.suspendStoreTracking();

        if (value == null || value === '') {
            // remove filter if setting to empty
            grid.store.removeFieldFilter(field.name);
        }
        else {
            grid.store.filter(Object.assign({
                property : field.name
            }, me.parseFilterValue(value)));
        }

        me.resumeStoreTracking();
    }
So adding suspendStoreTracking/resumeStoreTracking to your custom function will help to prevent the input from clearing.
class FilterBarOverride {
    static get target() {
        return {
            class      : FilterBar,
            product    : 'scheduler',
            minVersion : '1.2.2',
            maxVersion : '1.2.2'
        };
    }

    onColumnFilterFieldChange(eventData) {
        this.suspendStoreTracking();

        // In case you need to call parent
        // this._overridden.onColumnFilterFieldChange.call(this, eventData);

        if (eventData.userAction) {
            var value  = eventData.value,
                column = eventData.source.name,
                field  = eventData.event.target;

            _filterSpecificColumn(value, column, field);
        }

        this.resumeStoreTracking();
    }
}

Override.apply(FilterBarOverride);
Hope this will help you.

Best,
Pavel

Pavlo Miklashevych
Sr. Frontend Developer


Post by pmiklashevich »

Also please keep in mind that every store supports filterBy function where you can describe your complex filtering.

Pavlo Miklashevych
Sr. Frontend Developer


Post by henrikbackman »

Ok! But is there any other event I can use in order to avoid having to override your function? Like onChange or something?

Post by henrikbackman »

What is "this" in the "FilterBarOverride" solution? We can't use ES6 at the moment. How do I fix my problem? Alternatively, when will you support the filtering in a string, array and object?

Post by pmiklashevich »

Please see this event: https://www.bryntum.com/docs/scheduler/ ... ent-filter

"this" in FilterBarOverride is FilterBar feature. The scope is bound in constructor of the FilterBar.
    onColumnFilterFieldChange(eventData) {
        console.log(this.$name); // FilterBar

Pavlo Miklashevych
Sr. Frontend Developer


Post by henrikbackman »

this.$name
is in our case not "FilterBar" but "TextField". I'll use my own solution instead.

Post Reply