NOTE: This article is out of date.
Read about the new dgrid for something fresh!

Last week, we reviewed the basic capabilities of the Dojo Grid by exploring the world of gaskets by creating a simple gasket lookup application. This week, we’ll extend that application with some requests from the fictional president, “Bob”, of our fictional gasket company. Let’s get started!

Bob wants more

Bob has had a week to use the gasket lookup and show it to all of his friends in the industry. Now he has more ideas about what this grid should display and how:

  • Each gasket needs more data displayed (which Bob will provide)
  • Each gasket needs to have an image to show what it looks like
  • The temperatures need to be side-by-side (he thinks it looks confusing)

The first request is pretty simple. Bob wants 4 new columns: type of gasket (ring or full face), thickness, inner diameter, and outer diameter. Let’s take a look at the data:

var data = [
	/* part #, min T rtg, max T rtg, max P rtg, type, thk, i.d., o.d. */
	['4001', -946, 931, 647, 1, 0.25, 0.9375, 13.4375],
	['4002', -601, 1894, 208, 1, 0.03125, 4.0, 13.75],
	['4003', 456, 791, 132, 1, 0.125, 2.3125, 6.1875],
	...
];

We also need to modify our structure from last week to make the temperature columns appear side-by-side. In addition to this, we’re going to add formatting to our new columns to make the diameters and thicknesses more readable and to give a text representation to our type numbers (for now):

function formatType(value){
	switch(value){
		case 0:
			return 'Ring';
		case 1:
			return 'Full Face';
	}
}

// adapted from http://www.yaldex.com/FSCalculators/LcmAndGcd.htm
function getGCD(num, den){
	var a=num, b=den;
	for(var c=a%b; c!=0; a=b, b=c, c=a%b);
	return b;
}

// adapted from 
// http://www.webdeveloper.com/forum/archive/index.php/t-18205.html
function formatFraction(value){
	var parts = String(value).split('.');
	if(parts.length<2){
		return String(value);
	}

	var whole = parseInt(parts[0]);
	var decimal = parseInt(parts[1],10);
	var power = Math.pow(10, parts[1].length);

	var gcd = getGCD(decimal, power);

	return ((whole==0)?"":whole+" ")+decimal/gcd+"/" +
		power/gcd+""";
}

var subrow = [
	{ name: 'Part Number' },
	{ name: 'Min. Temp. (F)', formatter: formatDegrees },
	{ name: 'Max. Temp. (F)', formatter: formatDegrees },
	{ name: 'Max. Pressure' },
	{ name: 'Type', formatter: formatType },
	{ name: 'Thickness', formatter: formatFraction },
	{ name: 'I.D.', formatter: formatFraction },
	{ name: 'O.D.', formatter: formatFraction }
];

var view = {
	rows: [
		subrow
	]
};

var structure = [
	view
];

This Dojo Grid example provides 2/3 of what Bob asked for (and a little more). The next step is to get an image into the grid.

A picture's worth...

So far, we've only returned text from our format functions, but with the Dojo Grid you can return text that contains HTML and the grid will render it. This makes image insertion into our grid a piece of cake:

function formatType(value){
	switch(value){
		case 0:
			return "Ring";
		case 1:
			return "";
	}
}

The resulting Dojo Grid example meets Bob's criteria.

Sorting things out

However, if you remember, Bob is a bit picky. After using the grid for a bit, he notices that ring gaskets sort before full face gaskets, which is backwards (alphabetically). This is because the code we use is 0 for rings and 1 for full face. Rather than change the entire data set, let's define a custom sorting routine:

model.fields.get(4).compare = function(a, b){
	return (b > a ? 1 : (a == b ? 0 : -1));
}

You can put this code right after the instantiation of the model and the grid will sort as requested. Let's divert from Bob's requests for a second so I can show you something that you might be asking yourself: how do we initially sort a column (something that Bob might want to do in the future) when the page loads:

dojo.addOnLoad(function(){
	dijit.byId("grid").setSortIndex(1);
});

We now have a Dojo Grid example sorting the minimum temperature rating column.

Back to Bob

While we were looking at how to initially sort a column, Bob took a look at our sorting demo and came up with some more suggestions. First, he requests ordering the columns differently: part number, type, thickness, inner diameter, outer diameter, minimum temperature, maximum temperature, and maximum pressure. Second, he wants to prevent the part number and type columns from scrolling horizontally. Reordering columns is quite straight-forward. Each cell of a subrow can take a field property which corresponds to the index of the array of each row of our data. To reorder the columns as suggested by Bob, we need to change the subrow:

var subrow = [
	{ name: 'Part Number', height: '64px', field: 0 },
	{ name: 'Type', formatter: formatType, field: 4 },
	{ name: 'Thickness', formatter: formatFraction, field: 5 },
	{ name: 'I.D.', formatter: formatFraction, field: 6 },
	{ name: 'O.D.', formatter: formatFraction, field: 7 },
	{ name: 'Min. Temp. (F)', formatter: formatDegrees, field: 1 },
	{ name: 'Max. Temp. (F)', formatter: formatDegrees, field: 2 },
	{ name: 'Max. Pressure', field: 3 }
];

We have now fairly painlessly created a Dojo Grid with the right sort order. The next part requires a bit of explanation about the way grid layouts work.

Layout Specifics

Last week, I touched on the subject of the structure of the grid by showing you how to define cells in one view of a structure. If you remember, each structure can have multiple views, but I really didn't get into what the structure is. The structure you define for the grid is not the structure of the grid as a whole; rather, it's like a template for each row of the grid. And within that template, you can define multiple views that contain mini tables (subrows) to group data together. Let's take a look at an example of a subrow Dojo Grid.

Both views are very simple, as per Bob. The left view contains the part number and type image columns:

var view1_subrow = [
	{ name: 'Part Number', field: 0 },
	{ name: 'Type', formatter: formatType, field: 4 }
];

var view1 = {
	rows: [
		view1_subrow
	]
};

And the right view contains everything else:

var view2_subrow = [
	{ name: 'Thickness', formatter: formatFraction, field: 5 },
	{ name: 'I.D.', formatter: formatFraction, field: 6 },
	{ name: 'O.D.', formatter: formatFraction, field: 7 },
	{ name: 'Min. Temp. (F)', formatter: formatDegrees, field: 1 },
	{ name: 'Max. Temp. (F)', formatter: formatDegrees, field: 2 },
	{ name: 'Max. Pressure', field: 3 }
];

var view2 = {
	rows: [
		view2_subrow
	]
};

As you can see, each of these views is just a table-like structure, similar to our previous definitions. And structure has been changed to contain these views:

var structure = [
	view1,
	view2
];

The only thing left to do is get rid of that scrollbar in the middle of the grid. You can define an optional noscroll property to tell the view not to render the scrollbars:

var view1 = {
	noscroll: true,
	rows: [
		view1_subrow
	]
};

We end up with a Dojo Grid that Bob can easily use to look up parts and their attributes.

Objectifying Data

Let's take a look at one little improvement we could make. If you're like me, a list of values doesn't mean a whole lot to you. I like my data to have context and it turns out that the Dojo Grid provides a model capable of handling data in that form: dojox.grid.data.Objects. First, we transform our data:

var data = [
	{ part_num: '4001', min_temp: -946, max_temp: 931, max_pres: 647,
	   type: 1, thick: 0.25, inner: 0.9375, outer: 13.4375 },
	{ part_num: '4002', min_temp: -601, max_temp: 1894, max_pres: 208,
	   type: 1, thick: 0.03125, inner: 4.0, outer: 13.75 },
	{ part_num: '4003', min_temp: 456, max_temp: 791, max_pres: 132,
	   type: 1, thick: 0.125, inner: 2.3125, outer: 6.1875 },
	...
];

Next, we instantiate the Objects model instead of the Table model and revised our custom compare function:

var model = new dojox.grid.data.Objects(null, data);
model.fields.get(model.fields.indexOf("type")).compare = function(a, b){
	return (b > a ? 1 : (a == b ? 0 : -1));
}

We must use indexOf and get instead of just get because get works on indexes rather than keys and the object properties are considered keys in the Objects model. This means the keys have to be translated to indexes before using get.

We then change our layout to define which field of the object to use for each cell:

var view1 = {
	noscroll: true,
	rows: [[
		{ name: 'Part Number', field: 'part_num' },
		{ name: 'Type', formatter: formatType, field: 'type' }
	]]
};

var view2 = {
	rows: [[
		{ name: 'Thickness', formatter: formatFraction, 
				field: 'thick' },
		{ name: 'I.D.', formatter: formatFraction, field: 'inner' },
		{ name: 'O.D.', formatter: formatFraction, field: 'outer' },
		{ name: 'Min. Temp. (F)', formatter: formatDegrees,
				 field: 'min_temp' },
		{ name: 'Max. Temp. (F)', formatter: formatDegrees, 
				field: 'max_temp' },
		{ name: 'Max. Pressure', field: 'max_pres' }
	]]
};

The resulting Dojo Grid example functions the same as the one approved by Bob, while making your life easier once you start pulling data out of the model to use in your application.

Next week, we'll be digging into more advanced techniques of the grid like sub-grids (which I said would be this week, but I lied), selections, and editors.

Gaskets Grid