MDL-35590 block_navigation: fix remaining issues

This commit is contained in:
Simey Lameze 2016-01-22 17:54:38 +08:00
parent 10ac8baf6e
commit 6759dc35fe
15 changed files with 101 additions and 75 deletions

1
lib/amd/build/tree.min.js vendored Normal file
View file

@ -0,0 +1 @@
define(["jquery"],function(a){var b={ITEM:"[role=treeitem]",GROUP:"[role=treeitem]:has([role=group]), [role=treeitem][data-requires-ajax=true]",CLOSED_GROUP:"[role=treeitem]:has([role=group])[aria-expanded=false], [role=treeitem][data-requires-ajax=true][aria-expanded=false]",FIRST_ITEM:"[role=treeitem]:first",VISIBLE_ITEM:"[role=treeitem]:visible",UNLOADED_AJAX_ITEM:"[role=treeitem][data-requires-ajax=true][data-loaded=false][aria-expanded=true]"},c=function(c,d){this.treeRoot=a(c),this.treeRoot.data("activeItem",null),this.selectCallback=d,this.keys={tab:9,enter:13,space:32,pageup:33,pagedown:34,end:35,home:36,left:37,up:38,right:39,down:40,asterisk:106},this.initialiseNodes(this.treeRoot),this.setActiveItem(this.treeRoot.find(b.FIRST_ITEM)),this.refreshVisibleItemsCache(),this.bindEventHandlers()};return c.prototype.refreshVisibleItemsCache=function(){this.treeRoot.data("visibleItems",this.treeRoot.find(b.VISIBLE_ITEM))},c.prototype.getVisibleItems=function(){return this.treeRoot.data("visibleItems")},c.prototype.setActiveItem=function(a){var b=this.treeRoot.data("activeItem");a!==b&&(null!==b&&(b.attr("tabindex","-1"),b.attr("aria-selected","false")),a.attr("tabindex","0"),a.attr("aria-selected","true"),this.treeRoot.data("activeItem",a),"function"==typeof this.selectCallback&&this.selectCallback(a))},c.prototype.isGroupItem=function(a){return a.is(b.GROUP)},c.prototype.initialiseNodes=function(c){this.removeAllFromTabOrder(c),this.setAriaSelectedFalseOnItems(c);var d=this;c.find(b.UNLOADED_AJAX_ITEM).each(function(){var b=a(this);d.collapseGroup(b),d.expandGroup(b)})},c.prototype.removeAllFromTabOrder=function(a){a.find("*").attr("tabindex","-1")},c.prototype.setAriaSelectedFalseOnItems=function(a){a.find(b.ITEM).attr("aria-selected","false")},c.prototype.expandAllGroups=function(){this.expandAllChildGroups(this.treeRoot)},c.prototype.expandAllChildGroups=function(c){var d=this;c.find(b.CLOSED_GROUP).each(function(){var b=a(this);d.expandGroup(b).done(function(){d.expandAllChildGroups(b)})})},c.prototype.expandGroup=function(b){var c=a.Deferred();if("false"!==b.attr("data-expandable")&&"true"!==b.attr("aria-expanded"))if("true"===b.attr("data-requires-ajax")&&"true"!==b.attr("data-loaded")){b.attr("data-loaded",!1);var d=b.closest("[data-ajax-loader]").attr("data-ajax-loader"),e=this;b.addClass("loading"),require([d],function(a){a.load(b).done(function(){b.attr("data-loaded",!0),e.initialiseNodes(b),e.finishExpandingGroup(b),b.removeClass("loading"),c.resolve()})})}else this.finishExpandingGroup(b),c.resolve();else c.resolve();return c},c.prototype.finishExpandingGroup=function(a){var c=a.children(b.GROUP);c.show().attr("aria-hidden","false"),a.attr("aria-expanded","true"),this.refreshVisibleItemsCache()},c.prototype.collapseGroup=function(a){if("false"!==a.attr("aria-expanded")){var c=a.children(b.GROUP);c.hide().attr("aria-hidden","true"),a.attr("aria-expanded","false"),this.refreshVisibleItemsCache()}},c.prototype.toggleGroup=function(a){"true"===a.attr("aria-expanded")?this.collapseGroup(a):this.expandGroup(a)},c.prototype.handleKeyDown=function(a,c){var d=this.getVisibleItems().index(a);if(c.altKey||c.ctrlKey||c.shiftKey&&c.keyCode!=this.keys.tab)return!0;switch(c.keyCode){case this.keys.home:return this.getVisibleItems().first().focus(),c.stopPropagation(),!1;case this.keys.end:return this.getVisibleItems().last().focus(),c.stopPropagation(),!1;case this.keys.enter:var e=a.children().not(b.GROUP).children("a");return e.length?window.location.href=e.first().attr("href"):this.isGroupItem(a)&&this.toggleGroup(a,!0),c.stopPropagation(),!1;case this.keys.space:return this.isGroupItem(a)&&this.toggleGroup(a,!0),c.stopPropagation(),!1;case this.keys.left:if(this.isGroupItem(a)){this.collapseGroup(a);var f=this.getVisibleItems().filter(b.GROUP),g=f.index(a),h=g-1>0?g-1:0;f.eq(h).focus()}return c.stopPropagation(),!1;case this.keys.right:return this.isGroupItem(a)&&this.expandGroup(a).done(function(){a.find(b.ITEM).first().focus()}),c.stopPropagation(),!1;case this.keys.up:if(d>0){var i=this.getVisibleItems().eq(d-1);i.focus()}return c.stopPropagation(),!1;case this.keys.down:if(d<this.getVisibleItems().length-1){var j=this.getVisibleItems().eq(d+1);j.focus()}return c.stopPropagation(),!1;case this.keys.asterisk:return this.expandAllGroups(),c.stopPropagation(),!1}return!0},c.prototype.handleClick=function(a,b){return b.altKey||b.ctrlKey||b.shiftKey?!0:(a.focus(),this.isGroupItem(a)&&this.toggleGroup(a),b.stopPropagation(),!0)},c.prototype.handleFocus=function(a,b){return this.setActiveItem(a),b.stopPropagation(),!0},c.prototype.bindEventHandlers=function(){var c=this;this.treeRoot.on({click:function(b){return c.handleClick(a(this),b)},keydown:function(b){return c.handleKeyDown(a(this),b)},focus:function(b){return c.handleFocus(a(this),b)}},b.ITEM)},c});

View file

@ -15,7 +15,7 @@
/**
* Implement an accessible aria tree widget, from a nested unordered list.
* Based on http://oaa-accessibility.org/example/41/
* Based on http://oaa-accessibility.org/example/41/.
*
* @module tool_lp/tree
* @package core
@ -27,14 +27,15 @@ define(['jquery'], function($) {
var SELECTORS = {
ITEM: '[role=treeitem]',
GROUP: '[role=treeitem]:has([role=group]), [role=treeitem][data-requires-ajax=true]',
CLOSED_GROUP: '[role=treeitem]:has([role=group])[aria-expanded=false], [role=treeitem][data-requires-ajax=true][aria-expanded=false]',
CLOSED_GROUP: '[role=treeitem]:has([role=group])[aria-expanded=false], [role=treeitem]' +
'[data-requires-ajax=true][aria-expanded=false]',
FIRST_ITEM: '[role=treeitem]:first',
VISIBLE_ITEM: '[role=treeitem]:visible',
UNLOADED_AJAX_ITEM: '[role=treeitem][data-requires-ajax=true][data-loaded=false][aria-expanded=true]'
};
/**
* Constructor
* Constructor.
*
* @param {String} selector
* @param {function} selectCallback Called when the active node is changed.
@ -44,7 +45,6 @@ define(['jquery'], function($) {
this.treeRoot.data('activeItem', null);
this.selectCallback = selectCallback;
this.keys = {
tab: 9,
enter: 13,
@ -60,11 +60,9 @@ define(['jquery'], function($) {
asterisk: 106
};
// Apply the standard default initialisation for all nodes, starting
// with the tree root.
// Apply the standard default initialisation for all nodes, starting with the tree root.
this.initialiseNodes(this.treeRoot);
// Make the first item the active item for the tree so that it is
// added to the tab order.
// Make the first item the active item for the tree so that it is added to the tab order.
this.setActiveItem(this.treeRoot.find(SELECTORS.FIRST_ITEM));
// Create the cache of the visible items.
this.refreshVisibleItemsCache();
@ -72,8 +70,6 @@ define(['jquery'], function($) {
this.bindEventHandlers();
};
// Public variables and functions.
/**
* Find all visible tree items and save a cache of them on the tree object.
*
@ -83,16 +79,20 @@ define(['jquery'], function($) {
this.treeRoot.data('visibleItems', this.treeRoot.find(SELECTORS.VISIBLE_ITEM));
};
/**
* Get all visible tree items.
*
* @method getVisibleItems
*/
Tree.prototype.getVisibleItems = function() {
return this.treeRoot.data('visibleItems');
}
};
/**
* Mark the given item as active within the tree and fire the callback for
* when the active item is set.
* Mark the given item as active within the tree and fire the callback for when the active item is set.
*
* @method setActiveItem
* @param {object} a jquery object representing an item on the tree.
* @param {object} item jquery object representing an item on the tree.
*/
Tree.prototype.setActiveItem = function(item) {
var currentActive = this.treeRoot.data('activeItem');
@ -117,11 +117,10 @@ define(['jquery'], function($) {
};
/**
* Determines if the given item is a group item (contains child tree items) in
* the tree.
* Determines if the given item is a group item (contains child tree items) in the tree.
*
* @method isGroupItem
* @param {object} a jquery object representing an item on the tree.
* @param {object} item jquery object representing an item on the tree.
* @returns {bool}
*/
Tree.prototype.isGroupItem = function(item) {
@ -134,14 +133,13 @@ define(['jquery'], function($) {
* on items.
*
* @method initialiseNodes
* @param {object} a jquery object representing an item on the tree.
* @param {object} node jquery object representing a node.
*/
Tree.prototype.initialiseNodes = function(node) {
this.removeAllFromTabOrder(node);
this.setAriaSelectedFalseOnItems(node);
// Get all ajax nodes that have been rendered as expanded but
// haven't loaded the child items yet.
// Get all ajax nodes that have been rendered as expanded but haven't loaded the child items yet.
var thisTree = this;
node.find(SELECTORS.UNLOADED_AJAX_ITEM).each(function() {
var unloadedNode = $(this);
@ -155,18 +153,17 @@ define(['jquery'], function($) {
* Removes all child DOM elements of the given node from the tab order.
*
* @method removeAllFromTabOrder
* @param {object} a jquery object representing an item on the tree.
* @param {object} node jquery object representing a node.
*/
Tree.prototype.removeAllFromTabOrder = function(node) {
node.find('*').attr('tabindex', '-1');
};
/**
* Find all child tree items from the given node and set the aria selected
* attribute to false.
* Find all child tree items from the given node and set the aria selected attribute to false.
*
* @method setAriaSelectedFalseOnItems
* @param {object} a jquery object representing an item on the tree.
* @param {object} node jquery object representing a node.
*/
Tree.prototype.setAriaSelectedFalseOnItems = function(node) {
node.find(SELECTORS.ITEM).attr('aria-selected', 'false');
@ -179,13 +176,13 @@ define(['jquery'], function($) {
*/
Tree.prototype.expandAllGroups = function() {
this.expandAllChildGroups(this.treeRoot);
}
};
/**
* Find all child group nodes from the given node and expand them.
*
* @method expandAllChildGroups
* @param {object} a jquery object representing an item on the tree.
* @param {object} node jquery object representing a node.
*/
Tree.prototype.expandAllChildGroups = function(node) {
var thisTree = this;
@ -199,12 +196,13 @@ define(['jquery'], function($) {
};
/**
* Expand a collapsed group. Handles expanding nodes that are ajax loaded (marked
* with a data-requires-ajax attribute).
* Expand a collapsed group.
*
* Handles expanding nodes that are ajax loaded (marked with a data-requires-ajax attribute).
*
* @method expandGroup
* @param {Object} item is the jquery id of the parent item of the group
* @return {Object} a promise that is resolved when the group has been expanded
* @param {Object} item is the jquery id of the parent item of the group.
* @return {Object} a promise that is resolved when the group has been expanded.
*/
Tree.prototype.expandGroup = function(item) {
var promise = $.Deferred();
@ -239,15 +237,14 @@ define(['jquery'], function($) {
} else {
promise.resolve();
}
return promise;
};
/**
* Performes the necessary DOM changes to display a group item.
* Perform the necessary DOM changes to display a group item.
*
* @method finishExpandingGroup
* @param {Object} item is the jquery id of the parent item of the group
* @param {Object} item is the jquery id of the parent item of the group.
*/
Tree.prototype.finishExpandingGroup = function(item) {
// Find the first child node.
@ -266,7 +263,7 @@ define(['jquery'], function($) {
* Collapse an expanded group.
*
* @method collapseGroup
* @param {Object} item is the jquery id of the parent item of the group
* @param {Object} item is the jquery id of the parent item of the group.
*/
Tree.prototype.collapseGroup = function(item) {
// If the item is already collapsed then do nothing.
@ -274,11 +271,9 @@ define(['jquery'], function($) {
return;
}
// Get and collapse the group.
var group = item.children(SELECTORS.GROUP);
// Collapse the group.
group.hide().attr('aria-hidden', 'true');
item.attr('aria-expanded', 'false');
// Update the list of visible items.
@ -289,7 +284,7 @@ define(['jquery'], function($) {
* Expand or collapse a group.
*
* @method toggleGroup
* @param {Object} item is the jquery id of the parent item of the group
* @param {Object} item is the jquery id of the parent item of the group.
*/
Tree.prototype.toggleGroup = function(item) {
if (item.attr('aria-expanded') === 'true') {
@ -303,7 +298,7 @@ define(['jquery'], function($) {
* Handle a key down event - ie navigate the tree.
*
* @method handleKeyDown
* @param {Object} item is the jquery id of the parent item of the group
* @param {Object} item is the jquery id of the parent item of the group.
* @param {Event} e The event.
*/
Tree.prototype.handleKeyDown = function(item, e) {
@ -394,21 +389,17 @@ define(['jquery'], function($) {
next.focus();
}
e.stopPropagation();
return false;
}
case this.keys.asterisk: {
// Expand all groups.
var thisObj = this;
this.expandAllGroups();
e.stopPropagation();
return false;
}
}
return true;
};
@ -416,7 +407,7 @@ define(['jquery'], function($) {
* Handle a click (select).
*
* @method handleClick
* @param {Object} item is the jquery id of the parent item of the group
* @param {Object} item The jquery id of the parent item of the group.
* @param {Event} e The event.
*/
Tree.prototype.handleClick = function(item, e) {
@ -439,10 +430,10 @@ define(['jquery'], function($) {
};
/**
* Handle a focus event
* Handle a focus event.
*
* @method handleFocus
* @param {Object} item item is the jquery id of the parent item of the group
* @param {Object} item The jquery id of the parent item of the group.
* @param {Event} e The event.
*/
Tree.prototype.handleFocus = function(item, e) {