Technologies are often linked together, and knowledge that you have in one area can help you gain skill in another. This article introduces the major features of Dojo Grid from an Model-View-Controller (MVC) design pattern perspective. Using the article, discover how you can understand and easily master Dojo Grid, even you haven't used it before.

Share:

Luo Ling (luol@cn.ibm.com), Software Engineer, IBM

Photo of Luo LingLuo Ling is an expert on Dojo development and accessibility technology



Jin Pan (panjin@cn.ibm.com), Staff Software Engineer, IBM

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.



Qiangguo Tong (tongqg@cn.ibm.com), Staff Software Engineer, IBM

Qiang Guo Tong is a staff software engineer with more than 5 years experience in Web development. He is the technical leader of several globalization tools used in IBM.



Dan Wang (wdsunny@hotmail.com),

Photo of Dan WangDan Wang has worked in the IBM China Development Lab since 2006, and has participated in several J2EE projects. He is interested in new Web technology such Dojo and Web 2.0.



03 November 2009

Also available in Chinese Japanese

Introduction

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.


Model

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 id attribute 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
A data grid shows a table of info about employees, with data for each employee on rows underneath like a spreadsheet.

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.

Querying the data store

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 storePurpose
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.

Customizing Dojo data stores

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.Read provides 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.Write provides 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.Identity provides 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.Notification provides 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.

View

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
A spreadsheet-style grid shows a list of labels at the top with various employee data underneath.

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 with the same data from Figure 2, but now the horizontal scroll bar only extends underneath the columns to the right of ID and Name

Multi-rowed rows

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
Same grid but now Total annual leave days spans under ID and Name, and Annual leave days already taken spans under Is Manager and Sex

Grid data formatting

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
Same grid except On Board Date is formatted as DD-MMM-YYYY and Is manager shows checkboxes rather than true/false

Using the get interface

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
Grid with a new column of Annual leave days left as calculated value of Annual leave days already taken subtracted from Total annual leave days

Controller

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.

Event handle

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
grid from the exercise is displayed in the background with an overlaying dialog box reading value:40 column:4 row:2

Customized sort

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
Same grid but data is sorted in descending ASCII order by Name column with a triangle in the header you can click to change the sort order

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
Same grid shows the same employee info from Figure 8, but the Name column is sorted in descending alphabetical order ignoring the case of the names

Filters

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
Same dataset is shown as a grid except there is a button at the bottom reading 'filter name' and the list only contains two names: Alex and Alio'

Conclusion

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.

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=442626
ArticleTitle=Dojo Grid using the MVC design pattern
publish-date=11032009