Notice: This is a tutorial for a previous version of dgrid and may be out of date.

Using Grids and Stores

In previous dgrid tutorials, you may have noticed demos which create grids referencing dojo/store instances. This tutorial will delve into the topic of store communication in more detail, and demonstrate dgrid's OnDemandList and Pagination components which interact with stores in various ways.

dojo/store provides a common API for accessing and manipulating data. It is intended to replace the older dojo/data API. As opposed to dojox/grid which interfaced with dojo/data, dgrid interfaces directly with dojo/store.

If this is your first time working with stores, we recommend starting with the Dojo Object Store tutorial.

Before diving into specific components, it is important to understand a few fundamental details of how dgrid interacts with stores. Many of these principles are also applicable to other widgets.

Uniquely Identifying Store Items

Stores are used to represent collections of data items. It is quite common for these items to possess a field which uniquely identifies each item, making it possible to request a specific item. In the dojo/store API, this ability is represented by the get and getIdentity methods. dgrid components generally expect these methods to be supported on connected stores, for purposes of accessing the item represented by a specific row.

In order for these methods to work, the store needs to know how to uniquely identify items. This is typically achieved by specifying idProperty in the arguments passed to the store constructor. If not specified, it usually defaults to "id".

Don't forget to configure the store to properly detect item identities. Failing to do so can lead to a variety of unpredictable behaviors.

Querying Stores

When widgets interact with stores, they do so by directly calling the store's methods, such as query and get. Situations often arise where custom query options need to be passed. Developers frequently attempt to accomplish this task by querying the store directly. However, while this will directly yield filtered results, it will have no effect on the queries performed by any widgets using the store. After all, the widgets are also performing queries on the store themselves, and queries are atomic — that is, arguments passed to one query call have no effect on subsequent calls.

For this reason, widgets that consume stores usually expose their own APIs for specifying custom query options. dgrid's store-enabled components are no exception; we will discuss its APIs for this purpose later in the tutorial.

When dgrid interacts with a store, all paging, filtering, and sorting responsibilities fall upon the store, not the grid. As a result, dgrid has a tendency to bring store- or service-related issues to light. When encountering data rendering issues, always check that the store implementation (and backend service, if applicable) are performing as expected. Don't forget to consult the store documentation; links for the implementations shipped with Dojo are included at the end of this tutorial.

With these general principles now behind us, let's take a look at the components dgrid offers for presenting store data.

Virtual Scrolling with OnDemandGrid

When using dgrid with stores, the most commonly-used component is OnDemandGrid, which combines the tabular display logic of Grid with the on-demand loading and rendering of OnDemandList. The result is a grid which scrolls fluidly as if all items were rendered, but only actually requests items from the store as necessary to fill the current viewport. Developers migrating to dgrid from dojox/grid/DataGrid should find this behavior familiar.

The following example creates a grid which will query an in-memory store. The store is populated with data fetched using Dojo 1.8's dojo/request module.

	require([
		"dojo/request",
		"dojo/store/Memory",
		"dgrid/OnDemandGrid"
	], function (request, Memory, OnDemandGrid) {
		request("hof-batting.json", {
			handleAs: "json"
		}).then(function (response) {
			// Once the response is received, build an in-memory store
			// with the data
			var store = new Memory({ data: response });

			// Create an instance of OnDemandGrid referencing the store
			var grid = new OnDemandGrid({
				store: store,
				columns: {
					first: "First Name",
					last: "Last Name",
					totalG: "Games Played"
				}
			}, "grid");

			grid.startup();
		});
	});
View Demo

OnDemandList and OnDemandGrid provide a number of properties for fine-tuning the lazy-loading behavior, though in many cases, the default values will suffice. The following list outlines a few of the most commonly-altered settings:

minRowsPerPage
The minimum number of items to request from the store at a given time. This number should never be less than the number of rows expected to be visible in the grid at any given time, in order to avoid excessive query calls. The default is 25.
farOffRemoval
The distance, in pixels, at which to consider rows far enough away from the current viewport to begin removing them from the DOM. This can be reduced to more aggressively remove rows from the DOM as the user scrolls away, but it should never be less than the height of the grid. The default is 2000.
pagingDelay
The minimum amount of time (in milliseconds) to wait after scrolling occurs before requesting items from the store. This prevents excessive querying as the user is scrolling, which is especially important when using a store that retrieves data from a server on every call to query. The default is 15.

A complete list of supported properties is available in the OnDemandList and OnDemandGrid documentation.

The following demo includes two instances of OnDemandGrid with different settings for minRowsPerPage and farOffRemoval, to provide an example of how changing these properties can affect user experience.

View Demo

The Pagination Extension

While the virtual scrolling provided by OnDemandGrid is often preferable, sometimes an application calls for presenting data in discrete pages, following a more traditional approach. dgrid includes a Pagination extension for this purpose, displaying a set number of results at any given time. The Pagination extension populates a footer area with controls to switch between pages, which can be customized via various properties outlined in the Pagination documentation.

Note that the Pagination extension is incompatible with OnDemandGrid, since each has its own way of dealing with store queries. Pagination should be mixed into Grid, not OnDemandGrid.

The following example is very similar to the first OnDemandGrid example above, but instantiates a Grid with the Pagination extension mixed in instead:

	require([
		"dojo/_base/declare",
		"dojo/request",
		"dojo/store/Memory",
		"dgrid/Grid",
		"dgrid/extensions/Pagination"
	], function (declare, request, Memory, Grid, Pagination) {
		request("hof-batting.json", {
			handleAs: "json"
		}).then(function (response) {
			// Once the response is received, build an in-memory store
			// with the data
			var store = new Memory({ data: response });

			// Create a Grid instance using Pagination,
			// referencing the store
			var grid = new (declare([Grid, Pagination]))({
				store: store,
				className: "dgrid-autoheight",
				columns: {
					first: "First Name",
					last: "Last Name",
					totalG: "Games Played"
				}
			}, "grid");

			grid.startup();
		});
	});
View Demo

For further examples, including a form which allows experimenting with various properties supported by the Pagination extension, see the test page from the dgrid repository.

Customizing Messages

Both OnDemandGrid and Pagination include functionality allowing for custom messages to be displayed while data is being loaded, and when an empty result set is received.

loadingMessage
An optional message to be displayed in the loading node which appears when a new page of results is requested.
noDataMessage
An optional message to be displayed when a query yields no results.
	var grid = new (declare([Grid, Pagination]))({
		store: store,
		columns: columns,
		loadingMessage: "Loading data...",
		noDataMessage: "No results found."
	}, "grid");

	grid.startup();

These messages may be styled using the dgrid-loading and dgrid-no-data classes, respectively:

	.dgrid-no-data,
	.dgrid-loading {
		color: #aaa;
		font-size: 3em;
		padding: 3em;
		text-align: center;
	}
View Demo

Handling Errors

You may have noticed that there is no equivalent for defining an error message. This is because errors can occur for a wide variety of reasons, at different points in time, so providing a place for an error message might not have a solution that fits all situations. However, dgrid instances will emit a `dgrid-error` event when an error occurs during a store operation. This event can be listened to in order to react however is deemed appropriate.

In addition to the dgrid-error event, the OnDemandGrid and Pagination modules also emit a dgrid-refresh-complete event any time a refresh call completes successfully.

The following example demonstrates listening to these two events in conjunction to show or hide a node containing an error message:

	grid.on("dgrid-error", function(event) {
		// Display an error message above the grid when an error occurs.
		messageNode.className = "message error";
		messageNode.innerHTML = event.error.message;
	});

	grid.on("dgrid-refresh-complete", function(event) {
		// Hide any previous error message when a refresh
		// completes successfully.
		messageNode.className = "message hidden";
		messageNode.innerHTML = "";
	});
View Demo

APIs for Customizing Queries

Earlier, we mentioned that dgrid includes APIs for specifying custom query options. The following properties can be set when calling the constructor:

sort
Specifies the sort order to be used for store queries. The value is normally an array of objects where each object contains an attribute property identifying the data field to sort, and optionally a descending property which, if true, indicates to sort descending rather than ascending. For simple cases, dgrid supports passing a string, simply referencing the name of a data field to be sorted in ascending order.
query
Specifies the query argument to be passed to the store's query method.
queryOptions
Contains properties to be passed in the queryOptions argument to the store's query method. Note that sort will be mixed in from the grid's sort property, and start and count will generally be specified by the particular dgrid component in use (e.g. OnDemandList or Pagination, discussed below).

These options can also be modified later after instantiation, using the set method:

set("sort", newSort)
Resets the sort order for store queries.
set("query", newQuery[, newQueryOptions])
Resets the query argument to be passed to the store's query method; queryOptions may optionally be changed at the same time.
set("store", newStore[, newQuery[, newQueryOptions]])
Instructs the grid to reference a different store; query and queryOptions may optionally be changed at the same time.

The following example demonstrates hooking up form controls to handlers which use the set method to update sort and query:

	on(dom.byId("queryForm"), "submit", function(event) {
		event.preventDefault();
		grid.set("query", {
			// Pass a RegExp to Memory's SimpleQueryEngine
			// Note: this code does not go out of its way to escape
			// characters that have special meaning in RegExps
			last: new RegExp(this.elements.last.value, "i")
		});
	});

	on(dom.byId("queryForm"), "reset", function() {
		// Reset the query when the form is reset
		grid.set("query", {});
	});

	on(dom.byId("sortLastAsc"), "click", function() {
		// Simple case: pass a string representing field to be
		// sorted in ascending order
		grid.set("sort", "last");
	});

	on(dom.byId("sortGamesDesc"), "click", function() {
		// Advanced case: pass array of objects, following
		// the same format as queryOptions.sort in stores
		grid.set("sort", [{ attribute: "totalG", descending: true }]);
	});
View Demo

Remember that dgrid only understands dojo/store APIs. In cases where you need to connect an older dojo/data store with a dgrid instance, wrap the store using the dojo/store/DataStore module.

Conclusion

dgrid offers two distinct ways to efficiently retrieve data using the dojo/store API: the OnDemandList and OnDemandGrid modules, and the Pagination mixin. These components directly call the store's query method, which is expected to handle all logic pertaining to sorting, paging, and filtering; however, they support various properties which allow passing necessary information through to the store.

Resources