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