You found a bug! We've classified it as a bug in our system. We encourage you to continue the discussion and to find an acceptable workaround while we work on a permanent fix.
  1. #1
    Sencha User
    Join Date
    Jun 2009
    Location
    Heidelberg, Germany
    Posts
    52
    Vote Rating
    1
    kleins is on a distinguished road

      0  

    Default combobox with remote store not finding records

    combobox with remote store not finding records


    I know this issue with combobox in remote mode has been raised before (e.g. here). But to my mind it has not gotten the attention it deserves as the very common usecase it is. It's been bugging me for a long time now having to apply my own patches again and again. So I would like to try to get some attention onto it again.

    Usecase
    A booking form contains a combo that allows the booking agent to select a client. On a database level the client is linked to the booking by a foreign key, so in the combo, the valueField contains the clients id, the displayField contains name and address. There are thousands of clients, so the combo cannot possibly display all options at once. Instead it should only load a subset. I the case of loading an existing booking with a selected client, it should load that single client. In the case of typing into the combo it should search the remote store for matching clients. Also, it should allow the use of forceSelection:true to force the booking agent to select an existing client.
    This to me seems a usecase that will come up in hundreds of business applications.

    Issues in ExtJS 4.0.7
    The combobox is kind of ignorant of the existence of a remote store when it comes to selecting or asserting values. It only considers the local store which is always only a subset of the remote store:
    • Loading an existing booking will lead to combobox not finding that record in its local store and thus not displaying the previously selected client.
    • Using forceSelection:true will equally lead to the combobox not finding the record and thus clearing the value

    Suggested Solution
    In both cases, combobox should check whether it is in remote mode and if so, check the remote store for the record it is looking for. It should use the name of the valueField as a request param to load exactly the record it is looking for.


    Patch
    Let me first say that there are certain issues with my patch that make it unsuitable as a general solution:
    • I cannot say how it will behave with multiSelect. I am only using it with single values and it would have to be adapted to multiSelect
    • I overrode setValue and assertValue separately. However, since they both use the findRecord... methods, those methods might be a better place for a fix. However that would require those methods to accept a callback to return their result instead of an ordinary return statement, since it might be necessary to query the remote store and wait for the result.
    This said: My patch works and it serves me well for the moment. I see it as a starting point and I am happy to help out with a more complete solution.

    BUT PLEASE DO ADDRESS THIS ISSUE, SENCHA!

    Code:
    Ext.form.field.ComboBox.override({
        /**
         * override to allow suspending the onLoad handler
         */
        onLoad: function() {
            if (this.suspendOnLoad) {
                return;
            }
            else {
                this.callOverridden();
            }
        },        
        
        isValid: function() {
            // while we are waiting for a result from the remote store
            // the field has to be considered invalid
            if (this.queryMode === 'remote' && this.queryingRemoteStore) {
                return false;
            }
            else {
                return this.callOverridden();
            }
        },
        
         setValue: function(value, doSelect) {
            var me = this,
                valueNotFoundText = me.valueNotFoundText,
                inputEl = me.inputEl,
                i, len, record,
                models = [],
                displayTplData = [],
                processedValue = [];
    
            if (me.store.loading) {
                // Called while the Store is loading. Ensure it is processed by the onLoad method.
                me.value = value;
                me.setHiddenValue(me.value);
                return me;
            }
    
            // This method processes multi-values, so ensure value is an array.
            value = Ext.Array.from(value);
    
            // Loop through values
            for (i = 0, len = value.length; i < len; i++) {
                record = value[i];
                if (!record || !record.isModel) {
                    record = me.findRecordByValue(record);
                }
                // record found, select it.
                if (record) {
                    models.push(record);
                    displayTplData.push(record.data);
                    processedValue.push(record.get(me.valueField));
                }
                // record was not found, this could happen because
                // store is not loaded or they set a value not in the store
                // in remote mode it could also be that the value exists 
                // in the remote store, but not in the local store, so let's check
                else if (me.queryMode === 'remote' && !me.queryingRemoteStore) {
                    var params = {};
                    params[me.valueField] = value;
                    me.suspendOnLoad = true;
                    me.disable();
                    me.setRawValue('Loading...');
                    me.queryingRemoteStore = true;
                    me.store.load({
                        params: params,
                        callback: function() {
                            me.setValue(value, doSelect, false);
                            if (me.triggerAction == 'query') {
                                // make sure the store is not loaded again when the trigger is clicked
                                var record = me.findRecordByValue(value[i]);
                                if (record) {
                                    me.lastQuery = record.get(me.displayField)
                                }
                            }
                            me.enable();
                            me.suspendOnLoad = false;
                            me.queryingRemoteStore = false;
                        }
                    })
                    return me;
                }      
                else {
                    // If we are allowing insertion of values not represented in the Store, then set the value, and the display value
                    if (!me.forceSelection) {
                        displayTplData.push(value[i]);
                        processedValue.push(value[i]);
                    }
                    // Else, if valueNotFoundText is defined, display it, otherwise display nothing for this value
                    else if (Ext.isDefined(valueNotFoundText)) {
                        displayTplData.push(valueNotFoundText);
                    }
                }
            }
    
            // Set the value of this field. If we are multiselecting, then that is an array.
            me.setHiddenValue(processedValue);
            me.value = me.multiSelect ? processedValue : processedValue[0];
            if (!Ext.isDefined(me.value)) {
                me.value = null;
            }
            me.displayTplData = displayTplData; //store for getDisplayValue method
            me.lastSelection = me.valueModels = models;
    
            if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
                inputEl.removeCls(me.emptyCls);
            }
    
            // Calculate raw value from the collection of Model data
            me.setRawValue(me.getDisplayValue());
            me.checkChange();
    
            if (doSelect !== false) {
                me.syncSelection();
            }
            me.applyEmptyText();
    
            return me;
        },
    
        assertValue: function() {
            var me = this,
                value = me.getRawValue(),
                rec;
                
            if (me.forceSelection) {
                if (me.multiSelect) {
                    // For multiselect, check that the current displayed value matches the current
                    // selection, if it does not then revert to the most recent selection.
                    if (value !== me.getDisplayValue()) {
                        me.setValue(me.lastSelection);
                    }
                } else {
                    // For single-select, match the displayed value to a record and select it,
                    // if it does not match a record then revert to the most recent selection.             
                    rec = me.findRecordByDisplay(value);
    
                    if (rec) {
                        me.select(rec);             
                    } else if (me.queryMode === 'remote' && !me.queryingRemoteStore) {
                        var params = {};
                        params[me.valueField] = value;
                        me.disable();
                        var value = me.getRawValue();
                        me.setRawValue('Loading...');
                        me.suspendOnLoad = true;
                        me.queryingRemoteStore = true;
                        me.store.load({
                            params: params,
                            callback: function() {
                                me.setRawValue(value);
                                me.assertValue();
                                me.enable();
                                me.suspendOnLoad = false;
                                me.queryingRemoteStore = false;
                            }
                        })
                        return;                   
                    } else {                    
                        me.setValue(me.lastSelection);
                    }
                }
            }
            me.collapse();
        },
    });

  2. #2
    Sencha User
    Join Date
    Jun 2009
    Location
    Heidelberg, Germany
    Posts
    52
    Vote Rating
    1
    kleins is on a distinguished road

      0  

    Default


    no-one interested in this? no-one having similar issues? what does the ext team say about this? I feel combobox should work out of the box with remote stores and foreign keys, but it doesn't. Is there no time to deal with this or is it not considered a requirement?

  3. #3
    Sencha - Ext JS Dev Team evant's Avatar
    Join Date
    Apr 2007
    Location
    Sydney, Australia
    Posts
    16,992
    Vote Rating
    649
    evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute

      0  

    Default


    I agree the combo could be a bit smarter about how it handles values and remote stores.
    Evan Trimboli
    Sencha Developer
    Twitter - @evantrimboli
    Don't be afraid of the source code!

  4. #4
    Sencha User
    Join Date
    Jun 2009
    Location
    Heidelberg, Germany
    Posts
    52
    Vote Rating
    1
    kleins is on a distinguished road

      0  

    Default


    evant, thanks for confirming this. Good to know this is not just in my head.
    But why then is this is not getting any attention? I stumbled across this within a few hours when attempting to write my first ext app two or three years ago and it's come in my way a lot of times since.
    I'd be happy to contribute code and even adapt it to sencha's requirements, but I would have to know what requirements the team considers useful and desirable.

    PD: I'll be away for a few days, so bear with me for answers

  5. #5
    Sencha User
    Join Date
    Feb 2011
    Posts
    174
    Vote Rating
    1
    netemp is on a distinguished road

      0  

    Default


    Quote Originally Posted by kleins View Post
    The combobox is kind of ignorant of the existence of a remote store when it comes to selecting or asserting values. It only considers the local store which is always only a subset of the remote store:
    • Loading an existing booking will lead to combobox not finding that record in its local store and thus not displaying the previously selected client.
    • Using forceSelection:true will equally lead to the combobox not finding the record and thus clearing the value
    Suggested Solution
    In both cases, combobox should check whether it is in remote mode and if so, check the remote store for the record it is looking for. It should use the name of the valueField as a request param to load exactly the record it is looking for.
    Thanks for posting Kleins, I faced a similar issue (one which was more severe with 4.0.2a) as mentioned here:

    http://www.sencha.com/forum/showthread.php?151696-How-to-avoid-grid-column-value-becoming-null-when-column-editor-is-combo-box

    S
    till with 4.0.7 though, forceSelection:false is working fine, but what you have suggested would be really the best possible solution!

  6. #6
    Sencha User
    Join Date
    Feb 2010
    Posts
    6
    Vote Rating
    0
    shaoshuai_496@163.com is on a distinguished road

      0  

    Default i meet this bug yesterday ....

    i meet this bug yesterday ....


    i think it's my fault , but i try 4.1beta it's ok , so it's really 4.0.7's bug