define([ 'dojo/_base/lang', 'dojo/_base/declare', 'dojo/_base/array', 'dojo/dom-class', 'dojo/on', 'dojo/query', 'dojo/dnd/Source' ], function (lang, declare, arrayUtil, domClass, on, query, DndSource) { var dndTypeRx = /(\d+)(?:-(\d+))?$/; // used to determine subrow from dndType // The following 2 functions are used by onDropInternal logic for // retrieving/modifying a given subRow. The `match` variable in each is // expected to be the result of executing dndTypeRx on a subRow ID. function getMatchingSubRow(grid, match) { var hasColumnSets = match[2], rowOrSet = grid[hasColumnSets ? 'columnSets' : 'subRows'][match[1]]; return hasColumnSets ? rowOrSet[match[2]] : rowOrSet; } function setMatchingSubRow(grid, match, subRow) { if (match[2]) { grid.columnSets[match[1]][match[2]] = subRow; } else { grid.subRows[match[1]] = subRow; } } // Builds a prefix for a dndtype value based on a grid id. function makeDndTypePrefix(gridId) { return 'dgrid-' + gridId + '-'; } // Removes the grid id prefix from a dndtype value. This allows the grid id to contain // a dash-number suffix. This works only if a column is dropped on the grid from which it // originated. Otherwise, a dash-number suffix will cause the regex to match on the wrong values. function stripIdPrefix(gridId, dndtype) { return dndtype.slice(makeDndTypePrefix(gridId).length); } var ColumnDndSource = declare(DndSource, { // summary: // Custom dojo/dnd source extension configured specifically for // dgrid column reordering. copyState: function () { return false; // never copy }, checkAcceptance: function (source) { return source === this; // self-accept only }, _legalMouseDown: function (evt) { // Overridden to prevent blocking ColumnResizer resize handles. return evt.target.className.indexOf('dgrid-resize-handle') > -1 ? false : this.inherited(arguments); }, onDropInternal: function (nodes) { var grid = this.grid, match = dndTypeRx.exec(stripIdPrefix(grid.id, nodes[0].getAttribute('dndType'))), structureProperty = match[2] ? 'columnSets' : 'subRows', oldSubRow = getMatchingSubRow(grid, match), columns = grid.columns; // First, allow original DnD logic to place node in new location. this.inherited(arguments); if (!match) { return; } // Then, iterate through the header cells in their new order, // to populate a new row array to assign as a new sub-row to the grid. // (Wait until the next turn to avoid errors in Opera.) setTimeout(function () { var newSubRow = arrayUtil.map(nodes[0].parentNode.childNodes, function (col) { return columns[col.columnId]; }), eventObject; setMatchingSubRow(grid, match, newSubRow); eventObject = { grid: grid, subRow: newSubRow, column: columns[nodes[0].columnId], bubbles: true, cancelable: true, // Set parentType to indicate this is the result of user interaction. parentType: 'dnd' }; // Set columnSets or subRows depending on which the grid is using. eventObject[structureProperty] = grid[structureProperty]; // Emit a custom event which passes the new structure. // Allow calling preventDefault() to cancel the reorder operation. if (on.emit(grid.domNode, 'dgrid-columnreorder', eventObject)) { // Event was not canceled - force processing of modified structure. grid.set(structureProperty, grid[structureProperty]); } else { // Event was canceled - revert the structure and re-render the header // (since the inherited logic invoked above will have shifted cells). setMatchingSubRow(grid, match, oldSubRow); grid.renderHeader(); // After re-rendering the header, re-apply the sort arrow if needed. if (grid.sort.length) { grid.updateSortArrow(grid.sort); } } }, 0); } }); var ColumnReorder = declare(null, { // summary: // Extension allowing reordering of columns in a grid via drag'n'drop. // Reordering of columns within the same subrow or columnset is also // supported; between different ones is not. // columnDndConstructor: Function // Constructor to call for instantiating DnD sources within the grid's // header. columnDndConstructor: ColumnDndSource, _initSubRowDnd: function (subRow, dndType) { // summary: // Initializes a dojo/dnd source for one subrow of a grid; // this could be its only subrow, one of several, or a subrow within a // columnset. var dndParent, c, len, col, th; for (c = 0, len = subRow.length; c < len; c++) { col = subRow[c]; if (col.reorderable === false) { continue; } th = col.headerNode; // Add dojoDndItem class, and a dndType unique to this subrow. domClass.add(th, 'dojoDndItem'); th.setAttribute('dndType', dndType); if (!dndParent) { dndParent = th.parentNode; } } if (dndParent) { this._columnDndSources.push(new this.columnDndConstructor(dndParent, { horizontal: true, grid: this })); } // (If dndParent wasn't set, no columns are draggable) }, renderHeader: function () { var dndTypePrefix = makeDndTypePrefix(this.id), csLength, cs; this.inherited(arguments); // After header is rendered, set up a dnd source on each of its subrows. this._columnDndSources = []; if (this.columnSets) { // Iterate columnsets->subrows->columns. for (cs = 0, csLength = this.columnSets.length; cs < csLength; cs++) { arrayUtil.forEach(this.columnSets[cs], function (subRow, sr) { this._initSubRowDnd(subRow, dndTypePrefix + cs + '-' + sr); }, this); } } else { // Iterate subrows->columns. arrayUtil.forEach(this.subRows, function (subRow, sr) { this._initSubRowDnd(subRow, dndTypePrefix + sr); }, this); } }, _destroyColumns: function () { if (this._columnDndSources) { // Destroy old dnd sources. arrayUtil.forEach(this._columnDndSources, function (source) { source.destroy(); }); } this.inherited(arguments); } }); ColumnReorder.ColumnDndSource = ColumnDndSource; return ColumnReorder; });