Dojo is an open-source, portable JavaScript toolkit that lets you quickly build rich-client Web applications. It has a rich set of utilities for building responsive applications. Even more, it provides its collection of prepackaged out-of-the-box widgets that can get you using Dojo right away. However, Dojo lacks documentation illustrating how to use each widget, such as Dojo Grid, in detail. Dojo Grid is somewhat like a mini-spreadsheet that can be presented on a Web page. In this article, I will guide you through the major features of Dojo Grid from the Model-View-Controller (MVC) design pattern perspective, which helps you to understand and master Dojo Grid easily, even if you have never used it before.
MVC is an architectural pattern used in software engineering. Successful use of the pattern isolates the concerns of business logic from the user interface and permits one to be freely modified without affecting the other. The controller of this pattern handles the input events through the user interface and triggers the modification of the model behind the scene. The model manipulates application data, and the view uses the model to present results to the user. This pattern is widely used in many frameworks such as Microsoft® MFC, Java™ Swing, Java Enterprise Edition, and so on. In the following sections, I will introduce grid features grouped by MVC respectively.
To differentiate raw data from the fancy appearance of the UI, Dojo Grid maintains a data model to store all the raw data that the grid will manipulate. For example, the date/time type data will usually be stored as milliseconds instead of formatted for human readability like "2009-7-22" so it can be more easily constructed or converted into other kinds of date objects.
Like all MVC widgets, grid has its own data model called a
data store. In Dojo, nearly all widgets that are aware of data stores can
access the common data store using functions such as ItemFileReadStore and ItemFileWriteStore, without having to learn new APIs specific to their
data.
ItemFileReadStore is used for reading
a particular format of JSON data. The DojoX project also provides more stores
such as XmlStore, CsvStore, and OpmlStore. These stores are
used for working with servers that can output data in such formats.
In Dojo Grid, and many other MVC widgets, all data is usually manipulated as an item or as an attribute of an item in their data stores. This way, data can be accessed in a standard fashion and can be used by many widgets at the same time. Listing 1 shows the example data store structure used in this article.
Listing 1. Example of a simple data store structure
{
identifier: 'id',
label:'name',
items: [
{id:0, name: 'Alex', manager: true, sex: 'Male', age:30, date: 1097990400000,
annualLeaveTotal: 15, annualLeaveTaken:2},
{id:1, name: 'Jack', manager: false, sex: 'Male', age:26, date: 1184995200000,
annualLeaveTotal: 10, annualLeaveTaken:3},
{id:2, name: 'Rose', manager: true, sex: 'Female', age:40, date: 894604800000,
annualLeaveTotal: 20, annualLeaveTaken:4},
{id:3, name: 'Wang', manager: true, sex: 'Male', age:44, date: 836630400000,
annualLeaveTotal: 20, annualLeaveTaken:5},
…..
} |
In this example:
- Each item has eight attributes.
- The
idattribute is an identifier that can't be duplicated.
The data stores can be built in two ways: declared as markup tags or constructed programmatically.
Building data stores with markup tags
To build data stores with markup tags, you first need JSON files that will store all the data organized (see Listing 2). In this article, I use data.json. Then, you can write the markup from Listing 2 in HTML files.
Listing 2. Declare datastore in HTML
<span dojoType="dojo.data.ItemFileWriteStore" jsId="myStore"
url="data.json"></span> |
Next, appoint the store to the grid, as shown in Listing 3.
Listing 3. Appoint grid datastore
<table id="grid" jsId="grid" dojoType="dojox.grid.DataGrid" store="myStore" rowSelector="10px"> <thead> ... <thead> <table> |
Now, when Dojo is parsing the HTML code and constructing this grid, it creates a Dojo store object that will get data from the data.json file and then set the grid store to "myStore." An example of the resulting grid is shown in Figure 1.
Figure 1. Simple grid after constructing
Using markup tags to build the grid store is very simple and easy to use. However, if the data comes from a server and is organized dynamically, you need to programmatically build a grid and its store.
Building data stores programmatically
To construct and change the grid's store dynamically in conjunction with server-side responses, you must:
- Use JavaScript to programmatically reorganize the incoming data into data familiar to Dojo.
- Create a Dojo store.
- Set the store to be a grid.
The code in Listing 4 constructs a JSON object formatted as a data store.
Listing 4. Reorganize the data
generateStoreData: function(/*JSON array*/itemList){
var data = {};
var items = [];
for (var i = 0; i < itemList.length; i++) {
var item = {};
item["id"] = itemList[i].id;
item["name"] = itemList[i].name;
item["manger"] = itemList[i].isManger;
item["sex"] = itemList[i].sex;
item["age"] = itemList[i].age;
item["date"] = itemList[i].date;
item["annualLeaveTotal"] = itemList[i].altotal;
item["annualLeaveTaken"] = itemList[i].altaken;
items.push(item);
}
data["identifier"] = "id";
data["label"] = "name";
data["items"] = items;
return data;
} |
Next, you can create a store and set it to be a grid.
Listing 5. Create and set the grid store
dijit.byId("grid").store = new dojo.data.ItemFileReadStore({
data: this.generateStoreData(itemList)
}); |
All these steps give you the same grid as shown in Figure 1.
Dojo Grid usually stores the whole data source in its data model. But, this may cause some performance issues as the size of data grows. Practically, when the items in the Dojo Grid store exceed a certain number, and if there are many attributes for each item, the performance of grid operations such as sort, search, and rendering drop dramatically.
However, there are some methods to improve performance. You can write code to let
servers send limited data to the browser and construct them into a grid data
store, or you can simply use or extend the QueryReadStore provided by the DojoX project to load the data
dynamically from the server. This method can be used for retrieving chunks of data from huge data
stores on the server.
Listing 6. Use query store to handle huge data
<div dojoType="dojox.data.QueryReadStore" jsId="store" url="../someServlet"
requestMethod="post"></div>
<div dojoType="dojox.grid.data.DojoData" jsId="model" store="store"
sortFields="[{attribute: 'name', descending: true}]" rowsPerPage="30"> </div>
<div id="grid" jsId="grid" dojoType="dojox.grid.DataGrid" model="model" structure="layout"
rowSelector="10px"><div> |
The DojoX project provides many other data stores for different purposes. Table 1 shows the currently available stores in Dojo as well as their targets.
Table 1. Available stores in Dojo
| Dojo store | Purpose |
| dojo.data.ItemFileReadStore | Read-only store for JSON data. |
| dojo.data.ItemFileWriteStore | Read/write store for JSON data. |
| dojox.data.CsvStore | Read-only store for comma-separated variable (CSV) formatted data. |
| dojox.data.OpmlStore | Read-only store for Outline Processor Markup Language (OPML). |
| dojox.data.HtmlTableStore | Read-only store for data kept in HTML-formatted tables. |
| dojox.data.XmlStore | Read/write store for basic XML data. |
| dojox.data.FlickrStore | Read store for queries on flickr.com and a good example data store for Web services. |
| dojox.data.FlickrRestStore | Read store for queries on flickr.com and a good example data store for Web services. This is a more advanced version of FlickrStore. |
| dojox.data.QueryReadStore | Similar to ItemFileReadStore, read-only store for JSON data, but queries servers on each request. |
| dojox.data.AtomReadStore | Read store for Atom XML documents. |
You can also write customized data stores using Dojo.data APIs. The data access should be broken into several parts, and data stores should implement each part using appropriate APIs.
-
dojo.data.api.Readprovides the ability to read data items and attributes of those data items. This also includes the ability to search, sort, and filter data items. -
dojo.data.api.Writeprovides the ability to create, delete, and update data items and attributes of those data items. Not all back-end services allow for modification of data items. In fact, most public services like Flikr, Delicious, and GoogleMaps, for example, are primarily read-based data providers. -
dojo.data.api.Identityprovides the ability to locate and look up an item based on its unique identifier, if it has one. Not all data formats have unique identifiers that can be used to look up data items. -
dojo.data.api.Notificationprovides the ability to notify listeners for change events on data items in a store. The basic change events for an item are create, delete, and update. These are particularly useful for cases such as a data store that periodically polls a back-end service for a data refresh.
In the MVC design pattern, the view retrieves application data from the model and presents it to the user. A grid provides a number of functions that are allowed to easily change the presentation. In the following sections, I show some typical uses, demonstrating the strong capabilities of a grid from the view perspective.
Grid layout definition with markup tags
At a high level, a grid can be defined either declaratively in HTML markup or programmatically in JavaScript. Listing 7 shows a definition of the high-level structure using markup tags, which generates the display shown in Figure 2.
Listing 7. JavaScript codes to define a layout with markup
<table id="grid" jsId="grid" dojoType="dojox.grid.DataGrid" store="myStore"
rowSelector="10px">
<thead>
<tr>
<th field="id" width="10px">ID</th>
<th field="name">Name</th>
<th field="manager" with="50px">Is manager</th>
<th field="sex" width="50px">Sex</th>
<th field="age" width="50px">Age</th>
<th field="date" width="100px">On Board date</th>
</tr>
<tr>
<th field="annualLeaveTotal" colspan="3">
Total annual leave days
</th>
<th field="annualLeaveTaken" colspan="3">
Annual leave days already taken
</th>
</tr>
</thead>
</table> |
Figure 2. Grid with layout definition in markup
Grid layout definition programmatically
The structure of the table can also be set
programmatically. The attribute called structure can name an object that
defines the cell structure.
Listing 8. JavaScript codes to define a layout programmatically
var layout = [{
name: 'ID',
field: 'id',
width: '10px'
}, {
name: 'Name',
field: 'name',
width: '50px'
}, {
name: 'Is manager',
field:'manager',
width:'100px'
}, {
name: 'Sex',
field: 'sex',
width: '50px'
}, {
name: 'Age',
field: 'age',
width: '50px'
},{
name: 'On Board date',
field: 'date',
width: '100px'
}, {
name: 'Total annual leave days',
field: 'annualLeaveTotal',
width: '100px'
}, {
name: 'Annual leave days already taken',
field: 'annualLeaveTaken',
width: '100px'
}];
var grid = new dojox.grid.DataGrid({
id: 'grid',
store: myStore,
structure: layout
}, dojo.byId('grid')); |
Locking columns against horizontal scrolling
A set of columns can be locked to prevent them
from scrolling horizontally while allowing other columns to continue to
scroll. To achieve this, you can use two structures and set the noscroll attribute to true on one of the structures.
In the example shown in Listing 9, I declare two structures. One has the
noscroll attribute set to true for the ID and Name columns. Then
I combine these two structures into a layout structure by using an
array.
Listing 9 .JavaScript codes to lock the ID and Name columns
var fixlayout = {
noscroll: true,
cells: [{
name: 'ID',
field: 'id',
width: '10px'
}, {
name: 'Name',
field: 'name',
width: '50px'
}]
};
var mainlayout = {
onBeforeRow: beforerow,
onAfterRow: afterrow,
cells: [{
name: 'Is manager',
field: 'manager',
width: '200px'
}, {
name: 'Sex',
field: 'sex',
width: '50px'
}, {
name: 'Age',
field: 'age',
width: '50px'
}, {
name: 'On Board date',
field: 'date',
width: '100px',
}, {
name: 'Total annual leave days',
field: 'annualLeaveTotal',
width: '100px'
}, {
name: 'Annual leave days already taken',
field: 'annualLeaveTaken',
width: '100px'
}]
};
var layout = [fixlayout, mainlayout]; |
Figure 3 shows that the columns ID and Name are locked, but that the remaining columns have the ability to scroll horizontally.
Figure 3. Grid with fixed columns
A grid
provides the ability for a single logical row to contain multiple lines of
data. This can be achieved by adding the colSpan
attribute into the layout definition, as shown in Listing
10.
Listing 10. JavaScript codes to define multi-rowed rows
var layout = [[{
name: 'ID',
field: 'id',
width: '10px'
}, {
name: 'Name',
field: 'name',
width: '50px'
}, {
name: 'Is manager',
field:'manager',
width:'100px'
}, {
name: 'Sex',
field: 'sex',
width: '50px'
}, {
name: 'Age',
field: 'age',
width: '50px'
},{
name: 'On Board date',
field: 'date',
width: '100px'
}], [ {
name: 'Total annual leave days',
field: 'annualLeaveTotal',
colSpan: '2'
}, {
name: 'Annual leave days already taken',
field: 'annualLeaveTaken',
colSpan: '2'
}]]; |
The columns called "Total annual leave days" and "Annual leave days already taken" are in the same row of data with the other columns
Figure 4. Grid with multi-rows
You can use a grid format function to change the presentation of the data in a data store. It is one of the core concepts of MVC. It can format a datum that conforms to the user's locale, like date, and even construct HTML components, such as a checkbox. Listing 11 shows an example.
Listing 11. JavaScript codes to format grid data
var dateFormatter = function(data, rowIndex){
return dojo.date.locale.format(new Date(data), {
datePattern: "dd MMM yyyy",
selector: "date",
locale: "en"
});
};
var managerFormatter = function(data, rowIndex){
if (data) {
return "<input type='checkbox' checked />";
}
else {
return "<input type='checkbox' />";
}
};
var layout = [{
name: 'ID',
field: 'id',
width: '10px'
}, {
name: 'Name',
field: 'name',
width: '50px'
}, {
name: 'Is manager',
field: 'manager',
formatter: managerFormatter,
width: '100px'
}, {
name: 'Sex',
field: 'sex',
width: '50px'
}, {
name: 'Age',
field: 'age',
width: '50px'
}, {
name: 'On Board date',
field: 'date',
width: '100px',
formatter: dateFormatter
}, {
name: 'Total annual leave days',
field: 'annualLeaveTotal',
width: '100px'
}, {
name: 'Annual leave days already taken',
field: 'annualLeaveTaken',
width: '100px'
}]; |
Figure 5. Grid data formatting
You
can use get interface to define additional columns
outside of the data store to retrieve values dynamically. In
the above example, I have the two "Total annual leave days" and "Annual leave
days already taken" columns. If I want to know how many annual leave days are
remaining, which could be calculated according to the existing two columns,
I could use the get interface to retrieve
it dynamically.
I add a new column named "Annual leave days left," whose value is calculated by subtracting the "Annual leave days already taken" value from the "Total annual leave days" value, as shown in Listing 12.
Listing 12. JavaScript codes to use get interface
function getLeftDays(rowIndex, item){
if (item != null) {
return item.annualLeaveTotal - item.annualLeaveTaken;
}
}
var layout = [{
name: 'ID',
field: 'id',
width: '10px'
}, {
name: 'Name',
field: 'name',
width: '50px'
}, {
name: 'Is manager',
field: 'manager',
formatter: managerFormatter,
width: '100px'
}, {
name: 'Sex',
field: 'sex',
width: '50px'
}, {
name: 'Age',
field: 'age',
width: '50px'
}, {
name: 'On Board date',
field: 'date',
width: '100px',
formatter: dateFormatter
}, {
name: 'Total annual leave days',
field: 'annualLeaveTotal',
width: '100px'
}, {
name: 'Annual leave days already taken',
field: 'annualLeaveTaken',
width: '100px'
}, {
name: 'Annual leave days left',
get: getLeftDays,
width: '100px'
}]; |
Figure 6. Using get interface
In the MVC design pattern, the controller processes and responds to events (typically user actions) and may indirectly invoke changes on the model. The controller in Dojo Grid is very powerful, and it provides many approaches to customize grid behavior; for example, how to handle the event, how to sort the data, how to filter the data, and so on.
In the following sections, I show how to use and customize the controller in Dojo Grid.
Dojo Grid has a powerful event handle mechanism that provides the event invoking interface based on different Grid elements and
event types. For example, it can respond to the click event on a row or cell, and it can respond to a mouseover event. So, it is very useful for you to
customize those event handles to perform a specific task.
I use
onCellClick as an example to show how to add
your own handler on Dojo Grid. In this case, I customize the method to show
the value of the cell and the index of the column and row. (See Listing
13.)
Listing 13. Javascript codes to customize the onCellClick event handler of Grid
<script>
var showDetail = function(e){
var value = e.cellNode.firstChild.data;
alert('value:' + value + " column:" + e.cellIndex + " row:" + e.rowIndex);
}
dojo.addOnLoad(function(){
dojo.connect(grid, "onCellClick", showDetail);
}
</script> |
First,
you need to define the event handler showDetail
to show the cell detail information (value, column index, and row index).
Next, you need to use dojo.connect to connect
the customized handler to the onCellClick event.
You must also do this in dojo.addOnLoad,
because this method ensures that all Dojo widgets have completed
initialization and are ready to use.
When the user clicks on the cell of the grid, the application will display an alert window. Figure 7 shows the result.
Figure 7. The customized event handler for Grid
Dojo Grid has provided the basic sorting functions based on the data type of the column. For example, in my example the ID column is sorted by numerical order, and the name column is sorted by alphabetical order.
The sort
function in Dojo Grid can also be customized. You can define customized sort
behaviors or prevent users from sorting some columns. If you don't want
users to sort some columns, use the canSort
attribute of Dojo Grid to specify which columns can be sorted.
Listing 14 shows the JavaScript codes to disable the sort function on the ID column.
Listing 14. Javascript codes to specify which columns can be sorted
<script>
dojo.addOnLoad(function(){
grid.canSort = function(index) {
if(index == 1) return false;
return true;
};
}
</script> |
The
parameter index is the column index of the grid, which starts from 1. If the
canSort function returns as false, the column sort
function is disabled.
Besides specifying which columns can be sorted, you can also specify how the columns are sorted. I use the Name column in my example. Figure 8 shows the default sort behavior of Dojo Grid.
Figure 8. Default sort behavior of Dojo Grid
I sort the name column in descending order. Note the last three rows: The order is Victor, Wang, and vicky. The grid default sort is case sensitive and sorts using an ASCII code order. So, the lowercase letters will be placed following the uppercase letters. However, this behavior doesn't comply with software globalization standards. In this case, you need to customize the sort function to support globalized sorting.
Take a look at the JavaScript codes in Listing 15 to see how to customize the sort function of Dojo Grid.
Listing 15. Customize the sort function of Dojo Grid
<script>
dojo.addOnLoad(function(){
myStore.comparatorMap = {};
myStore.comparatorMap["name"] = function (a, b) {
return a.localeCompare(b);
}
}
</script> |
There
is field called comparatorMap in the data store
object so you can change the sort behavior. In this example, I define the
comparator method for the name column and use localeCompare to support globalized sorting. Figure 9 shows the
result after the sorting customization.
Figure 9. Customized sort behavior of Dojo Grid
Dojo Grid provides a very convenient way to filter data on the client side. You can define the filter condition for one column. Listing 16 shows how to filter the grid and show only the names that start with the letter A.
Listing 16. Filter on name column
<div dojoType="dijit.form.Button">filter name
<script type="dojo/method" event="onClick" args="evt">
// Filter the name from the data store:
grid.filter({name: "A*"});
</script>
</div> |
After clicking the filter name button, the filter result displays as shown in Figure 10.
Figure 10. Filtered grid
This article describes the Dojo Grid features using the MVC design pattern. Usually, a function can be implemented in various ways. For example, to display a date in the grid, you can either use a string to represent the date in the data store or declare a long and set the correct format for it in the final display. At first, the first choice seems easier. However, if the grid has to be globalized, the latter choice is better. I encourage you to use the MVC design pattern for your Dojo Grid projects. You will get a new level of code robustness and reuse.
- Look to Wikipedia for a definition of
the MVC design pattern.
- Find more information about the Dojo library
at Dojo toolkit official
Web site.
- Read Dojo Grid for the Dojo
Grid reference.
- Read "Dojo Grid
1.2" for new features introduction of Dojo Grid 1.2.
Jin Pan has worked in the IBM China Development Lab since 2006, and has participated in and lead multiple J2EE projects. She is interested in new Web technology such Dojo and Web 2.0. She received her B.S. and M.S. in computer science from Fudan University in 2003 and 2006.
Comments (Undergoing maintenance)







