1. #1

    Default Ext.form.SelectBox (Make a ComboBox work behave like an HTML SELECT)

    Ext.form.SelectBox (Make a ComboBox work behave like an HTML SELECT)


    This started out as an extra function for Ext.form.ComboBox, and Jeff pointed out that you couldn't use the keyboard to navigate.

    I've added basic searching to Ext.data.SimpleStore and extended Ext.form.ComboBox to create Ext.form.SelectBox. Type a letter to jump to and select the first matching result. Press that same key to find the next match. Home/End jump to the top/bottom of the list, PgUp/PgDn move the selection one 'page' at a time. There's only one new config option, searchResetDelay which is the amount of time before a search is reset. With a little extra effort this could be modified to support a deeper search so that typing 'Ver' fast would match Vermont instead of searching for matches to V, E and then R.

    See the example page for more details:
    http://crepitus.com/misc/ext/combo.html

    May 29, 2007 - Fixed a bug with the way superclass functions were called that broke when using an Ext.form.Form

    Edit: I didn't test it in Safari, fixed a few bugs there and with page up/down scrolling the page.

  2. #2
    Ext User lgerndt's Avatar
    Join Date
    Apr 2007
    Location
    Sunnyvale, CA
    Posts
    48
    Vote Rating
    0
    lgerndt is on a distinguished road

      0  

    Default


    Excellent code, thank you so much for writing and posting it. One problem: when the user clicks once to open and then clicks again to select (as oppposed to just clicking once and releasing on the item they want), "onselect" gets called twice. I need to hook into that and do something and I don't want it done twice, do you know an easy fix for this?

  3. #3
    Ext User lgerndt's Avatar
    Join Date
    Apr 2007
    Location
    Sunnyvale, CA
    Posts
    48
    Vote Rating
    0
    lgerndt is on a distinguished road

      0  

    Default SelectGroupBox

    SelectGroupBox


    This code is marriage of GroupComboBox and SelectBox, both third party contributions to this forum. I started with SelectBox, which is a very clean extension to Ext.form.ComboBox to make it behave like a normal HTTP select box, and then extracted the excellent parts of GroupComboBox that provide the support for optgroups.

    Usage: input records should be as specified by the GroupComboBox example. I've added some code to stylize the optgroup headers nicely, as you can see in the picture below.

    PHP Code:
    /**
     * Searches through records and returns the index of the first match.
     * @param {String} field A field in the records being searched
     * @param {String/RegExp} value The string to compare to the field's value
     * @param {Number} startIndex Index to begin searching at (defaults to 0). (optional)
     * @param {Object} config (optional)
     */
    Ext.data.Store.prototype.searchByString = function(fieldvaluestartIndex) {
        var 
    record falserc this.getCount(), range;
        
    startIndex startIndex || 0;
        
    startIndex startIndex >= rc startIndex;

        if(!(
    value instanceof RegExp)) { // not a regex
            
    value String(value);
            if(
    value.length === 0){
                return 
    false;
            }
            
    value = new RegExp("^" Ext.escapeRe(value), "i");
        }
        if(
    rc 0) {
            
    range this.getRange(startIndex);
            
    Ext.each(range, function(r) {
                if( 
    value.test(r.data[field]) ) {
                    
    record r;
                    return 
    false;
                }
            });
            if( !
    record && startIndex ) {
                
    // if a startIndex was provided and we have no match, restart the search from the beginning
                
    return this.searchByString(fieldvalue0);
            }
        }
        return 
    record;
    };

    /*
    This code is marriage of [B]GroupComboBox[/B] and [B]SelectBox[/B],
    both third party contributions to this forum.  I started with
    [B]SelectBox[/B], which is a very clean extension to
    [B]Ext.form.ComboBox[/B] to make it behave like a normal HTTP select
    box, and then extracted the excellent parts of [B]GroupComboBox[/B] that
    provide the support for optgroups.

    Usage:  input records should be as specified by the [B]GroupComboBox[/B]
    example.  I've added some code to stylize the optgroup headers nicely,
    as you can see in the picture below.
    */

    /**
     * Original quote from Ext.form.SelectBox:
     * Makes a ComboBox more closely mimic an HTML SELECT.    Supports clicking and dragging
     * through the list, with item selection occurring when the mouse button is released.
     * When used will automatically set {@link #editable} to false and call {@link Ext.Element#unselectable}
     * on inner elements.  Re-enabling editable after calling this will NOT work.
     */
    Ext.form.SelectGroupBox = function(config){
        
    this.searchResetDelay 1000;
        
    config config || {};
        
    config Ext.apply(config || {}, {
            
    editablefalse,
            
    forceSelectiontrue,
            
    rowHeightfalse,
            
    lastSearchTermfalse
        
    });
        
        var 
    cls 'x-combo-list';
        
    this.tpl '<div class="'+cls+'-item x-combo-list-hd">{' config.groupField '}</div><div class="'+cls+'-item x-combo-list-groupitem">{' config.displayField '}</div>';
        
    Ext.form.SelectGroupBox.superclass.constructor.apply(thisarguments);
        
        
    this.lastSelectedIndex this.selectedIndex || 0;
    };

    Ext.extend(Ext.form.SelectGroupBoxExt.form.ComboBox, {

        
    groupFieldundefined,

        
    initEvents : function(){
            
    Ext.form.SelectGroupBox.superclass.initEvents.apply(thisarguments);
            
    // you need to use keypress to capture upper/lower case and shift+key, but it doesn't work in IE
            
    this.el.on('keydown'this.keySearchthistrue);
            
    this.on('beforeselect'this.beforeSelectthistrue);
            
    this.cshTask = new Ext.util.DelayedTask(this.clearSearchHistorythis);
        },

        
    expand: function()
        {
            var 
    this.innerList.dom.childNodes.length 1;

            for (var 
    i=li>=0i--)
            {
               var 
    this.innerList.dom.childNodes[i];
               
                if(
    Ext.util.Format.trim(e.innerHTML).length === 0)
                {
                    
    Ext.get(e).remove();
                }
                
            }
            
            
    this.view.updateIndexes();
            
            
    Ext.form.SelectGroupBox.superclass.expand.call(this); 
        },
        
        
    keySearch : function(etargetoptions) {
            var 
    raw e.getKey();
            var 
    key String.fromCharCode(raw);
            var 
    startIndex 0;

            if( !
    this.store.getCount() ) {
                return;
            }

            switch(
    raw) {
                case 
    Ext.EventObject.HOME:
                    
    e.stopEvent();
                    
    this.selectFirst();
                    return;

                case 
    Ext.EventObject.END:
                    
    e.stopEvent();
                    
    this.selectLast();
                    return;

                case 
    Ext.EventObject.PAGEDOWN:
                    
    this.selectNextPage();
                    
    e.stopEvent();
                    return;

                case 
    Ext.EventObject.PAGEUP:
                    
    this.selectPrevPage();
                    
    e.stopEvent();
                    return;
            }

            
    // skip special keys other than the shift key
            
    if( (e.hasModifier() && !e.shiftKey) || e.isNavKeyPress() || e.isSpecialKey() ) {
                return;
            }
            if( 
    this.lastSearchTerm == key ) {
                
    startIndex this.lastSelectedIndex;
            }
            
    this.search(this.displayFieldkeystartIndex);
            
    this.cshTask.delay(this.searchResetDelay);
        },

        
    onRender : function(ctposition) {
            
    this.store.on('load'this.calcRowsPerPagethis);
            
    Ext.form.SelectGroupBox.superclass.onRender.apply(thisarguments);
            if( 
    this.mode == 'local' ) {
                
    this.calcRowsPerPage();
            }
        },

        
    onSelect : function(recordindexskipCollapse){
            if(
    this.fireEvent('beforeselect'thisrecordindex) !== false){
                
    this.setValue(record.data[this.valueField || this.displayField]);
                if( !
    skipCollapse ) {
                    
    this.collapse();
                }
                
    this.lastSelectedIndex index 1;
                
    this.fireEvent('select'thisrecordindex);
            }
        },
        
        
    render : function(ct) {
            
    Ext.form.SelectGroupBox.superclass.render.apply(thisarguments);
            if( 
    Ext.isSafari ) {
                
    this.el.swallowEvent('mousedown'true);
            }
            
    this.el.unselectable();
            
    this.innerList.unselectable();
            
    this.trigger.unselectable();
            
    this.innerList.on('mouseup', function(etargetoptions) {
                if( 
    target.id && target.id == this.innerList.id ) {
                    return;
                }
                
    this.onViewClick();
            }, 
    this);

            
    this.innerList.on('mouseover', function(etargetoptions) {
                if( 
    target.id && target.id == this.innerList.id ) {
                    return;
                }
                
    this.lastSelectedIndex this.view.getSelectedIndexes()[0] + 1;
                
    this.cshTask.delay(this.searchResetDelay);
            }, 
    this);

            
    this.trigger.un('click'this.onTriggerClickthis);
            
    this.trigger.on('mousedown', function(etargetoptions) {
                
    e.preventDefault();
                
    this.onTriggerClick();
            }, 
    this);

            
    this.on('collapse', function(etargetoptions) {
                
    Ext.get(document).un('mouseup'this.collapseIfthis);
            }, 
    thistrue);

            
    this.on('expand', function(etargetoptions) {
                
    Ext.get(document).on('mouseup'this.collapseIfthis);
            }, 
    thistrue);
        },

        
    clearSearchHistory : function() {
            
    this.lastSelectedIndex 0;
            
    this.lastSearchTerm false;
        },

        
    selectFirst : function() {
            
    this.focusAndSelect(this.store.data.first());
        },

        
    selectLast : function() {
            
    this.focusAndSelect(this.store.data.last());
        },

        
    selectPrevPage : function() {
            if( !
    this.rowHeight ) {
                return;
            }
            var 
    index Math.max(this.selectedIndex-this.rowsPerPage0);
            
    this.focusAndSelect(this.store.getAt(index));
        },

        
    selectNextPage : function() {
            if( !
    this.rowHeight ) {
                return;
            }
            var 
    index Math.min(this.selectedIndex+this.rowsPerPagethis.store.getCount() - 1);
            
    this.focusAndSelect(this.store.getAt(index));
        },

        
    search : function(fieldvaluestartIndex) {
            
    field field || this.displayField;
            
    this.lastSearchTerm value;
            var 
    record this.store.searchByString.apply(this.storearguments);
            if( 
    record !== false ) {
                
    this.focusAndSelect(record);
            }
        },

        
    focusAndSelect : function(record) {
            var 
    index this.store.indexOf(record);
            
    this.select(indexthis.isExpanded());
            
    this.onSelect(recordindexthis.isExpanded());
        },

        
    calcRowsPerPage : function() {
            if( 
    this.store.getCount() ) {
                
    this.rowHeight Ext.fly(this.view.getNode(0)).getHeight();
                
    this.rowsPerPage this.maxHeight this.rowHeight;
            } else {
                
    this.rowHeight false;
            }
        },

        
    onViewClick : function(doFocus)
        {
            var 
    index this.view.getSelectedIndexes()[0];
            
            var 
    this.store.getAt(index);
            
            if(
    r)
            {

                if(
    r.data.optgroup.length)
                {
                    
    this.selectNext();
                }
                else
                {
                    
    this.onSelect(rindex);
                }
            }
            if(
    doFocus !== false){
                
    this.el.focus();
            }
        },
        
        
        
    onViewOver : function(et)
        {
            if(
    this.inKeyMode){ // prevent key nav and mouse over conflicts
                
    return;
            }
            var 
    item this.view.findItemFromChild(t);
           
            if(
    item){
                var 
    index this.view.indexOf(item);
                
                if(
    Ext.get(item).hasClass('x-combo-list-hd')) 
                {             
                    
    //this.selectNext();
                
    }
                else
                {
                    
    this.select(indexfalse);
                }
            }
        },
        
        
    selectNext : function(){
            var 
    ct this.store.getCount();
            
            if(
    ct 0)
            {
                var 
    index this.selectedIndex;
               
                if(
    index ct-1)
                {
                    var 
    this.store.getAt(index+1);
                    
                    
                    if(
    r.data.optgroup.length)
                    {
                        
    this.selectedIndex += 1;

                        
    this.selectNext();
                    }
                    else
                    {
                        
    this.select(index+1);
                    }
                }
                else
                {
                    
    this.selectedIndex = -1;
                    
                    
    this.selectNext();
                }
            }
        },

        
    selectPrev : function()
        {
            var 
    ct this.store.getCount();
            
            if(
    ct 0)
            {
                var 
    index this.selectedIndex;
                var 
    r;
                if(
    index === 0)
                {
                    
    this.store.getAt(ct-1);
                    
                    if(
    r.data.optgroup.length)
                    {
                        
    this.selectedIndex ct;
                        
    this.selectPrev();
                    }
                    else
                    {
                        
    this.select(ct-1);
                    }
                }
                else
                {
                    
    this.store.getAt(index-1);
                    
                    if(
    r.data.optgroup.length)
                    {
                        
    this.selectedIndex -= 1;
                        
    this.selectPrev();
                    }
                    else
                    {
                        
    this.select(index-1);
                    }
                }
            }
        },
       
        
    beforeSelect : function(comborecordindex){
            if (
    record.data.text == "Add more...")
            {
                
    // Becuase the author implemented detection of mouseup, he introduced a sort of bug:
                // we get called here twice, once while expanded, and again after closed.  We only want
                // to do Add more... once, so we check for expanded, but we want to return false
                // both times, so that this item never stays selected.
                
    if (combo.isExpanded())
                {
                    
    this.collapse();
                    
    window.alert("Add more... will be implemented soon!");
                }
                return 
    false// i.e. cancel the selection of this item
            
    }
            return 
    true;
        },
       
       
    onLoad : function(){
            if(!
    this.hasFocus){
                return;
            }
            if(
    this.store.getCount() > 0){
                
    this.expand();
                
    this.restrictHeight();
                if(
    this.lastQuery == this.allQuery){
                    if(
    this.editable){
                        
    this.el.dom.select();
                    }
                    if(!
    this.selectByValue(this.valuetrue)){
                        
    this.selectNext(); // changed from this.select(0, true);
                    
    }
                }else{
                    
    this.selectNext();
                    if(
    this.typeAhead && this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE){
                        
    this.taTask.delay(this.typeAheadDelay);
                    }
                }
            }else{
                
    this.onEmptyResults();
            }
        }
    }); 
    Attached Images
    Last edited by lgerndt; 25 Jun 2007 at 10:29 PM. Reason: give it a title

  4. #4
    Ext User
    Join Date
    Jun 2007
    Posts
    10
    Vote Rating
    0
    rajesha is on a distinguished road

      0  

    Default


    very good code, but i want use the select box code in the html,not an input field

  5. #5
    Sencha User HarryC's Avatar
    Join Date
    Mar 2007
    Location
    London, England
    Posts
    89
    Vote Rating
    0
    HarryC is on a distinguished road

      0  

    Default


    The SelectGroupBox looks like just what I'm currently looking for, but I can't find the GroupComboBox mentioned above when searching the forum to see the correct syntax for using this. Can someone please elaborate or provide the correct link. Thanks.

  6. #6
    Ext User lgerndt's Avatar
    Join Date
    Apr 2007
    Location
    Sunnyvale, CA
    Posts
    48
    Vote Rating
    0
    lgerndt is on a distinguished road

      0  

    Default


    Quote Originally Posted by HarryC View Post
    The SelectGroupBox looks like just what I'm currently looking for, but I can't find the GroupComboBox mentioned above when searching the forum to see the correct syntax for using this. Can someone please elaborate or provide the correct link. Thanks.
    Sorry about that, I posted this in two different threads. Here's the thread link: http://extjs.com/forum/showthread.php?p=31645

  7. #7
    Ext User
    Join Date
    Mar 2007
    Posts
    70
    Vote Rating
    0
    skyey is on a distinguished road

      0  

    Default


    can i make a tree or grid inside the combobox? tks

  8. #8
    Ext User
    Join Date
    May 2007
    Posts
    42
    Vote Rating
    0
    johnnycannuk is on a distinguished road

      0  

    Thumbs up Works with slider in BorderLayout?

    Works with slider in BorderLayout?


    This looks really great...well done.

    Now, I have not had the opportunity to test it out, but does this eliminate the IE bug where html selects are always on top, even when layout regions slide over them? If so, sold!

  9. #9
    Sencha User ChrisR's Avatar
    Join Date
    Apr 2007
    Location
    Belgium
    Posts
    83
    Vote Rating
    0
    ChrisR is on a distinguished road

      0  

    Default


    Excellent code! Would be great if this would make it into the main Ext build!

  10. #10
    Sencha Premium Member Troy Wolf's Avatar
    Join Date
    May 2007
    Location
    Kansas City
    Posts
    248
    Vote Rating
    2
    Troy Wolf is on a distinguished road

      0  

    Question


    corey.gilmore or others,

    Looking at your Ext.form.SelectBox demo and code, it looks and works very nicely. I found it because I want a Select (or ComboBox) that works more like a normal HTML Select. Mainly, when an item is selected, I do not want the list of options to change to only those that match the selected option text. This default functionality is nice for when a person is typing into the combobox looking for an option, but it is very unnatural when the user wants to later click the arrow to select a different option from the list----and nothing shows but the one selected option.

    Wanting the list of options to remain static upon typing and selection seems like such a natural choice, that I would be very surprised to find that the Ext ComboBox cannot be naturally tweaked to behave this way.

    corey.gilmore, since you obviously had to learn a lot about the ComboBox object, I figure you can easily answer my question. Do I need to use your SelectBox just to get this "basic" functionality?

    Thank you!