AutorÃa | Ultima modificación | Ver Log |
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @module moodle-editor_atto-editor
* @submodule toolbarnav
*/
/**
* Toolbar Navigation functions for the Atto editor.
*
* See {{#crossLink "M.editor_atto.Editor"}}{{/crossLink}} for details.
*
* @namespace M.editor_atto
* @class EditorToolbarNav
*/
function EditorToolbarNav() {}
EditorToolbarNav.ATTRS = {
};
EditorToolbarNav.prototype = {
/**
* The current focal point for tabbing.
*
* @property _tabFocus
* @type Node
* @default null
* @private
*/
_tabFocus: null,
/**
* Set up the watchers for toolbar navigation.
*
* @method setupToolbarNavigation
* @chainable
*/
setupToolbarNavigation: function() {
// Listen for Arrow left and Arrow right keys.
this._wrapper.delegate('key',
this.toolbarKeyboardNavigation,
'down:37,39',
'.' + CSS.TOOLBAR,
this);
this._wrapper.delegate('focus',
function(e) {
this._setTabFocus(e.currentTarget);
}, '.' + CSS.TOOLBAR + ' button', this);
return this;
},
/**
* Implement arrow key navigation for the buttons in the toolbar.
*
* @method toolbarKeyboardNavigation
* @param {EventFacade} e - the keyboard event.
*/
toolbarKeyboardNavigation: function(e) {
// Prevent the default browser behaviour.
e.preventDefault();
// On cursor moves we loops through the buttons.
var buttons = this.toolbar.all('button'),
direction = 1,
button,
current = e.target.ancestor('button', true),
innerButtons = e.target.all('button');
// If we are not on a button and the element we are on contains some buttons, then move between the inner buttons.
if (!current && innerButtons) {
buttons = innerButtons;
}
if (e.keyCode === 37) {
// Moving left so reverse the direction.
direction = -1;
}
button = this._findFirstFocusable(buttons, current, direction);
if (button) {
button.focus();
this._setTabFocus(button);
} else {
Y.log("Unable to find a button to focus on", 'debug', LOGNAME);
}
},
/**
* Find the first focusable button.
*
* @param {NodeList} buttons A list of nodes.
* @param {Node} startAt The node in the list to start the search from.
* @param {Number} direction The direction in which to search (1 or -1).
* @return {Node | Undefined} The Node or undefined.
* @method _findFirstFocusable
* @private
*/
_findFirstFocusable: function(buttons, startAt, direction) {
var checkCount = 0,
candidate,
button,
index;
// Determine which button to start the search from.
index = buttons.indexOf(startAt);
if (index < -1) {
Y.log("Unable to find the button in the list of buttons", 'debug', LOGNAME);
index = 0;
}
// Try to find the next.
while (checkCount < buttons.size()) {
index += direction;
if (index < 0) {
index = buttons.size() - 1;
} else if (index >= buttons.size()) {
// Handle wrapping.
index = 0;
}
candidate = buttons.item(index);
// Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item.
checkCount++;
// Loop while:
// * we haven't checked every button;
// * the button is hidden or disabled;
// * the button is inside a hidden wrapper element.
if (candidate.hasAttribute('hidden') || candidate.hasAttribute('disabled') || candidate.ancestor('[hidden]')) {
continue;
}
button = candidate;
break;
}
return button;
},
/**
* Check the tab focus.
*
* When we disable or hide a button, we should call this method to ensure that the
* focus is not currently set on an inaccessible button, otherwise tabbing to the toolbar
* would be impossible.
*
* @method checkTabFocus
* @chainable
*/
checkTabFocus: function() {
if (this._tabFocus) {
if (this._tabFocus.hasAttribute('disabled') || this._tabFocus.hasAttribute('hidden')
|| this._tabFocus.ancestor('.atto_group').hasAttribute('hidden')) {
// Find first available button.
var button = this._findFirstFocusable(this.toolbar.all('button'), this._tabFocus, -1);
if (button) {
if (this._tabFocus.compareTo(document.activeElement)) {
// We should also move the focus, because the inaccessible button also has the focus.
button.focus();
}
this._setTabFocus(button);
}
}
}
return this;
},
/**
* Sets tab focus for the toolbar to the specified Node.
*
* @method _setTabFocus
* @param {Node} button The node that focus should now be set to
* @chainable
* @private
*/
_setTabFocus: function(button) {
if (this._tabFocus) {
// Unset the previous entry.
this._tabFocus.setAttribute('tabindex', '-1');
}
// Set up the new entry.
this._tabFocus = button;
this._tabFocus.setAttribute('tabindex', 0);
// And update the activedescendant to point at the currently selected button.
this.toolbar.setAttribute('aria-activedescendant', this._tabFocus.generateID());
return this;
}
};
Y.Base.mix(Y.M.editor_atto.Editor, [EditorToolbarNav]);