diff -r d334a616c023 -r e16a97fb364a src/cm/media/js/lib/yui/yui3-3.15.0/build/datatable-keynav/datatable-keynav-debug.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cm/media/js/lib/yui/yui3-3.15.0/build/datatable-keynav/datatable-keynav-debug.js Mon Mar 10 15:19:48 2014 +0100
@@ -0,0 +1,706 @@
+YUI.add('datatable-keynav', function (Y, NAME) {
+
+/**
+ Provides keyboard navigation of DataTable cells and support for adding other
+ keyboard actions.
+
+ @module datatable
+ @submodule datatable-keynav
+*/
+var arrEach = Y.Array.each,
+
+/**
+ A DataTable class extension that provides navigation via keyboard, based on
+ WAI-ARIA recommendation for the [Grid widget](http://www.w3.org/WAI/PF/aria-practices/#grid)
+ and extensible to support other actions.
+
+
+ @class DataTable.KeyNav
+ @for DataTable
+*/
+ DtKeyNav = function (){};
+
+/**
+Mapping of key codes to friendly key names that can be used in the
+[keyActions](#property_keyActions) property and [ARIA_ACTIONS](#property_ARIA_ACTIONS)
+property.
+
+It contains aliases for the following keys:
+
+ - backspace
+ - tab
+ - enter
+ - esc
+ - space
+ - pgup
+ - pgdown
+ - end
+ - home
+ - left
+ - up
+ - right
+ - down
+ - f1 .. f12
+
+
+
+@property KEY_NAMES
+@type {Object}
+@static
+**/
+DtKeyNav.KEY_NAMES = {
+ 8: 'backspace',
+ 9: 'tab',
+ 13: 'enter',
+ 27: 'esc',
+ 32: 'space',
+ 33: 'pgup',
+ 34: 'pgdown',
+ 35: 'end',
+ 36: 'home',
+ 37: 'left',
+ 38: 'up',
+ 39: 'right',
+ 40: 'down',
+ 112:'f1',
+ 113:'f2',
+ 114:'f3',
+ 115:'f4',
+ 116:'f5',
+ 117:'f6',
+ 118:'f7',
+ 119:'f8',
+ 120:'f9',
+ 121:'f10',
+ 122:'f11',
+ 123:'f12'
+};
+
+/**
+Mapping of key codes to actions according to the WAI-ARIA suggestion for the
+[Grid Widget](http://www.w3.org/WAI/PF/aria-practices/#grid).
+
+The key for each entry is a key-code or [keyName](#property_KEY_NAMES) while the
+value can be a function that performs the action or a string. If a string,
+it can either correspond to the name of a method in this module (or any
+method in a DataTable instance) or the name of an event to fire.
+@property ARIA_ACTIONS
+@type Object
+@static
+ */
+DtKeyNav.ARIA_ACTIONS = {
+ left: '_keyMoveLeft',
+ right: '_keyMoveRight',
+ up: '_keyMoveUp',
+ down: '_keyMoveDown',
+ home: '_keyMoveRowStart',
+ end: '_keyMoveRowEnd',
+ pgup: '_keyMoveColTop',
+ pgdown: '_keyMoveColBottom'
+};
+
+DtKeyNav.ATTRS = {
+ /**
+ Cell that's currently either focused or
+ focusable when the DataTable gets the focus.
+
+ @attribute focusedCell
+ @type Node
+ @default first cell in the table.
+ **/
+ focusedCell: {
+ setter: '_focusedCellSetter'
+ },
+
+ /**
+ Determines whether it is possible to navigate into the header area.
+ The examples referenced in the document show both behaviors so it seems
+ it is optional.
+
+ @attribute keyIntoHeaders
+ @type Boolean
+ @default true
+ */
+ keyIntoHeaders: {
+ value: true
+ }
+
+};
+
+Y.mix( DtKeyNav.prototype, {
+
+ /**
+ Table of actions to be performed for each key. It is loaded with a clone
+ of [ARIA_ACTIONS](#property_ARIA_ACTIONS) by default.
+
+ The key for each entry is either a key-code or an alias from the
+ [KEY_NAMES](#property_KEY_NAMES) table. They can be prefixed with any combination
+ of the modifier keys `alt`, `ctrl`, `meta` or `shift` each followed by a hyphen,
+ such as `"ctrl-shift-up"` (modifiers, if more than one, should appear in alphabetical order).
+
+ The value for each entry should be a function or the name of a method in
+ the DataTable instance. The method will receive the original keyboard
+ EventFacade as its only argument.
+
+ If the value is a string and it cannot be resolved into a method,
+ it will be assumed to be the name of an event to fire. The listener for that
+ event will receive an EventFacade containing references to the cell that has the focus,
+ the row, column and, unless it is a header row, the record it corresponds to.
+ The second argument will be the original EventFacade for the keyboard event.
+
+ @property keyActions
+ @type {Object}
+ @default Y.DataTable.keyNav.ARIA_ACTIONS
+ */
+
+ keyActions: null,
+
+ /**
+ Array containing the event handles to any event that might need to be detached
+ on destruction.
+ @property _keyNavSubscr
+ @type Array
+ @default null,
+ @private
+ */
+ _keyNavSubscr: null,
+
+ /**
+ Reference to the THead section that holds the headers for the datatable.
+ For a Scrolling DataTable, it is the one visible to the user.
+ @property _keyNavTHead
+ @type Node
+ @default: null
+ @private
+ */
+ _keyNavTHead: null,
+
+ /**
+ Indicates if the headers of the table are nested or not.
+ Nested headers makes navigation in the headers much harder.
+ @property _keyNavNestedHeaders
+ @default false
+ @private
+ */
+ _keyNavNestedHeaders: false,
+
+ /**
+ CSS class name prefix for columns, used to search for a cell by key.
+ @property _keyNavColPrefix
+ @type String
+ @default null (initialized via getClassname() )
+ @private
+ */
+ _keyNavColPrefix:null,
+
+ /**
+ Regular expression to extract the column key from a cell via its CSS class name.
+ @property _keyNavColRegExp
+ @type RegExp
+ @default null (initialized based on _keyNavColPrefix)
+ @private
+ */
+ _keyNavColRegExp:null,
+
+ initializer: function () {
+ this.onceAfter('render', this._afterKeyNavRender);
+ this._keyNavSubscr = [
+ this.after('focusedCellChange', this._afterKeyNavFocusedCellChange),
+ this.after('focusedChange', this._afterKeyNavFocusedChange)
+ ];
+ this._keyNavColPrefix = this.getClassName('col', '');
+ this._keyNavColRegExp = new RegExp(this._keyNavColPrefix + '(.+?)(\\s|$)');
+ this.keyActions = Y.clone(DtKeyNav.ARIA_ACTIONS);
+
+ },
+
+ destructor: function () {
+ arrEach(this._keyNavSubscr, function (evHandle) {
+ if (evHandle && evHandle.detach) {
+ evHandle.detach();
+ }
+ });
+ },
+
+ /**
+ Sets the tabIndex on the focused cell and, if the DataTable has the focus,
+ sets the focus on it.
+
+ @method _afterFocusedCellChange
+ @param e {EventFacade}
+ @private
+ */
+ _afterKeyNavFocusedCellChange: function (e) {
+ var newVal = e.newVal,
+ prevVal = e.prevVal;
+
+ if (prevVal) {
+ prevVal.set('tabIndex', -1);
+ }
+
+ if (newVal) {
+ newVal.set('tabIndex', 0);
+
+ if (this.get('focused')) {
+ newVal.scrollIntoView();
+ newVal.focus();
+ }
+ } else {
+ this.set('focused', null);
+ }
+ },
+
+ /**
+ When the DataTable gets the focus, it ensures the correct cell regains
+ the focus.
+
+ @method _afterKeyNavFocusedChange
+ @param e {EventFacade}
+ @private
+ */
+ _afterKeyNavFocusedChange: function (e) {
+ var cell = this.get('focusedCell');
+ if (e.newVal) {
+ if (cell) {
+ cell.scrollIntoView();
+ cell.focus();
+ } else {
+ this._keyMoveFirst();
+ }
+ } else {
+ if (cell) {
+ cell.blur();
+ }
+ }
+ },
+
+ /**
+ Subscribes to the events on the DataTable elements once they have been rendered,
+ finds out the header section and makes the top-left element focusable.
+
+ @method _afterKeyNavRender
+ @private
+ */
+ _afterKeyNavRender: function () {
+ var cbx = this.get('contentBox');
+ this._keyNavSubscr.push(
+ cbx.on('keydown', this._onKeyNavKeyDown, this),
+ cbx.on('click', this._onKeyNavClick, this)
+ );
+ this._keyNavTHead = (this._yScrollHeader || this._tableNode).one('thead');
+ this._keyMoveFirst();
+
+ // determine if we have nested headers
+ this._keyNavNestedHeaders = (this.get('columns').length !== this.head.theadNode.all('th').size());
+ },
+
+ /**
+ In response to a click event, it sets the focus on the clicked cell
+
+ @method _onKeyNavClick
+ @param e {EventFacade}
+ @private
+ */
+ _onKeyNavClick: function (e) {
+ var cell = e.target.ancestor((this.get('keyIntoHeaders') ? 'td, th': 'td'), true);
+ if (cell) {
+ this.focus();
+ this.set('focusedCell', cell);
+ }
+ },
+
+ /**
+ Responds to a key down event by executing the action set in the
+ [keyActions](#property_keyActions) table.
+
+ @method _onKeyNavKeyDown
+ @param e {EventFacade}
+ @private
+ */
+ _onKeyNavKeyDown: function (e) {
+ var keyCode = e.keyCode,
+ keyName = DtKeyNav.KEY_NAMES[keyCode] || keyCode,
+ action;
+
+ arrEach(['alt', 'ctrl', 'meta', 'shift'], function (modifier) {
+ if (e[modifier + 'Key']) {
+ keyCode = modifier + '-' + keyCode;
+ keyName = modifier + '-' + keyName;
+ }
+ });
+ action = this.keyActions[keyCode] || this.keyActions[keyName];
+
+ if (typeof action === 'string') {
+ if (this[action]) {
+ this[action].call(this, e);
+ } else {
+ this._keyNavFireEvent(action, e);
+ }
+ } else {
+ action.call(this, e);
+ }
+ },
+
+ /**
+ If the action associated to a key combination is a string and no method
+ by that name was found in this instance, this method will
+ fire an event using that string and provides extra information
+ to the listener.
+
+ @method _keyNavFireEvent
+ @param action {String} Name of the event to fire
+ @param e {EventFacade} Original facade from the keydown event.
+ @private
+ */
+ _keyNavFireEvent: function (action, e) {
+ var cell = e.target.ancestor('td, th', true);
+ if (cell) {
+ this.fire(action, {
+ cell: cell,
+ row: cell.ancestor('tr'),
+ record: this.getRecord(cell),
+ column: this.getColumn(cell.get('cellIndex'))
+ }, e);
+ }
+ },
+
+ /**
+ Sets the focus on the very first cell in the header of the table.
+
+ @method _keyMoveFirst
+ @private
+ */
+ _keyMoveFirst: function () {
+ this.set('focusedCell' , (this.get('keyIntoHeaders') ? this._keyNavTHead.one('th') : this._tbodyNode.one('td')), {src:'keyNav'});
+ },
+
+ /**
+ Sets the focus on the cell to the left of the currently focused one.
+ Does not wrap, following the WAI-ARIA recommendation.
+
+ @method _keyMoveLeft
+ @param e {EventFacade} Event Facade for the keydown event
+ @private
+ */
+ _keyMoveLeft: function (e) {
+ var cell = this.get('focusedCell'),
+ index = cell.get('cellIndex'),
+ row = cell.ancestor();
+
+ e.preventDefault();
+
+ if (index === 0) {
+ return;
+ }
+ cell = row.get('cells').item(index - 1);
+ this.set('focusedCell', cell , {src:'keyNav'});
+ },
+
+ /**
+ Sets the focus on the cell to the right of the currently focused one.
+ Does not wrap, following the WAI-ARIA recommendation.
+
+ @method _keyMoveRight
+ @param e {EventFacade} Event Facade for the keydown event
+ @private
+ */
+ _keyMoveRight: function (e) {
+ var cell = this.get('focusedCell'),
+ row = cell.ancestor('tr'),
+ section = row.ancestor(),
+ inHead = section === this._keyNavTHead,
+ nextCell,
+ parent;
+
+ e.preventDefault();
+
+ // a little special with nested headers
+ /*
+ +-------------+-------+
+ | ABC | DE |
+ +-------+-----+---+---+
+ | AB | | | |
+ +---+---+ | | |
+ | A | B | C | D | E |
+ +---+---+-----+---+---+
+ */
+
+ nextCell = cell.next();
+
+ if (row.get('rowIndex') !== 0 && inHead && this._keyNavNestedHeaders) {
+ if (nextCell) {
+ cell = nextCell;
+ } else { //-- B -> C
+ parent = this._getTHParent(cell);
+
+ if (parent && parent.next()) {
+ cell = parent.next();
+ } else { //-- E -> ...
+ return;
+ }
+ }
+
+ } else {
+ if (!nextCell) {
+ return;
+ } else {
+ cell = nextCell;
+ }
+ }
+
+ this.set('focusedCell', cell, { src:'keyNav' });
+
+ },
+
+ /**
+ Sets the focus on the cell above the currently focused one.
+ It will move into the headers when the top of the data rows is reached.
+ Does not wrap, following the WAI-ARIA recommendation.
+
+ @method _keyMoveUp
+ @param e {EventFacade} Event Facade for the keydown event
+ @private
+ */
+ _keyMoveUp: function (e) {
+ var cell = this.get('focusedCell'),
+ cellIndex = cell.get('cellIndex'),
+ row = cell.ancestor('tr'),
+ rowIndex = row.get('rowIndex'),
+ section = row.ancestor(),
+ sectionRows = section.get('rows'),
+ inHead = section === this._keyNavTHead,
+ parent;
+
+ e.preventDefault();
+
+ if (!inHead) {
+ rowIndex -= section.get('firstChild').get('rowIndex');
+ }
+
+ if (rowIndex === 0) {
+ if (inHead || !this.get('keyIntoHeaders')) {
+ return;
+ }
+
+ section = this._keyNavTHead;
+ sectionRows = section.get('rows');
+
+ if (this._keyNavNestedHeaders) {
+ key = this._getCellColumnName(cell);
+ cell = section.one('.' + this._keyNavColPrefix + key);
+ cellIndex = cell.get('cellIndex');
+ row = cell.ancestor('tr');
+ } else {
+ row = section.get('firstChild');
+ cell = row.get('cells').item(cellIndex);
+ }
+ } else {
+ if (inHead && this._keyNavNestedHeaders) {
+ key = this._getCellColumnName(cell);
+ parent = this._columnMap[key]._parent;
+ if (parent) {
+ cell = section.one('#' + parent.id);
+ }
+ } else {
+ row = sectionRows.item(rowIndex -1);
+ cell = row.get('cells').item(cellIndex);
+ }
+ }
+ this.set('focusedCell', cell);
+ },
+
+ /**
+ Sets the focus on the cell below the currently focused one.
+ It will move into the data rows when the bottom of the header rows is reached.
+ Does not wrap, following the WAI-ARIA recommendation.
+
+ @method _keyMoveDown
+ @param e {EventFacade} Event Facade for the keydown event
+ @private
+ */
+ _keyMoveDown: function (e) {
+ var cell = this.get('focusedCell'),
+ cellIndex = cell.get('cellIndex'),
+ row = cell.ancestor('tr'),
+ rowIndex = row.get('rowIndex') + 1,
+ section = row.ancestor(),
+ inHead = section === this._keyNavTHead,
+ tbody = (this.body && this.body.tbodyNode),
+ sectionRows = section.get('rows'),
+ key,
+ children;
+
+ e.preventDefault();
+
+ if (inHead) { // focused cell is in the header
+ if (this._keyNavNestedHeaders) { // the header is nested
+ key = this._getCellColumnName(cell);
+ children = this._columnMap[key].children;
+
+ rowIndex += (cell.getAttribute('rowspan') || 1) - 1;
+
+ if (children) {
+ // stay in thead
+ cell = section.one('#' + children[0].id);
+ } else {
+ // moving into tbody
+ cell = tbody.one('.' + this._keyNavColPrefix + key);
+ section = tbody;
+ sectionRows = section.get('rows');
+ }
+ cellIndex = cell.get('cellIndex');
+
+ } else { // the header is not nested
+ row = tbody.one('tr');
+ cell = row.get('cells').item(cellIndex);
+ }
+ }
+
+ // offset row index to tbody
+ rowIndex -= sectionRows.item(0).get('rowIndex');
+
+
+ if (rowIndex >= sectionRows.size()) {
+ if (!inHead) { // last row in tbody
+ return;
+ }
+ section = tbody;
+ row = section.one('tr');
+
+ } else {
+ row = sectionRows.item(rowIndex);
+ }
+
+ this.set('focusedCell', row.get('cells').item(cellIndex));
+ },
+
+ /**
+ Sets the focus on the left-most cell of the row containing the currently focused cell.
+
+ @method _keyMoveRowStart
+ @param e {EventFacade} Event Facade for the keydown event
+ @private
+ */
+ _keyMoveRowStart: function (e) {
+ var row = this.get('focusedCell').ancestor();
+ this.set('focusedCell', row.get('firstChild'), {src:'keyNav'});
+ e.preventDefault();
+ },
+
+ /**
+ Sets the focus on the right-most cell of the row containing the currently focused cell.
+
+ @method _keyMoveRowEnd
+ @param e {EventFacade} Event Facade for the keydown event
+ @private
+ */
+ _keyMoveRowEnd: function (e) {
+ var row = this.get('focusedCell').ancestor();
+ this.set('focusedCell', row.get('lastChild'), {src:'keyNav'});
+ e.preventDefault();
+ },
+
+ /**
+ Sets the focus on the top-most cell of the column containing the currently focused cell.
+ It would normally be a header cell.
+
+ @method _keyMoveColTop
+ @param e {EventFacade} Event Facade for the keydown event
+ @private
+ */
+ _keyMoveColTop: function (e) {
+ var cell = this.get('focusedCell'),
+ cellIndex = cell.get('cellIndex'),
+ key, header;
+
+ e.preventDefault();
+
+ if (this._keyNavNestedHeaders && this.get('keyIntoHeaders')) {
+ key = this._getCellColumnName(cell);
+ header = this._columnMap[key];
+ while (header._parent) {
+ header = header._parent;
+ }
+ cell = this._keyNavTHead.one('#' + header.id);
+
+ } else {
+ cell = (this.get('keyIntoHeaders') ? this._keyNavTHead: this._tbodyNode).get('firstChild').get('cells').item(cellIndex);
+ }
+ this.set('focusedCell', cell , {src:'keyNav'});
+ },
+
+ /**
+ Sets the focus on the last cell of the column containing the currently focused cell.
+
+ @method _keyMoveColBottom
+ @param e {EventFacade} Event Facade for the keydown event
+ @private
+ */
+ _keyMoveColBottom: function (e) {
+ var cell = this.get('focusedCell'),
+ cellIndex = cell.get('cellIndex');
+
+ this.set('focusedCell', this._tbodyNode.get('lastChild').get('cells').item(cellIndex), {src:'keyNav'});
+ e.preventDefault();
+
+ },
+
+ /**
+ Setter method for the [focusedCell](#attr_focusedCell) attribute.
+ Checks that the passed value is a Node, either a TD or TH and is
+ contained within the DataTable contentBox.
+
+ @method _focusedCellSetter
+ @param cell {Node} DataTable cell to receive the focus
+ @return cell or Y.Attribute.INVALID_VALUE
+ @private
+ */
+ _focusedCellSetter: function (cell) {
+ if (cell instanceof Y.Node) {
+ var tag = cell.get('tagName').toUpperCase();
+ if ((tag === 'TD' || tag === 'TH') && this.get('contentBox').contains(cell) ) {
+ return cell;
+ }
+ } else if (cell === null) {
+ return cell;
+ }
+ return Y.Attribute.INVALID_VALUE;
+ },
+
+ /**
+ Retrieves the parent cell of the given TH cell. If there is no parent for
+ the provided cell, null is returned.
+ @protected
+ @method _getTHParent
+ @param {Node} thCell Cell to find parent of
+ @return {Node} Parent of the cell provided or null
+ */
+ _getTHParent: function (thCell) {
+ var key = this._getCellColumnName(thCell),
+ parent = this._columnMap[key] && this._columnMap[key]._parent;
+
+ if (parent) {
+ return thCell.ancestor().ancestor().one('.' + this._keyNavColPrefix + parent.key);
+ }
+
+ return null;
+ },
+
+ /**
+ Retrieves the column name based from the data attribute on the cell if
+ available. Other wise, extracts the column name from the classname
+ @protected
+ @method _getCellColumnName
+ @param {Node} cell Cell to get column name from
+ @return String Column name of the provided cell
+ */
+ _getCellColumnName: function (cell) {
+ return cell.getData('yui3-col-id') || this._keyNavColRegExp.exec(cell.get('className'))[1];
+ }
+});
+
+Y.DataTable.KeyNav = DtKeyNav;
+Y.Base.mix(Y.DataTable, [DtKeyNav]);
+
+
+}, '@VERSION@', {"requires": ["datatable-base"]});