Dojo Grids: Diving Deeper

By on November 13, 2007 10:27 pm
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

Comments

  • ts

    Good stuff. One little quirk – in the examples above in FF2, if I move the scroll bar to show the right-most columns and click on the column on the far right, the columns shift back to the far left. I can’t see the column I just sorted.

  • Les

    How can I disable sorting for a particular column?

    For example, I would like to disable sorting the grid on the column that displays images.

  • ptwobrussell

    Bryan — I have spent quite a while getting a custom widget into the grid today. In the end, I included a “” string value as a part of my data model, and then I manually parsed it after page load. I couldn’t figure out any other way to make this happen.

    So, it renders and works…sort of…

    The problem is that any custom action on the grid that triggers a redraw (say, a column resize, a double click in the row that contains the custom widget, etc.) causes the widget to disappear. Okay, I get that (sort of), because the grid is using the model to render itself. So, for now, I disabled column resizing, column sorting, and double click events so that my grid doesn’t “lose” the custom widget. It’s ugly, but it work (for now, anyway).

    By now, I’m sure you know my question: what’s the right way to include a custom widget in the grid? I’m sure there’s a much cleaner way to get custom widgets into the grid without having to lose all of those grid features. I’m just not sure how. Seems to me that there would be a way to include a widget in the data model and have the grid deal with it properly. Can you please help?

  • ptwobrussell

    Argh, looks like the part of my post that contained markup got wiped out. That first paragraph should have included a div element that had a dojoType=”fooWidget” attribute in it.

  • Excellent tutorial, thanks!

    dojox.grid.data.Objects is really lovely. The only minor quirk I’ve noticed is: I need to invoke update() after setModel(), otherwise my formatters are fed with “undefined” arguments. When I resize the window, the grid does not adapt either.

    In both cases, clicking the column header fixes the problem. If I’m reading the source code correctly, this (eventually) leads to VirtualGrid.setSortInfo() being invoked which in turn calls update(), so this might be the proper way of doing it, anyway. How would one invoke update() in reaction to resizing, though?

  • Avinash Punekar

    I have 2 grids and one of them displays data from MySQL. How can I drag a row from the grid and drop it on another grid? FYI, I used to do this on 0.4 but am not able to do the same on 1.0

  • Pingback: Hack Your Life » Blog Archive » HOWTO: Dojo Grid, Checkbox Selection()

  • Simeon

    Great tutorial.
    Is it possible to embed a dojo grid into another layout container? for example a tab container? I tried this and for some reason the tab gets messed up. Any idea why?

  • Great Tutotial.
    The grid is a very nice widget …
    Is there community in spanish working with Dojo or SitePen in spanish country ???

    Alex

  • Quang Le

    Hi Bryan Forbes,

    It is a great tutorial, you make it easy to understand the concept of the grid layout. Thank you very much.

    I come up a question: how can you exchange the position of the two cells (two columns) on the layout without changing your data (switching two fields)?

    If you have the solution, it will be more useful to customize the layout presentation with out changing the data sent from server.

    Would the author or any one in the forum answer for me? Thank you very much.

    Quang Le

  • Seshu

    That’s a great tutorial there.

    But, can you tell me how i can use nested objects inside a grid.
    for e.g. the serverData is like:
    serverData = [{“ab”:1,”bc”:1234567891,”cd”:79.23,”de”:1100,”ef”:2109,”fg”:1000,”gh”:190,”hi”:900, “empDetail”:{“firstName”:”abc”, “lastName”:”def”}},{“ab”:2,”bc”:”1234567892″,”cd”:80.30,”de”:1200,”ef”:3118,”fg”:2000,”gh”:290,”hi”:1000, “empDetail”:{“firstName”:”BLAH”, “lastName”:”BLAHBLAH”}}];

    Here, i want to access “empDetail” properties like “empDetail.firstName”, “empDetail.lastName”.

    I am trying to define the grid layout like this:

    var empView = {
    cells: [[
    { name: ‘First Name’, field: ’empDetail.firstName’, width: ’10em’ },
    { name: ‘Last Name’, field: ’empDetail.lastName’, width: ’10em’ }
    ]]
    };

    how do i display the employee name in a different grid using the object notation empDetail.firstName?

  • David

    Is search/filter dataset function builtin in grid? is the search case sensitive? if so, is there a way make it in-sensitive?
    Thanks,

    David

  • This is great: nice widget, nice tutorial (something dojo definitely lacks) and nice style. But, what if Bob actually wants to select one of the values to copy paste? or even to copy the image?

    I think libraries like this one are great, and I use them regularly too, but the basic stuff just can’t be forgotten: people bookmark, people hit the Go Back button and they Copy Paste.

    My 2 cents :)

  • Anon

    There is one thing I am not able to figure out in the example with two views where the “noscrollbar” property is set to true for the first view. If I interpret each view as a separate grid, how does the first view (grid) also scroll up when the second view is scrolled up or down? What would have happened if the “noscrollbar” was “false” in the first view? Would there be two vertical scrollbars? Then it would seem like the scrollbar of the second view includes the first view in some cases and not others. That would be a strange, non-intuitive behavior. Does it really work like this?

    If I think of the two views as part of one grid, then they cannot have separate scrollbars at all.

    I am not quite getting the purpose of “views”.

    Nice article!

  • JD

    Can you provide an example where the grid is created programmatically? Where the model is created by a URL. In my case, the URL is linked to a Java Action Class that creates a JSON array.

  • @JD: Here’s an example using RPC: http://archive.dojotoolkit.org/nightly/dojotoolkit/dojox/grid/tests/test_yahoo_search.html (Dojo 1.2-based) and if you prefer to use a DataStore, you could follow the lead at http://persevere.sitepen.com:9080/jsclient/dojox/grid/tests/test_dojo_data_model_persevere.html (that one is markup-based rather than programmtic, but should be easy to follow how to make it programmatic instead).

  • Pingback: SitePen Blog » Dojo 1.2 Grid()

  • Jeff

    Let’s say you have a few hundred rows in your grid, how would you print all the content?

  • Sridhar

    How will you create pagination for the grid like
    1 – 2 – 3 -4 ,each link has page linked to it based on rows per page. I tried to include rowsPerPage attribute but it didnt work. It would be of great help to me if you give me an example for college project

  • Aroop

    If I want to Filter the Dojo Grid Like Excel sheet auto filter.
    How can I add this feature into the Grid?

  • Jon

    The setSortIndex doesn’t work to set an initial sort. I’ve googled this and I didn’t find anyone who this works for. Where does this piece of code go? After the grid.startup()? Here is what I have. What am I missing?

    var eDetailGrid = new dojox.grid.DataGrid({
    id: 'gridDetails',
    store: TicketBehavior.DetailStore,
    query: {materialTypeDescription: '*'},
    structure: detailLayout,
    selectionMode: "single",
    columnReordering: false,
    style: {width:'905px', height:'120px', fontSize: '12px'}
    }, document.createElement('div'));

    dojo.byId("Data_Details").innerHTML = '';
    dojo.byId("Data_Details").appendChild(eDetailGrid.domNode);

    eDetailGrid.startup();

    dojo.addOnLoad(function() {
    dijit.byId('gridDetails').setSortIndex(0, false);
    });

  • venkat

    Hi sir… this is nice site.

    Sir I have one requirement in my project, that is i need to load my grid dynamically. for that I need to pass parameters when i am loading my grid. How can I pass params when i am loading my grid?
    Plz help me…

    Thank you sir

  • venkat

    I need to add hyperlink in the rows and checkbox how can i do that

  • dolly

    How I can have more than two views in last example? If I simply add view3, it shows me multiple scroller. I want view3 to be scrollable and added behind to view2 in single scroll. Appreciate your help.