【问题标题】:EXT JS : In Combobox, how to set a displayfield for a record which is not in the store?EXT JS:在 Combobox 中,如何为不在商店中的记录设置显示字段?
【发布时间】:2013-10-04 04:27:41
【问题描述】:

我想在组合框中为在数据库中没有条目的记录(这是一个历史数据,存储在数据库中并加载到表单上)设置一个显示字段。

例如,我在商店中有 3 个条目,如下所示 - value : 显示字段 一个苹果 b:球 c : 猫

像往常一样,它用于预填充组合框(直到这里都可以正常工作)。

我将历史数据存储在数据库中,其中包含一些记录的值 id: d,商店中不再存在,但我仍然想支持旧数据并想用它显示关联的显示字段('doll')。

我的查询是,即使此记录不在与组合框关联的存储中,如何设置从数据库获取记录时的显示字段? 目前,我们在 extJs 中没有诸如 setDisplayField 之类的方法,但是我们有一个方法 'setValue' 可以更改组合框中的值,这是不希望的。

如果遇到数据库中的“id =d”,我想将显示字段更改为“娃娃”。

请帮忙。

谢谢

【问题讨论】:

    标签: javascript extjs extjs4 extjs4.1


    【解决方案1】:

    如果你真正想要的是一个setDisplayField 方法,它实际上是存在的,它被称为setRawValue

    但是,它本身是否足以解决您的问题是值得怀疑的。确实,你可以用它来获得正确的显示,但你会失去你的价值......

    var legacyData = [
        {id: 'd', name: "Doll"}
    ];
    
    var combo = Ext.widget({
        renderTo: Ext.getBody()
        ,xtype: 'combo'
        ,fieldLabel: "Regular combo"
        ,valueField: 'id'
        ,displayField: 'name'
        ,queryMode: 'local'
        ,value: 'd'
        ,store: {
            fields: ['id', 'name']
            ,data: []
            ,autoLoad: true
            ,listeners: {
                load: function() {
                    var value = combo.value,
                        valueField = combo.valueField,
                        displayField = combo.displayField,
                        o = Ext.Array.findBy(legacyData, function(data) {
                            return data[valueField] === value;
                        });
    
                    if (o) {
                        combo.setRawValue(o[displayField]);
                    }
    
                    // Fail! This will alert 'Doll' instead of 'd'.
                    //
                    // If you set forceSelection to true, the value will be cleared
                    // all together.
                    //
                    alert(combo.getValue());
                }
            }
        }
    });
    

    按照 dbring 的建议,将历史记录添加到组合存储中确实会解决您的问题,当且仅当您愿意让用户选择历史记录时。据我了解,情况并非如此,否则你一开始就不会有任何问题......

    在检查了组合类的代码后,我认为实现所需的最佳方法是覆盖findRecord 方法。此方法返回的任何记录都将用于更新组合框显示及其值,与此记录是否存在于组合存储中无关,并且不会影响可以在组合中选择的值。完美!

    假设我们在组合中添加了legacyStore,下面是如何覆盖findRecord 方法来做到这一点:

    findRecord: function(field, value) {
        var ls = this.legacyStore,
            record = this.callParent(arguments);
        if (record) {
            // found in current store
            return record;
        } else if (ls) {
            // try legacy value store
            var idx = ls.findExact(field, value);
            return idx !== -1 ? ls.getAt(idx) : false;
        } else {
            return false;
        }
    }
    

    现在,我们还有另外几个问题要处理...

    首先,如果在调用 findRecord 时未加载旧版存储怎么办?那是行不通的。在其 vanilla 形式中,如果未加载其存储,组合框将中止其 setValue 方法,并从其 onLoad 方法中调用它。我们应该添加一些代码来为我们的旧版商店复制这种行为。

    第二个问题比较简单,但是如果组合是用历史值初始化的,而用户改变了组合的值但又后悔自己的选择怎么办?如果不取消并重新填写整个表单,他们将无法取回原始值。为了避免他们感到沮丧,我们可以更改组合行为以在清除时恢复其原始值,而不是其最后一个值。由于我不知道这是否可取,我们将其添加为选项。

    考虑到所有这些,这里是一个“ux”的完整代码,它将处理您的特定用例。

    请注意,如果 forceSelection 设置为 true 并且商店是使用 autoLoad: true 创建的,则它包含一个针对取消组合框初始值的错误的全局修复。在this other question 中了解更多信息。或者,您可以跳过全局修复并将loading: true 添加到使用forceSelection: true 的组合的所有商店和旧商店。

    /**
     * Fixes the bug with autoLoad: true and forceSelection: true.
     * 
     * See: https://stackoverflow.com/a/14440479/1387519
     */
    Ext.define('Ext.ux.fix.data.Store.AutoLoadLoadingState', {
        override: 'Ext.data.Store'
        ,constructor: function() {
            this.callParent(arguments);
            if (this.autoLoad) {
                this.loading = true;
            }
        }
    });
    
    /**
     * A combo box with support for legacy values.
     *
     * Legacy values will be displayed if their value is assigned to the 
     * combo, but they won't be selectable (nor queryable) by the user.
     */
    Ext.define('Ext.ux.form.field.LegacyValueCombo', {
        extend: 'Ext.form.field.ComboBox'
    
        ,alias: 'widget.legacyvaluecombo'
    
        ,requires: [
            'Ext.ux.fix.data.Store.AutoLoadLoadingState'
        ]
    
        /**
         * The store that provides legacy values.
         *
         * @cfg {Ext.data.Store/Object/String}
         */
        ,legacyStore: undefined
    
        /**
         * If `true` and {@link #forceSelection} is also `true`, the
         * {@link #originalValue original value} of the field will be restored when
         * the field is cleared, instead of the last value.
         *
         * This may be useful considering that if the combo original value is a
         * legacy one, and the user changes this value, they will have no other
         * means to get it back afterward.
         *
         * @cfg {Boolean}
         */
        ,restoreOriginalValue: true
    
        /**
         * Ensure the legacy store is a store instance before initializing the
         * combo box.
         */
        ,initComponent: function() {
    
            var legacyStore = this.legacyStore;
            if (legacyStore) {
                this.legacyStore = Ext.data.StoreManager.lookup(legacyStore);
            }
    
            this.callParent(arguments);
        }
    
        /**
         * Overridden to return a legacy record if the value is not found in the
         * regular store.
         *
         * This method will only work if both stores have been loaded.
         */
        ,findRecord: function(field, value) {
            var ls = this.legacyStore,
                record = this.callParent(arguments);
            if (record) {
                // found in current store
                return record;
            } else if (ls) {
                // try legacy value store
                var idx = ls.findExact(field, value);
                return idx !== -1 ? ls.getAt(idx) : false;
            } else {
                return false;
            }
        }
    
        /**
         * This method is overridden to support the case where the legacy store
         * loads after the regular store.
         */
        ,setValue: function(value) {
            var store = this.store,
                legacyStore = this.legacyStore;
    
            if (legacyStore) {
    
                // If the legacy store has not been loaded, trigger it.
                if (!legacyStore.lastOptions) {
                    if (!legacyStore.loading) {
                        legacyStore.load();
                    }
                }
    
                // Legacy store is loading, we must wait for it to be done before
                // processing the value.
                if (legacyStore.loading) {
    
                    // This is done in the parent setValue method, but we are not
                    // sure it will be called in time so we must replicate this 
                    // code here.
                    this.value = value;
                    this.setHiddenValue(value);
    
                    // If the regular store is loading, then this method will be
                    // called again when it is done. We will see at this time if we
                    // still have to wait for the legacy store. In the other case,
                    // we have to simulate a call to onLoad when the legacy store
                    // has loaded.
                    if (!store.loading) {
                        legacyStore.on({
                            single: true
                            ,scope: this
                            ,load: function(legacyStore, records, success) {
                                this.onLoad(store, store.getRange(), success);
                            }
                        });
                    }
                }
                // Legacy store is loaded, that's OK to continue.
                else {
                    this.callParent(arguments);
    
                    // Implements restoreOriginalValue.
                    if (this.restoreOriginalValue) {
                        this.lastSelection = this.originalValue;
                    }
                }
    
                // setValue must return this
                return this;
            }
            // No legacy store, just defer to the super method.
            else {
                return this.callParent(arguments);
            }
        }
    });
    

    最后,这里有一个live demo,其中包含大量示例,证明此用户体验适用于同步和异步存储的所有组合,而forceSelection... 而setRawValue 则不行。

    【讨论】:

      【解决方案2】:

      我认为您可以通过创建 Ext.XTemplate 并将该模板设置为组合框上的 displayTpl 属性来做到这一点。然后在您的模板中,您可以有一种方法来测试应该以不同方式显示的记录。

      displayTpl 配置没有记录,但如果您查看ComboBox Source Code 并搜索 me.displayTpl,您会发现他们只是创建了一个默认 displayTpl(如果不存在)。 p>

      【讨论】:

        【解决方案3】:

        您只需在使用store.add({id:'d',name:'doll'}) 加载该记录后将该记录添加到存储中

        这是一个不是您需要的示例,但显示了概念:

        Ext.define('MyApp.store.Users', {
            extend:'Ext.data.Store',
            model:'MyApp.model.User',
            listeners:{
                load:function (store, recs) {
                    store.add({uid:'', name:''});  //adding empty record to enable deselection of assignment
                }
            }
        });
        

        【讨论】:

          猜你喜欢
          • 2014-02-03
          • 1970-01-01
          • 1970-01-01
          • 2013-08-10
          • 2013-08-16
          • 1970-01-01
          • 1970-01-01
          • 2011-12-24
          • 1970-01-01
          相关资源
          最近更新 更多