Skip to content

Latest commit

 

History

History
610 lines (488 loc) · 21.4 KB

0.4-Migration.md

File metadata and controls

610 lines (488 loc) · 21.4 KB

dgrid 0.4 Migration Guide

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 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 for further information.

Replacing dojo/store with dstore

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.

Real-time updates

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.

Legacy stores

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 });

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:

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:

// 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:

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).

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:

// 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' }));
});
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):

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.

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:

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:

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.

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:

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' }),

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:

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.

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:

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.

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 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:

// 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:

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 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:

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));
	};

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 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.

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
    });
});