_FocusManager.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. define("dojox/grid/_FocusManager", [
  2. "dojo/_base/array",
  3. "dojo/_base/lang",
  4. "dojo/_base/declare",
  5. "dojo/_base/connect",
  6. "dojo/_base/event",
  7. "dojo/_base/sniff",
  8. "dojo/query",
  9. "./util",
  10. "dojo/_base/html"
  11. ], function(array, lang, declare, connect, event, has, query, util, html){
  12. // focus management
  13. return declare("dojox.grid._FocusManager", null, {
  14. // summary:
  15. // Controls grid cell focus. Owned by grid and used internally for focusing.
  16. // Note: grid cell actually receives keyboard input only when cell is being edited.
  17. constructor: function(inGrid){
  18. this.grid = inGrid;
  19. this.cell = null;
  20. this.rowIndex = -1;
  21. this._connects = [];
  22. this._headerConnects = [];
  23. this.headerMenu = this.grid.headerMenu;
  24. this._connects.push(connect.connect(this.grid.domNode, "onfocus", this, "doFocus"));
  25. this._connects.push(connect.connect(this.grid.domNode, "onblur", this, "doBlur"));
  26. this._connects.push(connect.connect(this.grid.domNode, "mousedown", this, "_mouseDown"));
  27. this._connects.push(connect.connect(this.grid.domNode, "mouseup", this, "_mouseUp"));
  28. this._connects.push(connect.connect(this.grid.domNode, "oncontextmenu", this, "doContextMenu"));
  29. this._connects.push(connect.connect(this.grid.lastFocusNode, "onfocus", this, "doLastNodeFocus"));
  30. this._connects.push(connect.connect(this.grid.lastFocusNode, "onblur", this, "doLastNodeBlur"));
  31. this._connects.push(connect.connect(this.grid,"_onFetchComplete", this, "_delayedCellFocus"));
  32. this._connects.push(connect.connect(this.grid,"postrender", this, "_delayedHeaderFocus"));
  33. },
  34. destroy: function(){
  35. array.forEach(this._connects, connect.disconnect);
  36. array.forEach(this._headerConnects, connect.disconnect);
  37. delete this.grid;
  38. delete this.cell;
  39. },
  40. _colHeadNode: null,
  41. _colHeadFocusIdx: null,
  42. _contextMenuBindNode: null,
  43. tabbingOut: false,
  44. focusClass: "dojoxGridCellFocus",
  45. focusView: null,
  46. initFocusView: function(){
  47. this.focusView = this.grid.views.getFirstScrollingView() || this.focusView || this.grid.views.views[0];
  48. this._initColumnHeaders();
  49. },
  50. isFocusCell: function(inCell, inRowIndex){
  51. // summary:
  52. // states if the given cell is focused
  53. // inCell: object
  54. // grid cell object
  55. // inRowIndex: int
  56. // grid row index
  57. // returns:
  58. // true of the given grid cell is focused
  59. return (this.cell == inCell) && (this.rowIndex == inRowIndex);
  60. },
  61. isLastFocusCell: function(){
  62. if(this.cell){
  63. return (this.rowIndex == this.grid.rowCount-1) && (this.cell.index == this.grid.layout.cellCount-1);
  64. }
  65. return false;
  66. },
  67. isFirstFocusCell: function(){
  68. if(this.cell){
  69. return (this.rowIndex === 0) && (this.cell.index === 0);
  70. }
  71. return false;
  72. },
  73. isNoFocusCell: function(){
  74. return (this.rowIndex < 0) || !this.cell;
  75. },
  76. isNavHeader: function(){
  77. // summary:
  78. // states whether currently navigating among column headers.
  79. // returns:
  80. // true if focus is on a column header; false otherwise.
  81. return (!!this._colHeadNode);
  82. },
  83. getHeaderIndex: function(){
  84. // summary:
  85. // if one of the column headers currently has focus, return its index.
  86. // returns:
  87. // index of the focused column header, or -1 if none have focus.
  88. if(this._colHeadNode){
  89. return array.indexOf(this._findHeaderCells(), this._colHeadNode);
  90. }else{
  91. return -1;
  92. }
  93. },
  94. _focusifyCellNode: function(inBork){
  95. var n = this.cell && this.cell.getNode(this.rowIndex);
  96. if(n){
  97. html.toggleClass(n, this.focusClass, inBork);
  98. if(inBork){
  99. var sl = this.scrollIntoView();
  100. try{
  101. if(!this.grid.edit.isEditing()){
  102. util.fire(n, "focus");
  103. if(sl){ this.cell.view.scrollboxNode.scrollLeft = sl; }
  104. }
  105. }catch(e){}
  106. }
  107. }
  108. },
  109. _delayedCellFocus: function(){
  110. if(this.isNavHeader()||!this.grid.focused){
  111. return;
  112. }
  113. var n = this.cell && this.cell.getNode(this.rowIndex);
  114. if(n){
  115. try{
  116. if(!this.grid.edit.isEditing()){
  117. html.toggleClass(n, this.focusClass, true);
  118. if(this._colHeadNode){
  119. this.blurHeader();
  120. }
  121. util.fire(n, "focus");
  122. }
  123. }
  124. catch(e){}
  125. }
  126. },
  127. _delayedHeaderFocus: function(){
  128. if(this.isNavHeader()){
  129. this.focusHeader();
  130. this.grid.domNode.focus();
  131. }
  132. },
  133. _initColumnHeaders: function(){
  134. array.forEach(this._headerConnects, connect.disconnect);
  135. this._headerConnects = [];
  136. var headers = this._findHeaderCells();
  137. for(var i = 0; i < headers.length; i++){
  138. this._headerConnects.push(connect.connect(headers[i], "onfocus", this, "doColHeaderFocus"));
  139. this._headerConnects.push(connect.connect(headers[i], "onblur", this, "doColHeaderBlur"));
  140. }
  141. },
  142. _findHeaderCells: function(){
  143. // This should be a one liner:
  144. // query("th[tabindex=-1]", this.grid.viewsHeaderNode);
  145. // But there is a bug in query() for IE -- see trac #7037.
  146. var allHeads = query("th", this.grid.viewsHeaderNode);
  147. var headers = [];
  148. for (var i = 0; i < allHeads.length; i++){
  149. var aHead = allHeads[i];
  150. var hasTabIdx = html.hasAttr(aHead, "tabIndex");
  151. var tabindex = html.attr(aHead, "tabIndex");
  152. if (hasTabIdx && tabindex < 0) {
  153. headers.push(aHead);
  154. }
  155. }
  156. return headers;
  157. },
  158. _setActiveColHeader: function(/*Node*/colHeaderNode, /*Integer*/colFocusIdx, /*Integer*/ prevColFocusIdx){
  159. //console.log("setActiveColHeader() - colHeaderNode:colFocusIdx:prevColFocusIdx = " + colHeaderNode + ":" + colFocusIdx + ":" + prevColFocusIdx);
  160. this.grid.domNode.setAttribute("aria-activedescendant",colHeaderNode.id);
  161. if (prevColFocusIdx != null && prevColFocusIdx >= 0 && prevColFocusIdx != colFocusIdx){
  162. html.toggleClass(this._findHeaderCells()[prevColFocusIdx],this.focusClass,false);
  163. }
  164. html.toggleClass(colHeaderNode,this.focusClass, true);
  165. this._colHeadNode = colHeaderNode;
  166. this._colHeadFocusIdx = colFocusIdx;
  167. this._scrollHeader(this._colHeadFocusIdx);
  168. },
  169. scrollIntoView: function(){
  170. var info = (this.cell ? this._scrollInfo(this.cell) : null);
  171. if(!info || !info.s){
  172. return null;
  173. }
  174. var rt = this.grid.scroller.findScrollTop(this.rowIndex);
  175. // place cell within horizontal view
  176. if(info.n && info.sr){
  177. if(info.n.offsetLeft + info.n.offsetWidth > info.sr.l + info.sr.w){
  178. info.s.scrollLeft = info.n.offsetLeft + info.n.offsetWidth - info.sr.w;
  179. }else if(info.n.offsetLeft < info.sr.l){
  180. info.s.scrollLeft = info.n.offsetLeft;
  181. }
  182. }
  183. // place cell within vertical view
  184. if(info.r && info.sr){
  185. if(rt + info.r.offsetHeight > info.sr.t + info.sr.h){
  186. this.grid.setScrollTop(rt + info.r.offsetHeight - info.sr.h);
  187. }else if(rt < info.sr.t){
  188. this.grid.setScrollTop(rt);
  189. }
  190. }
  191. return info.s.scrollLeft;
  192. },
  193. _scrollInfo: function(cell, domNode){
  194. if(cell){
  195. var cl = cell,
  196. sbn = cl.view.scrollboxNode,
  197. sbnr = {
  198. w: sbn.clientWidth,
  199. l: sbn.scrollLeft,
  200. t: sbn.scrollTop,
  201. h: sbn.clientHeight
  202. },
  203. rn = cl.view.getRowNode(this.rowIndex);
  204. return {
  205. c: cl,
  206. s: sbn,
  207. sr: sbnr,
  208. n: (domNode ? domNode : cell.getNode(this.rowIndex)),
  209. r: rn
  210. };
  211. }
  212. return null;
  213. },
  214. _scrollHeader: function(currentIdx){
  215. var info = null;
  216. if(this._colHeadNode){
  217. var cell = this.grid.getCell(currentIdx);
  218. if(!cell){ return; }
  219. info = this._scrollInfo(cell, cell.getNode(0));
  220. }
  221. if(info && info.s && info.sr && info.n){
  222. // scroll horizontally as needed.
  223. var scroll = info.sr.l + info.sr.w;
  224. if(info.n.offsetLeft + info.n.offsetWidth > scroll){
  225. info.s.scrollLeft = info.n.offsetLeft + info.n.offsetWidth - info.sr.w;
  226. }else if(info.n.offsetLeft < info.sr.l){
  227. info.s.scrollLeft = info.n.offsetLeft;
  228. }else if(has("ie") <= 7 && cell && cell.view.headerNode){
  229. // Trac 7158: scroll dojoxGridHeader for IE7 and lower
  230. cell.view.headerNode.scrollLeft = info.s.scrollLeft;
  231. }
  232. }
  233. },
  234. _isHeaderHidden: function(){
  235. // summary:
  236. // determine if the grid headers are hidden
  237. // relies on documented technique of setting .dojoxGridHeader { display:none; }
  238. // returns: Boolean
  239. // true if headers are hidden
  240. // false if headers are not hidden
  241. var curView = this.focusView;
  242. if (!curView){
  243. // find one so we can determine if headers are hidden
  244. // there is no focusView after adding items to empty grid (test_data_grid_empty.html)
  245. for (var i = 0, cView; (cView = this.grid.views.views[i]); i++) {
  246. if(cView.headerNode ){
  247. curView=cView;
  248. break;
  249. }
  250. }
  251. }
  252. return (curView && html.getComputedStyle(curView.headerNode).display == "none");
  253. },
  254. colSizeAdjust: function (e, colIdx, delta){ // adjust the column specified by colIdx by the specified delta px
  255. var headers = this._findHeaderCells();
  256. var view = this.focusView;
  257. if (!view) {
  258. for (var i = 0, cView; (cView = this.grid.views.views[i]); i++) {
  259. // find first view with a tableMap in order to work with empty grid
  260. if(cView.header.tableMap.map ){
  261. view=cView;
  262. break;
  263. }
  264. }
  265. }
  266. var curHeader = headers[colIdx];
  267. if (!view || (colIdx == headers.length-1 && colIdx === 0)){
  268. return; // can't adjust single col. grid
  269. }
  270. view.content.baseDecorateEvent(e);
  271. // need to adjust event with header cell info since focus is no longer on header cell
  272. e.cellNode = curHeader; //this.findCellTarget(e.target, e.rowNode);
  273. e.cellIndex = view.content.getCellNodeIndex(e.cellNode);
  274. e.cell = (e.cellIndex >= 0 ? this.grid.getCell(e.cellIndex) : null);
  275. if (view.header.canResize(e)){
  276. var deltaObj = {
  277. l: delta
  278. };
  279. var drag = view.header.colResizeSetup(e,false);
  280. view.header.doResizeColumn(drag, null, deltaObj);
  281. view.update();
  282. }
  283. },
  284. styleRow: function(inRow){
  285. return;
  286. },
  287. setFocusIndex: function(inRowIndex, inCellIndex){
  288. // summary:
  289. // focuses the given grid cell
  290. // inRowIndex: int
  291. // grid row index
  292. // inCellIndex: int
  293. // grid cell index
  294. this.setFocusCell(this.grid.getCell(inCellIndex), inRowIndex);
  295. },
  296. setFocusCell: function(inCell, inRowIndex){
  297. // summary:
  298. // focuses the given grid cell
  299. // inCell: object
  300. // grid cell object
  301. // inRowIndex: int
  302. // grid row index
  303. if(inCell && !this.isFocusCell(inCell, inRowIndex)){
  304. this.tabbingOut = false;
  305. if (this._colHeadNode){
  306. this.blurHeader();
  307. }
  308. this._colHeadNode = this._colHeadFocusIdx = null;
  309. this.focusGridView();
  310. this._focusifyCellNode(false);
  311. this.cell = inCell;
  312. this.rowIndex = inRowIndex;
  313. this._focusifyCellNode(true);
  314. }
  315. // even if this cell isFocusCell, the document focus may need to be rejiggered
  316. // call opera on delay to prevent keypress from altering focus
  317. if(has("opera")){
  318. setTimeout(lang.hitch(this.grid, 'onCellFocus', this.cell, this.rowIndex), 1);
  319. }else{
  320. this.grid.onCellFocus(this.cell, this.rowIndex);
  321. }
  322. },
  323. next: function(){
  324. // summary:
  325. // focus next grid cell
  326. if(this.cell){
  327. var row=this.rowIndex, col=this.cell.index+1, cc=this.grid.layout.cellCount-1, rc=this.grid.rowCount-1;
  328. if(col > cc){
  329. col = 0;
  330. row++;
  331. }
  332. if(row > rc){
  333. col = cc;
  334. row = rc;
  335. }
  336. if(this.grid.edit.isEditing()){ //when editing, only navigate to editable cells
  337. var nextCell = this.grid.getCell(col);
  338. if (!this.isLastFocusCell() && (!nextCell.editable ||
  339. this.grid.canEdit && !this.grid.canEdit(nextCell, row))){
  340. this.cell=nextCell;
  341. this.rowIndex=row;
  342. this.next();
  343. return;
  344. }
  345. }
  346. this.setFocusIndex(row, col);
  347. }
  348. },
  349. previous: function(){
  350. // summary:
  351. // focus previous grid cell
  352. if(this.cell){
  353. var row=(this.rowIndex || 0), col=(this.cell.index || 0) - 1;
  354. if(col < 0){
  355. col = this.grid.layout.cellCount-1;
  356. row--;
  357. }
  358. if(row < 0){
  359. row = 0;
  360. col = 0;
  361. }
  362. if(this.grid.edit.isEditing()){ //when editing, only navigate to editable cells
  363. var prevCell = this.grid.getCell(col);
  364. if (!this.isFirstFocusCell() && !prevCell.editable){
  365. this.cell=prevCell;
  366. this.rowIndex=row;
  367. this.previous();
  368. return;
  369. }
  370. }
  371. this.setFocusIndex(row, col);
  372. }
  373. },
  374. move: function(inRowDelta, inColDelta) {
  375. // summary:
  376. // focus grid cell or simulate focus to column header based on position relative to current focus
  377. // inRowDelta: int
  378. // vertical distance from current focus
  379. // inColDelta: int
  380. // horizontal distance from current focus
  381. var colDir = inColDelta < 0 ? -1 : 1;
  382. // Handle column headers.
  383. if(this.isNavHeader()){
  384. var headers = this._findHeaderCells();
  385. var savedIdx = currentIdx = array.indexOf(headers, this._colHeadNode);
  386. currentIdx += inColDelta;
  387. while(currentIdx >=0 && currentIdx < headers.length && headers[currentIdx].style.display == "none"){
  388. // skip over hidden column headers
  389. currentIdx += colDir;
  390. }
  391. if((currentIdx >= 0) && (currentIdx < headers.length)){
  392. this._setActiveColHeader(headers[currentIdx],currentIdx, savedIdx);
  393. }
  394. }else{
  395. if(this.cell){
  396. // Handle grid proper.
  397. var sc = this.grid.scroller,
  398. r = this.rowIndex,
  399. rc = this.grid.rowCount-1,
  400. row = Math.min(rc, Math.max(0, r+inRowDelta));
  401. if(inRowDelta){
  402. if(inRowDelta>0){
  403. if(row > sc.getLastPageRow(sc.page)){
  404. //need to load additional data, let scroller do that
  405. this.grid.setScrollTop(this.grid.scrollTop+sc.findScrollTop(row)-sc.findScrollTop(r));
  406. }
  407. }else if(inRowDelta<0){
  408. if(row <= sc.getPageRow(sc.page)){
  409. //need to load additional data, let scroller do that
  410. this.grid.setScrollTop(this.grid.scrollTop-sc.findScrollTop(r)-sc.findScrollTop(row));
  411. }
  412. }
  413. }
  414. var cc = this.grid.layout.cellCount-1,
  415. i = this.cell.index,
  416. col = Math.min(cc, Math.max(0, i+inColDelta));
  417. var cell = this.grid.getCell(col);
  418. while(col>=0 && col < cc && cell && cell.hidden === true){
  419. // skip hidden cells
  420. col += colDir;
  421. cell = this.grid.getCell(col);
  422. }
  423. if (!cell || cell.hidden === true){
  424. // don't change col if would move to hidden
  425. col = i;
  426. }
  427. //skip hidden row|cell
  428. var n = cell.getNode(row);
  429. if(!n && inRowDelta){
  430. if((row + inRowDelta) >= 0 && (row + inRowDelta) <= rc){
  431. this.move(inRowDelta > 0 ? ++inRowDelta : --inRowDelta, inColDelta);
  432. }
  433. return;
  434. }else if((!n || html.style(n, "display") === "none") && inColDelta){
  435. if((col + inColDelta) >= 0 && (col + inColDelta) <= cc){
  436. this.move(inRowDelta, inColDelta > 0 ? ++inColDelta : --inColDelta);
  437. }
  438. return;
  439. }
  440. this.setFocusIndex(row, col);
  441. if(inRowDelta){
  442. this.grid.updateRow(r);
  443. }
  444. }
  445. }
  446. },
  447. previousKey: function(e){
  448. if(this.grid.edit.isEditing()){
  449. event.stop(e);
  450. this.previous();
  451. }else if(!this.isNavHeader() && !this._isHeaderHidden()) {
  452. this.grid.domNode.focus(); // will call doFocus and set focus into header.
  453. event.stop(e);
  454. }else{
  455. this.tabOut(this.grid.domNode);
  456. if (this._colHeadFocusIdx != null) { // clear grid header focus
  457. html.toggleClass(this._findHeaderCells()[this._colHeadFocusIdx], this.focusClass, false);
  458. this._colHeadFocusIdx = null;
  459. }
  460. this._focusifyCellNode(false);
  461. }
  462. },
  463. nextKey: function(e) {
  464. var isEmpty = (this.grid.rowCount === 0);
  465. if(e.target === this.grid.domNode && this._colHeadFocusIdx == null){
  466. this.focusHeader();
  467. event.stop(e);
  468. }else if(this.isNavHeader()){
  469. // if tabbing from col header, then go to grid proper.
  470. this.blurHeader();
  471. if(!this.findAndFocusGridCell()){
  472. this.tabOut(this.grid.lastFocusNode);
  473. }
  474. this._colHeadNode = this._colHeadFocusIdx= null;
  475. }else if(this.grid.edit.isEditing()){
  476. event.stop(e);
  477. this.next();
  478. }else{
  479. this.tabOut(this.grid.lastFocusNode);
  480. }
  481. },
  482. tabOut: function(inFocusNode){
  483. this.tabbingOut = true;
  484. inFocusNode.focus();
  485. },
  486. focusGridView: function(){
  487. util.fire(this.focusView, "focus");
  488. },
  489. focusGrid: function(inSkipFocusCell){
  490. this.focusGridView();
  491. this._focusifyCellNode(true);
  492. },
  493. findAndFocusGridCell: function(){
  494. // summary:
  495. // find the first focusable grid cell
  496. // returns: Boolean
  497. // true if focus was set to a cell
  498. // false if no cell found to set focus onto
  499. var didFocus = true;
  500. var isEmpty = (this.grid.rowCount === 0); // If grid is empty this.grid.rowCount == 0
  501. if (this.isNoFocusCell() && !isEmpty){
  502. var cellIdx = 0;
  503. var cell = this.grid.getCell(cellIdx);
  504. if (cell.hidden) {
  505. // if first cell isn't visible, use _colHeadFocusIdx
  506. // could also use a while loop to find first visible cell - not sure that is worth it
  507. cellIdx = this.isNavHeader() ? this._colHeadFocusIdx : 0;
  508. }
  509. this.setFocusIndex(0, cellIdx);
  510. }
  511. else if (this.cell && !isEmpty){
  512. if (this.focusView && !this.focusView.rowNodes[this.rowIndex]){
  513. // if rowNode for current index is undefined (likely as a result of a sort and because of #7304)
  514. // scroll to that row
  515. this.grid.scrollToRow(this.rowIndex);
  516. }
  517. this.focusGrid();
  518. }else {
  519. didFocus = false;
  520. }
  521. this._colHeadNode = this._colHeadFocusIdx= null;
  522. return didFocus;
  523. },
  524. focusHeader: function(){
  525. var headerNodes = this._findHeaderCells();
  526. var saveColHeadFocusIdx = this._colHeadFocusIdx;
  527. if (this._isHeaderHidden()){
  528. // grid header is hidden, focus a cell
  529. this.findAndFocusGridCell();
  530. }
  531. else if (!this._colHeadFocusIdx) {
  532. if (this.isNoFocusCell()) {
  533. this._colHeadFocusIdx = 0;
  534. }
  535. else {
  536. this._colHeadFocusIdx = this.cell.index;
  537. }
  538. }
  539. this._colHeadNode = headerNodes[this._colHeadFocusIdx];
  540. while(this._colHeadNode && this._colHeadFocusIdx >=0 && this._colHeadFocusIdx < headerNodes.length &&
  541. this._colHeadNode.style.display == "none"){
  542. // skip over hidden column headers
  543. this._colHeadFocusIdx++;
  544. this._colHeadNode = headerNodes[this._colHeadFocusIdx];
  545. }
  546. if(this._colHeadNode && this._colHeadNode.style.display != "none"){
  547. // Column header cells know longer receive actual focus. So, for keyboard invocation of
  548. // contextMenu to work, the contextMenu must be bound to the grid.domNode rather than the viewsHeaderNode.
  549. // unbind the contextmenu from the viewsHeaderNode and to the grid when header cells are active. Reset
  550. // the binding back to the viewsHeaderNode when header cells are no longer acive (in blurHeader) #10483
  551. if (this.headerMenu && this._contextMenuBindNode != this.grid.domNode){
  552. this.headerMenu.unBindDomNode(this.grid.viewsHeaderNode);
  553. this.headerMenu.bindDomNode(this.grid.domNode);
  554. this._contextMenuBindNode = this.grid.domNode;
  555. }
  556. this._setActiveColHeader(this._colHeadNode, this._colHeadFocusIdx, saveColHeadFocusIdx);
  557. this._scrollHeader(this._colHeadFocusIdx);
  558. this._focusifyCellNode(false);
  559. }else {
  560. // all col head nodes are hidden - focus the grid
  561. this.findAndFocusGridCell();
  562. }
  563. },
  564. blurHeader: function(){
  565. html.removeClass(this._colHeadNode, this.focusClass);
  566. html.removeAttr(this.grid.domNode,"aria-activedescendant");
  567. // reset contextMenu onto viewsHeaderNode so right mouse on header will invoke (see focusHeader)
  568. if (this.headerMenu && this._contextMenuBindNode == this.grid.domNode) {
  569. var viewsHeader = this.grid.viewsHeaderNode;
  570. this.headerMenu.unBindDomNode(this.grid.domNode);
  571. this.headerMenu.bindDomNode(viewsHeader);
  572. this._contextMenuBindNode = viewsHeader;
  573. }
  574. },
  575. doFocus: function(e){
  576. // trap focus only for grid dom node
  577. if(e && e.target != e.currentTarget){
  578. event.stop(e);
  579. return;
  580. }
  581. // don't change focus if clicking on scroller bar
  582. if(this._clickFocus){
  583. return;
  584. }
  585. // do not focus for scrolling if grid is about to blur
  586. if(!this.tabbingOut){
  587. this.focusHeader();
  588. }
  589. this.tabbingOut = false;
  590. event.stop(e);
  591. },
  592. doBlur: function(e){
  593. event.stop(e); // FF2
  594. },
  595. doContextMenu: function(e){
  596. //stop contextMenu event if no header Menu to prevent default/browser contextMenu
  597. if (!this.headerMenu){
  598. event.stop(e);
  599. }
  600. },
  601. doLastNodeFocus: function(e){
  602. if (this.tabbingOut){
  603. this._focusifyCellNode(false);
  604. }else if(this.grid.rowCount >0){
  605. if (this.isNoFocusCell()){
  606. this.setFocusIndex(0,0);
  607. }
  608. this._focusifyCellNode(true);
  609. }else {
  610. this.focusHeader();
  611. }
  612. this.tabbingOut = false;
  613. event.stop(e); // FF2
  614. },
  615. doLastNodeBlur: function(e){
  616. event.stop(e); // FF2
  617. },
  618. doColHeaderFocus: function(e){
  619. this._setActiveColHeader(e.target,html.attr(e.target, "idx"),this._colHeadFocusIdx);
  620. this._scrollHeader(this.getHeaderIndex());
  621. event.stop(e);
  622. },
  623. doColHeaderBlur: function(e){
  624. html.toggleClass(e.target, this.focusClass, false);
  625. },
  626. _mouseDown: function(e){
  627. // a flag indicating grid is being focused by clicking
  628. this._clickFocus = dojo.some(this.grid.views.views, function(v){
  629. return v.scrollboxNode === e.target;
  630. });
  631. },
  632. _mouseUp: function(e){
  633. this._clickFocus = false;
  634. }
  635. });
  636. });