We have recently reached the alpha milestone in the development of dgrid, a new component for creating lists and grids. Built on the latest Dojo technology, dgrid is designed to be lightweight, fast, mobile-ready, and easy-to-use. This SitePen-led project brings the best innovations and techniques from extensive experience on the DataGrid, to create a brand new simple and fast architecture. Let’s explore some of the examples included in the project to demonstrate how to use it.
Download/Installation
dgrid is a package in the Dojo Foundation package repository. To install the grid, you can either download it (and its dependencies) or install it into the current working directory using CPM:
cpm install dgrid
Getting Started
The two most basic modules in the dgrid package are List and Grid. List provides the base functionality for rendering any kind of list. Grid extends List, adding functionality for defining columns and displaying tabular data with headers. Let’s take a look at how we can use the Grid module, as it is likely to be the primary topic of interest.
Simple Grid
The most basic usage of the Grid module is to simply take an array of objects and render them in tabular form. In this simple grid example, our dataset is an array of steps in a recipe that looks like:
var data = [
{order: 1, name:"preheat", description:"Preheat your oven to 350°F"},
{order: 2, name:"mix dry", description:
"In a medium bowl, combine flour, salt, and baking soda"},
...
];
The only real configuration needed for the grid to render this data is column definitions. dgrid allows us to define columns with an object hash (or an array) where the property names correspond to object fields by default, and the values are configuration objects where we can specify the sortability of a column, the label, and other information. The property value can alternately be a simple string, which is interpreted as the label of the column (used in the column header). In our example, we define our columns to render:
var columns = {
order: "step", // simply defining the label
name: {}, // reuses key (field name) as label
description: { label: "what to do", sortable: false }
};
We have defined three columns. The first will render the order property for each object, the second will render the name property, and the third will render the description property. The first two columns will be sortable by default (you can click on the column header to sort it), and since no label is defined in the column definition for the name field, the column header defaults to the property name. The first column uses a string as the property value which defines the column name (equivalent to order: { label: "step" }).
With our column configuration we can now easily instantiate our grid. We just provide the columns and give the id of a target element to the Grid module/constructor:
require(["dgrid/Grid", "dojo/domReady!"],
function(Grid){
// var data = ..., columns = ..., as above
var grid = new Grid({
columns: columns
}, "grid"); // id of target element
...
And then to render the data:
grid.renderArray(data);
With all of these examples, feel free to check the API documentation or README for more information on specific methods and properties.
Skinning
Typically we will also want to apply a skin to our grid. It should be noted that the grid utilizes CSS best practices of structure and skin separation. The structural CSS required for the grid is automatically dynamically loaded as a dependency. However, we can optionally choose a look and feel with one of the provided themes including claro, tundra, nihilo, soria, slate, sage, cactus, and squid.
For example, we could easily apply the Claro theme by adding the following to our page:
<head>
...
<link rel="stylesheet" href="css/skins/claro.css">
</head>
<body class="claro">
...
</body>
We can also easily create custom themes. Because the structural CSS is completely separate, there are only a few rules that need to be specified to provide a new color scheme. Additionally, dgrid follows class naming conventions from jQuery ThemeRoller, so ThemeRoller skins can be easily used on dgrid components.
Store-driven Grid
Next we will create a grid driven by an object store, an API based on the HTML5 IndexedDB API. It can be used with data providers that wrap in-memory data, JSON/REST-based data, or any other source.
Store-driven grids are critical for scaling up to large data sets, as they allow the grid to interact with the data provider and only retrieve the data needed for the visible set of rows. The data provider implements querying, sorting, and paged retrieval of data from the data source. The grid then provides virtual paging where it will request data from the data source as the rows are scrolled into view. It will request sorted data in response to header clicks. The store-driven grids can also automatically send data changes back to the data provider in response to editing cells (more on that later).
Let’s start out by using dojo.store.Memory, a simple store based on an in-memory JavaScript array. This can later be substituted by an alternate dojo.store implementation, such as the JsonRest store, which is a great choice for RESTful JSON communication with a server with integrated support for sorting and paging.
To create the Memory store, we simply instantiate it with an array of data:
define(["dojo/store/Memory"], function(Memory){
var testStore = new Memory({data:arrayOfData});
...
To use the store with a grid, we will use the OnDemandGrid module/constructor. We provide the store to the constructor:
require(["dgrid/OnDemandGrid", "dojo/store/Memory"],
function(Grid, Memory){
var testStore = new Memory({data:arrayOfData});
var grid = new Grid({
store: testStore,
columns: columns
}, "grid");
...
The grid will now immediately query the store for data, retrieving limited blocks or pages of data. If we click on any of the sortable column headers, the grid will re-query the store automatically. The grid can also instantly respond to changes in the underlying data provider if the store supports observation of query results (often achieved by wrapping it with the Observable module).
We can also provide a base query to be passed to the store when queries are executed, by including a “query” property on the grid:
var grid = new Grid({
store: testStore,
query: someQuery,
...
});
Styling Columns
dgrid is designed to be extremely fast and follow styling best practices, achieving column styling via CSS rules. Each column is assigned class names based on the column id and field, of the form “column-
.field-description {
width: 50em;
background-color: blue;
}
Adding Functionality with Mixins
dgrid is designed to easily support adding functionality to the list or grid instance, via the standard mixin composition mechanisms in Dojo via dojo.declare. dgrid includes several mixin modules:
dgrid/Keyboard: Adds keyboard navigation supportdgrid/Selection: Adds row selection supportdgrid/CellSelection: Adds cell selection support (extendsdgrid/Selection)dgrid/ColumnSet: Adds support for sets of column to provide column locking or independent horizontal scrolling
In addition to these “core” mixins, there is an “extensions” subfolder containing additional mixins which add functionality that is potentially useful, but less commonly desired:
dgrid/extensions/DnD: Adds drag’n'drop supportdgrid/extensions/ColumnResizer: Adds column resizing support
In this example, we will add keyboard navigation and row selection support to our grid. We can quickly do this with an anonymous inline dojo.declare instantiation that creates a Grid combined with the Selection and Keyboard mixins:
require(["dgrid/OnDemandGrid","dgrid/Selection", "dgrid/Keyboard"],
function(Grid, Selection, Keyboard){
dojo.declare([Grid, Selection, Keyboard])({
store: testStore,
columns: columns
}, "grid");
...
View the example (see the first grid).
Column Plugins
The Keyboard and Selection mixins are grid-level plugins, but we can create and use column-level plugins as well. Column plugin modules expose functions to be applied to a column definition in our set of columns.
One such plugin is the Editor plugin, which will make cells in the target column editable. With our current sample grid, we can make certain columns editable using this plugin:
require(["dgrid/OnDemandGrid","dgrid/Selection", "dgrid/Keyboard",
"dgrid/Editor"],
function(Grid, Selection, Keyboard, Editor){
var columns = {
// always editable:
col1: Editor({name: "Column 1"}, "text"),
col2: "Column 2",
// editable on double-click:
col3: Editor({name: "Column 3"}, "text", "dblclick"),
col4: {name: "Column 4"},
// editable on single-click:
"last-col": Editor({name: "Column 5", field: "col5"},
"text", "click")
};
...
});
With this you can now edit cells in the grid, similar to the first grid in this example page (however, the example page activates all its Editors on double-click).
The Editor plugin can do much more than render a simple textbox, however. The Editor plugin takes three arguments. The first is the standard column definition object. The second is the editor to use. The third is the event to trigger activation of the editor.
If a string is provided as the second argument, a plain HTML input will be used, and the value of this argument identifies the type of the input to use. Common types would include “radio”, “checkbox”, or “text”. The second argument can alternately be a Dijit form widget constructor, in which case an instance of the widget is used as the editor.
The third argument defines the trigger event for the editor. Common events to use would be “click” or “dblclick”, for mouse events, or the custom “dgrid-cellfocusin” event, which handles both mouse- and keyboard-driven focus events. If the third argument is omitted, the editor will always be visible.
Here is an example of creating radio and checkbox columns (with no trigger event so the radio and checkbox will always be shown):
var columns = [
Editor({name: "CheckBox", field: "bool"}, "checkbox"),
Editor({name: "Radio", sortable: false}, "radio"),
...
];
As stated above, we can also include Dijit form widgets as editors for cells. We simply provide the widget constructor as the second argument. Here is an example of using the DateTextBox (for dates), Slider (for numbers), and NumberSpinner (for numbers) as editors for column cells:
require(["dgrid/Editor", "dijit/form/DateTextBox",
"dijit/form/HorizontalSlider", "dijit/form/NumberSpinner"],
function(Editor, DateTextBox, Slider, NumberSpinner){
var columns = [
Editor({name: "A Date", field: "date"},
DateTextBox),
Editor({name: "Real Number", field: "floatNum"},
Slider),
Editor({name: "Integer", field: "integer"},
NumberSpinner),
...
];
...
});
The editor also supports a function property named canEdit in the column definition object. If provided, this function defines whether a cell in a particular row is editable. For each row/cell, the canEdit function is called and passed the object to be rendered. If canEdit returns a truthy value, the cell will be editable. For example:
var columns = [
Editor({name: "Text editable if checkbox checked", field: "text",
canEdit: function(object){
return object.bool;
}
}, "text", "dblclick"),
...
];
We can see these different editor examples together on this page.
Saving Changes
What happens when we edit a cell in an OnDemandGrid? The grid will store the change in its cache of dirty objects and be ready to save the changes on demand. To save the changes, we simply call the save() method on the grid. Any dirty data will then be sent to the store. Using the Memory store, custom handling would need to be implemented if this data is to be persisted in any way. The JsonRest store will automatically send changes back to the server through PUT requests.
Because OnDemandGrid can automatically respond to changes in data, it is easy to see the editing and saving capability in action. When an OnDemandGrid references a store instance which supports the observe method on its query results, the grid will reflect any changes made or reported at the store level. This can be seen in action in the second and third examples on this page.
When using the Editor column plugin, it is possible to cause edits to a column to automatically save changes to the Grid. Set the autoSave property in the column definition to true, and any changes will be saved as soon as a cell in that column loses focus after being edited.
Tree
Another powerful column plugin is the Tree plugin, which allows for expandable rows to easily navigate hierarchical data. Like Editor and other column plugins, this module returns a function to be applied to a column definition.
To use the Tree plugin, we need a store which provides a getChildren method, which implements the logic for finding the children of an object. In this example, we create a getChildren method that simply gets the children array property, then iterates through the references and retrieves each child item:
var testStore = new Memory({
data: arrayOfData,
getChildren: function(parent){
var store = this;
return dojo.map(parent.children, function(child){
return store.get(child._reference);
});
},
...
We can also (optionally) provide a mayHaveChildren method which will indicate whether a given object has children (and thus whether or not to display an expansion icon).
var testStore = new Memory({
data: arrayOfData,
mayHaveChildren: function(parent){
return parent.children;
},
...
});
And now we simply define the Tree column:
var tree = new Grid({
store: testCountryStore,
query: {type: "continent"},
columns: {
name: new Tree({name:'Name'}),
...
}
});
Another thing to note about this particular example is that we are using a query that will only retrieve the top level items for the initial rendering of the grid. In our example, only objects with a type of “continent” are top level items; we expand those to see the children, which are countries and cities.
Plain Lists and Custom Row Rendering
One of the most direct and low-level ways to use the dgrid package is to use the List module. This module is the base class, and provides basic scrolling (including mobile touch scrolling), row rendering, and row access which the Grid module builds upon. We can directly use the List for situations when we don’t want the grid’s tabular layout, or we need to use our own custom row rendering. We can use the List module the same way as the Grid, simply rendering an array of values or objects, or use OnDemandList (the store-driven version of the List module) to render data from a store. To render an array, we could simply do the following:
require(["dgrid/List"], function(List){
var list = new List({}, "list");
list.renderArray(arrayOfStrings);
});
See our example page again (under the second heading, to the right) for an example List component with Selection and Keyboard functionality mixed in.
If the the array has strings as values (or other values with appropriate toString() methods), it can be directly rendered by the List. If we have an array of objects (or are getting data from a store), we can implement our own renderRow method to render the objects:
var list = new List({
renderRow: function(object, options){
return dojo.create("div", {
innerHTML: "First name: " + object.first +
" last name: " + object.last
});
}
}, "list");
Check it Out
Check out the dgrid project page for more documentation, issue tracking, and source code. We certainly encourage you to peruse the source. It is remarkably small due to the minimalistic design principles, making it very accessible and easy to learn and extend.
In addition, the test pages and a few preliminary demos are available. (These are part of the source package, so you can tinker with them on your own server as well.)

Hi,
I love that new grid. Really better global performance and also faster scrolling…
But i do like to know why we can not provide simple way to:
* provide paging (not virtual paging), paging that send a new request to the store
* as there’s a workable tree plugin, provide declarative grouping
First off, the grid looks nice. Since I have only had a quick look and might be missing something, take these ideas with that in mind.
It would be cool to have “multi-column” sort. I have seen that done in other grids where by holding the Shift key when clicking a second column, it preserves the initial sort then sorts by the clicked column as a second (like in Excel — Sort By column, Then By column…)
Also, for grids that are used in certain applications, it is nice to be able to highlight some rows (even with simple ‘left-click and drag’) then copy them for paste into an external application. Ideally each column/cell entry in a row is separated by a tab when pasted, with the the first column cells in each row the start of a new line. I tested this on one of the demo grids and noticed it mostly worked, but each row started with a tab (that might have been because the first column had images in them, but another test on another grid did the same.) Also, each grid row should be copy/pasted as a single row, which one demo did not do.
Here is some output:
http://sitepen.github.com/dgrid/dgrid/demos/multiview/
dojo Dojo Core
dijit Dijit
dgrid dgrid
xstyle xstyle
put-selector put-selector
http://sitepen.github.com/dgrid/dgrid/demos/dTuned/
Nothin’ Song 5:40 1995 Alice In Chains Alice In Chains Alternative
Frogs 8:18 1995 Alice In Chains Alice In Chains Alternative
Over Now 7:03 1995 Alice In Chains Alice In Chains Alternative
Man In the Box 4:44 1990 Alice In Chains Facelift Alternative
Again, part of the first example and the new lines with one just a tab and another just a space (I know this won’t show up well in a text box) could be because of the images in the first column. Anyway, just some thoughts. Thanks for the good work.
Sorry, that output on the bottom did not show too well. I have created a pastebin that expires in a month. Highlight text to see the tab and space characters:
http://pastebin.com/tg72uKb3
Why is the dgrid not bundled with the 1.7 release?
When using dgrid with an Editor in autoSave mode I was really missing the ability to press escape to cancel the update and revert the value to the original.
I hacked this into dgrid/Editor.js
function onkeypress(event) {
if (event.keyCode == 27) {
var target = event.target;
if(“lastValue” in target && target.className.indexOf(“dgrid-input”) > -1){
target[target.type == "checkbox" || target.type == "radio" ? "checked" : "value"] = target.lastValue;
this.focus();
}
}
}
and added in the renderWidget function the following:
grid.on(“keydown”, dojo.hitch(cell, onkeypress));
It seems to work well for me but rather would like to see this type of functionality be officially added to the codebase so I won’t run into trouble after you update the code.
thanks for the very promising new grid!
@Martin, we are pushing to move new components out of Dojo so they can be released on an independent release cycle, and have some level of autonomy. See this post for more information: http://www.sitepen.com/blog/2011/07/25/dojo-foundation-packages/
Anyone know if it’s possible to use a combination of the onDemandGrid and the Tree? I have a small subset of root nodes, but I would like to lazy load the, say, 10000 children of each of those root nodes. anyone every do this? does the Tree column support this out of the box?
Thanks!
@John: Yes, the Tree column can be used with the OnDemandGrid. This should work as long as your getChildren method accepts a second arg for query options (that will include the start and count for paging) and returns a query result set.
@Kris,
Yeah, that’s what I thought as well.. it’s requesting the first 25 rows successfully, I’m returning them wrapped in a QueryResults object and it’s showing them properly.. however it never seems to request the next 25. My Store could be doing something strange, but I can’t figure out what it might be.
John
I have reproduced what seems to be a problem with the Tree plugin.. I have updated dgrid/test/Tree.html to pass in a modified testCountryStore that contains 100 cities under ‘Argentina’. It only ever shows 25. I’m sure this isn’t the place to be posting feature requests/bugs, but please allow me this one annoying post. :)
“Multiple” selection is supported in the grid but the “dgrid-select” event doesn’t support “multiple” selection. It only has one row. Am I missing something ?
@John: I just fixed the paging issue with the Tree in https://github.com/kriszyp/dgrid/tree/scrolling (we will merge into master soon).
@Superbird: You can get the cumulative set of all selected items from grid.selection (an object hash of selected objects).
Looks like dgrid-datachange is not fired on IE when using a jsonrest store….
see http://dojo-toolkit.33424.n3.nabble.com/dgrid-dgrid-datachange-not-fired-with-IE-9-tp3671607p3671607.html
I’ve put an OnDemandGrid into a mobile dojo application (1.7.1). I’m seeing an issue where the rows recycle when you scroll down (i.e. the first row re-appears, then the second, etc..). Is that a known behaviour? Could it be paging related?
I really like the grid and it works great. But what to do if I can’t tell how many results a query might have (and I really can’t – there is no way around it). I tried to extend the range with every request (like 0-24/50, next request sets header to 25-49/75 and so on), but that does not work ….is there a way to do something like this with dgrid? Thanks for any help …