NestedSorting.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  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.NestedSorting"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.grid.enhanced.plugins.NestedSorting"] = true;
  8. dojo.provide("dojox.grid.enhanced.plugins.NestedSorting");
  9. dojo.require("dojox.grid.enhanced._Plugin");
  10. dojo.declare("dojox.grid.enhanced.plugins.NestedSorting", dojox.grid.enhanced._Plugin, {
  11. // summary:
  12. // Provides nested sorting feature
  13. //
  14. // description:
  15. // A flexible way to control multiple column sorting, including
  16. // 1. Set default sorting order
  17. // 2. Disable sorting for certain columns
  18. // 3. Set sorting order dynamically with JS API
  19. //
  20. // example:
  21. // | <script type="text/javascript">
  22. // | var grid = new dojox.grid.EnhancedGrid({plugins : {nestedSorting: true}},
  23. // | sortFields: [{attribute: 'col4', descending: false},...],//set default sorting order
  24. // | canSort: function(index, field){ return true},//disable sorting for a column
  25. // | ... }, dojo.byId('gridDiv'));
  26. // | grid.startup();
  27. // | //set new sorting order
  28. // | grid.setSortIndex([{attribute: 'col3', descending: true},...])
  29. // | </script>
  30. // name: String
  31. // Plugin name
  32. name: "nestedSorting",
  33. _currMainSort: 'none',//'none'|'asc'|'desc'
  34. _currRegionIdx: -1,
  35. _a11yText: {
  36. 'dojoxGridDescending' : '&#9662;',
  37. 'dojoxGridAscending' : '&#9652;',
  38. 'dojoxGridAscendingTip' : '&#1784;',
  39. 'dojoxGridDescendingTip': '&#1783;',
  40. 'dojoxGridUnsortedTip' : 'x' //'&#10006;'
  41. },
  42. constructor: function(){
  43. this._sortDef = [];
  44. this._sortData = {};
  45. this._headerNodes = {};
  46. //column index that are hidden, un-sortable or indirect selection etc.
  47. this._excludedColIdx = [];
  48. this.nls = this.grid._nls;
  49. this.grid.setSortInfo = function(){};
  50. this.grid.setSortIndex = dojo.hitch(this, '_setGridSortIndex');
  51. this.grid.getSortProps = dojo.hitch(this, 'getSortProps');
  52. if(this.grid.sortFields){
  53. this._setGridSortIndex(this.grid.sortFields, null, true);
  54. }
  55. this.connect(this.grid.views, 'render', '_initSort');//including column resize
  56. this.initCookieHandler();
  57. if(this.grid.plugin('rearrange')){
  58. this.subscribe("dojox/grid/rearrange/move/" + this.grid.id, dojo.hitch(this, '_onColumnDnD'));
  59. }else{
  60. this.connect(this.grid.layout, 'moveColumn', '_onMoveColumn');
  61. }
  62. },
  63. onStartUp: function(){
  64. //overwrite base Grid functions
  65. this.inherited(arguments);
  66. this.connect(this.grid, 'onHeaderCellClick', '_onHeaderCellClick');
  67. this.connect(this.grid, 'onHeaderCellMouseOver', '_onHeaderCellMouseOver');
  68. this.connect(this.grid, 'onHeaderCellMouseOut', '_onHeaderCellMouseOut');
  69. },
  70. _onMoveColumn: function(sourceViewIndex, destViewIndex, cellIndex, targetIndex, before){
  71. var cr = this._getCurrentRegion(),
  72. idx = cr && this._getRegionHeader(cr).getAttribute('idx'),
  73. c = this._headerNodes[idx],
  74. sortData = this._sortData,
  75. newSortData = {},
  76. sortIndex, data;
  77. if(cr){
  78. this._blurRegion(cr);
  79. this._currRegionIdx = dojo.indexOf(this._getRegions(), c.firstChild);
  80. }
  81. if(targetIndex < cellIndex){
  82. for(sortIndex in sortData){
  83. sortIndex = parseInt(sortIndex, 10);
  84. data = sortData[sortIndex];
  85. if(data){
  86. if(sortIndex >= targetIndex && sortIndex < cellIndex){
  87. newSortData[sortIndex + 1] = data;
  88. }else if(sortIndex == cellIndex){
  89. newSortData[targetIndex] = data;
  90. }else{
  91. newSortData[sortIndex] = data;
  92. }
  93. }
  94. }
  95. }else if(targetIndex > cellIndex + 1){
  96. if(!before){
  97. targetIndex++;
  98. }
  99. for(sortIndex in sortData){
  100. sortIndex = parseInt(sortIndex, 10);
  101. data = sortData[sortIndex];
  102. if(data){
  103. if(sortIndex > cellIndex && sortIndex < targetIndex){
  104. newSortData[sortIndex - 1] = data;
  105. }else if(sortIndex == cellIndex){
  106. newSortData[targetIndex - 1] = data;
  107. }else{
  108. newSortData[sortIndex] = data;
  109. }
  110. }
  111. }
  112. }
  113. this._sortData = newSortData;
  114. this._initSort(false);
  115. },
  116. _onColumnDnD: function(type, mapping){
  117. // summary:
  118. // Update nested sorting after column moved
  119. if(type !== 'col'){return;}
  120. var m = mapping, obj = {}, d = this._sortData, p;
  121. var cr = this._getCurrentRegion();
  122. this._blurRegion(cr);
  123. var idx = dojo.attr(this._getRegionHeader(cr), 'idx');
  124. for(p in m){
  125. if(d[p]){
  126. obj[m[p]] = d[p];
  127. delete d[p];
  128. }
  129. if(p === idx){
  130. idx = m[p];
  131. }
  132. }
  133. for(p in obj){
  134. d[p] = obj[p];
  135. }
  136. var c = this._headerNodes[idx];
  137. this._currRegionIdx = dojo.indexOf(this._getRegions(), c.firstChild);
  138. this._initSort(false);
  139. },
  140. _setGridSortIndex: function(inIndex, inAsc, noRefresh){
  141. if(dojo.isArray(inIndex)){
  142. var i, d, cell;
  143. for(i = 0; i < inIndex.length; i++){
  144. d = inIndex[i];
  145. cell = this.grid.getCellByField(d.attribute);
  146. if(!cell){
  147. console.warn('Invalid sorting option, column ', d.attribute, ' not found.');
  148. return;
  149. }
  150. if(cell['nosort'] || !this.grid.canSort(cell.index, cell.field)){
  151. console.warn('Invalid sorting option, column ', d.attribute, ' is unsortable.');
  152. return;
  153. }
  154. }
  155. this.clearSort();
  156. dojo.forEach(inIndex, function(d, i){
  157. cell = this.grid.getCellByField(d.attribute);
  158. this.setSortData(cell.index, 'index', i);
  159. this.setSortData(cell.index, 'order', d.descending ? 'desc': 'asc');
  160. }, this);
  161. }else if(!isNaN(inIndex)){
  162. if(inAsc === undefined){ return; }//header click from base DataGrid
  163. this.setSortData(inIndex, 'order', inAsc ? 'asc' : 'desc');
  164. }else{
  165. return;
  166. }
  167. this._updateSortDef();
  168. if(!noRefresh){
  169. this.grid.sort();
  170. }
  171. },
  172. getSortProps: function(){
  173. // summary:
  174. // Overwritten, see DataGrid.getSortProps()
  175. return this._sortDef.length ? this._sortDef : null;
  176. },
  177. _initSort: function(postSort){
  178. // summary:
  179. // Initiate sorting
  180. var g = this.grid, n = g.domNode, len = this._sortDef.length;
  181. dojo.toggleClass(n, 'dojoxGridSorted', !!len);
  182. dojo.toggleClass(n, 'dojoxGridSingleSorted', len === 1);
  183. dojo.toggleClass(n, 'dojoxGridNestSorted', len > 1);
  184. if(len > 0){
  185. this._currMainSort = this._sortDef[0].descending ? 'desc' : 'asc';
  186. }
  187. var idx, excluded = this._excludedCoIdx = [];//reset it
  188. //cache column index of hidden, un-sortable or indirect selection
  189. this._headerNodes = dojo.query("th", g.viewsHeaderNode).forEach(function(n){
  190. idx = parseInt(dojo.attr(n, 'idx'), 10);
  191. if(dojo.style(n, 'display') === 'none' || g.layout.cells[idx]['nosort'] || (g.canSort && !g.canSort(idx, g.layout.cells[idx]['field']))){
  192. excluded.push(idx);
  193. }
  194. });
  195. this._headerNodes.forEach(this._initHeaderNode, this);
  196. this._initFocus();
  197. if(postSort){
  198. this._focusHeader();
  199. }
  200. },
  201. _initHeaderNode: function(node){
  202. // summary:
  203. // Initiate sort for each header cell node
  204. var sortNode = dojo.query('.dojoxGridSortNode', node)[0];
  205. if(sortNode){
  206. dojo.toggleClass(sortNode, 'dojoxGridSortNoWrap', true);
  207. }
  208. if(dojo.indexOf(this._excludedCoIdx, dojo.attr(node,'idx')) >= 0){
  209. dojo.addClass(node, 'dojoxGridNoSort');
  210. return;
  211. }
  212. if(!dojo.query('.dojoxGridSortBtn', node).length){
  213. //clear any previous connects
  214. this._connects = dojo.filter(this._connects, function(conn){
  215. if(conn._sort){
  216. dojo.disconnect(conn);
  217. return false;
  218. }
  219. return true;
  220. });
  221. var n = dojo.create('a', {
  222. className: 'dojoxGridSortBtn dojoxGridSortBtnNested',
  223. title: this.nls.nestedSort + ' - ' + this.nls.ascending,
  224. innerHTML: '1'
  225. }, node.firstChild, 'last');
  226. n.onmousedown = dojo.stopEvent;
  227. n = dojo.create('a', {
  228. className: 'dojoxGridSortBtn dojoxGridSortBtnSingle',
  229. title: this.nls.singleSort + ' - ' + this.nls.ascending
  230. }, node.firstChild, 'last');
  231. n.onmousedown = dojo.stopEvent;
  232. }else{
  233. //deal with small height grid which doesn't re-render the grid after refresh
  234. var a1 = dojo.query('.dojoxGridSortBtnSingle', node)[0];
  235. var a2 = dojo.query('.dojoxGridSortBtnNested', node)[0];
  236. a1.className = 'dojoxGridSortBtn dojoxGridSortBtnSingle';
  237. a2.className = 'dojoxGridSortBtn dojoxGridSortBtnNested';
  238. a2.innerHTML = '1';
  239. dojo.removeClass(node, 'dojoxGridCellShowIndex');
  240. dojo.removeClass(node.firstChild, 'dojoxGridSortNodeSorted');
  241. dojo.removeClass(node.firstChild, 'dojoxGridSortNodeAsc');
  242. dojo.removeClass(node.firstChild, 'dojoxGridSortNodeDesc');
  243. dojo.removeClass(node.firstChild, 'dojoxGridSortNodeMain');
  244. dojo.removeClass(node.firstChild, 'dojoxGridSortNodeSub');
  245. }
  246. this._updateHeaderNodeUI(node);
  247. },
  248. _onHeaderCellClick: function(e){
  249. // summary
  250. // See dojox.grid.enhanced._Events._onHeaderCellClick()
  251. this._focusRegion(e.target);
  252. if(dojo.hasClass(e.target, 'dojoxGridSortBtn')){
  253. this._onSortBtnClick(e);
  254. dojo.stopEvent(e);
  255. this._focusRegion(this._getCurrentRegion());
  256. }
  257. },
  258. _onHeaderCellMouseOver: function(e){
  259. // summary
  260. // See dojox.grid._Events._onHeaderCellMouseOver()
  261. // When user mouseover other columns than sorted column in a single sorted grid,
  262. // We need to show 1 in the sorted column
  263. if(!e.cell){return; }
  264. if(this._sortDef.length > 1){ return; }
  265. if(this._sortData[e.cellIndex] && this._sortData[e.cellIndex].index === 0){ return; }
  266. var p;
  267. for(p in this._sortData){
  268. if(this._sortData[p] && this._sortData[p].index === 0){
  269. dojo.addClass(this._headerNodes[p], 'dojoxGridCellShowIndex');
  270. break;
  271. }
  272. }
  273. if(!dojo.hasClass(dojo.body(), 'dijit_a11y')){ return; }
  274. //a11y support
  275. var i = e.cell.index, node = e.cellNode;
  276. var singleSortBtn = dojo.query('.dojoxGridSortBtnSingle', node)[0];
  277. var nestedSortBtn = dojo.query('.dojoxGridSortBtnNested', node)[0];
  278. var sortMode = 'none';
  279. if(dojo.hasClass(this.grid.domNode, 'dojoxGridSingleSorted')){
  280. sortMode = 'single';
  281. }else if(dojo.hasClass(this.grid.domNode, 'dojoxGridNestSorted')){
  282. sortMode = 'nested';
  283. }
  284. var nestedIndex = dojo.attr(nestedSortBtn, 'orderIndex');
  285. if(nestedIndex === null || nestedIndex === undefined){
  286. dojo.attr(nestedSortBtn, 'orderIndex', nestedSortBtn.innerHTML);
  287. nestedIndex = nestedSortBtn.innerHTML;
  288. }
  289. if(this.isAsc(i)){
  290. nestedSortBtn.innerHTML = nestedIndex + this._a11yText.dojoxGridDescending;
  291. }else if(this.isDesc(i)){
  292. nestedSortBtn.innerHTML = nestedIndex + this._a11yText.dojoxGridUnsortedTip;
  293. }else{
  294. nestedSortBtn.innerHTML = nestedIndex + this._a11yText.dojoxGridAscending;
  295. }
  296. if(this._currMainSort === 'none'){
  297. singleSortBtn.innerHTML = this._a11yText.dojoxGridAscending;
  298. }else if(this._currMainSort === 'asc'){
  299. singleSortBtn.innerHTML = this._a11yText.dojoxGridDescending;
  300. }else if(this._currMainSort === 'desc'){
  301. singleSortBtn.innerHTML = this._a11yText.dojoxGridUnsortedTip;
  302. }
  303. },
  304. _onHeaderCellMouseOut: function(e){
  305. // summary
  306. // See dojox.grid.enhanced._Events._onHeaderCellMouseOut()
  307. var p;
  308. for(p in this._sortData){
  309. if(this._sortData[p] && this._sortData[p].index === 0){
  310. dojo.removeClass(this._headerNodes[p], 'dojoxGridCellShowIndex');
  311. break;
  312. }
  313. }
  314. },
  315. _onSortBtnClick: function(e){
  316. // summary:
  317. // If the click target is single sort button, do single sort.
  318. // Else if the click target is nested sort button, do nest sort.
  319. // Otherwise return.
  320. var cellIdx = e.cell.index;
  321. if(dojo.hasClass(e.target, 'dojoxGridSortBtnSingle')){
  322. this._prepareSingleSort(cellIdx);
  323. }else if(dojo.hasClass(e.target, 'dojoxGridSortBtnNested')){
  324. this._prepareNestedSort(cellIdx);
  325. }else{
  326. return;
  327. }
  328. dojo.stopEvent(e);
  329. this._doSort(cellIdx);
  330. },
  331. _doSort: function(cellIdx){
  332. if(!this._sortData[cellIdx] || !this._sortData[cellIdx].order){
  333. this.setSortData(cellIdx, 'order', 'asc'); //no sorting data
  334. }else if(this.isAsc(cellIdx)){
  335. this.setSortData(cellIdx, 'order', 'desc'); //change to 'desc'
  336. }else if(this.isDesc(cellIdx)){
  337. this.removeSortData(cellIdx); //remove from sorting sequence
  338. }
  339. this._updateSortDef();
  340. this.grid.sort();
  341. this._initSort(true);
  342. },
  343. setSortData: function(cellIdx, attr, value){
  344. // summary:
  345. // Set sorting data for a column.
  346. var sd = this._sortData[cellIdx];
  347. if(!sd){
  348. sd = this._sortData[cellIdx] = {};
  349. }
  350. sd[attr] = value;
  351. },
  352. removeSortData: function(cellIdx){
  353. var d = this._sortData, i = d[cellIdx].index, p;
  354. delete d[cellIdx];
  355. for(p in d){
  356. if(d[p].index > i){
  357. d[p].index--;
  358. }
  359. }
  360. },
  361. _prepareSingleSort: function(cellIdx){
  362. // summary:
  363. // Prepare the single sort, also called main sort, this will clear any existing sorting and just sort the grid by current column.
  364. var d = this._sortData, p;
  365. for(p in d){
  366. delete d[p];
  367. }
  368. this.setSortData(cellIdx, 'index', 0);
  369. this.setSortData(cellIdx, 'order', this._currMainSort === 'none' ? null : this._currMainSort);
  370. if(!this._sortData[cellIdx] || !this._sortData[cellIdx].order){
  371. this._currMainSort = 'asc';
  372. }else if(this.isAsc(cellIdx)){
  373. this._currMainSort = 'desc';
  374. }else if(this.isDesc(cellIdx)){
  375. this._currMainSort = 'none';
  376. }
  377. },
  378. _prepareNestedSort: function(cellIdx){
  379. // summary
  380. // Prepare the nested sorting, this will order the column on existing sorting result.
  381. var i = this._sortData[cellIdx] ? this._sortData[cellIdx].index : null;
  382. if(i === 0 || !!i){ return; }
  383. this.setSortData(cellIdx, 'index', this._sortDef.length);
  384. },
  385. _updateSortDef: function(){
  386. this._sortDef.length = 0;
  387. var d = this._sortData, p;
  388. for(p in d){
  389. this._sortDef[d[p].index] = {
  390. attribute: this.grid.layout.cells[p].field,
  391. descending: d[p].order === 'desc'
  392. };
  393. }
  394. },
  395. _updateHeaderNodeUI: function(node){
  396. // summary:
  397. // Update the column header UI based on current sorting state.
  398. // Show indicator of the sorting order of the column, no order no indicator
  399. var cell = this._getCellByNode(node);
  400. var cellIdx = cell.index;
  401. var data = this._sortData[cellIdx];
  402. var sortNode = dojo.query('.dojoxGridSortNode', node)[0];
  403. var singleSortBtn = dojo.query('.dojoxGridSortBtnSingle', node)[0];
  404. var nestedSortBtn = dojo.query('.dojoxGridSortBtnNested', node)[0];
  405. dojo.toggleClass(singleSortBtn, 'dojoxGridSortBtnAsc', this._currMainSort === 'asc');
  406. dojo.toggleClass(singleSortBtn, 'dojoxGridSortBtnDesc', this._currMainSort === 'desc');
  407. if(this._currMainSort === 'asc'){
  408. singleSortBtn.title = this.nls.singleSort + ' - ' + this.nls.descending;
  409. }else if(this._currMainSort === 'desc'){
  410. singleSortBtn.title = this.nls.singleSort + ' - ' + this.nls.unsorted;
  411. }else{
  412. singleSortBtn.title = this.nls.singleSort + ' - ' + this.nls.ascending;
  413. }
  414. var _this = this;
  415. function setWaiState(){
  416. var columnInfo = 'Column ' + (cell.index + 1) + ' ' + cell.field;
  417. var orderState = 'none';
  418. var orderAction = 'ascending';
  419. if(data){
  420. orderState = data.order === 'asc' ? 'ascending' : 'descending';
  421. orderAction = data.order === 'asc' ? 'descending' : 'none';
  422. }
  423. var a11ySingleLabel = columnInfo + ' - is sorted by ' + orderState;
  424. var a11yNestedLabel = columnInfo + ' - is nested sorted by ' + orderState;
  425. var a11ySingleLabelHover = columnInfo + ' - choose to sort by ' + orderAction;
  426. var a11yNestedLabelHover = columnInfo + ' - choose to nested sort by ' + orderAction;
  427. dijit.setWaiState(singleSortBtn, 'label', a11ySingleLabel);
  428. dijit.setWaiState(nestedSortBtn, 'label', a11yNestedLabel);
  429. var handles = [
  430. _this.connect(singleSortBtn, "onmouseover", function(){
  431. dijit.setWaiState(singleSortBtn, 'label', a11ySingleLabelHover);
  432. }),
  433. _this.connect(singleSortBtn, "onmouseout", function(){
  434. dijit.setWaiState(singleSortBtn, 'label', a11ySingleLabel);
  435. }),
  436. _this.connect(nestedSortBtn, "onmouseover", function(){
  437. dijit.setWaiState(nestedSortBtn, 'label', a11yNestedLabelHover);
  438. }),
  439. _this.connect(nestedSortBtn, "onmouseout", function(){
  440. dijit.setWaiState(nestedSortBtn, 'label', a11yNestedLabel);
  441. })
  442. ];
  443. dojo.forEach(handles, function(handle){ handle._sort = true; });
  444. }
  445. setWaiState();
  446. var a11y = dojo.hasClass(dojo.body(), "dijit_a11y");
  447. if(!data){
  448. nestedSortBtn.innerHTML = this._sortDef.length + 1;
  449. return;
  450. }
  451. if(data.index || (data.index === 0 && this._sortDef.length > 1)){
  452. nestedSortBtn.innerHTML = data.index + 1;
  453. }
  454. dojo.addClass(sortNode, 'dojoxGridSortNodeSorted');
  455. if(this.isAsc(cellIdx)){
  456. dojo.addClass(sortNode, 'dojoxGridSortNodeAsc');
  457. nestedSortBtn.title = this.nls.nestedSort + ' - ' + this.nls.descending;
  458. if(a11y){sortNode.innerHTML = this._a11yText.dojoxGridAscendingTip;}
  459. }else if(this.isDesc(cellIdx)){
  460. dojo.addClass(sortNode, 'dojoxGridSortNodeDesc');
  461. nestedSortBtn.title = this.nls.nestedSort + ' - ' + this.nls.unsorted;
  462. if(a11y){sortNode.innerHTML = this._a11yText.dojoxGridDescendingTip;}
  463. }
  464. dojo.addClass(sortNode, (data.index === 0 ? 'dojoxGridSortNodeMain' : 'dojoxGridSortNodeSub'));
  465. },
  466. isAsc: function(cellIndex){
  467. return this._sortData[cellIndex].order === 'asc';
  468. },
  469. isDesc: function(cellIndex){
  470. return this._sortData[cellIndex].order === 'desc';
  471. },
  472. _getCellByNode: function(node){
  473. var i;
  474. for(i = 0; i < this._headerNodes.length; i++){
  475. if(this._headerNodes[i] === node){
  476. return this.grid.layout.cells[i];
  477. }
  478. }
  479. return null;
  480. },
  481. clearSort: function(){
  482. this._sortData = {};
  483. this._sortDef.length = 0;
  484. },
  485. //persistence
  486. initCookieHandler: function(){
  487. if(this.grid.addCookieHandler){
  488. this.grid.addCookieHandler({
  489. name: "sortOrder",
  490. onLoad: dojo.hitch(this, '_loadNestedSortingProps'),
  491. onSave: dojo.hitch(this, '_saveNestedSortingProps')
  492. });
  493. }
  494. },
  495. _loadNestedSortingProps: function(sortInfo, grid){
  496. this._setGridSortIndex(sortInfo);
  497. },
  498. _saveNestedSortingProps: function(grid){
  499. return this.getSortProps();
  500. },
  501. //focus & keyboard
  502. _initFocus: function(){
  503. var f = this.focus = this.grid.focus;
  504. this._focusRegions = this._getRegions();
  505. if(!this._headerArea){
  506. var area = this._headerArea = f.getArea('header');
  507. area.onFocus = f.focusHeader = dojo.hitch(this, '_focusHeader');
  508. area.onBlur = f.blurHeader = f._blurHeader = dojo.hitch(this, '_blurHeader');
  509. area.onMove = dojo.hitch(this, '_onMove');
  510. area.onKeyDown = dojo.hitch(this, '_onKeyDown');
  511. area._regions = [];
  512. area.getRegions = null;
  513. this.connect(this.grid, 'onBlur', '_blurHeader');
  514. }
  515. },
  516. _focusHeader: function(evt){
  517. // summary:
  518. // Overwritten, see _FocusManager.focusHeader()
  519. //delayed: Boolean
  520. // If called from "this.focus._delayedHeaderFocus()"
  521. if(this._currRegionIdx === -1){
  522. this._onMove(0, 1, null);
  523. }else{
  524. this._focusRegion(this._getCurrentRegion());
  525. }
  526. try{
  527. dojo.stopEvent(evt);
  528. }catch(e){}
  529. return true;
  530. },
  531. _blurHeader: function(evt){
  532. this._blurRegion(this._getCurrentRegion());
  533. return true;
  534. },
  535. _onMove: function(rowStep, colStep, evt){
  536. var curr = this._currRegionIdx || 0, regions = this._focusRegions;
  537. var region = regions[curr + colStep];
  538. if(!region){
  539. return;
  540. }else if(dojo.style(region, 'display') === 'none' || dojo.style(region, 'visibility') === 'hidden'){
  541. //if the region is invisible, keep finding next
  542. this._onMove(rowStep, colStep + (colStep > 0 ? 1 : -1), evt);
  543. return;
  544. }
  545. this._focusRegion(region);
  546. //keep grid body scrolled by header
  547. var view = this._getRegionView(region);
  548. view.scrollboxNode.scrollLeft = view.headerNode.scrollLeft;
  549. },
  550. _onKeyDown: function(e, isBubble){
  551. if(isBubble){
  552. switch(e.keyCode){
  553. case dojo.keys.ENTER:
  554. case dojo.keys.SPACE:
  555. if(dojo.hasClass(e.target, 'dojoxGridSortBtnSingle') ||
  556. dojo.hasClass(e.target, 'dojoxGridSortBtnNested')){
  557. this._onSortBtnClick(e);
  558. }
  559. }
  560. }
  561. },
  562. _getRegionView: function(region){
  563. var header = region;
  564. while(header && !dojo.hasClass(header, 'dojoxGridHeader')){ header = header.parentNode; }
  565. if(header){
  566. return dojo.filter(this.grid.views.views, function(view){
  567. return view.headerNode === header;
  568. })[0] || null;
  569. }
  570. return null;
  571. },
  572. _getRegions: function(){
  573. var regions = [], cells = this.grid.layout.cells;
  574. this._headerNodes.forEach(function(n, i){
  575. if(dojo.style(n, 'display') === 'none'){return;}
  576. if(cells[i]['isRowSelector']){
  577. regions.push(n);
  578. return;
  579. }
  580. dojo.query('.dojoxGridSortNode,.dojoxGridSortBtnNested,.dojoxGridSortBtnSingle', n)
  581. .forEach(function(node){
  582. dojo.attr(node, 'tabindex', 0);
  583. regions.push(node);
  584. });
  585. },this);
  586. return regions;
  587. },
  588. _focusRegion: function(region){
  589. // summary
  590. // Focus the given region
  591. if(!region){return;}
  592. var currRegion = this._getCurrentRegion();
  593. if(currRegion && region !== currRegion){
  594. this._blurRegion(currRegion);
  595. }
  596. var header = this._getRegionHeader(region);
  597. dojo.addClass(header, 'dojoxGridCellSortFocus');
  598. if(dojo.hasClass(region, 'dojoxGridSortNode')){
  599. dojo.addClass(region, 'dojoxGridSortNodeFocus');
  600. }else if(dojo.hasClass(region, 'dojoxGridSortBtn')){
  601. dojo.addClass(region, 'dojoxGridSortBtnFocus');
  602. }
  603. //For invisible nodes, IE will throw error when calling focus().
  604. try{
  605. region.focus();
  606. }catch(e){}
  607. this.focus.currentArea('header');
  608. this._currRegionIdx = dojo.indexOf(this._focusRegions, region);
  609. },
  610. _blurRegion: function(region){
  611. if(!region){return;}
  612. var header = this._getRegionHeader(region);
  613. dojo.removeClass(header, 'dojoxGridCellSortFocus');
  614. if(dojo.hasClass(region, 'dojoxGridSortNode')){
  615. dojo.removeClass(region, 'dojoxGridSortNodeFocus');
  616. }else if(dojo.hasClass(region, 'dojoxGridSortBtn')){
  617. dojo.removeClass(region, 'dojoxGridSortBtnFocus');
  618. }
  619. region.blur();
  620. },
  621. _getCurrentRegion: function(){
  622. return this._focusRegions[this._currRegionIdx];
  623. },
  624. _getRegionHeader: function(region){
  625. while(region && !dojo.hasClass(region, 'dojoxGridCell')){
  626. region = region.parentNode;
  627. }
  628. return region;
  629. },
  630. destroy: function(){
  631. this._sortDef = this._sortData = null;
  632. this._headerNodes = this._focusRegions = null;
  633. this.inherited(arguments);
  634. }
  635. });
  636. dojox.grid.EnhancedGrid.registerPlugin(dojox.grid.enhanced.plugins.NestedSorting);
  637. }