# dgrid 0.4 Migration Guide dgrid 0.4 [incorporates significant changes](https://github.com/SitePen/dgrid/releases/tag/v0.4.0) 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 with `dojo/mouse` ## Dojo version support 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](http://dojotoolkit.org/reference-guide/releasenotes/) for further information. ## Replacing dojo/store with dstore dgrid 0.4 interacts with [dstore](https://github.com/SitePen/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`](https://github.com/SitePen/dstore/blob/master/docs/Collection.md). **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`](../components/core-components/OnDemandList-and-OnDemandGrid.md), and the [`Pagination` extension](../components/extensions/Pagination.md). ### Real-time updates The `Observable` wrapper in `dojo/store` is replaced by dstore's [`Trackable` mixin](https://github.com/SitePen/dstore/blob/master/docs/Stores.md#trackable): ```js 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: ```js 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. ### Legacy stores Ideally, stores should be refactored to use the dstore API. dstore also provides the [StoreAdapter](https://github.com/SitePen/dstore/blob/master/docs/Adapters.md#storeadapter) module to bridge the dojo/store and dstore APIs, but note that this does not support trackable stores. ```js var dstoreStore = new StoreAdapter({ objectStore: dojoStore }); ``` ### dgrid APIs interacting with dstore #### dgrid 0.3 ##### Assigning In dgrid 0.3 and earlier, stores were passed to store-based grids via the `store` property: ```js 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 Filtering was performed by setting the `query` property: ```js // Create a grid referencing the store. var grid = new OnDemandGrid({ store: store, query: { size: 'large'; } // Show large items only. columns: { /* Define columns... */ } }, 'grid'); ``` ##### Sorting 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`: ```js var grid = new OnDemandGrid({ store: store, columns: { /* Define columns... */ }, // sort in descending order by the "title" property sort: [ { attribute: 'title', descending: true } ] }, 'grid'); ``` #### dgrid 0.4 ##### Assigning 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). ```js 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(); } ``` ##### Filtering 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: ```js // 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. ```js 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' })); }); ``` ##### Sorting 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): ```js 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](http://dgrid.io/tutorials/0.4/grids_and_stores/) tutorial. ## Using mixins converted from column plugins 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. ### Editor The Editor module is used to add an input field to one or more grid columns. #### dgrid 0.3 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: ```js 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'); }); ``` #### dgrid 0.4 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: ```js 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](../components/mixins/Editor.md) for more information. ### Selector 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. #### dgrid 0.3 In dgrid 0.3 and earlier, you would apply the `selector` column plugin to the desired column: ```js 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: ```js // As second argument: col1: selector({ label: 'Select'}, 'radio'), // In column definition object: col1: selector({ label: 'Select', selectorType: 'radio' }), ``` #### dgrid 0.4 In dgrid 0.4, you would incorporate the `Selector` mixin in your constructor, and specify the `selector` property on the desired column: ```js 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](../components/mixins/Selector.md) for more information. ### Tree The Tree module allows expanding rows to display children in a hierarchical store. #### dgrid 0.3 In dgrid 0.3 and earlier, you would apply the `tree` plugin around the desired column: ```js 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'); }); ``` #### dgrid 0.4 With dgrid 0.4, combine the `Tree` mixin with `OnDemandGrid` or `Pagination`. ```js 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` (formerly `indentWidth`) * `shouldExpand` * `enableTreeTransitions` (formerly `enableTransitions`) Refer to the [`Tree` mixin documentation](../components/mixins/Tree.md) for more information. ### Creating hierarchical stores for use with `dgrid/Tree` 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: ```js // 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 and the `dojo/store` API 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 a `QueryResults` object like the `query` method returns. It should also accept a second parameter, an options object. dgrid will specify an `originalQuery` property on the options object so that the store can maintain any filtering that has been applied to the grid (via its `query` property). * `mayHaveChildren`: this method should accept a node and return a boolean value indicating whether the node may have children * `query`: 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 allow `query` to be used for other filtering purposes A simple `dojo/store/Memory` instance with these methods defined will work: ```js 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); } }); ``` #### dgrid 0.4 and the dstore API 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`](https://github.com/SitePen/dstore/blob/master/docs/Stores.md#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 to `false` * Any top-level items have `parent` set to `null` 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: ```js 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`): ```js 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`](https://github.com/SitePen/dstore/blob/1.1/Tree.js) 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: ```js var childFilter = ...; store.getChildren = function (parent) { return this.root.filter(lang.mixin({ parent: parent }, childFilter)); }; ``` ## Proper use of renderArray 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](http://dgrid.io/tutorials/0.4/single_query/) 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. ## Replacing dgrid/util/mouse with dojo/mouse 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 grid * `leaveRow`: mouse moves out of a row within the body of a list or grid * `enterCell`: mouse moves into a cell within the body of a grid * `leaveCell`: mouse moves out of a cell within the body of a grid * `enterHeaderCell`: mouse moves into a cell within the header of a grid * `leaveHeaderCell`: 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](http://dojotoolkit.org/reference-guide/dojo/on.html#extension-events). ```js 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 }); }); ```