2
votes

I am working on a Search-As-You-Type component for Ext.js. It features AND-filtering several words, each chained in an OR-filtering against any properties of the records; subsequently. When I type "Ke Ba" it will filter for "Kevin Bacon" and "Becky Kennedy".

I am loading the initial dataset in remote mode.

What happens is that the Data gets loaded into the store via my REST-Proxy and JSON Reader. Currently I do not "filter" on the server side but return everything and Mock the filterin in the client as if the dataset came filtered from the server. So it seems to the combobox.

Now when I change the search string to "Kre Ba", it will re-load from the REST-API (all data), but the Store will be empty to the combobox as I don't have any records matching "Kre Ba".

Somehow Ext-js decides to reset the value of the combobox and the whole search-string disappears.

Is there a neat way to keep the searchstring even when the store has no matching Items (value-wise) after filtering?!

Here is the Ext.js code I found that resets the value:

 onLoad: function(store, records, success) {
    var me = this;

    if (me.ignoreSelection > 0) {
        --me.ignoreSelection;
    }

    // If not querying using the raw field value, we can set the value now we have data
    if (success && !store.lastOptions.rawQuery) {
        // Set the value on load

        // There's no value.
        if (me.value == null) {
            // Highlight the first item in the list if autoSelect: true
            if (me.store.getCount()) {
                me.doAutoSelect();
            } else {
                // assign whatever empty value we have to prevent change from firing
                me.setValue(me.value);
            }
        } else {
            me.setValue(me.value);
        }
    }
},

During debugging I find that me.store.getCount() returns false for the check.

UPDATE:

Here is my Combobox config:

Ext.define('MyApp.view.SearchAsYouTypeCombobox', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.SearchAsYouTypeCombobox',

fieldLabel: 'Label',
hideLabel: true,
emptyText: 'Search',
hideTrigger: true,
minChars: 3,

initComponent: function() {
    var me = this;

    Ext.applyIf(me, {
            //some templates
    });

    me.callParent(arguments);
}

});

I am trying to build the component as a Controller where you only need to config a Combobox as View and a Store:

Ext.define('MyApp.controller.SearchAsYouTypeComboboxController', {
extend: 'Ext.app.Controller',

stores: [
    'SearchAsYouTypeStore'
],
views: [
    'SearchAsYouTypeCombobox'
],

onComboboxBeforeQuery1: function(queryPlan, eOpts) {
    var me = this;
    //determine tokens
    //add filter per token

    //One could optimize by not clearing the filter when the query String is only extended (leading or trailing) and not changed.
    //consider maybe the tokens 

    //console.log('beforeQuery');
    var comboBox = queryPlan.combo;
    var myStore = comboBox.getStore(); 

    var queryString = queryPlan.query;
    var prevQueryString = comboBox.lastQuery || '';

    var words = Ext.String.splitWords(queryString);

    //var filterAllPropertiesFilter = {};


    console.log('Query String :' + queryString);
    console.log("Words: " + words);

    //console.log(Ext.JSON.encode(myStore.storeId));

    //myStore.clearFilter(true);

    if(!Ext.isEmpty(queryString))
    {
        var filters = [];


        //When query has not been only expanded
        if(prevQueryString && 
        (queryString.length <= prevQueryString.length || 
        queryString.toLowerCase().lastIndexOf(prevQueryString.toLowerCase(), 0) !== 0))
        {
            //Reload everything new
            myStore.remoteFilter = true;
            myStore.clearFilter(true); //careful with this, it loads all the data unfiltered!


            myStore.remoteFilter = false;
            console.log('Creating Filters for each word.');
            Ext.Array.each(words,
            function(word, index, allWords){
                me.currentFilterWord = word;
                console.log('NEW FILTER: word:' + word);

                var filterAllPropertiesFilter = Ext.create('Ext.util.Filter',
                    {filterString: word,
                    filterFn: me.filterAllPropertiesFn});
                filters.push(filterAllPropertiesFilter);

            });

        }
        else{   
            if(!prevQueryString){
                myStore.remoteFilter = true;
                myStore.clearFilter();
            }
            myStore.remoteFilter = false;

            //only for the last word
            me.currentFilterWord = words[words.length-1];
            console.log('NEW FILTER: word: ' + me.currentFilterWord);

            var filterAllPropertiesFilter = Ext.create('Ext.util.Filter',
                {filterString: me.currentFilterWord,
                filterFn: me.filterAllPropertiesFn});

            filters.push(filterAllPropertiesFilter);
            Ext.Array.each(myStore.filters.items,
            function(filter, index, allFilters){
                myStore.removeFilter(filter, false);
            });
            //myStore.filter(filterAllPropertiesFilter);
        }

        myStore.filter(filters);
        comboBox.lastQuery = queryString;
        //filterBy(filters);
    }
    else
    {
        myStore.clearFilter(false);
    }

    comboBox.expand();
    queryPlan.cancel = true;
    return queryPlan;
},

filterAllPropertiesFn: function(item) {
    // filter magic
},

filterOverride: function(filters, value) {
    //changed the filter to subsequently filter 
},

init: function(application) {
    var me = this;
    console.log("init of Controller");

    //when more than 1 store then error
    console.log("Stores: " + this.stores[0]);

    var myStoreName = this.stores[0];
    var MyStore = me.getStore(myStoreName);

    console.log("MyStore: " + MyStore.storeId);

    Ext.override(MyStore,{override: myStoreName,
    filter: me.filterOverride});

    var myComboBoxName = this.views[0];
    var MyComboBox = me.getView(myComboBoxName);

    console.log("MyComboName: " + myComboBoxName);
    console.log("Data: " + MyStore.data.collect('id'));

    MyComboBox.store = MyStore;
    Ext.override(MyComboBox, {override: myComboBoxName,
    store: MyStore});

    console.log("myCombo.store: " + MyComboBox.store.storeId);


    this.control({
        "combobox": {
            beforequery: this.onComboboxBeforeQuery1,
        }
    });
},
});
2
What does your combobox config look like? i.e. do you have lastQuery: '', triggerAction: 'all' and forceSelection: false set?radtad
I have added them in the question. Thx.haynzz

2 Answers

0
votes

I made some trial and error...

It seems to work know with these changes to the comboboxes beforequery method. But somehow it does not "autoselect" the first match after the first local filtering, anymore.

    onComboboxBeforeQuery1: function(queryPlan, eOpts) {
    var me = this;

    var comboBox = queryPlan.combo;
    var myStore = comboBox.getStore(); 

    var queryString = queryPlan.query;
    var prevQueryString = comboBox.lastQuery || '';

    var words = Ext.String.splitWords(queryString);

    console.log('Query String :' + queryString);
    console.log("Words: " + words);A


    if(!Ext.isEmpty(queryString))
    {
        var filters = [];


        //When query string has not just only expanded
        if(prevQueryString && 
        (queryString.length <= prevQueryString.length || 
        queryString.toLowerCase().lastIndexOf(prevQueryString.toLowerCase(), 0) !== 0))
        {
            //Reload everything new
            //myStore.remoteFilter = true;
            myStore.clearFilter(false);
            //myStore.load();


            //myStore.remoteFilter = false;
            console.log('Creating Filters for each word.');
            Ext.Array.each(words,
            function(word, index, allWords){
                me.currentFilterWord = word;
                console.log('NEW FILTER: word:' + word);

                var filterAllPropertiesFilter = Ext.create('Ext.util.Filter',
                    {filterString: word,
                    filterFn: me.filterAllPropertiesFn});
                filters.push(filterAllPropertiesFilter);

            });

        }
        else{   
            if(!prevQueryString){
                myStore.remoteFilter = true;
                myStore.clearFilter();
            }
            myStore.remoteFilter = false;

            //only for the last word
            me.currentFilterWord = words[words.length-1];
            console.log('NEW FILTER: word: ' + me.currentFilterWord);

            var filterAllPropertiesFilter = Ext.create('Ext.util.Filter',
                {filterString: me.currentFilterWord,
                filterFn: me.filterAllPropertiesFn});

            filters.push(filterAllPropertiesFilter);
            Ext.Array.each(myStore.filters.items,
            function(filter, index, allFilters){
                myStore.removeFilter(filter, false);
            });
        }

        myStore.filter(filters);
        comboBox.lastQuery = queryString;
    }
    else
    {
        myStore.clearFilter(false);
    }

    comboBox.expand();
    queryPlan.cancel = true;
    return queryPlan;
}
0
votes

I think you're trying to do too much. If you're extending the combobox, why don't you just override the one function you need to use?

For example, I have similar functionality as you want for your combobox in someting I wrote. I have a store for a combo on autoLoad to get the data. After that, if I type anything it forces doLocalQuery to be fired since I have queryMode: 'local'. I am overriding that function and replacing one section:

    var me = this,
        queryString = queryPlan.query,
        picker = me.getPicker();

    /** 
     * @author tjohnston
     * added queryPlan to combo so we can use it later 
     * added query and caseSensitive to picker (boundlist) to use in highlighting
     */
    me.queryPlan = queryPlan;
    picker.query = queryString;
    picker.caseSensitive = me.caseSensitive;

    // Create our filter when first needed
    if (!me.queryFilter) {
        // Create the filter that we will use during typing to filter the Store
        me.queryFilter = new Ext.util.Filter({
            id: me.id + '-query-filter',
            anyMatch: me.anyMatch,
            combo: me,
            caseSensitive: me.caseSensitive,
            root: 'data',
            /**
             * removed property as we're using filterFn
             * added filterFn functionality 
             */
            /** property: me.displayField, **/
            filterFn: function(rec) {
                return me.filterFn(me,rec);
            }
        });
        me.store.addFilter(me.queryFilter, false);
    }

This then it turn calls 2 functions:

/** @public **/
/** config for any instance we create if we want to override. **/
filterTest: function(combo,str,regex,rec) {
    return regex.test(rec.data[combo.displayField]);
},

/** @private **/
/** our filter function for **/
filterFn: function(combo,rec) {
    var me = this,
        queryPlan = combo.queryPlan,
        anyMatch = me.anyMatch,
        caseSensitive = me.caseSensitive ? '' : 'i',
        queryString = anyMatch ? queryPlan.query.split(/\s+/) : '^'+queryPlan.query,
        total = queryString.length,
        isNoMatch = 0,
        isMatch = 0,
        i = 0;

    /** make sure we filter for each text string in the query **/
    Ext.each(queryString, function(str) {
        if (str !== '') {
        //if (str !== '' && str.length >= combo.minChars) {
            var query = new RegExp(str,caseSensitive+'g');
            if (combo.filterTest(combo,str,query,rec)) {
                isMatch++;
            } else {
                isNoMatch++;
            }
        }
    });

    if (isNoMatch > 0) {
        /** we match in OR so if we have one match from any query word, return true **/
        if (combo.orMatch == true && isMatch > 0 ) {
            return true;
        }
        return false;
    } else {
        return true;
    }
},