Search code examples
extjsextjs3

How to render the initial display value for a remote combo box inside an editor grid panel?


NOTE: I recently posted this question which asks about a simple remote combo box. This question is similar but not duplicate, because it places that remote combo in a remote grid.

I am rendering an EditorGridPanel. One of the columns uses a ComboBox editor. The grid and the combo box load their respective data from separate HTTP endpoints. How can I force the combo column to render the display field on initial load?

I found a few workarounds, but it seems like there should be an easier way. My workarounds basically force these asynchronous loads to be synchronous: first load the combo store, then load the grid store. This gets complicated if, for example, I have multiple remote combos in a single grid. Is there no way to force the combo to re-render itself after the underlying data changes?

Here is an example. You can drop this code in any ExtJS 3.4 Fiddle.

// Create a remote store for the combo box.
var remoteStore = new Ext.data.JsonStore({
    url: 'https://jsonplaceholder.typicode.com/todos',
    autoLoad: true,
    fields: ['id', 'title']
});

// Create the combo box.
var remoteCombo = new Ext.form.ComboBox({
    fieldLabel: 'Remote Store (busted)',
    triggerAction: 'all',
    mode: 'local',
    store: remoteStore,
    valueField: 'id',
    displayField: 'title',
    lazyRender: true
});

// Create the column model for the grid.
var columnModel = new Ext.grid.ColumnModel(
    [{
        header: 'Column 1',
        dataIndex: 'column1',
        width: 220,
        editor: new Ext.form.TextField()
    }, {
        id: 'column2',
        header: 'Column 2',
        dataIndex: 'column2',
        width: 130,
        editor: remoteCombo,
        renderer: (value) => {
            var record = remoteCombo.findRecord(remoteCombo.valueField, value);
            return record ? record.get(remoteCombo.displayField) : 'Loading...';
        }
    }]
);

// Create a local grid store with some fake data.
var fakeData = [{
    column1: 'row 1',
    column2: 1
}, {
    column1: 'row 2',
    column2: '2'
}, {
    column1: 'row 3',
    column2: '3'
}];
var gridStore = new Ext.data.JsonStore({
    autoDestroy: true,
    autoLoad: false,
    fields: ['column1', 'column2'],
    data: fakeData
});

// Workaround #1: load grid store AFTER all required combo stores are loaded.
// remoteStore.on('load', () => gridStore.loadData(fakeData));

// Workaround #2: force the column to re-render all records after the store is loaded.
// Kludgy and also marks the records as dirty.
// remoteStore.on('load', () => {
//     gridStore.each((record) => {
//         var originalValue = record.get('column2');
//         record.set('column2', undefined);
//         record.set('column2', originalValue);
//     });
// });

// Render the grid.
new Ext.grid.EditorGridPanel({
    store: gridStore,
    cm: columnModel,
    autoExpandColumn: 'column2',
    renderTo: Ext.getBody(),
    width: 600,
    height: 300,
    title: 'Grid with remote combo',
    frame: true,
});

Solution

  • I think you can avoid synchronous load of the stores. Just put single listener for remoteStore 'load' event in the grid 'render' event. Something like this:

    // Create a remote store for the combo box.
    var remoteStore = new Ext.data.JsonStore({
        url: 'https://jsonplaceholder.typicode.com/todos',
        autoLoad: true,
        fields: ['id', 'title']
    });
    
    // Create the combo box.
    var remoteCombo = new Ext.form.ComboBox({
        fieldLabel: 'Remote Store (busted)',
        triggerAction: 'all',
        mode: 'local',
        store: remoteStore,
        valueField: 'id',
        displayField: 'title',
        lazyRender: true
    });
    
    // Create the column model for the grid.
    var columnModel = new Ext.grid.ColumnModel(
        [{
            header: 'Column 1',
            dataIndex: 'column1',
            width: 220,
            editor: new Ext.form.TextField()
        }, {
            id: 'column2',
            header: 'Column 2',
            dataIndex: 'column2',
            width: 130,
            editor: remoteCombo,
            renderer: (value, metaData, record, rowIndex, colIndex, store) => {
                var recordIndex = remoteCombo.getStore().findExact(remoteCombo.valueField, value);
                var record = remoteCombo.getStore().getAt(recordIndex);
                return record ? record.get(remoteCombo.displayField) : 'Loading...';
            }
        }]
    );
    
    // Create a local grid store with some fake data.
    var fakeData = [{
        column1: 'row 1',
        column2: 1
    }, {
        column1: 'row 2',
        column2: 2
    }, {
        column1: 'row 3',
        column2: 3
    }];
    var gridStore = new Ext.data.JsonStore({
        autoDestroy: true,
        autoLoad: false,
        fields: ['column1', 'column2'],
        data: fakeData
    });
    
    // Workaround #1: load grid store AFTER all required combo stores are loaded.
    // remoteStore.on('load', () => gridStore.loadData(fakeData));
    
    // Workaround #2: force the column to re-render all records after the store is loaded.
    // Kludgy and also marks the records as dirty.
    // remoteStore.on('load', () => {
    //     gridStore.each((record) => {
    //         var originalValue = record.get('column2');
    //         record.set('column2', undefined);
    //         record.set('column2', originalValue);
    //     });
    // });
    
    // Render the grid.
    new Ext.grid.EditorGridPanel({
        store: gridStore,
        cm: columnModel,
        autoExpandColumn: 'column2',
        renderTo: Ext.getBody(),
        width: 600,
        height: 300,
        title: 'Grid with remote combo',
        frame: true,
        listeners: {
            render: function (grid) {
                if (remoteStore.lastOptions === null) {
                    remoteStore.on('load', function (store) {
                        grid.getView().refresh();
                    }, this, {
                        single: true
                    });
                }
            }
        }
    });
    

    P.S. I have fixed the renderer. P.P.S. be careful with string and integer values by findExact.. AFAIK it is type-sensitive method.