dojo 1.0 was finally released a couple of weeks ago and it packs once again (it was missing in v0.9) one of the most useful widgets, the Grid (formerly FilteringTable). This new widget (actually it's a revised version of TurboGrid) features a nice set of options (in a brief summary):

So now, let's review step by step how to do it. The first thing to do is declare the widget:
- Performant and robust
- Backed by a model (data store)
- Configurable (even programmatically)
- Inline edition
- Not always a huge number of records is available but just little chunks
- Pagination is required or preferred to scrolling
- The current data stores are not meant to interact with a server

So now, let's review step by step how to do it. The first thing to do is declare the widget:
<script type="text/javascript"
src='/dojo/dojo.js'
djConfig="isDebug:false, parseOnLoad: true">
</script>
<script type='text/javascript'>
dojo.require("dojox.grid.VirtualGrid");
dojo.require("dojo.parser");
</script>
<div
id="grid"
dojoType="dojox.VirtualGrid"
autoHeight="true"
autoWidth="false"
rowCount="10"
get="getRow">
</div>
src='/dojo/dojo.js'
djConfig="isDebug:false, parseOnLoad: true">
</script>
<script type='text/javascript'>
dojo.require("dojox.grid.VirtualGrid");
dojo.require("dojo.parser");
</script>
<div
id="grid"
dojoType="dojox.VirtualGrid"
autoHeight="true"
autoWidth="false"
rowCount="10"
get="getRow">
</div>
Nothing fancy there. Just the usual declaration of a dojo widget (dojox in this case). The HTML div includes just one weird attribute named get. It refers to the javascript function that will be called to populate each cell. The table as is cannot be painted. The information about the layout is missing:
dojo.addOnLoad(init);
function init() {
var view = {noscroll: true, cells:[]};
var header = [];
for (var column in headers) {
aColumn = new Object();
aColumn.name = headers[column];
aColumn.formatter = formatCell;
aColumn.width = widths[column] + "px";
header.push(aColumn);
}
view.cells.push(header);
layout.push(view);
}
function init() {
var view = {noscroll: true, cells:[]};
var header = [];
for (var column in headers) {
aColumn = new Object();
aColumn.name = headers[column];
aColumn.formatter = formatCell;
aColumn.width = widths[column] + "px";
header.push(aColumn);
}
view.cells.push(header);
layout.push(view);
}
The code above uses a couple global variables (headers, widths) to create a layout. This layout is used along with the data to create the widget itself. The data will be obtained using the previously declared get function.
function getRow(inRowIndex) {
var obj = dataArray[inRowIndex];
var field = fields[this.index];
return eval("obj." + field)
}
function formatCell(cellData) {
return cellData;
}
var obj = dataArray[inRowIndex];
var field = fields[this.index];
return eval("obj." + field)
}
function formatCell(cellData) {
return cellData;
}
That is enough. Supposing the data is stored as an object array (typical DWR response for example) named dataArray that is globally accesible, the code above retrieves the value of specific field of the object (the field names have to be stored in another array). this.index refers to the current cell, inRowIndex refers to the current row number. The formatCell function is called to format the output, for example to parse date objects. In our case it will rely on the default javascript toString representation. To load and populate the grid call the following function once there's data in the array:
function loadTable() {
dijit.byId("grid").setStructure(layout);
}
dijit.byId("grid").setStructure(layout);
}
By now, a ten row table should be visible on the screen (quite ugly if you haven't imported the stylesheets by the way). This is the base table to work with. It's very basic and lacks all the features. Let's start with sorting. Add the following lines to the init function
var grid = dijit.byId("grid");
grid.canSort = function(column) { return true; }
grid.sort = function() {...}
grid.setSortIndex(0, false);
grid.canSort = function(column) { return true; }
grid.sort = function() {...}
grid.setSortIndex(0, false);
I cannot provide the sort function code here as is quite extensive but you can find it in the example WAR below. With sorting working it's time to add filtering. The only real interest is obtaining the cell value. I'll use a context menu to do it:
<div
dojoType="dijit.Menu"
id="gridMenu"
style="display: none;">
<div
divdojoType="dijit.MenuItem"
onClick="applyCellFilter(cellNode)">
Filter by [<span id="cellFilter"></span>]
</div>
</div>
<script>
var selectedCellNode;
dojo.addOnLoad(function() {
grid = dijit.byId("grid");
gridMenu = dijit.byId("gridMenu");
gridMenu.bindDomNode(grid.domNode);
grid.onCellContextMenu = function(e) {
byId('cellFilter').innerHTML = e.cellNode.innerHTML;
selectedCellNode = e.cellNode;
};
});
function applyCellFilter(id, cell) {...}
</script>
dojoType="dijit.Menu"
id="gridMenu"
style="display: none;">
<div
divdojoType="dijit.MenuItem"
onClick="applyCellFilter(cellNode)">
Filter by [<span id="cellFilter"></span>]
</div>
</div>
<script>
var selectedCellNode;
dojo.addOnLoad(function() {
grid = dijit.byId("grid");
gridMenu = dijit.byId("gridMenu");
gridMenu.bindDomNode(grid.domNode);
grid.onCellContextMenu = function(e) {
byId('cellFilter').innerHTML = e.cellNode.innerHTML;
selectedCellNode = e.cellNode;
};
});
function applyCellFilter(id, cell) {...}
</script>
As before, the code to filter the object array is available in the example WAR. The interesting part in the code above is the onCellContextMenu. It iwill save the cell clicked in a javascript variable for later use.
Finally, the widget is in a position to add pagination. This is done externally using a list of page numbers an a couple of buttons to enable navigation. All the work is done against the data array. Basically, you only need to take care of the current offset. There's a little trick, always ask for one record more than the showed ones so you can easily test if there are more records available. The code can get really complex (a lot of functionality is working together by now) so you're better off reading it completely.
And now to the example. Some things to take into account just before:
Finally, the widget is in a position to add pagination. This is done externally using a list of page numbers an a couple of buttons to enable navigation. All the work is done against the data array. Basically, you only need to take care of the current offset. There's a little trick, always ask for one record more than the showed ones so you can easily test if there are more records available. The code can get really complex (a lot of functionality is working together by now) so you're better off reading it completely.
And now to the example. Some things to take into account just before:
- All the styles and stuff that makes everything look pretty was done by Ignacio Gros, a fellow team member. Check his site if interested.
- The example is provided as a proof of concept, with no warranty of any kind. Don't use it in production!
- Look here for ideas with pagination
- The server side code is mocked by a javascript function. It should be pretty straightforward to plug in something like DWR.
- The widget is wrapped inside a tag file to be easier to use and deploy in a page
- It downloads dojo.js from AOL CDN, change if needed
- Look (use Firebug) to the number of requests when trying to improve performance
