CellMerge.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojox.grid.enhanced.plugins.CellMerge"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.grid.enhanced.plugins.CellMerge"] = true;
  8. dojo.provide("dojox.grid.enhanced.plugins.CellMerge");
  9. dojo.require("dojox.grid.enhanced._Plugin");
  10. dojo.declare("dojox.grid.enhanced.plugins.CellMerge", dojox.grid.enhanced._Plugin, {
  11. // summary:
  12. // This plugin provides functions to merge(un-merge) adjacent cells within one row.
  13. // Acceptable plugin paramters:
  14. // 1. mergedCells: Array
  15. // An array of objects with structure:
  16. // {
  17. // row: function(Integer)|Integer
  18. // If it's a function, it's a predicate to decide which rows are to be merged.
  19. // It takes an integer (the row index), and should return true or false;
  20. // start: Integer
  21. // The column index of the left most cell that shall be merged.
  22. // end: Integer
  23. // The column index of the right most cell that shall be merged.
  24. // major: Integer
  25. // The column index of the cell whose content should be used as the content of the merged cell.
  26. // It must be larger than or equal to the startColumnIndex, and less than or equal to the endColumnIndex.
  27. // If it is omitted, the content of the leading edge (left-most for ltr, right most for rtl) cell will be used.
  28. // }
  29. // name: String
  30. // Plugin name
  31. name: "cellMerge",
  32. constructor: function(grid, args){
  33. this.grid = grid;
  34. this._records = [];
  35. this._merged = {};
  36. if(args && dojo.isObject(args)){
  37. this._setupConfig(args.mergedCells);
  38. }
  39. this._initEvents();
  40. this._mixinGrid();
  41. },
  42. //----------------Public----------------------------
  43. mergeCells: function(rowTester, startColumnIndex, endColumnIndex, majorColumnIndex){
  44. // summary:
  45. // Merge cells from *startColumnIndex* to *endColumnIndex* at rows that make *rowTester* return true,
  46. // using the content of the cell at *majorColumnIndex*
  47. // tags:
  48. // public
  49. // rowTester: function(Integer)|Integer
  50. // If it's a function, it's a predicate to decide which rows are to be merged.
  51. // It takes an integer (the row index), and should return true or false;
  52. // startColumnIndex: Integer
  53. // The column index of the left most cell that shall be merged.
  54. // endColumnIndex: Integer
  55. // The column index of the right most cell that shall be merged.
  56. // majorColumnIndex: Integer?
  57. // The column index of the cell whose content should be used as the content of the merged cell.
  58. // It must be larger than or equal to the startColumnIndex, and less than or equal to the endColumnIndex.
  59. // If it is omitted, the content of the leading edge (left-most for ltr, right most for rtl) cell will be used.
  60. // return: Object | null
  61. // A handler for the merged cells created by a call of this function.
  62. // This handler can be used later to unmerge cells using the function unmergeCells
  63. // If the merge is not valid, returns null;
  64. var item = this._createRecord({
  65. "row": rowTester,
  66. "start": startColumnIndex,
  67. "end": endColumnIndex,
  68. "major": majorColumnIndex
  69. });
  70. if(item){
  71. this._updateRows(item);
  72. }
  73. return item;
  74. },
  75. unmergeCells: function(mergeHandler){
  76. // summary:
  77. // Unmerge the cells that are merged by the *mergeHandler*, which represents a call to the function mergeCells.
  78. // tags:
  79. // public
  80. // mergeHandler: object
  81. // A handler for the merged cells created by a call of function mergeCells.
  82. var idx;
  83. if(mergeHandler && (idx = dojo.indexOf(this._records, mergeHandler)) >= 0){
  84. this._records.splice(idx, 1);
  85. this._updateRows(mergeHandler);
  86. }
  87. },
  88. getMergedCells: function(){
  89. // summary:
  90. // Get all records of currently merged cells.
  91. // tags:
  92. // public
  93. // return: Array
  94. // An array of records for merged-cells.
  95. // The record has the following structure:
  96. // {
  97. // "row": 1, //the row index
  98. // "start": 2, //the start column index
  99. // "end": 4, //the end column index
  100. // "major": 3, //the major column index
  101. // "handle": someHandle, //The handler that covers this merge cell record.
  102. // }
  103. var res = [];
  104. for(var i in this._merged){
  105. res = res.concat(this._merged[i]);
  106. }
  107. return res;
  108. },
  109. getMergedCellsByRow: function(rowIndex){
  110. // summary:
  111. // Get the records of currently merged cells at the given row.
  112. // tags:
  113. // public
  114. // return: Array
  115. // An array of records for merged-cells. See docs of getMergedCells.
  116. return this._merged[rowIndex] || [];
  117. },
  118. //----------------Private--------------------------
  119. _setupConfig: function(config){
  120. dojo.forEach(config, this._createRecord, this);
  121. },
  122. _initEvents: function(){
  123. dojo.forEach(this.grid.views.views, function(view){
  124. this.connect(view, "onAfterRow", dojo.hitch(this, "_onAfterRow", view.index));
  125. }, this);
  126. },
  127. _mixinGrid: function(){
  128. var g = this.grid;
  129. g.mergeCells = dojo.hitch(this, "mergeCells");
  130. g.unmergeCells = dojo.hitch(this, "unmergeCells");
  131. g.getMergedCells = dojo.hitch(this, "getMergedCells");
  132. g.getMergedCellsByRow = dojo.hitch(this, "getMergedCellsByRow");
  133. },
  134. _getWidth: function(colIndex){
  135. var node = this.grid.layout.cells[colIndex].getHeaderNode();
  136. return dojo.position(node).w;
  137. },
  138. _onAfterRow: function(viewIdx, rowIndex, subrows){
  139. try{
  140. if(rowIndex < 0){
  141. return;
  142. }
  143. var result = [], i, j, len = this._records.length,
  144. cells = this.grid.layout.cells;
  145. //Apply merge-cell requests one by one.
  146. for(i = 0; i < len; ++i){
  147. var item = this._records[i];
  148. var storeItem = this.grid._by_idx[rowIndex];
  149. if(item.view == viewIdx && item.row(rowIndex, storeItem && storeItem.item, this.grid.store)){
  150. var res = {
  151. record: item,
  152. hiddenCells: [],
  153. totalWidth: 0,
  154. majorNode: cells[item.major].getNode(rowIndex),
  155. majorHeaderNode: cells[item.major].getHeaderNode()
  156. };
  157. //Calculated the width of merged cell.
  158. for(j = item.start; j <= item.end; ++j){
  159. var w = this._getWidth(j, rowIndex);
  160. res.totalWidth += w;
  161. if(j != item.major){
  162. res.hiddenCells.push(cells[j].getNode(rowIndex));
  163. }
  164. }
  165. //If width is valid, remember it. There may be multiple merges within one row.
  166. if(subrows.length != 1 || res.totalWidth > 0){
  167. //Remove conflicted merges.
  168. for(j = result.length - 1; j >= 0; --j){
  169. var r = result[j].record;
  170. if((r.start >= item.start && r.start <= item.end) ||
  171. (r.end >= item.start && r.end <= item.end)){
  172. result.splice(j, 1);
  173. }
  174. }
  175. result.push(res);
  176. }
  177. }
  178. }
  179. this._merged[rowIndex] = [];
  180. dojo.forEach(result, function(res){
  181. dojo.forEach(res.hiddenCells, function(node){
  182. dojo.style(node, "display", "none");
  183. });
  184. var pbm = dojo.marginBox(res.majorHeaderNode).w - dojo.contentBox(res.majorHeaderNode).w;
  185. var tw = res.totalWidth;
  186. //Tricky for WebKit.
  187. if(!dojo.isWebKit){
  188. tw -= pbm;
  189. }
  190. dojo.style(res.majorNode, "width", tw + "px");
  191. //In case we're dealing with multiple subrows.
  192. dojo.attr(res.majorNode, "colspan", res.hiddenCells.length + 1);
  193. this._merged[rowIndex].push({
  194. "row": rowIndex,
  195. "start": res.record.start,
  196. "end": res.record.end,
  197. "major": res.record.major,
  198. "handle": res.record
  199. });
  200. }, this);
  201. }catch(e){
  202. console.warn("CellMerge._onAfterRow() error: ", rowIndex, e);
  203. }
  204. },
  205. _createRecord: function(item){
  206. if(this._isValid(item)){
  207. item = {
  208. "row": item.row,
  209. "start": item.start,
  210. "end": item.end,
  211. "major": item.major
  212. };
  213. var cells = this.grid.layout.cells;
  214. item.view = cells[item.start].view.index;
  215. item.major = typeof item.major == "number" && !isNaN(item.major) ? item.major : item.start;
  216. if(typeof item.row == "number"){
  217. var r = item.row;
  218. item.row = function(rowIndex){
  219. return rowIndex === r;
  220. };
  221. }else if(typeof item.row == "string"){
  222. var id = item.row;
  223. item.row = function(rowIndex, storeItem, store){
  224. try{
  225. if(store && storeItem && store.getFeatures()['dojo.data.api.Identity']){
  226. return store.getIdentity(storeItem) == id;
  227. }
  228. }catch(e){
  229. console.error(e);
  230. }
  231. return false;
  232. };
  233. }
  234. if(dojo.isFunction(item.row)){
  235. this._records.push(item);
  236. return item;
  237. }
  238. }
  239. return null;
  240. },
  241. _isValid: function(item){
  242. var cells = this.grid.layout.cells,
  243. colCount = cells.length;
  244. return (dojo.isObject(item) && ("row" in item) && ("start" in item) && ("end" in item) &&
  245. item.start >= 0 && item.start < colCount &&
  246. item.end > item.start && item.end < colCount &&
  247. cells[item.start].view.index == cells[item.end].view.index &&
  248. cells[item.start].subrow == cells[item.end].subrow &&
  249. !(typeof item.major == "number" && (item.major < item.start || item.major > item.end)));
  250. },
  251. _updateRows: function(item){
  252. var min = null;
  253. for(var i = 0, count = this.grid.rowCount; i < count; ++i){
  254. var storeItem = this.grid._by_idx[i];
  255. if(storeItem && item.row(i, storeItem && storeItem.item, this.grid.store)){
  256. this.grid.views.updateRow(i);
  257. if(min === null){ min = i; }
  258. }
  259. }
  260. if(min >= 0){
  261. this.grid.scroller.rowHeightChanged(min);
  262. }
  263. }
  264. });
  265. dojox.grid.EnhancedGrid.registerPlugin(dojox.grid.enhanced.plugins.CellMerge/*name:'cellMerge'*/);
  266. }