ComboBox editor (remote) and renderer for ExtJS EditorGridPanel
Adding a ComboBox editor to a ExtJS EditorGridPanel column can by tricky if your ComboBox uses a remote store. This post demonstrates using a custom grid column type and associated renderer to solve this problem.
ExtJS EditorGridPanel
The ExtJS EditorGrid Panel is excellent for editing records from within the grid. The component allows one to specify an editor type per column. The ExtJS 3 examples show a nice example of this
The problem
The editor works perfectly for columns in which the stored value is also the display value. The EditorGridPanel also allows for a ComboBox to be used as the editor. When the displayField and valueField of the combobox are the same this works ok, but less so for columns that are for instance foreign keys into other models. An example illustrates this:
In this example the Assignee column lists the ID’s of users. A ComboBox has been specified as the editor. When editing the field, the ComboBox comes into action and correctly lists the Users by name:
After selecting a User the column value is updated with the user’s id. This allows us to change the column value easily, but of course this is not what we want. Wat we actually want is to display the User’s name in the grid, hiding the value. At the same time the column’s raw value should still be the user id:
A number of specialized ComboBox renderers have been published in the Sencha forum that allow to do just this. The renderer looks up the corresponding record in the ComboBox’s store by it’s valueField and returns the corresponding displayField. For ComboBoxes with local stores that usually works but without hacks this will not fly for ComboBoxes with remote stores such as a JsonStore or DirectStore. This has to do with the fact that at the time the grid is created and the custom renderer is asked for the record’s displayField the ComboBoxes store has not finished loading yet. The only times this does work is if you get luck and win the race condition… Not what we want at all in production applications.
The solution
I am releasing a custom grid column with associated renderer that solves exactly this problem. The only thing the column configuration needs is the EditorGrid’s id and an editor that we are specifying anyway. The renderer is setup automatically. The way this works is that the renderer checks if the ComboBox editor’s store has already been loaded. If this is not the case it sets up a one-time event listener on the ComboBox store’s ‘load’ event. The event handler lets the Editor Grid’s view refresh, essentially re-calling the columns renderers. This time the store is loaded and the record and displayField can be found.
The following code snipped explains the configuration neccessary:
/* create the ComboBox editor */
var userCombo = new Ext.ComboBox({
id: 'myCombo',
valueField:'id',
displayField:'name',
store: ...
});
/* We need a unique id for the grid, either create one yourself or let Ext create it for you */
var gridId = Ext.id();
/* Create the grid */
var todoGrid = new Ext.grid.EditorGridPanel({
store: myStore,
id: gridId, // Be sure to include the grid id
cm: new Ext.grid.ColumnModel({
columns: [{
id: 'todo',
header: 'Todo',
dataIndex: 'todo'
},{
id: 'owner',
width: 150,
header: 'Assignee',
dataIndex: 'owner',
xtype: 'combocolumn', // Use the custom column or use the column's render manually
editor: userCombo, // The custom column needs a ComboBox editor to be able to render the displayValue, without it just renders value
gridId: gridId // Don't forget to specify the grid's id, the columns renderer needs it
}]
}),
clicksToEdit: 1
});
And VOILA, an EditorGrid that displays the record by it’s displayField while still functioning exactly the same and also working with remote stores. The complete custom column specification and renderer is displayed below and also attached as a zip file.
The code
/**
* @class Ext.ux.grid.ComboColumn
* @extends Ext.grid.Column
*
* A Column definition class which renders a value using a ComboBox editor. The ComboBox is used to
* convert the column value to a display value using the ComboBox's valueField and displayField.
* If the ComboBox editor uses a remote store the column definition needs to be passed the grid's id.
* See the {@Ext.ux.grid.ComboColumn#gridId gridId} config option of {@link Ext.ux.grid.ComboColumn}
* for more details.
*
* @author Rob Boerman
* @copyright (c) 2011, by Rob Boerman
* @date 20. May 2011
* @version 1.0
Example:
var userCombo = new Ext.ComboBox({
id: 'myCombo',
valueField:'id',
displayField:'name',
store: ...
});
* We need a unique id for the grid, either create one yourself or let Ext create it for you
var gridId = Ext.id();
* Create the grid
var todoGrid = new Ext.grid.EditorGridPanel({
store: myStore,
id: gridId, // Be sure to include the grid id
cm: new Ext.grid.ColumnModel({
columns: [{
id: 'todo',
header: 'Todo',
dataIndex: 'todo'
},{
id: 'owner',
width: 150,
header: 'Assignee',
dataIndex: 'owner',
xtype: 'combocolumn', // Use the custom column or use the column's render manually
editor: userCombo, // The custom column needs a ComboBox editor to be able to render the displayValue, without it just renders value
gridId: gridId // Don't forget to specify the grid's id, the columns renderer needs it
}]
}),
clicksToEdit: 1
});
* @license Ext.ux.grid.ComboColumn is licensed under the terms of
* the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
* that the code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
*
*
License details: <a href="http://www.gnu.org/licenses/lgpl.html" target="_blank">http://www.gnu.org/licenses/lgpl.html</a>
*
* @donate
*
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
*
<input name="cmd" type="hidden" value="_donations" />
*
<input name="business" type="hidden" value="ZRRCZDNRCVVEE" />
*
<input name="lc" type="hidden" value="NL" />
*
<input name="item_name" type="hidden" value="robboerman.com" />
*
<input name="currency_code" type="hidden" value="EUR" />
*
<input name="bn" type="hidden" value="PP-DonationsBF:btn_donate_LG.gif:NonHosted" />
*
<input alt="PayPal - The safer, easier way to pay online!" name="submit" src="https://www.paypalobjects.com/WEBSCR-640-20110429-1/en_US/i/btn/btn_donate_LG.gif" type="image" />
* <img src="https://www.paypalobjects.com/WEBSCR-640-20110429-1/en_US/i/scr/pixel.gif" border="0" alt="" width="1" height="1" />
* </form>
*/
Ext.ns("Ext.ux.renderer","Ext.ux.grid");
Ext.ux.grid.ComboColumn = Ext.extend(Ext.grid.Column, {
/**
* @cfg {String} gridId
*
* The id of the grid this column is in. This is required to be able to refresh the view once the combo store has loaded
*/
gridId: undefined,
constructor: function(cfg){
Ext.ux.grid.ComboColumn.superclass.constructor.call(this, cfg);
// Detect if there is an editor and if it at least extends a combobox, otherwise just treat it as a normal column and render the value itself
this.renderer = (this.editor && this.editor.triggerAction) ? Ext.ux.renderer.ComboBoxRenderer(this.editor,this.gridId) : function(value) {return value;};
}
});
Ext.grid.Column.types['combocolumn'] = Ext.ux.grid.ComboColumn;
/* a renderer that makes a editorgrid panel render the correct value */
Ext.ux.renderer.ComboBoxRenderer = function(combo, gridId) {
/* Get the displayfield from the store or return the value itself if the record cannot be found */
var getValue = function(value) {
var idx = combo.store.find(combo.valueField, value);
var rec = combo.store.getAt(idx);
if (rec) {
return rec.get(combo.displayField);
}
return value;
}
return function(value) {
/* If we are trying to load the displayField from a store that is not loaded, add a single listener to the combo store's load event to refresh the grid view */
if (combo.store.getCount() == 0 && gridId) {
combo.store.on(
'load',
function() {
var grid = Ext.getCmp(gridId);
if (grid) {
grid.getView().refresh();
}
},
{
single: true
}
);
return value;
}
return getValue(value);
};
};
Good luck and don’t forget to share this post if it’s of use to you, it might also be to others.
Rob
36 Responses to ComboBox editor (remote) and renderer for ExtJS EditorGridPanel
Leave a Reply Cancel reply
POST CATEGORIES
ExtJS
- Sencha.com Sencha.com – creators of ExtJS and Sencha Touch











Nice writeup Rob. See you at SenchaCon 2011 in October?
Hi Ed,
Thanks, If you are there, presenting a nice new featured item again (say something like Sencha Touch 2) I will definitely come
Rob
Thank you very much, have been looking for a solid solution for this for a while now.
What happens when the store fetches only limited values based on type-ahead or pageSize? To resolve that issue, we create a “proxy” column that holds the displayValue for each row. That way, even if store has paging or filtering based on the row, data on other rows won’t get affected.
Hi,
In that case this solution indeed does not work. I think for more complex setups a proxy column would be ideal. Thanks for the feedback
Thanks so much for this! Been struggling for about 3 hours trying to get a renderer setup, and this took care of it!
Much thanks!
No problem Dave, glad it helped you out. Good luck with your application
Does this working on Ext4?
Hi Rafael,
Honestly I don’t know whether this works on ExtJS 4. The grids in ExtJS 4 have been completely redesigned (for the better!)
Rob
As well as ExtJS4 is, I can’t find examples for it… This is one of the problems.
Hi Nik, the solution for ExtJS would be a new project. The ExtJS 4 grids have been redesigned from the ground up
I guess it doesn’t work in Ext Js 4,
since it seems to refer to classes that are no longer there.
Or have somebody implemented it in ExtJs 4?
Very nice work anyway.
Hi, you’re right. The solution was for ExtJS3. I will try it out under ExtJS4 since I am working on a project that requires it anyway
it’s great Ed.., Thanks..
anyway, is it possible to show all the editor components in a grid editor without clicking them first?
so, when the editor grid is rendered, the editor component in it also appear.
Thanks in Advanced
Hi Yagi,
No, that is not possible. That would require an editor instance for every cell instead of one that is re-used for all cells from a column. The way the editor is implemented in ExtJS is that the editor is created once for a grid column, when you click a cell the editor instance is moved to that cell to edit it (and thus removed from the cell that was previously being edited. Of course you can let the grid start editing with a single click so that the overhead is minimal.
Cheers,
Rob
This is fantastic, i was struggling with this problem since yesterday.
I’m very new with all this ext-js mumbo jumbo, previously i just code web in plain rails. Thanks for sharing, btw.
i think that there is a much easier solution:
http://www.sencha.com/forum/showthread.php?3145-ComboBox-on-an-editable-grid
Hi,
Thanks for your comment. If that solution works better for you, go for it.
Myself, I think it is much more involved and error prone: one has to specify a renderer manually and a custom listener for each and every ‘remote editor’ field. When I have a grid with 5 ‘foreign keys’ into other models I have to create a listener on the validateedit event and create 5 if statements in there to make it work. I would rather just use a custom column definition that does everything for me. I’m a fan of the ‘Don’t Make Me Think’ approach in complex systems
It happen bug “Ext.grid.Column.types undefined” at line Ext.grid.Column.types['combocolumn'] = Ext.ux.grid.ComboColumn;
Hi,
make sure your column classes are loaded before you load the custom column definition. The ExtJS sources create the Ext.grid.Column.types namespace. If you load the custom sources too soon that namespace does not exist yet
I mean error “Ext.grid.Column.types is undefined”.
I used ext js 4.x
Hello,
I have not tested the solution in ExtJS4. Probably it will not work because the ExtJS4 grids are completely rewritten
Will maybe find a solution for ExtJS4 in the near future
Hi! I had that same problem, trying to use your code with ExtJS 4.
I could make it work replacing lines 87 – 104 with the following:
Ext.define(‘Ext.ux.grid.ComboColumn’, {
extend: ‘Ext.grid.Column’,
alias: ‘widget.combocolumn’,
/**
* @cfg {String} gridId
*
* The id of the grid this column is in. This is required to be able to refresh the view once the combo store has loaded
*/
gridId: undefined,
constructor: function(cfg){
Ext.ux.grid.ComboColumn.superclass.constructor.call(this, cfg);
// Detect if there is an editor and if it at least extends a combobox, otherwise just treat it as a normal column and render the value itself
this.renderer = (this.editor && this.editor.triggerAction) ? Ext.ux.renderer.ComboBoxRenderer(this.editor,this.gridId) : function(value) {return value;};
}
});
Great thanks, will test it and update the post
Works like a charm! Perfect!
Good to hear. Good luck with your ExtJS project
I have a question. How to display two value fields in Ext JS combobox?
Thanks in advance
How if I want to dsplay, for example: name, date of birth when I select ID of the person combo box in grid. Name, dateof birth will dispayed in the same row of the grid..
Regards,
Pudjo
Hello and thank you for this custom and very useful column. After hours spent on the understanding of ComboBoxes and Stores, your class is my Graal
I’ll spread the Word on the ModX forums.
Thanks again.
Thanks, you’re welcome
This renderer is to refresh screen is not working if screen has more than one combo. it is loading and refresh only one combo but for others it is expecting twice DB hit as all combo is remote.
Please suggest some solution
Strange, multiple combo’s should not be a problem, will check it out
This component is exactly what I was looking for. I brought this into our project, and was testing this against a simple grid with two lookups. Each lookup was a remote data store with 12 and 19 records.
Most of the time, this was fine (like when the grid was rendering AFTER those lookup stores have already loaded), however, when the onload event was added, I noticed a serious performance hit.
Environment
Ext 4.1, beta 2
Firefox 10
If left alone, the rendering would eventually work (like 1-2 minutes later).
Has anyone else seen this type of thing?
Thanks again for the great component.
Hi,
I will test it out under ExtJS 4 next week, will let you know
Hey Rob! Any luck with 4.0?
Thanks
Hey Luke,
Unfortunately I haven’t had time yet. Currently porting the Sencha Touch Timepicker over to ST2. After that I will try to find time for this one but the next 2 weeks are completely full