As a part of our Free Dojo Support initiative, we received the following question from Jiho Han about using Dojo’s DataGrid component.

The Question

“The typical configuration for me is to have two views where the view on the left is “frozen” and the second view on the right scrolls horizontally. Currently I’m running into an issue where the header and data cell heights are rendered differently between the views”

Our Answer

Let’s look at how the DataGrid handles variable height of column headers and cell contents, where the gotchas are and how to resolve them.

DataGrid Recap

The DataGrid (dojox/grid/DataGrid) is a powerful and flexible component for presenting and interacting with tabular data. The arrangement and population of the grid’s columns are defined in a “structure”, which is provided to the grid when it instantiates. The structure can be one or more “views” – independently fixed and scrollable sets of columns. For clarity and usability, it is critical that the height of all cells in a row are synchronized – across views if necessary – so that each row is visually aligned and coherent, and the data remains readable.

Variable Heights

The good news is that DataGrid seamlessly handles wrapping and variable height in both headers and cell content, automatically aligning rows across all views. Unlike many Grid implementations, there is no single-line assumption made of the data; the DataGrid is more web-ish, if content needs to wrap to fit the available width, it does. We can demonstrate this with the following layout, and some sample data with variable field content lengths

DataGrid with 2 views, text wrapping in headers and cell content

This behavior is consistent across our supported browsers, in Dojo version 1.6, and back to at least version 1.4.

Gotchas

The DataGrid actively measures content heights and strives to keep rows aligned. So what can go wrong? One way to break row alignment is to insert content into a header or cell whose height is not resolved until after the grid has finished calculating its dimensions. This can occur when you have images in either your header or cell content. In the following demo, the data includes a url property that points to a JPEG image. We use a formatter to turn that into an <img> tag which gets rendered into the cell. The first two columns belong to one fixed view, the remainder are defined by a second view and are allowed to scroll horizontally as necessary.

DataGrid with images, 2 views, misaligned rows

In this case, the final dimensions of the images, and therefore of each cell are not known until the image has finished loading. By the time the image is loaded and the browser draws the box for it with the dimensions it finally has, the DataGrid’s rendering process has long since finished. As a result we see rows misaligned across the views.

The Fix

The simplest fix is to always supply dimensions with images. This allows the browser to create the correct sized box for the image, and the DataGrid to accurately calculate row height and correctly align the rows in its first pass. If the image size is fixed and known in advanced (e.g for an icon), you could add a class to the image markup, or use an inline style to set the width/height at run-time. A simple solution might use a formatter function:
function formatImg(iconUrl, inRowIndex) {
  return '<img src="' + iconUrl + '" style="width: 16px; height: 16px;">';
}

But what if the image dimensions are unknown in advance? Or if you need to change the content of a cell or row after the initial rendering, and in doing so change its height? In our demo, we mocked up a grid rendering results including thumbnail images, similar to the results you might get from a Flickr API search result. These have a variable height, and the dimensions are not included in the response item. There are two possible approaches here. One, is to re-render the grid by calling grid.render() at a time when all images are loaded (or other height variations are resolved). Knowing when that moment is will depend on the specific data and use case. The other, more adaptable approach is to use the grid’s rowHeightChanged method.

Handling dynamic height change with rowHeightChanged

The rowHeightChanged method takes a row index, and triggers recalculation of the height of the row. It also notifies the view and the scroller so any necessary adjustments can be made. In our example, we can call it from the load event handler of the image.

function formatImgHandleLoad(url, inRowIndex) {
	var img = new Image(), 
	      grid = this.grid;
	img.src = url
	
	img.onload = function() { 
		grid.rowHeightChanged(inRowIndex);
	};
	return '<img src="' + url + '">';
}

The result confirms the approach:

DataGrid with images, 2 views, rows re-aligned via rowHeightChanged

Recap

  • Most content in even the most complex view combinations will “just work”. The DataGrid actively sizes rows based on the content in each
  • Provide the browser and the DataGrid any dimensions it needs to calculate row height once with accuracy. This means specifying at least the height of elements like images via CSS – either inline or with a stylesheet.
  • The grid provides a rowHeightChanged method to selectively re-measure individual rows. If you change any content in a row after it has rendered, or have images whose dimensions are not known until loaded, use rowHeightChanged to update and correct row height and alignment across all your views.
  • As a last resort, or when many rows change, you can apply a second rendering pass to correct row height and alignment in bulk by calling the grid’s .render() method.