1. #1
    Ext User
    Join Date
    Apr 2008
    Posts
    2
    Vote Rating
    0
    neves is on a distinguished road

      0  

    Default combobox typeahead search not only the beginning of string

    combobox typeahead search not only the beginning of string


    On this sample:
    http://extjs.com/deploy/dev/examples/form/combos.html

    How can I start writing dakota por example, and the combo filter the state South Dakota?
    The same for writing sas and it filter Kansas.

    Filter by work ocurring not only the beginning of string, but in the middle.

  2. #2
    jay@moduscreate.com's Avatar
    Join Date
    Mar 2007
    Location
    Frederick MD, NYC, DC
    Posts
    16,353
    Vote Rating
    77
    jay@moduscreate.com is a name known to all jay@moduscreate.com is a name known to all jay@moduscreate.com is a name known to all jay@moduscreate.com is a name known to all jay@moduscreate.com is a name known to all jay@moduscreate.com is a name known to all

      0  

    Default


    if it's remote, then the answer is easy.

    If it's local, you'll have to look at modifying the regex in the combobox code.

  3. #3
    Sencha User NightAvatar's Avatar
    Join Date
    Nov 2008
    Location
    Norway
    Posts
    206
    Vote Rating
    0
    NightAvatar is on a distinguished road

      0  

    Default A real answer please

    A real answer please


    I have the same question and hope somebody can be a little more helpful. I didn't get anything out of jgarcia's reply.

    Is he teasing us? "I have the answer, but I won't tell you!"

    Could anybody explain how this is done? It's one of the 2 or 3 factors that will decide if the company I work for will buy a team license.

    Thanks!

  4. #4
    Sencha User BitPoet's Avatar
    Join Date
    Sep 2008
    Location
    Bavaria
    Posts
    277
    Vote Rating
    1
    BitPoet is on a distinguished road

      0  

    Default


    Most probably jgarcia hasn't the answer at hand, but his suggestion to start with the ComboBox code itself is correct. By extending the ComboBox code, it's a matter of a bit copy&paste and a few keystrokes to get everything working, as the ComboBox just calls the Store's "filter" method which is already able to match inside strings.

    If you put this on the top of your script (real changes to the original code in red/bold):
    Code:
    Ext.override( Ext.form.ComboBox, {
        anyMatch: false,
        doQuery : function(q, forceAll){
            if(q === undefined || q === null){
                q = '';
            }
            var qe = {
                query: q,
                forceAll: forceAll,
                combo: this,
                cancel:false
            };
            if(this.fireEvent('beforequery', qe)===false || qe.cancel){
                return false;
            }
            q = qe.query;
            forceAll = qe.forceAll;
            if(forceAll === true || (q.length >= this.minChars)){
                if(this.lastQuery !== q){
                    this.lastQuery = q;
                    if(this.mode == 'local'){
                        this.selectedIndex = -1;
                        if(forceAll){
                            this.store.clearFilter();
                        }else{
                            this.store.filter(this.displayField, q, this.anyMatch);
                        }
                        this.onLoad();
                    }else{
                        this.store.baseParams[this.queryParam] = q;
                        this.store.load({
                            params: this.getParams(q)
                        });
                        this.expand();
                    }
                }else{
                    this.selectedIndex = -1;
                    this.onLoad();
                }
            }
        }
    });
    and use the ComboBox like this:
    Code:
    var cbTesting = new Ext.form.ComboBox({
            typeAhead:    true,
            anyMatch:    true,
            store:        ssTest,
            mode:        'local',
            width:        120,
            listWidth:    120,
            displayField:    'key',
            valueField:    'value'
    });
    it should work.

  5. #5
    Sencha User NightAvatar's Avatar
    Join Date
    Nov 2008
    Location
    Norway
    Posts
    206
    Vote Rating
    0
    NightAvatar is on a distinguished road

      0  

    Default Thanks for your help!

    Thanks for your help!


    I appreciate your response with some code and example for us. I imagine for those who have this figured out and have used extjs for some time find we seem like lazy amateurs. I have spent a few hours looking at the code and so far am unable to figure out where you mean I should edit to get it to work this way.

    I am using the latest build, 2.2.


    EDIT:

    I got it to work by making your edits to examples\form\combos.js

    I see it introduces some new usability issues that need to be addressed for it to really work.

    For example, typing in "ne" and then pausing, selects "Connecticut" with "Co" (the first two characters) not highlighted and the rest of the name highlighted/selected. So if I pause and then continue typing (expecting to continue from "ne") I actually end up typing "Co" + what ever I type next. So "New" becomes "Cow".

    I hope you understand what I mean.

    The following changes/additions would fix this issue:
    1. No match from the list should be selected/highlighted by default. (today the first match in the list is selected)
    2. Nothing the user types should be overridden / erased / replaced by something else to match.
    3. What the user types (example "ne") should be highlighted in the list below. Preferably with span tags surrounding it and some class so we can decide how it looks. (Example red, bold or italic, etc).
    Not that I expect somebody to do the above changes for me, but maybe some of this is already built in, and I just need to define the setting to true or false?

    Does anybody know where I would start or how much work this would be? Especially the last point. I guess it would go under "customizing templates"? Even though it's so minimal as to be nearly identical to the default template/look.

  6. #6
    Sencha User NightAvatar's Avatar
    Join Date
    Nov 2008
    Location
    Norway
    Posts
    206
    Vote Rating
    0
    NightAvatar is on a distinguished road

      0  

    Default typeAhead

    typeAhead


    I realize typeAhead is what selects the first match from the list while you type. So setting that to false solved point 2 from the list above.

    All that is left is to prevent automatic selection from the list, and to find a way to highlight what is typed so the users can easier see why/how the list matches what they type.

    Is there a way to wrap span tags around the matching text? Or EM tags? Something?

  7. #7
    Sencha User BitPoet's Avatar
    Join Date
    Sep 2008
    Location
    Bavaria
    Posts
    277
    Vote Rating
    1
    BitPoet is on a distinguished road

      0  

    Default


    Quote Originally Posted by NightAvatar View Post
    I realize typeAhead is what selects the first match from the list while you type. So setting that to false solved point 2 from the list above.

    All that is left is to prevent automatic selection from the list, and to find a way to highlight what is typed so the users can easier see why/how the list matches what they type.

    Is there a way to wrap span tags around the matching text? Or EM tags? Something?
    You can solve the automatic selection issue by overriding the ComboBox's onTypeAhead method (that's where the selected item is filled into the combo's text field) with an empty function, and this also brought me to the idea of abusing the onTypeAhead to replace the DataView's content with the highlighted version of each entry:
    Code:
    Ext.override( Ext.form.ComboBox, {
        anyMatch: false,
        doQuery : function(q, forceAll){
            if(q === undefined || q === null){
                q = '';
            }
            var qe = {
                query: q,
                forceAll: forceAll,
                combo: this,
                cancel:false
            };
            if(this.fireEvent('beforequery', qe)===false || qe.cancel){
                return false;
            }
            q = qe.query;
            forceAll = qe.forceAll;
            if(forceAll === true || (q.length >= this.minChars)){
                if(this.lastQuery !== q){
                    this.lastQuery = q;
                    if(this.mode == 'local'){
                        this.selectedIndex = -1;
                        if(forceAll){
                            this.store.clearFilter();
                        }else{
                            this.store.filter(this.displayField, q, this.anyMatch);
                        }
                        this.onLoad();
                    }else{
                        this.store.baseParams[this.queryParam] = q;
                        this.store.load({
                            params: this.getParams(q)
                        });
                        this.expand();
                    }
                }else{
                    this.selectedIndex = -1;
                    this.onLoad();
                }
            }
        }
        ,onTypeAhead: function() {
            var nodes = this.view.getNodes();
            for( var i = 0; i < nodes.length; i++ )
            {
                var n = nodes[i];
                var d = this.view.getRecord( n ).data;
                var re = new RegExp( '(.*?)(' + this.getRawValue() + ')(.*)' );
                var h = d[this.displayField];
                h = h.replace( re, '$1<span class="mark-combo-match">$2</span>$3' );
                n.innerHTML = h;
            }
        }
    });
    Of course, the according css class has to be defined in the page:
    Code:
        .mark-combo-match { font-weight: bold; color: blue }
    Now you can set typeAhead back to true again and it will (hopefully) work as expected - I'm not really an .

  8. #8
    Sencha User NightAvatar's Avatar
    Join Date
    Nov 2008
    Location
    Norway
    Posts
    206
    Vote Rating
    0
    NightAvatar is on a distinguished road

      0  

    Default Hurray for BitPoet!!

    Hurray for BitPoet!!


    You really know your stuff!

    I wish I had half your talent!

    It works, but appears to be case-sensitive. (See this post)

    And one last thing would be to have it not select the first option in the list automatically on blur (it does when I tab out, but not when I click outside the field). It would be nice if users had to either select by arrowing down to their choice, or by mouse click. And that unless they manually selected the data they enter would remain unchanged. (Unless forceSelection is true or other specification overrides it I guess.)
    Last edited by NightAvatar; 13 Nov 2008 at 2:13 AM. Reason: correction and link to image

  9. #9
    Sencha User NightAvatar's Avatar
    Join Date
    Nov 2008
    Location
    Norway
    Posts
    206
    Vote Rating
    0
    NightAvatar is on a distinguished road

      0  

    Default


    I just realized I am an and asked for the wrong thing. Or I'm making an addition now. I love what you did and find it very useful!

    What I need now is to be able to search from the beginning of any word in the string/list item. To filter only first letters from start of string, after spaces and/or after hyphens. So searching for "co" would filter "Colorado" "Connecticut" as well as "District of Colombia"

    What addition do I need to accomplish this?

  10. #10
    Sencha User BitPoet's Avatar
    Join Date
    Sep 2008
    Location
    Bavaria
    Posts
    277
    Vote Rating
    1
    BitPoet is on a distinguished road

      1  

    Default


    For that, the call to the store's "filter" method has to be formatted a bit differently, but seeing it also takes a RegExp object, this isn't too difficult. But one has also to be aware that the highlighting code needs to be adapted accordingly. Therefore, I've added two new attributes: queryIgnoreCase and matchWordStart, which should be quite self-explanatory:

    Code:
    Ext.override( Ext.form.ComboBox, {
        anyMatch: false,
        /* Set to true to match case-insenstively: */
        queryIgnoreCase: false,
        /* Set to true to only match on word start (beginning of string,
           after whitespace or quotation marks: */
        matchWordStart: true,
        doQuery : function(q, forceAll){
            if(q === undefined || q === null){
                q = '';
            }
            var qe = {
                query: q,
                forceAll: forceAll,
                combo: this,
                cancel:false
            };
            if(this.fireEvent('beforequery', qe)===false || qe.cancel){
                return false;
            }
            q = qe.query;
            forceAll = qe.forceAll;
            if(forceAll === true || (q.length >= this.minChars)){
                if(this.lastQuery !== q){
                    this.lastQuery = q;
                    if(this.mode == 'local'){
                        this.selectedIndex = -1;
                        if(forceAll){
                            this.store.clearFilter();
                        }else{
                            var qr;
                            if( this.matchWordStart ){
                                if( this.queryIgnoreCase ){
                                    qr = new RegExp( '\\b' + q, "i" );
                                } else {
                                    qr = new RegExp( '\\b' + q );
                                }
                            } else {
                                qr = q;
                            }
                            this.store.filter(this.displayField, qr, this.anyMatch, this.queryIgnoreCase);
                        }
                        this.onLoad();
                    }else{
                        this.store.baseParams[this.queryParam] = q;
                        this.store.load({
                            params: this.getParams(q)
                        });
                        this.expand();
                    }
                }else{
                    this.selectedIndex = -1;
                    this.onLoad();
                }
            }
        }
        ,onTypeAhead: function() {
            var nodes = this.view.getNodes();
            for( var i = 0; i < nodes.length; i++ )
            {
                var n = nodes[i];
                var d = this.view.getRecord( n ).data;
                var re;
                var ptrn = '(.*?)(' + (this.matchWordStart ? '\\b' : '') + this.getRawValue() + ')(.*)';
                if( this.queryIgnoreCase ){
                    re = new RegExp( ptrn, "i" );
                } else {
                    re = new RegExp( ptrn );
                }
                var h = d[this.displayField];
                h = h.replace( re, '$1<span class="mark-combo-match">$2</span>$3' );
                n.innerHTML = h;
            }
        }
    });