|
1 YUI.add('datatable-keynav', function (Y, NAME) { |
|
2 |
|
3 /** |
|
4 Provides keyboard navigation of DataTable cells and support for adding other |
|
5 keyboard actions. |
|
6 |
|
7 @module datatable |
|
8 @submodule datatable-keynav |
|
9 */ |
|
10 var arrEach = Y.Array.each, |
|
11 |
|
12 /** |
|
13 A DataTable class extension that provides navigation via keyboard, based on |
|
14 WAI-ARIA recommendation for the [Grid widget](http://www.w3.org/WAI/PF/aria-practices/#grid) |
|
15 and extensible to support other actions. |
|
16 |
|
17 |
|
18 @class DataTable.KeyNav |
|
19 @for DataTable |
|
20 */ |
|
21 DtKeyNav = function (){}; |
|
22 |
|
23 /** |
|
24 Mapping of key codes to friendly key names that can be used in the |
|
25 [keyActions](#property_keyActions) property and [ARIA_ACTIONS](#property_ARIA_ACTIONS) |
|
26 property. |
|
27 |
|
28 It contains aliases for the following keys: |
|
29 <ul> |
|
30 <li>backspace</li> |
|
31 <li>tab</li> |
|
32 <li>enter</li> |
|
33 <li>esc</li> |
|
34 <li>space</li> |
|
35 <li>pgup</li> |
|
36 <li>pgdown</li> |
|
37 <li>end</li> |
|
38 <li>home</li> |
|
39 <li>left</li> |
|
40 <li>up</li> |
|
41 <li>right</li> |
|
42 <li>down</li> |
|
43 <li>f1 .. f12</li> |
|
44 </ul> |
|
45 |
|
46 |
|
47 @property KEY_NAMES |
|
48 @type {Object} |
|
49 @static |
|
50 **/ |
|
51 DtKeyNav.KEY_NAMES = { |
|
52 8: 'backspace', |
|
53 9: 'tab', |
|
54 13: 'enter', |
|
55 27: 'esc', |
|
56 32: 'space', |
|
57 33: 'pgup', |
|
58 34: 'pgdown', |
|
59 35: 'end', |
|
60 36: 'home', |
|
61 37: 'left', |
|
62 38: 'up', |
|
63 39: 'right', |
|
64 40: 'down', |
|
65 112:'f1', |
|
66 113:'f2', |
|
67 114:'f3', |
|
68 115:'f4', |
|
69 116:'f5', |
|
70 117:'f6', |
|
71 118:'f7', |
|
72 119:'f8', |
|
73 120:'f9', |
|
74 121:'f10', |
|
75 122:'f11', |
|
76 123:'f12' |
|
77 }; |
|
78 |
|
79 /** |
|
80 Mapping of key codes to actions according to the WAI-ARIA suggestion for the |
|
81 [Grid Widget](http://www.w3.org/WAI/PF/aria-practices/#grid). |
|
82 |
|
83 The key for each entry is a key-code or [keyName](#property_KEY_NAMES) while the |
|
84 value can be a function that performs the action or a string. If a string, |
|
85 it can either correspond to the name of a method in this module (or any |
|
86 method in a DataTable instance) or the name of an event to fire. |
|
87 @property ARIA_ACTIONS |
|
88 @type Object |
|
89 @static |
|
90 */ |
|
91 DtKeyNav.ARIA_ACTIONS = { |
|
92 left: '_keyMoveLeft', |
|
93 right: '_keyMoveRight', |
|
94 up: '_keyMoveUp', |
|
95 down: '_keyMoveDown', |
|
96 home: '_keyMoveRowStart', |
|
97 end: '_keyMoveRowEnd', |
|
98 pgup: '_keyMoveColTop', |
|
99 pgdown: '_keyMoveColBottom' |
|
100 }; |
|
101 |
|
102 DtKeyNav.ATTRS = { |
|
103 /** |
|
104 Cell that's currently either focused or |
|
105 focusable when the DataTable gets the focus. |
|
106 |
|
107 @attribute focusedCell |
|
108 @type Node |
|
109 @default first cell in the table. |
|
110 **/ |
|
111 focusedCell: { |
|
112 setter: '_focusedCellSetter' |
|
113 }, |
|
114 |
|
115 /** |
|
116 Determines whether it is possible to navigate into the header area. |
|
117 The examples referenced in the document show both behaviors so it seems |
|
118 it is optional. |
|
119 |
|
120 @attribute keyIntoHeaders |
|
121 @type Boolean |
|
122 @default true |
|
123 */ |
|
124 keyIntoHeaders: { |
|
125 value: true |
|
126 } |
|
127 |
|
128 }; |
|
129 |
|
130 Y.mix( DtKeyNav.prototype, { |
|
131 |
|
132 /** |
|
133 Table of actions to be performed for each key. It is loaded with a clone |
|
134 of [ARIA_ACTIONS](#property_ARIA_ACTIONS) by default. |
|
135 |
|
136 The key for each entry is either a key-code or an alias from the |
|
137 [KEY_NAMES](#property_KEY_NAMES) table. They can be prefixed with any combination |
|
138 of the modifier keys `alt`, `ctrl`, `meta` or `shift` each followed by a hyphen, |
|
139 such as `"ctrl-shift-up"` (modifiers, if more than one, should appear in alphabetical order). |
|
140 |
|
141 The value for each entry should be a function or the name of a method in |
|
142 the DataTable instance. The method will receive the original keyboard |
|
143 EventFacade as its only argument. |
|
144 |
|
145 If the value is a string and it cannot be resolved into a method, |
|
146 it will be assumed to be the name of an event to fire. The listener for that |
|
147 event will receive an EventFacade containing references to the cell that has the focus, |
|
148 the row, column and, unless it is a header row, the record it corresponds to. |
|
149 The second argument will be the original EventFacade for the keyboard event. |
|
150 |
|
151 @property keyActions |
|
152 @type {Object} |
|
153 @default Y.DataTable.keyNav.ARIA_ACTIONS |
|
154 */ |
|
155 |
|
156 keyActions: null, |
|
157 |
|
158 /** |
|
159 Array containing the event handles to any event that might need to be detached |
|
160 on destruction. |
|
161 @property _keyNavSubscr |
|
162 @type Array |
|
163 @default null, |
|
164 @private |
|
165 */ |
|
166 _keyNavSubscr: null, |
|
167 |
|
168 /** |
|
169 Reference to the THead section that holds the headers for the datatable. |
|
170 For a Scrolling DataTable, it is the one visible to the user. |
|
171 @property _keyNavTHead |
|
172 @type Node |
|
173 @default: null |
|
174 @private |
|
175 */ |
|
176 _keyNavTHead: null, |
|
177 |
|
178 /** |
|
179 Indicates if the headers of the table are nested or not. |
|
180 Nested headers makes navigation in the headers much harder. |
|
181 @property _keyNavNestedHeaders |
|
182 @default false |
|
183 @private |
|
184 */ |
|
185 _keyNavNestedHeaders: false, |
|
186 |
|
187 /** |
|
188 CSS class name prefix for columns, used to search for a cell by key. |
|
189 @property _keyNavColPrefix |
|
190 @type String |
|
191 @default null (initialized via getClassname() ) |
|
192 @private |
|
193 */ |
|
194 _keyNavColPrefix:null, |
|
195 |
|
196 /** |
|
197 Regular expression to extract the column key from a cell via its CSS class name. |
|
198 @property _keyNavColRegExp |
|
199 @type RegExp |
|
200 @default null (initialized based on _keyNavColPrefix) |
|
201 @private |
|
202 */ |
|
203 _keyNavColRegExp:null, |
|
204 |
|
205 initializer: function () { |
|
206 this.onceAfter('render', this._afterKeyNavRender); |
|
207 this._keyNavSubscr = [ |
|
208 this.after('focusedCellChange', this._afterKeyNavFocusedCellChange), |
|
209 this.after('focusedChange', this._afterKeyNavFocusedChange) |
|
210 ]; |
|
211 this._keyNavColPrefix = this.getClassName('col', ''); |
|
212 this._keyNavColRegExp = new RegExp(this._keyNavColPrefix + '(.+?)(\\s|$)'); |
|
213 this.keyActions = Y.clone(DtKeyNav.ARIA_ACTIONS); |
|
214 |
|
215 }, |
|
216 |
|
217 destructor: function () { |
|
218 arrEach(this._keyNavSubscr, function (evHandle) { |
|
219 if (evHandle && evHandle.detach) { |
|
220 evHandle.detach(); |
|
221 } |
|
222 }); |
|
223 }, |
|
224 |
|
225 /** |
|
226 Sets the tabIndex on the focused cell and, if the DataTable has the focus, |
|
227 sets the focus on it. |
|
228 |
|
229 @method _afterFocusedCellChange |
|
230 @param e {EventFacade} |
|
231 @private |
|
232 */ |
|
233 _afterKeyNavFocusedCellChange: function (e) { |
|
234 var newVal = e.newVal, |
|
235 prevVal = e.prevVal; |
|
236 |
|
237 if (prevVal) { |
|
238 prevVal.set('tabIndex', -1); |
|
239 } |
|
240 |
|
241 if (newVal) { |
|
242 newVal.set('tabIndex', 0); |
|
243 |
|
244 if (this.get('focused')) { |
|
245 newVal.scrollIntoView(); |
|
246 newVal.focus(); |
|
247 } |
|
248 } else { |
|
249 this.set('focused', null); |
|
250 } |
|
251 }, |
|
252 |
|
253 /** |
|
254 When the DataTable gets the focus, it ensures the correct cell regains |
|
255 the focus. |
|
256 |
|
257 @method _afterKeyNavFocusedChange |
|
258 @param e {EventFacade} |
|
259 @private |
|
260 */ |
|
261 _afterKeyNavFocusedChange: function (e) { |
|
262 var cell = this.get('focusedCell'); |
|
263 if (e.newVal) { |
|
264 if (cell) { |
|
265 cell.scrollIntoView(); |
|
266 cell.focus(); |
|
267 } else { |
|
268 this._keyMoveFirst(); |
|
269 } |
|
270 } else { |
|
271 if (cell) { |
|
272 cell.blur(); |
|
273 } |
|
274 } |
|
275 }, |
|
276 |
|
277 /** |
|
278 Subscribes to the events on the DataTable elements once they have been rendered, |
|
279 finds out the header section and makes the top-left element focusable. |
|
280 |
|
281 @method _afterKeyNavRender |
|
282 @private |
|
283 */ |
|
284 _afterKeyNavRender: function () { |
|
285 var cbx = this.get('contentBox'); |
|
286 this._keyNavSubscr.push( |
|
287 cbx.on('keydown', this._onKeyNavKeyDown, this), |
|
288 cbx.on('click', this._onKeyNavClick, this) |
|
289 ); |
|
290 this._keyNavTHead = (this._yScrollHeader || this._tableNode).one('thead'); |
|
291 this._keyMoveFirst(); |
|
292 |
|
293 // determine if we have nested headers |
|
294 this._keyNavNestedHeaders = (this.get('columns').length !== this.head.theadNode.all('th').size()); |
|
295 }, |
|
296 |
|
297 /** |
|
298 In response to a click event, it sets the focus on the clicked cell |
|
299 |
|
300 @method _onKeyNavClick |
|
301 @param e {EventFacade} |
|
302 @private |
|
303 */ |
|
304 _onKeyNavClick: function (e) { |
|
305 var cell = e.target.ancestor((this.get('keyIntoHeaders') ? 'td, th': 'td'), true); |
|
306 if (cell) { |
|
307 this.focus(); |
|
308 this.set('focusedCell', cell); |
|
309 } |
|
310 }, |
|
311 |
|
312 /** |
|
313 Responds to a key down event by executing the action set in the |
|
314 [keyActions](#property_keyActions) table. |
|
315 |
|
316 @method _onKeyNavKeyDown |
|
317 @param e {EventFacade} |
|
318 @private |
|
319 */ |
|
320 _onKeyNavKeyDown: function (e) { |
|
321 var keyCode = e.keyCode, |
|
322 keyName = DtKeyNav.KEY_NAMES[keyCode] || keyCode, |
|
323 action; |
|
324 |
|
325 arrEach(['alt', 'ctrl', 'meta', 'shift'], function (modifier) { |
|
326 if (e[modifier + 'Key']) { |
|
327 keyCode = modifier + '-' + keyCode; |
|
328 keyName = modifier + '-' + keyName; |
|
329 } |
|
330 }); |
|
331 action = this.keyActions[keyCode] || this.keyActions[keyName]; |
|
332 |
|
333 if (typeof action === 'string') { |
|
334 if (this[action]) { |
|
335 this[action].call(this, e); |
|
336 } else { |
|
337 this._keyNavFireEvent(action, e); |
|
338 } |
|
339 } else { |
|
340 action.call(this, e); |
|
341 } |
|
342 }, |
|
343 |
|
344 /** |
|
345 If the action associated to a key combination is a string and no method |
|
346 by that name was found in this instance, this method will |
|
347 fire an event using that string and provides extra information |
|
348 to the listener. |
|
349 |
|
350 @method _keyNavFireEvent |
|
351 @param action {String} Name of the event to fire |
|
352 @param e {EventFacade} Original facade from the keydown event. |
|
353 @private |
|
354 */ |
|
355 _keyNavFireEvent: function (action, e) { |
|
356 var cell = e.target.ancestor('td, th', true); |
|
357 if (cell) { |
|
358 this.fire(action, { |
|
359 cell: cell, |
|
360 row: cell.ancestor('tr'), |
|
361 record: this.getRecord(cell), |
|
362 column: this.getColumn(cell.get('cellIndex')) |
|
363 }, e); |
|
364 } |
|
365 }, |
|
366 |
|
367 /** |
|
368 Sets the focus on the very first cell in the header of the table. |
|
369 |
|
370 @method _keyMoveFirst |
|
371 @private |
|
372 */ |
|
373 _keyMoveFirst: function () { |
|
374 this.set('focusedCell' , (this.get('keyIntoHeaders') ? this._keyNavTHead.one('th') : this._tbodyNode.one('td')), {src:'keyNav'}); |
|
375 }, |
|
376 |
|
377 /** |
|
378 Sets the focus on the cell to the left of the currently focused one. |
|
379 Does not wrap, following the WAI-ARIA recommendation. |
|
380 |
|
381 @method _keyMoveLeft |
|
382 @param e {EventFacade} Event Facade for the keydown event |
|
383 @private |
|
384 */ |
|
385 _keyMoveLeft: function (e) { |
|
386 var cell = this.get('focusedCell'), |
|
387 index = cell.get('cellIndex'), |
|
388 row = cell.ancestor(); |
|
389 |
|
390 e.preventDefault(); |
|
391 |
|
392 if (index === 0) { |
|
393 return; |
|
394 } |
|
395 cell = row.get('cells').item(index - 1); |
|
396 this.set('focusedCell', cell , {src:'keyNav'}); |
|
397 }, |
|
398 |
|
399 /** |
|
400 Sets the focus on the cell to the right of the currently focused one. |
|
401 Does not wrap, following the WAI-ARIA recommendation. |
|
402 |
|
403 @method _keyMoveRight |
|
404 @param e {EventFacade} Event Facade for the keydown event |
|
405 @private |
|
406 */ |
|
407 _keyMoveRight: function (e) { |
|
408 var cell = this.get('focusedCell'), |
|
409 row = cell.ancestor('tr'), |
|
410 section = row.ancestor(), |
|
411 inHead = section === this._keyNavTHead, |
|
412 nextCell, |
|
413 parent; |
|
414 |
|
415 e.preventDefault(); |
|
416 |
|
417 // a little special with nested headers |
|
418 /* |
|
419 +-------------+-------+ |
|
420 | ABC | DE | |
|
421 +-------+-----+---+---+ |
|
422 | AB | | | | |
|
423 +---+---+ | | | |
|
424 | A | B | C | D | E | |
|
425 +---+---+-----+---+---+ |
|
426 */ |
|
427 |
|
428 nextCell = cell.next(); |
|
429 |
|
430 if (row.get('rowIndex') !== 0 && inHead && this._keyNavNestedHeaders) { |
|
431 if (nextCell) { |
|
432 cell = nextCell; |
|
433 } else { //-- B -> C |
|
434 parent = this._getTHParent(cell); |
|
435 |
|
436 if (parent && parent.next()) { |
|
437 cell = parent.next(); |
|
438 } else { //-- E -> ... |
|
439 return; |
|
440 } |
|
441 } |
|
442 |
|
443 } else { |
|
444 if (!nextCell) { |
|
445 return; |
|
446 } else { |
|
447 cell = nextCell; |
|
448 } |
|
449 } |
|
450 |
|
451 this.set('focusedCell', cell, { src:'keyNav' }); |
|
452 |
|
453 }, |
|
454 |
|
455 /** |
|
456 Sets the focus on the cell above the currently focused one. |
|
457 It will move into the headers when the top of the data rows is reached. |
|
458 Does not wrap, following the WAI-ARIA recommendation. |
|
459 |
|
460 @method _keyMoveUp |
|
461 @param e {EventFacade} Event Facade for the keydown event |
|
462 @private |
|
463 */ |
|
464 _keyMoveUp: function (e) { |
|
465 var cell = this.get('focusedCell'), |
|
466 cellIndex = cell.get('cellIndex'), |
|
467 row = cell.ancestor('tr'), |
|
468 rowIndex = row.get('rowIndex'), |
|
469 section = row.ancestor(), |
|
470 sectionRows = section.get('rows'), |
|
471 inHead = section === this._keyNavTHead, |
|
472 parent; |
|
473 |
|
474 e.preventDefault(); |
|
475 |
|
476 if (!inHead) { |
|
477 rowIndex -= section.get('firstChild').get('rowIndex'); |
|
478 } |
|
479 |
|
480 if (rowIndex === 0) { |
|
481 if (inHead || !this.get('keyIntoHeaders')) { |
|
482 return; |
|
483 } |
|
484 |
|
485 section = this._keyNavTHead; |
|
486 sectionRows = section.get('rows'); |
|
487 |
|
488 if (this._keyNavNestedHeaders) { |
|
489 key = this._getCellColumnName(cell); |
|
490 cell = section.one('.' + this._keyNavColPrefix + key); |
|
491 cellIndex = cell.get('cellIndex'); |
|
492 row = cell.ancestor('tr'); |
|
493 } else { |
|
494 row = section.get('firstChild'); |
|
495 cell = row.get('cells').item(cellIndex); |
|
496 } |
|
497 } else { |
|
498 if (inHead && this._keyNavNestedHeaders) { |
|
499 key = this._getCellColumnName(cell); |
|
500 parent = this._columnMap[key]._parent; |
|
501 if (parent) { |
|
502 cell = section.one('#' + parent.id); |
|
503 } |
|
504 } else { |
|
505 row = sectionRows.item(rowIndex -1); |
|
506 cell = row.get('cells').item(cellIndex); |
|
507 } |
|
508 } |
|
509 this.set('focusedCell', cell); |
|
510 }, |
|
511 |
|
512 /** |
|
513 Sets the focus on the cell below the currently focused one. |
|
514 It will move into the data rows when the bottom of the header rows is reached. |
|
515 Does not wrap, following the WAI-ARIA recommendation. |
|
516 |
|
517 @method _keyMoveDown |
|
518 @param e {EventFacade} Event Facade for the keydown event |
|
519 @private |
|
520 */ |
|
521 _keyMoveDown: function (e) { |
|
522 var cell = this.get('focusedCell'), |
|
523 cellIndex = cell.get('cellIndex'), |
|
524 row = cell.ancestor('tr'), |
|
525 rowIndex = row.get('rowIndex') + 1, |
|
526 section = row.ancestor(), |
|
527 inHead = section === this._keyNavTHead, |
|
528 tbody = (this.body && this.body.tbodyNode), |
|
529 sectionRows = section.get('rows'), |
|
530 key, |
|
531 children; |
|
532 |
|
533 e.preventDefault(); |
|
534 |
|
535 if (inHead) { // focused cell is in the header |
|
536 if (this._keyNavNestedHeaders) { // the header is nested |
|
537 key = this._getCellColumnName(cell); |
|
538 children = this._columnMap[key].children; |
|
539 |
|
540 rowIndex += (cell.getAttribute('rowspan') || 1) - 1; |
|
541 |
|
542 if (children) { |
|
543 // stay in thead |
|
544 cell = section.one('#' + children[0].id); |
|
545 } else { |
|
546 // moving into tbody |
|
547 cell = tbody.one('.' + this._keyNavColPrefix + key); |
|
548 section = tbody; |
|
549 sectionRows = section.get('rows'); |
|
550 } |
|
551 cellIndex = cell.get('cellIndex'); |
|
552 |
|
553 } else { // the header is not nested |
|
554 row = tbody.one('tr'); |
|
555 cell = row.get('cells').item(cellIndex); |
|
556 } |
|
557 } |
|
558 |
|
559 // offset row index to tbody |
|
560 rowIndex -= sectionRows.item(0).get('rowIndex'); |
|
561 |
|
562 |
|
563 if (rowIndex >= sectionRows.size()) { |
|
564 if (!inHead) { // last row in tbody |
|
565 return; |
|
566 } |
|
567 section = tbody; |
|
568 row = section.one('tr'); |
|
569 |
|
570 } else { |
|
571 row = sectionRows.item(rowIndex); |
|
572 } |
|
573 |
|
574 this.set('focusedCell', row.get('cells').item(cellIndex)); |
|
575 }, |
|
576 |
|
577 /** |
|
578 Sets the focus on the left-most cell of the row containing the currently focused cell. |
|
579 |
|
580 @method _keyMoveRowStart |
|
581 @param e {EventFacade} Event Facade for the keydown event |
|
582 @private |
|
583 */ |
|
584 _keyMoveRowStart: function (e) { |
|
585 var row = this.get('focusedCell').ancestor(); |
|
586 this.set('focusedCell', row.get('firstChild'), {src:'keyNav'}); |
|
587 e.preventDefault(); |
|
588 }, |
|
589 |
|
590 /** |
|
591 Sets the focus on the right-most cell of the row containing the currently focused cell. |
|
592 |
|
593 @method _keyMoveRowEnd |
|
594 @param e {EventFacade} Event Facade for the keydown event |
|
595 @private |
|
596 */ |
|
597 _keyMoveRowEnd: function (e) { |
|
598 var row = this.get('focusedCell').ancestor(); |
|
599 this.set('focusedCell', row.get('lastChild'), {src:'keyNav'}); |
|
600 e.preventDefault(); |
|
601 }, |
|
602 |
|
603 /** |
|
604 Sets the focus on the top-most cell of the column containing the currently focused cell. |
|
605 It would normally be a header cell. |
|
606 |
|
607 @method _keyMoveColTop |
|
608 @param e {EventFacade} Event Facade for the keydown event |
|
609 @private |
|
610 */ |
|
611 _keyMoveColTop: function (e) { |
|
612 var cell = this.get('focusedCell'), |
|
613 cellIndex = cell.get('cellIndex'), |
|
614 key, header; |
|
615 |
|
616 e.preventDefault(); |
|
617 |
|
618 if (this._keyNavNestedHeaders && this.get('keyIntoHeaders')) { |
|
619 key = this._getCellColumnName(cell); |
|
620 header = this._columnMap[key]; |
|
621 while (header._parent) { |
|
622 header = header._parent; |
|
623 } |
|
624 cell = this._keyNavTHead.one('#' + header.id); |
|
625 |
|
626 } else { |
|
627 cell = (this.get('keyIntoHeaders') ? this._keyNavTHead: this._tbodyNode).get('firstChild').get('cells').item(cellIndex); |
|
628 } |
|
629 this.set('focusedCell', cell , {src:'keyNav'}); |
|
630 }, |
|
631 |
|
632 /** |
|
633 Sets the focus on the last cell of the column containing the currently focused cell. |
|
634 |
|
635 @method _keyMoveColBottom |
|
636 @param e {EventFacade} Event Facade for the keydown event |
|
637 @private |
|
638 */ |
|
639 _keyMoveColBottom: function (e) { |
|
640 var cell = this.get('focusedCell'), |
|
641 cellIndex = cell.get('cellIndex'); |
|
642 |
|
643 this.set('focusedCell', this._tbodyNode.get('lastChild').get('cells').item(cellIndex), {src:'keyNav'}); |
|
644 e.preventDefault(); |
|
645 |
|
646 }, |
|
647 |
|
648 /** |
|
649 Setter method for the [focusedCell](#attr_focusedCell) attribute. |
|
650 Checks that the passed value is a Node, either a TD or TH and is |
|
651 contained within the DataTable contentBox. |
|
652 |
|
653 @method _focusedCellSetter |
|
654 @param cell {Node} DataTable cell to receive the focus |
|
655 @return cell or Y.Attribute.INVALID_VALUE |
|
656 @private |
|
657 */ |
|
658 _focusedCellSetter: function (cell) { |
|
659 if (cell instanceof Y.Node) { |
|
660 var tag = cell.get('tagName').toUpperCase(); |
|
661 if ((tag === 'TD' || tag === 'TH') && this.get('contentBox').contains(cell) ) { |
|
662 return cell; |
|
663 } |
|
664 } else if (cell === null) { |
|
665 return cell; |
|
666 } |
|
667 return Y.Attribute.INVALID_VALUE; |
|
668 }, |
|
669 |
|
670 /** |
|
671 Retrieves the parent cell of the given TH cell. If there is no parent for |
|
672 the provided cell, null is returned. |
|
673 @protected |
|
674 @method _getTHParent |
|
675 @param {Node} thCell Cell to find parent of |
|
676 @return {Node} Parent of the cell provided or null |
|
677 */ |
|
678 _getTHParent: function (thCell) { |
|
679 var key = this._getCellColumnName(thCell), |
|
680 parent = this._columnMap[key] && this._columnMap[key]._parent; |
|
681 |
|
682 if (parent) { |
|
683 return thCell.ancestor().ancestor().one('.' + this._keyNavColPrefix + parent.key); |
|
684 } |
|
685 |
|
686 return null; |
|
687 }, |
|
688 |
|
689 /** |
|
690 Retrieves the column name based from the data attribute on the cell if |
|
691 available. Other wise, extracts the column name from the classname |
|
692 @protected |
|
693 @method _getCellColumnName |
|
694 @param {Node} cell Cell to get column name from |
|
695 @return String Column name of the provided cell |
|
696 */ |
|
697 _getCellColumnName: function (cell) { |
|
698 return cell.getData('yui3-col-id') || this._keyNavColRegExp.exec(cell.get('className'))[1]; |
|
699 } |
|
700 }); |
|
701 |
|
702 Y.DataTable.KeyNav = DtKeyNav; |
|
703 Y.Base.mix(Y.DataTable, [DtKeyNav]); |
|
704 |
|
705 |
|
706 }, '@VERSION@', {"requires": ["datatable-base"]}); |