NestedSorting.js 22 KB

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