ColumnReorder.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. define([
  2. "dojo/_base/lang",
  3. "dojo/_base/declare",
  4. "dojo/_base/array",
  5. "dojo/on",
  6. "dojo/query",
  7. "dojo/dnd/Source",
  8. "put-selector/put",
  9. "xstyle/css!../css/extensions/ColumnReorder.css"
  10. ], function(lang, declare, arrayUtil, on, query, DndSource, put){
  11. var dndTypeRx = /(\d+)(?:-(\d+))?$/; // used to determine subrow from dndType
  12. // The following 2 functions are used by onDropInternal logic for
  13. // retrieving/modifying a given subRow. The `match` variable in each is
  14. // expected to be the result of executing dndTypeRx on a subRow ID.
  15. function getMatchingSubRow(grid, match) {
  16. var hasColumnSets = match[2],
  17. rowOrSet = grid[hasColumnSets ? "columnSets" : "subRows"][match[1]];
  18. return hasColumnSets ? rowOrSet[match[2]] : rowOrSet;
  19. }
  20. function setMatchingSubRow(grid, match, subRow) {
  21. if(match[2]){
  22. grid.columnSets[match[1]][match[2]] = subRow;
  23. }else{
  24. grid.subRows[match[1]] = subRow;
  25. }
  26. }
  27. // Builds a prefix for a dndtype value based on a grid id.
  28. function makeDndTypePrefix(gridId) {
  29. return "dgrid-" + gridId + '-';
  30. }
  31. // Removes the grid id prefix from a dndtype value. This allows the grid id to contain
  32. // a dash-number suffix. This works only if a column is dropped on the grid from which it
  33. // originated. Otherwise, a dash-number suffix will cause the regex to match on the wrong values.
  34. function stripIdPrefix(gridId, dndtype) {
  35. return dndtype.slice(makeDndTypePrefix(gridId).length);
  36. }
  37. var ColumnDndSource = declare(DndSource, {
  38. // summary:
  39. // Custom dojo/dnd source extension configured specifically for
  40. // dgrid column reordering.
  41. copyState: function(){ return false; }, // never copy
  42. checkAcceptance: function(source, nodes){
  43. return source == this; // self-accept only
  44. },
  45. _legalMouseDown: function(evt){
  46. // Overridden to prevent blocking ColumnResizer resize handles.
  47. return evt.target.className.indexOf("dgrid-resize-handle") > -1 ? false :
  48. this.inherited(arguments);
  49. },
  50. onDropInternal: function(nodes){
  51. var grid = this.grid,
  52. match = dndTypeRx.exec(stripIdPrefix(grid.id, nodes[0].getAttribute("dndType"))),
  53. structureProperty = match[2] ? "columnSets" : "subRows",
  54. oldSubRow = getMatchingSubRow(grid, match),
  55. columns = grid.columns;
  56. // First, allow original DnD logic to place node in new location.
  57. this.inherited(arguments);
  58. if(!match){ return; }
  59. // Then, iterate through the header cells in their new order,
  60. // to populate a new row array to assign as a new sub-row to the grid.
  61. // (Wait until the next turn to avoid errors in Opera.)
  62. setTimeout(function(){
  63. var newSubRow = arrayUtil.map(nodes[0].parentNode.childNodes, function(col) {
  64. return columns[col.columnId];
  65. }),
  66. eventObject;
  67. setMatchingSubRow(grid, match, newSubRow);
  68. eventObject = {
  69. grid: grid,
  70. subRow: newSubRow,
  71. column: columns[nodes[0].columnId],
  72. bubbles: true,
  73. cancelable: true,
  74. // Set parentType to indicate this is the result of user interaction.
  75. parentType: "dnd"
  76. };
  77. // Set columnSets or subRows depending on which the grid is using.
  78. eventObject[structureProperty] = grid[structureProperty];
  79. // Emit a custom event which passes the new structure.
  80. // Allow calling preventDefault() to cancel the reorder operation.
  81. if(on.emit(grid.domNode, "dgrid-columnreorder", eventObject)){
  82. // Event was not canceled - force processing of modified structure.
  83. grid.set(structureProperty, grid[structureProperty]);
  84. }else{
  85. // Event was canceled - revert the structure and re-render the header
  86. // (since the inherited logic invoked above will have shifted cells).
  87. setMatchingSubRow(grid, match, oldSubRow);
  88. grid.renderHeader();
  89. // After re-rendering the header, re-apply the sort arrow if needed.
  90. if (this._sort && this._sort.length){
  91. this.updateSortArrow(this._sort);
  92. }
  93. }
  94. }, 0);
  95. }
  96. });
  97. var ColumnReorder = declare(null, {
  98. // summary:
  99. // Extension allowing reordering of columns in a grid via drag'n'drop.
  100. // Reordering of columns within the same subrow or columnset is also
  101. // supported; between different ones is not.
  102. // columnDndConstructor: Function
  103. // Constructor to call for instantiating DnD sources within the grid's
  104. // header.
  105. columnDndConstructor: ColumnDndSource,
  106. _initSubRowDnd: function(subRow, dndType){
  107. // summary:
  108. // Initializes a dojo/dnd source for one subrow of a grid;
  109. // this could be its only subrow, one of several, or a subrow within a
  110. // columnset.
  111. var dndParent, c, len, col, th;
  112. for(c = 0, len = subRow.length; c < len; c++){
  113. col = subRow[c];
  114. if(col.reorderable === false){ continue; }
  115. th = col.headerNode;
  116. if(th.tagName != "TH"){ th = th.parentNode; } // from IE < 8 padding
  117. // Add dojoDndItem class, and a dndType unique to this subrow.
  118. put(th, ".dojoDndItem[dndType=" + dndType + "]");
  119. if(!dndParent){ dndParent = th.parentNode; }
  120. }
  121. if(dndParent){ // (if dndParent wasn't set, no columns are draggable!)
  122. this._columnDndSources.push(new this.columnDndConstructor(dndParent, {
  123. horizontal: true,
  124. grid: this
  125. }));
  126. }
  127. },
  128. renderHeader: function(){
  129. var dndTypePrefix = makeDndTypePrefix(this.id),
  130. csLength, cs;
  131. this.inherited(arguments);
  132. // After header is rendered, set up a dnd source on each of its subrows.
  133. this._columnDndSources = [];
  134. if(this.columnSets){
  135. // Iterate columnsets->subrows->columns.
  136. for(cs = 0, csLength = this.columnSets.length; cs < csLength; cs++){
  137. arrayUtil.forEach(this.columnSets[cs], function(subRow, sr){
  138. this._initSubRowDnd(subRow, dndTypePrefix + cs + "-" + sr);
  139. }, this);
  140. }
  141. }else{
  142. // Iterate subrows->columns.
  143. arrayUtil.forEach(this.subRows, function(subRow, sr){
  144. this._initSubRowDnd(subRow, dndTypePrefix + sr);
  145. }, this);
  146. }
  147. },
  148. _destroyColumns: function(){
  149. if(this._columnDndSources){
  150. // Destroy old dnd sources.
  151. arrayUtil.forEach(this._columnDndSources, function(source){
  152. source.destroy();
  153. });
  154. }
  155. this.inherited(arguments);
  156. }
  157. });
  158. ColumnReorder.ColumnDndSource = ColumnDndSource;
  159. return ColumnReorder;
  160. });