dgrid 0.4 incorporates significant changes designed to make dgrid more reliable and easier to extend. Some changes will require updates to code written for dgrid 0.3. This document explains the major changes in more detail, with examples contrasting 0.3 and 0.4 API usage.
This document discusses the following changes:
- Dojo version support (1.8+)
- Replacing dojo/store with dstore
- Using the mixins which were converted from column plugins:
- editor
- selector
- tree
- Proper use of
List#renderArray
- Replacing
dgrid/util/mouse
usage withdojo/mouse
As a reminder, dgrid 0.4 no longer supports Dojo 1.7. dstore requires Dojo 1.8 or newer (primarily for
dojo/request
and the new Deferred and Promise implementation), so dgrid 0.4 requires it as well.
Upgrading from Dojo 1.7 to 1.8 or newer should be relatively painless, as 1.x releases are intended to be backwards-compatible. See Dojo's release notes for further information.
dgrid 0.4 interacts with dstore, and no longer directly supports the
dojo/store
API.
dstore's API contains familiar methods for manipulating individual items like
get
, put
and remove
. The primary difference with dojo/store lies in the querying API,
provided by dstore/Collection
.
Reminder: the base List
and Grid
modules do not contain logic for interacting with stores.
dgrid/_StoreMixin
contains the base logic for store interaction, which is inherited by
dgrid/OnDemandList
, dgrid/OnDemandGrid
, and
the Pagination
extension.
The Observable
wrapper in dojo/store
is replaced by dstore's Trackable
mixin:
var TrackableMemory = declare([ Memory, Trackable ]);
var store = new TrackableMemory({ data: ... });
Trackable
also exposes a create
function which can be used to make an existing store instance trackable:
var trackableStore = Trackable.create(existingStore);
As with dgrid 0.3 and observable stores, when a dgrid 0.4 instance receives a trackable collection
(via the collection
property in the constructor arguments object or set('collection', ...)
),
it will automatically track it for changes. Tracking can be explicitly disabled on an instance by setting
shouldTrackCollection
to false
.
Note that when using Trackable.create
, the store must be made trackable before passing it to a dgrid instance
in order for it to track it.
Ideally, stores should be refactored to use the dstore API. dstore also provides the StoreAdapter module to bridge the dojo/store and dstore APIs, but note that this does not support trackable stores.
var dstoreStore = new StoreAdapter({ objectStore: dojoStore });
In dgrid 0.3 and earlier, stores were passed to store-based grids via the store
property:
require([
'dojo/store/Memory', 'dgrid/OnDemandGrid'
]), function (Memory, OnDemandGrid) {
var data = [ /* Populate data with items... */ ];
// Create a store object.
var store = new Memory({ data: data });
// Create a grid referencing the store.
var grid = new OnDemandGrid({
store: store,
columns: { /* Define columns... */ }
}, 'grid');
}
Filtering was performed by setting the query
property:
// Create a grid referencing the store.
var grid = new OnDemandGrid({
store: store,
query: { size: 'large'; } // Show large items only.
columns: { /* Define columns... */ }
}, 'grid');
Basic sorting was performed by simply setting the sort
property to a string indicating the name of a property.
Advanced sorting could be performed by setting the sort
property to an array of one or more objects with
attribute
and (optionally) descending
properties, as expected by dojo/store
:
var grid = new OnDemandGrid({
store: store,
columns: { /* Define columns... */ },
// sort in descending order by the "title" property
sort: [ { attribute: 'title', descending: true } ]
}, 'grid');
In dgrid 0.4, the store
and query
properties have been replaced by the collection
property.
The collection
property expects an object implementing the dstore/Collection
API.
Since dstore/Store
extends dstore/Collection
, you may set a grid's collection
property to a either a store or a
collection (such as one which has been filtered).
require([
'dstore/Memory', 'dgrid/OnDemandGrid'
]), function (Memory, OnDemandGrid) {
var data = [ /* Populate data with items... */ ];
// Create a store object.
var store = new Memory({ data: data });
// Create a grid referencing the store.
var grid = new OnDemandGrid({
collection: store,
columns: { /* Define columns... */ }
}, 'grid');
grid.startup();
}
When you want to filter the displayed items, first use dstore's Collection
API to filter the items
and then assign the resulting collection to the grid's collection
property:
// Create a grid referencing filtered items from the store.
var grid = new OnDemandGrid({
collection: store.filter({ size: 'large' }), // Show only the large items.
columns: { /* Define columns... */ }
}, 'grid');
Note that since each Collection
method returns a new Collection
object, the method calls may be chained.
You can change a grid's store filter by reassigning the collection
property. Here is an
example of displaying items in a grid based on the items' sizes when buttons are clicked.
on(smallButtonNode, 'click', function () {
// When the "small" button is clicked, display only the small items.
grid.set('collection', store.filter({ size: 'small' }));
});
on(mediumButtonNode, 'click', function () {
// When the "medium" button is clicked, display only the medium items.
grid.set('collection', store.filter({ size: 'medium' }));
});
on(largeButtonNode, 'click', function () {
// When the "large" button is clicked, display only the large items.
grid.set('collection', store.filter({ size: 'large' }));
});
Basic sorting works the same as in 0.3 (set sort
to a property name). Advanced sorting behaves
almost the same, except that attribute
is replaced by property
(matching a change in dstore's sort API):
var grid = new OnDemandGrid({
collection: collection,
columns: { /* Define columns... */ },
// sort in descending order by the "title" property
sort: [ { property: 'title', descending: true } ]
}, 'grid');
Further information on using dstore with dgrid 0.4 is available in the updated Using Grids and Stores tutorial.
In dgrid 0.3 and earlier, several features were exposed as column plugins, functions that decorate column definition objects. In dgrid 0.4, the plugins were converted to mixins to make them easier to use and extend.
Each affected module is discussed below with examples.
The Editor module is used to add an input field to one or more grid columns.
In dgrid 0.3 and earlier, you would apply the editor
column plugin to each
individual editable column, and could pass editor
and editOn
either via the column definition object or via extra arguments:
require([
'dgrid/Grid', 'dgrid/editor'
], function (Grid, editor) {
var grid = new Grid({
columns: [
// Passing all editor properties in the column definition parameter:
editor({
field: 'firstName',
label: 'First',
editor: 'text',
editOn: 'click'
}),
// Passing the editor properties as additional parameters:
editor({
field: 'lastName',
label: 'Last'
}, 'text', 'click'),
// This column has no editors:
{
field: 'age',
label: 'Age'
},
// This field always shows a text input:
editor({
field: 'income',
label: 'Income'
})
]
}, 'grid');
});
In dgrid 0.4, you would incorporate the Editor
mixin in your constructor, and specify the editor
property (and optionally others) on each editable column:
require([
'dojo/_base/declare', 'dgrid/Grid', 'dgrid/Editor'
], function (declare, Grid, Editor) {
// Create a custom grid by mixing in Editor
var grid = new (declare([ Grid, Editor ]))({
columns: [
// These columns have the same effect as above,
// but there is only one way to specify editor and editOn in 0.4:
{
field: 'firstName',
label: 'First',
editor: 'text',
editOn: 'click'
},
{
field: 'lastName',
label: 'Last',
editor: 'text',
editOn: 'click'
),
// This column has no editors:
{
field: 'age',
label: 'Age'
},
// This field always shows a text input:
{
field: 'income',
label: 'Income',
editor: 'text'
}
]
}, 'grid');
});
Refer to the Editor
mixin documentation
for more information.
The Selector module is used in conjunction with the Selection mixin to add a selector component to a grid column. Clicking the selector component selects or deselects the entire row.
In dgrid 0.3 and earlier, you would apply the selector
column plugin to the desired column:
require([
'dojo/_base/declare', 'dgrid/OnDemandGrid', 'dgrid/Selection', 'dgrid/selector'
], function (declare, OnDemandGrid, Selection, selector) {
var grid = new (declare([ OnDemandGrid, Selection ]))({
store: store,
selectionMode: 'single',
columns: {
col1: selector({ label: 'Select' }),
col2: 'Column 2'
}
}, 'grid');
});
You could optionally pass a second argument to the plugin or include selectorType
in the column definition's
properties to override the default checkbox selector:
// As second argument:
col1: selector({ label: 'Select'}, 'radio'),
// In column definition object:
col1: selector({ label: 'Select', selectorType: 'radio' }),
In dgrid 0.4, you would incorporate the Selector
mixin in your constructor, and specify the selector
property on the desired column:
require([
'dojo/_base/declare', 'dgrid/Grid', 'dgrid/Selector', 'dstore/Memory'
], function (declare, Grid, Selector, Memory) {
var store = new Memory({ data: [ /* ... */ ]});
// In 0.4, Selector already inherits Selection so you don't have to
var grid = new (declare([ Grid, Selector ]))({
collection: store,
columns: {
col1: { label: 'Select', selector: 'checkbox' }),
col2: 'Column 2'
}
}, 'grid');
});
Notice that the presence of the selector
property indicates that the column should render selectors,
while also indicating the type of selector component to use.
This replaces the plugin invocation and the selectorType
property from 0.3.
Refer to the Selector
documentation
for more information.
The Tree module allows expanding rows to display children in a hierarchical store.
In dgrid 0.3 and earlier, you would apply the tree
plugin around the desired column:
require([
'dgrid/OnDemandGrid', 'dgrid/tree'
], function (OnDemandGrid, tree) {
var store = ...;
var treeGrid = new OnDemandGrid({
store: store,
columns: {
name: tree({ label: 'Name' }),
population: 'Population',
timezone: 'Timezone'
}
}, 'treeGrid');
});
With dgrid 0.4, combine the Tree
mixin with OnDemandGrid
or Pagination
.
require([
'dojo/_base/declare', 'dgrid/OnDemandGrid', 'dgrid/Tree'
], function (declare, OnDemandGrid, Tree) {
var store = ...;
var treeGrid = new (declare([ OnDemandGrid, Tree ]))({
collection: store,
columns: {
name: {
label: 'Name',
renderExpando: true
},
population: 'Population',
timezone: 'Timezone'
}
}, 'treeGrid');
});
The Tree
mixin will render the expando icon in the first column that contains the renderExpando
property. The
value of renderExpando
can also be a function that renders a custom expando icon or widget.
Also note that several of Tree
's configuration properties have been moved from the column definition to the
grid constructor options, as it is expected that there will be only one tree column:
collapseOnRefresh
treeIndentWidth
(formerlyindentWidth
)shouldExpand
enableTreeTransitions
(formerlyenableTransitions
)
Refer to the Tree
mixin documentation
for more information.
While dgrid's high-level interaction with hierarchical stores hasn't changed much in 0.4, common techniques to implement the stores themselves differ between dojo/store and dstore. The following example data will be used below in discussing migration from dgrid 0.3 to dgrid 0.4:
// The first 3 items are the top-level nodes that should expand
// The last 3 items are the children that should display under the expanded nodes
var data = [
{ id: 'AF', name: 'Africa', parent: null, hasChildren: true },
{ id: 'EU', name: 'Europe', parent: null, hasChildren: true },
{ id: 'NA', name: 'North America', parent: null, hasChildren: true },
{ id: 'EG', name: 'Egypt', parent: 'AF', hasChildren: false },
{ id: 'DE', name: 'Germany', parent: 'EU', hasChildren: false },
{ id: 'MX', name: 'Mexico', parent: 'NA', hasChildren: false }
];
(NOTE: This is just one example of structuring hierarchical data; your data can be structured differently as long as you implement appropriate logic in the store methods that dgrid depends on.)
dgrid 0.3's dgrid/tree
column plugin has 3 requirements for the store:
getChildren
: this method should accept a node and return its children, preferably via aQueryResults
object like thequery
method returns. It should also accept a second parameter, an options object. dgrid will specify anoriginalQuery
property on the options object so that the store can maintain any filtering that has been applied to the grid (via itsquery
property).mayHaveChildren
: this method should accept a node and return a boolean value indicating whether the node may have childrenquery
: the query method should be implemented such that queries that omit specifying the parent node should only return top-level nodes (nodes that have no parent), to allowquery
to be used for other filtering purposes
A simple dojo/store/Memory
instance with these methods defined will work:
var store = new Memory({
data: data,
getChildren: function (item, options) {
return this.query(
// Find items whose parent matches this item's identity,
// while preserving any other query parameters
lang.mixin({}, options && options.originalQuery || null, { parent: item.id } )
);
},
mayHaveChildren: function (item) {
return item.hasChildren;
},
query: function (query, options) {
query = query || {};
options = options || {};
if (!query.parent) {
// Only return top-level items by default
// (otherwise this would return items from all levels)
query.parent = null;
}
return this.queryEngine(query, options)(this.data);
}
});
Much like 0.3, dgrid 0.4 requires the store to implement getChildren
and mayHaveChildren
methods. dstore provides a
basic implementation of these methods in the
dstore/Tree
module.
By default, the Tree
module expects the following:
- Each child item references its parent's identity via the
parent
property - Any "leaf" items (with no children) have
hasChildren
set tofalse
- Any top-level items have
parent
set tonull
If your data structure follows this behavior, you can mix the module into your store and use it as-is.
If your data is different, you can extend dstore/Tree
and provide your own implementation of these methods.
With the example data structure above, we don't need to do anything besides create a store that mixes in the
dstore/Tree
module:
var store = new (declare([ Memory, TreeStore ]))({ data: data });
As explained above, dgrid 0.4 no longer supports the query
property on the grid - it is up to you to set the
collection
property to a filtered collection. This holds true with dgrid/Tree
as well, so for the initial grid
display with only top-level nodes visible we need to set the grid's collection
property to a filtered collection.
dstore/Tree
provides a getRootCollection
method specifically for this purpose (which performs a filter for
items whose parent
property is set to null
):
var grid = new (declare([ OnDemandGrid, Tree ]))({
// Initially list top-level items (items with no parent)
collection: store.getRootCollection(),
columns: {
// ...
}
}, 'treeGrid');
If you need to define the getChildren
and mayHaveChildren
methods yourself, it's worth looking at the source of the
dstore/Tree
module to see its usage of the root
property.
When dstore's filter
method is called, it returns a new sub-collection. The data in the sub-collection is different
from the collection that generated it, but other properties from the original collection are copied to the
sub-collection. This enables the root
property to be available to any sub-collections (or sub-sub-collections).
This is most useful in the getChildren
method to allow you to query the entire data set (as opposed to whatever filtered data set is available in the current sub-collection).
Unfortunately, one side effect of dstore/Tree
's root preservation is that it will always apply child queries against
the original, unfiltered store. If you wish to apply custom filters to child queries, you can override getChildren
. For example:
var childFilter = ...;
store.getChildren = function (parent) {
return this.root.filter(lang.mixin({
parent: parent
}, childFilter));
};
With dgrid 0.3 and earlier, you could call store.query()
and pass the results directly to List#renderArray
to
render all of the items returned by the store. In dgrid 0.4, List#renderArray
only accepts standard arrays,
greatly simplifying the implementation. Logic pertaining to query results is now located in
_StoreMixin#renderQueryResults
.
If you want a list or grid that will display all of the items in a store without paging and without the full
implementation of OnDemandList
, take a look at the
Rendering All Store Data at Once tutorial.
It demonstrates how to extend dgrid/_StoreMixin
to create a lightweight mixin that fetches all items from a
collection when a list or grid is refreshed.
Another benefit of renderArray
only handling arrays, synchronously, is that it greatly simplifies extensions.
Previously, properly extending renderArray
would involve using dojo/when
to wrap its return
value to ensure that code truly executes after the results resolve. This is no longer necessary now that
renderArray
is guaranteed to operate synchronously.
The util/mouse
module has been removed from dgrid 0.4. It was introduced to compensate for deficiencies
in the dojo/mouse
module's handling of event bubbling. The dojo/mouse
module was improved in Dojo 1.8, so
the functionality previously provided by dgrid/util/mouse
can now be achieved using dojo/mouse
.
The dgrid/util/mouse
module provided the following synthetic events for handling mouse movement in and out of dgrid
rows and cells:
enterRow
: mouse moves into a row within the body of a list or gridleaveRow
: mouse moves out of a row within the body of a list or gridenterCell
: mouse moves into a cell within the body of a gridleaveCell
: mouse moves out of a cell within the body of a gridenterHeaderCell
: mouse moves into a cell within the header of a gridleaveHeaderCell
: mouse moves out of a cell within the header of a grid
Equivalent functionality can be achieved using the dojo/on
and dojo/mouse
modules
(with dojo/query
loaded for event delegation):
Event | dojo/on extension event |
---|---|
enterRow |
on.selector('.dgrid-content .dgrid-row', mouse.enter) |
leaveRow |
on.selector('.dgrid-content .dgrid-row', mouse.leave) |
enterCell |
on.selector('.dgrid-content .dgrid-cell', mouse.enter) |
leaveCell |
on.selector('.dgrid-content .dgrid-cell', mouse.leave) |
enterHeaderCell |
on.selector('.dgrid-header .dgrid-cell', mouse.enter) |
leaveHeaderCell |
on.selector('.dgrid-header .dgrid-cell', mouse.leave) |
Extension events can be used as indicated in the following example, further
described in the respective section of the
dojo/on
Reference Guide.
require([
'dojo/on',
'dojo/mouse',
'dojo/query'
], function (on, mouse) {
// Assume we have a Grid instance in the variable `grid`...
grid.on(on.selector('.dgrid-content .dgrid-row', mouse.enter), function (event) {
var row = grid.row(event);
// Do something with `row` here in reaction to when the mouse enters
});
});