_FocusManager.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  1. define("dojox/grid/enhanced/_FocusManager", [
  2. "dojo/_base/kernel",
  3. "dojo/_base/lang",
  4. "dojo/_base/declare",
  5. "dojo/_base/array",
  6. "dojo/_base/connect",
  7. "dojo/_base/event",
  8. "dojo/_base/sniff",
  9. "dojo/_base/html",
  10. "dojo/keys",
  11. "dijit/a11y",
  12. "dijit/focus",
  13. "../_FocusManager"
  14. ], function(dojo, lang, declare, array, connect, event, has, html, keys, dijitA11y, dijitFocus, _FocusManager){
  15. var _FocusArea = declare("dojox.grid.enhanced._FocusArea", null, {
  16. // summary:
  17. // This is a friend class of _FocusManager
  18. /*=====
  19. // name: string
  20. // Name of this area.
  21. name: "",
  22. // onFocus: function(event, step)
  23. // Called when this area logically gets focus.
  24. // event: Event object
  25. // May be unavailable, should check before use.
  26. // step: Integer
  27. // The distance in the tab sequence from last focused area to this area.
  28. // returns:
  29. // whether this area is successfully focused. If not, the next area will get focus.
  30. onFocus: function(event, step){return true;},
  31. // onBlur: function(event, step)
  32. // Called when this area logically loses focus.
  33. // event: Event object
  34. // May be unavailable, should check before use.
  35. // step: Integer
  36. // The distance in the tab sequence from this area to the area to focus.
  37. // returns:
  38. // If Boolean, means whether this area has successfully blurred. If not, the next area to focus is still this one.
  39. // If String, means the next area to focus is given by this returned name.
  40. onBlur: function(event, step){return true;},
  41. // onMove: function(rowStep, colStep, event)
  42. // Called when focus is moving around within this area.
  43. // rowStep: Integer
  44. // colStep: Integer
  45. // event: Event object
  46. // May be unavailable, should check before use.
  47. onMove: function(rowStep, colStep, event){},
  48. // onKey: function(event, isBubble)
  49. // Called when some key is pressed when focus is logically in this area.
  50. // event: Event object
  51. // isBubble: Boolean
  52. // Whether is in bubble stage (true) or catch stage (false).
  53. // returns:
  54. // If you do NOT want the event to propagate any further along the area stack, return exactly false.
  55. // So if you return nothing (undefined), this event is still propagating.
  56. onKey: function(event, isBubble){return true},
  57. // getRegions: function()
  58. // Define the small regions (dom nodes) in this area.
  59. // returns: Array of dom nodes.
  60. getRegions: function(){},
  61. // onRegionFocus: function(event)
  62. // Connected to the onfocus event of the defined regions (if any)
  63. onRegionFocus: function(event){},
  64. // onRegionBlur: function(event)
  65. // Connected to the onblur event of the defined regions (if any)
  66. onRegionBlur: function(event){},
  67. =====*/
  68. constructor: function(area, focusManager){
  69. this._fm = focusManager;
  70. this._evtStack = [area.name];
  71. var dummy = function(){return true;};
  72. area.onFocus = area.onFocus || dummy;
  73. area.onBlur = area.onBlur || dummy;
  74. area.onMove = area.onMove || dummy;
  75. area.onKeyUp = area.onKeyUp || dummy;
  76. area.onKeyDown = area.onKeyDown || dummy;
  77. lang.mixin(this, area);
  78. },
  79. move: function(rowStep, colStep, evt){
  80. if(this.name){
  81. var i, len = this._evtStack.length;
  82. for(i = len - 1; i >= 0; --i){
  83. if(this._fm._areas[this._evtStack[i]].onMove(rowStep, colStep, evt) === false){
  84. return false;
  85. }
  86. }
  87. }
  88. return true;
  89. },
  90. _onKeyEvent: function(evt, funcName){
  91. if(this.name){
  92. var i, len = this._evtStack.length;
  93. for(i = len - 1; i >= 0; --i){
  94. if(this._fm._areas[this._evtStack[i]][funcName](evt, false) === false){
  95. return false;
  96. }
  97. }
  98. for(i = 0; i < len; ++i){
  99. if(this._fm._areas[this._evtStack[i]][funcName](evt, true) === false){
  100. return false;
  101. }
  102. }
  103. }
  104. return true;
  105. },
  106. keydown: function(evt){
  107. return this._onKeyEvent(evt, "onKeyDown");
  108. },
  109. keyup: function(evt){
  110. return this._onKeyEvent(evt, "onKeyUp");
  111. },
  112. contentMouseEventPlanner: function(){
  113. return 0;
  114. },
  115. headerMouseEventPlanner: function(){
  116. return 0;
  117. }
  118. });
  119. return declare("dojox.grid.enhanced._FocusManager", _FocusManager, {
  120. _stopEvent: function(evt){
  121. try{
  122. if(evt && evt.preventDefault){
  123. event.stop(evt);
  124. }
  125. }catch(e){}
  126. },
  127. constructor: function(grid){
  128. this.grid = grid;
  129. this._areas = {};
  130. this._areaQueue = [];
  131. this._contentMouseEventHandlers = [];
  132. this._headerMouseEventHandlers = [];
  133. this._currentAreaIdx = -1;
  134. this._gridBlured = true;
  135. this._connects.push(connect.connect(grid, "onBlur", this, "_doBlur"));
  136. this._connects.push(connect.connect(grid.scroller, "renderPage", this, "_delayedCellFocus"));
  137. this.addArea({
  138. name: "header",
  139. onFocus: lang.hitch(this, this.focusHeader),
  140. onBlur: lang.hitch(this, this._blurHeader),
  141. onMove: lang.hitch(this, this._navHeader),
  142. getRegions: lang.hitch(this, this._findHeaderCells),
  143. onRegionFocus: lang.hitch(this, this.doColHeaderFocus),
  144. onRegionBlur: lang.hitch(this, this.doColHeaderBlur),
  145. onKeyDown: lang.hitch(this, this._onHeaderKeyDown)
  146. });
  147. this.addArea({
  148. name: "content",
  149. onFocus: lang.hitch(this, this._focusContent),
  150. onBlur: lang.hitch(this, this._blurContent),
  151. onMove: lang.hitch(this, this._navContent),
  152. onKeyDown: lang.hitch(this, this._onContentKeyDown)
  153. });
  154. this.addArea({
  155. name: "editableCell",
  156. onFocus: lang.hitch(this, this._focusEditableCell),
  157. onBlur: lang.hitch(this, this._blurEditableCell),
  158. onKeyDown: lang.hitch(this, this._onEditableCellKeyDown),
  159. onContentMouseEvent: lang.hitch(this, this._onEditableCellMouseEvent),
  160. contentMouseEventPlanner: function(evt, areas){ return -1; }
  161. });
  162. this.placeArea("header");
  163. this.placeArea("content");
  164. this.placeArea("editableCell");
  165. this.placeArea("editableCell","above","content");
  166. },
  167. destroy: function(){
  168. for(var name in this._areas){
  169. var area = this._areas[name];
  170. array.forEach(area._connects, connect.disconnect);
  171. area._connects = null;
  172. if(area.uninitialize){
  173. area.uninitialize();
  174. }
  175. }
  176. this.inherited(arguments);
  177. },
  178. addArea: function(area){
  179. if(area.name && lang.isString(area.name)){
  180. if(this._areas[area.name]){
  181. //Just replace the original area, instead of remove it, so the position does not change.
  182. array.forEach(area._connects, connect.disconnect);
  183. }
  184. this._areas[area.name] = new _FocusArea(area, this);
  185. if(area.onHeaderMouseEvent){
  186. this._headerMouseEventHandlers.push(area.name);
  187. }
  188. if(area.onContentMouseEvent){
  189. this._contentMouseEventHandlers.push(area.name);
  190. }
  191. }
  192. },
  193. getArea: function(areaName){
  194. return this._areas[areaName];
  195. },
  196. _bindAreaEvents: function(){
  197. var area, hdl, areas = this._areas;
  198. array.forEach(this._areaQueue, function(name){
  199. area = areas[name];
  200. if(!area._initialized && lang.isFunction(area.initialize)){
  201. area.initialize();
  202. area._initialized = true;
  203. }
  204. if(area.getRegions){
  205. area._regions = area.getRegions() || [];
  206. array.forEach(area._connects || [], connect.disconnect);
  207. area._connects = [];
  208. array.forEach(area._regions, function(r){
  209. if(area.onRegionFocus){
  210. hdl = connect.connect(r, "onfocus", area.onRegionFocus);
  211. area._connects.push(hdl);
  212. }
  213. if(area.onRegionBlur){
  214. hdl = connect.connect(r, "onblur", area.onRegionBlur);
  215. area._connects.push(hdl);
  216. }
  217. });
  218. }
  219. });
  220. },
  221. removeArea: function(areaName){
  222. var area = this._areas[areaName];
  223. if(area){
  224. this.ignoreArea(areaName);
  225. var i = array.indexOf(this._contentMouseEventHandlers, areaName);
  226. if(i >= 0){
  227. this._contentMouseEventHandlers.splice(i, 1);
  228. }
  229. i = array.indexOf(this._headerMouseEventHandlers, areaName);
  230. if(i >= 0){
  231. this._headerMouseEventHandlers.splice(i, 1);
  232. }
  233. array.forEach(area._connects, connect.disconnect);
  234. if(area.uninitialize){
  235. area.uninitialize();
  236. }
  237. delete this._areas[areaName];
  238. }
  239. },
  240. currentArea: function(areaName, toBlurOld){
  241. // summary:
  242. // Set current area to the one areaName refers.
  243. // areaName: String
  244. var idx, cai = this._currentAreaIdx;
  245. if(lang.isString(areaName) && (idx = array.indexOf(this._areaQueue, areaName)) >= 0){
  246. if(cai != idx){
  247. this.tabbingOut = false;
  248. if(toBlurOld && cai >= 0 && cai < this._areaQueue.length){
  249. this._areas[this._areaQueue[cai]].onBlur();
  250. }
  251. this._currentAreaIdx = idx;
  252. }
  253. }else{
  254. return (cai < 0 || cai >= this._areaQueue.length) ?
  255. new _FocusArea({}, this) :
  256. this._areas[this._areaQueue[this._currentAreaIdx]];
  257. }
  258. return null;
  259. },
  260. placeArea: function(name, pos, otherAreaName){
  261. // summary:
  262. // Place the area refered by *name* at some logical position relative to an existing area.
  263. // example:
  264. // placeArea("myarea","before"|"after",...)
  265. // placeArea("myarea","below"|"above",...)
  266. if(!this._areas[name]){ return; }
  267. var idx = array.indexOf(this._areaQueue,otherAreaName);
  268. switch(pos){
  269. case "after":
  270. if(idx >= 0){ ++idx; }
  271. //intentional drop through
  272. case "before":
  273. if(idx >= 0){
  274. this._areaQueue.splice(idx,0,name);
  275. break;
  276. }
  277. //intentional drop through
  278. default:
  279. this._areaQueue.push(name);
  280. break;
  281. case "above":
  282. var isAbove = true;
  283. //intentional drop through
  284. case "below":
  285. var otherArea = this._areas[otherAreaName];
  286. if(otherArea){
  287. if(isAbove){
  288. otherArea._evtStack.push(name);
  289. }else{
  290. otherArea._evtStack.splice(0,0,name);
  291. }
  292. }
  293. }
  294. },
  295. ignoreArea: function(name){
  296. this._areaQueue = array.filter(this._areaQueue,function(areaName){
  297. return areaName != name;
  298. });
  299. },
  300. focusArea: function(/* int|string|areaObj */areaId,evt){
  301. var idx;
  302. if(typeof areaId == "number"){
  303. idx = areaId < 0 ? this._areaQueue.length + areaId : areaId;
  304. }else{
  305. idx = array.indexOf(this._areaQueue,
  306. lang.isString(areaId) ? areaId : (areaId && areaId.name));
  307. }
  308. if(idx < 0){ idx = 0; }
  309. var step = idx - this._currentAreaIdx;
  310. this._gridBlured = false;
  311. if(step){
  312. this.tab(step, evt);
  313. }else{
  314. this.currentArea().onFocus(evt, step);
  315. }
  316. },
  317. tab: function(step,evt){
  318. //console.log("===========tab",step,"curArea",this._currentAreaIdx,"areaCnt",this._areaQueue.length);
  319. this._gridBlured = false;
  320. this.tabbingOut = false;
  321. if(step === 0){
  322. return;
  323. }
  324. var cai = this._currentAreaIdx;
  325. var dir = step > 0 ? 1:-1;
  326. if(cai < 0 || cai >= this._areaQueue.length){
  327. cai = (this._currentAreaIdx += step);
  328. }else{
  329. var nextArea = this._areas[this._areaQueue[cai]].onBlur(evt,step);
  330. if(nextArea === true){
  331. cai = (this._currentAreaIdx += step);
  332. }else if(lang.isString(nextArea) && this._areas[nextArea]){
  333. cai = this._currentAreaIdx = array.indexOf(this._areaQueue,nextArea);
  334. }
  335. }
  336. //console.log("target area:",cai);
  337. for(; cai >= 0 && cai < this._areaQueue.length; cai += dir){
  338. this._currentAreaIdx = cai;
  339. if(this._areaQueue[cai] && this._areas[this._areaQueue[cai]].onFocus(evt,step)){
  340. //console.log("final target area:",this._currentAreaIdx);
  341. return;
  342. }
  343. }
  344. //console.log("tab out");
  345. this.tabbingOut = true;
  346. if(step < 0){
  347. this._currentAreaIdx = -1;
  348. dijitFocus.focus(this.grid.domNode);
  349. }else{
  350. this._currentAreaIdx = this._areaQueue.length;
  351. dijitFocus.focus(this.grid.lastFocusNode);
  352. }
  353. },
  354. _onMouseEvent: function(type, evt){
  355. var lowercase = type.toLowerCase(),
  356. handlers = this["_" + lowercase + "MouseEventHandlers"],
  357. res = array.map(handlers, function(areaName){
  358. return {
  359. "area": areaName,
  360. "idx": this._areas[areaName][lowercase + "MouseEventPlanner"](evt, handlers)
  361. };
  362. }, this).sort(function(a, b){
  363. return b.idx - a.idx;
  364. }),
  365. resHandlers = array.map(res, function(handler){
  366. return res.area;
  367. }),
  368. i = res.length;
  369. while(--i >= 0){
  370. if(this._areas[res[i].area]["on" + type + "MouseEvent"](evt, resHandlers) === false){
  371. return;
  372. }
  373. }
  374. },
  375. contentMouseEvent: function(evt){
  376. this._onMouseEvent("Content", evt);
  377. },
  378. headerMouseEvent: function(evt){
  379. this._onMouseEvent("Header", evt);
  380. },
  381. initFocusView: function(){
  382. // summary:
  383. // Overwritten
  384. this.focusView = this.grid.views.getFirstScrollingView() || this.focusView || this.grid.views.views[0];
  385. this._bindAreaEvents();
  386. },
  387. isNavHeader: function(){
  388. // summary:
  389. // Overwritten
  390. // Check whether currently navigating among column headers.
  391. // return:
  392. // true - focus is on a certain column header | false otherwise
  393. return this._areaQueue[this._currentAreaIdx] == "header";
  394. },
  395. previousKey: function(e){
  396. // summary:
  397. // Overwritten
  398. this.tab(-1,e);
  399. },
  400. nextKey: function(e){
  401. // summary:
  402. // Overwritten
  403. this.tab(1,e);
  404. },
  405. setFocusCell: function(/* Object */inCell, /* Integer */inRowIndex){
  406. // summary:
  407. // Overwritten - focuses the given grid cell
  408. if(inCell){
  409. this.currentArea(this.grid.edit.isEditing() ? "editableCell" : "content", true);
  410. //This is very slow when selecting cells!
  411. //this.focusGridView();
  412. this._focusifyCellNode(false);
  413. this.cell = inCell;
  414. this.rowIndex = inRowIndex;
  415. this._focusifyCellNode(true);
  416. }
  417. this.grid.onCellFocus(this.cell, this.rowIndex);
  418. },
  419. doFocus: function(e){
  420. // summary:
  421. // Overwritten
  422. // trap focus only for grid dom node
  423. // do not focus for scrolling if grid is about to blur
  424. if(e && e.target == e.currentTarget && !this.tabbingOut){
  425. if(this._gridBlured){
  426. this._gridBlured = false;
  427. if(this._currentAreaIdx < 0 || this._currentAreaIdx >= this._areaQueue.length){
  428. this.focusArea(0, e);
  429. }else{
  430. this.focusArea(this._currentAreaIdx, e);
  431. }
  432. }
  433. }else{
  434. this.tabbingOut = false;
  435. }
  436. event.stop(e);
  437. },
  438. _doBlur: function(){
  439. this._gridBlured = true;
  440. },
  441. doLastNodeFocus: function(e){
  442. // summary:
  443. // Overwritten
  444. if(this.tabbingOut){
  445. this.tabbingOut = false;
  446. }else{
  447. this.focusArea(-1, e);
  448. }
  449. },
  450. _delayedHeaderFocus: function(){
  451. // summary:
  452. // Overwritten
  453. if(this.isNavHeader() && !has("ie")){
  454. this.focusHeader();
  455. }
  456. },
  457. _delayedCellFocus: function(){
  458. // summary:
  459. // Overwritten
  460. this.currentArea("header", true);
  461. this.focusArea(this._currentAreaIdx);
  462. },
  463. _changeMenuBindNode: function(oldBindNode, newBindNode){
  464. var hm = this.grid.headerMenu;
  465. if(hm && this._contextMenuBindNode == oldBindNode){
  466. hm.unBindDomNode(oldBindNode);
  467. hm.bindDomNode(newBindNode);
  468. this._contextMenuBindNode = newBindNode;
  469. }
  470. },
  471. //---------------Header Area------------------------------------------
  472. focusHeader: function(evt, step){ //need a further look why these changes to parent's
  473. // summary:
  474. // Overwritten
  475. var didFocus = false;
  476. this.inherited(arguments);
  477. if(this._colHeadNode && html.style(this._colHeadNode, 'display') != "none"){
  478. dijitFocus.focus(this._colHeadNode);
  479. this._stopEvent(evt);
  480. didFocus = true;
  481. }
  482. return didFocus;
  483. },
  484. _blurHeader: function(evt,step){
  485. // summary:
  486. // Overwritten
  487. if(this._colHeadNode){
  488. html.removeClass(this._colHeadNode, this.focusClass);
  489. }
  490. html.removeAttr(this.grid.domNode,"aria-activedescendant");
  491. // reset contextMenu onto viewsHeaderNode so right mouse on header will invoke (see focusHeader)
  492. this._changeMenuBindNode(this.grid.domNode,this.grid.viewsHeaderNode);
  493. //moved here from nextKey
  494. this._colHeadNode = this._colHeadFocusIdx = null;
  495. return true;
  496. },
  497. _navHeader: function(rowStep, colStep, evt){
  498. var colDir = colStep < 0 ? -1 : 1,
  499. savedIdx = array.indexOf(this._findHeaderCells(), this._colHeadNode);
  500. if(savedIdx >= 0 && (evt.shiftKey && evt.ctrlKey)){
  501. this.colSizeAdjust(evt, savedIdx, colDir * 5);
  502. return;
  503. }
  504. this.move(rowStep, colStep);
  505. },
  506. _onHeaderKeyDown: function(e, isBubble){
  507. if(isBubble){
  508. var dk = keys;
  509. switch(e.keyCode){
  510. case dk.ENTER:
  511. case dk.SPACE:
  512. var colIdx = this.getHeaderIndex();
  513. if(colIdx >= 0 && !this.grid.pluginMgr.isFixedCell(e.cell)/*TODO*/){
  514. this.grid.setSortIndex(colIdx, null, e);
  515. event.stop(e);
  516. }
  517. break;
  518. }
  519. }
  520. return true;
  521. },
  522. _setActiveColHeader: function(){
  523. // summary:
  524. // Overwritten
  525. this.inherited(arguments);
  526. //EDG now will decorate event on header key events, if no focus, the cell will be wrong
  527. dijitFocus.focus(this._colHeadNode);
  528. },
  529. //---------------Content Area------------------------------------------
  530. findAndFocusGridCell: function(){
  531. // summary:
  532. // Overwritten
  533. this._focusContent();
  534. },
  535. _focusContent: function(evt,step){
  536. var didFocus = true;
  537. var isEmpty = (this.grid.rowCount === 0); // If grid is empty this.grid.rowCount == 0
  538. if(this.isNoFocusCell() && !isEmpty){
  539. //skip all the hidden cells
  540. for(var i = 0, cell = this.grid.getCell(0); cell && cell.hidden; cell = this.grid.getCell(++i)){}
  541. this.setFocusIndex(0, cell ? i : 0);
  542. }else if(this.cell && !isEmpty){
  543. if(this.focusView && !this.focusView.rowNodes[this.rowIndex]){
  544. // if rowNode for current index is undefined (likely as a result of a sort and because of #7304)
  545. // scroll to that row
  546. this.grid.scrollToRow(this.rowIndex);
  547. this.focusGrid();
  548. }else{
  549. this.setFocusIndex(this.rowIndex, this.cell.index);
  550. }
  551. }else{
  552. didFocus = false;
  553. }
  554. if(didFocus){ this._stopEvent(evt); }
  555. return didFocus;
  556. },
  557. _blurContent: function(evt,step){
  558. this._focusifyCellNode(false);
  559. return true;
  560. },
  561. _navContent: function(rowStep, colStep, evt){
  562. if((this.rowIndex === 0 && rowStep < 0) || (this.rowIndex === this.grid.rowCount - 1 && rowStep > 0)){
  563. return;
  564. }
  565. this._colHeadNode = null;
  566. this.move(rowStep, colStep, evt);
  567. if(evt){
  568. event.stop(evt);
  569. }
  570. },
  571. _onContentKeyDown: function(e, isBubble){
  572. if(isBubble){
  573. var dk = keys, s = this.grid.scroller;
  574. switch(e.keyCode){
  575. case dk.ENTER:
  576. case dk.SPACE:
  577. var g = this.grid;
  578. if(g.indirectSelection){ break; }
  579. g.selection.clickSelect(this.rowIndex, connect.isCopyKey(e), e.shiftKey);
  580. g.onRowClick(e);
  581. event.stop(e);
  582. break;
  583. case dk.PAGE_UP:
  584. if(this.rowIndex !== 0){
  585. if(this.rowIndex != s.firstVisibleRow + 1){
  586. this._navContent(s.firstVisibleRow - this.rowIndex, 0);
  587. }else{
  588. this.grid.setScrollTop(s.findScrollTop(this.rowIndex - 1));
  589. this._navContent(s.firstVisibleRow - s.lastVisibleRow + 1, 0);
  590. }
  591. event.stop(e);
  592. }
  593. break;
  594. case dk.PAGE_DOWN:
  595. if(this.rowIndex + 1 != this.grid.rowCount){
  596. event.stop(e);
  597. if(this.rowIndex != s.lastVisibleRow - 1){
  598. this._navContent(s.lastVisibleRow - this.rowIndex - 1, 0);
  599. }else{
  600. this.grid.setScrollTop(s.findScrollTop(this.rowIndex + 1));
  601. this._navContent(s.lastVisibleRow - s.firstVisibleRow - 1, 0);
  602. }
  603. event.stop(e);
  604. }
  605. break;
  606. }
  607. }
  608. return true;
  609. },
  610. //------------------editable content area-------------------------
  611. _blurFromEditableCell: false,
  612. _isNavigating: false,
  613. _navElems: null,
  614. _focusEditableCell: function(evt,step){
  615. var didFocus = false;
  616. if(this._isNavigating){
  617. didFocus = true;
  618. }else if(this.grid.edit.isEditing() && this.cell){
  619. if(this._blurFromEditableCell || !this._blurEditableCell(evt, step)){
  620. this.setFocusIndex(this.rowIndex,this.cell.index);
  621. didFocus = true;
  622. }
  623. this._stopEvent(evt);
  624. }
  625. return didFocus;
  626. },
  627. _applyEditableCell: function(){
  628. try{
  629. this.grid.edit.apply();
  630. }catch(e){
  631. console.warn("_FocusManager._applyEditableCell() error:", e);
  632. }
  633. },
  634. _blurEditableCell: function(evt,step){
  635. this._blurFromEditableCell = false;
  636. if(this._isNavigating){
  637. var toBlur = true;
  638. if(evt){
  639. var elems = this._navElems;
  640. var firstElem = elems.lowest || elems.first;
  641. var lastElem = elems.last || elems.highest || firstElem;
  642. var target = has("ie") ? evt.srcElement : evt.target;
  643. toBlur = target == (step > 0 ? lastElem : firstElem);
  644. }
  645. if(toBlur){
  646. this._isNavigating = false;
  647. html.setSelectable(this.cell.getNode(this.rowIndex), false);
  648. return "content";
  649. }
  650. return false;
  651. }else if(this.grid.edit.isEditing() && this.cell){
  652. if(!step || typeof step != "number"){ return false; }
  653. var dir = step > 0 ? 1 : -1;
  654. var cc = this.grid.layout.cellCount;
  655. for(var cell, col = this.cell.index + dir; col >= 0 && col < cc; col += dir){
  656. cell = this.grid.getCell(col);
  657. if(cell.editable){
  658. this.cell = cell;
  659. this._blurFromEditableCell = true;
  660. return false;
  661. }
  662. }
  663. if((this.rowIndex > 0 || dir == 1) && (this.rowIndex < this.grid.rowCount || dir == -1)){
  664. this.rowIndex += dir;
  665. //this.cell = this.grid.getCell(0); //There must be an editable cell, so this is not necessary.
  666. for(col = dir > 0 ? 0 : cc - 1; col >= 0 && col < cc; col += dir){
  667. cell = this.grid.getCell(col);
  668. if(cell.editable){
  669. this.cell = cell;
  670. break;
  671. }
  672. }
  673. this._applyEditableCell();
  674. return "content";
  675. }
  676. }
  677. return true;
  678. },
  679. _initNavigatableElems: function(){
  680. this._navElems = dijitA11y._getTabNavigable(this.cell.getNode(this.rowIndex));
  681. },
  682. _onEditableCellKeyDown: function(e, isBubble){
  683. var dk = keys,
  684. g = this.grid,
  685. edit = g.edit,
  686. editApplied = false,
  687. toPropagate = true;
  688. switch(e.keyCode){
  689. case dk.ENTER:
  690. if(isBubble && edit.isEditing()){
  691. this._applyEditableCell();
  692. editApplied = true;
  693. event.stop(e);
  694. }
  695. //intentional drop through
  696. case dk.SPACE:
  697. if(!isBubble && this._isNavigating){
  698. toPropagate = false;
  699. break;
  700. }
  701. if(isBubble){
  702. if(!this.cell.editable && this.cell.navigatable){
  703. this._initNavigatableElems();
  704. var toFocus = this._navElems.lowest || this._navElems.first;
  705. if(toFocus){
  706. this._isNavigating = true;
  707. html.setSelectable(this.cell.getNode(this.rowIndex), true);
  708. dijitFocus.focus(toFocus);
  709. event.stop(e);
  710. this.currentArea("editableCell", true);
  711. break;
  712. }
  713. }
  714. if(!editApplied && !edit.isEditing() && !g.pluginMgr.isFixedCell(this.cell)){
  715. edit.setEditCell(this.cell, this.rowIndex);
  716. }
  717. if(editApplied){
  718. this.currentArea("content", true);
  719. }else if(this.cell.editable && g.canEdit()){
  720. this.currentArea("editableCell", true);
  721. }
  722. }
  723. break;
  724. case dk.PAGE_UP:
  725. case dk.PAGE_DOWN:
  726. if(!isBubble && edit.isEditing()){
  727. //prevent propagating to content area
  728. toPropagate = false;
  729. }
  730. break;
  731. case dk.ESCAPE:
  732. if(!isBubble){
  733. edit.cancel();
  734. this.currentArea("content", true);
  735. }
  736. }
  737. return toPropagate;
  738. },
  739. _onEditableCellMouseEvent: function(evt){
  740. if(evt.type == "click"){
  741. var cell = this.cell || evt.cell;
  742. if(cell && !cell.editable && cell.navigatable){
  743. this._initNavigatableElems();
  744. if(this._navElems.lowest || this._navElems.first){
  745. var target = has("ie") ? evt.srcElement : evt.target;
  746. if(target != cell.getNode(evt.rowIndex)){
  747. this._isNavigating = true;
  748. this.focusArea("editableCell", evt);
  749. html.setSelectable(cell.getNode(evt.rowIndex), true);
  750. dijitFocus.focus(target);
  751. return false;
  752. }
  753. }
  754. }else if(this.grid.singleClickEdit){
  755. this.currentArea("editableCell");
  756. return false;
  757. }
  758. }
  759. return true;
  760. }
  761. });
  762. });