CellMerge.js 9.0 KB

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