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.
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 adescending
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'squery
method. queryOptions
- Contains properties to be passed in the
queryOptions
argument to the store'squery
method. Note thatsort
will be mixed in from the grid'ssort
property, andstart
andcount
will generally be specified by the particular dgrid component in use (e.g.OnDemandList
orPagination
, 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'squery
method;queryOptions
may optionally be changed at the same time. set("store", newStore[, newQuery[, newQueryOptions]])
- Instructs the grid to reference a different store;
query
andqueryOptions
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.