govlab-styleguide
Version:
A styleguide / lightweight frontend framework for GovLab websites
263 lines (240 loc) • 8.19 kB
JavaScript
/**
* AccordionMenu module.
* @module foundation.accordionMenu
* @requires foundation.util.keyboard
* @requires foundation.util.motion
* @requires foundation.util.nest
*/
!function($) {
'use strict';
/**
* Creates a new instance of an accordion menu.
* @class
* @fires AccordionMenu#init
* @param {jQuery} element - jQuery object to make into an accordion menu.
* @param {Object} options - Overrides to the default plugin settings.
*/
function AccordionMenu(element, options) {
this.$element = element;
this.options = $.extend({}, AccordionMenu.defaults, this.$element.data(), options);
Foundation.Nest.Feather(this.$element, 'accordion');
this._init();
Foundation.registerPlugin(this, 'AccordionMenu');
Foundation.Keyboard.register('AccordionMenu', {
'ENTER': 'toggle',
'SPACE': 'toggle',
'ARROW_RIGHT': 'open',
'ARROW_UP': 'up',
'ARROW_DOWN': 'down',
'ARROW_LEFT': 'close',
'ESCAPE': 'closeAll',
'TAB': 'down',
'SHIFT_TAB': 'up'
});
}
AccordionMenu.defaults = {
/**
* Amount of time to animate the opening of a submenu in ms.
* @option
* @example 250
*/
slideSpeed: 250,
/**
* Allow the menu to have multiple open panes.
* @option
* @example true
*/
multiOpen: true
};
/**
* Initializes the accordion menu by hiding all nested menus.
* @private
*/
AccordionMenu.prototype._init = function() {
this.$element.find('[data-submenu]').not('.is-active').slideUp(0);//.find('a').css('padding-left', '1rem');
this.$element.attr({
'role': 'tablist',
'aria-multiselectable': this.options.multiOpen
});
this.$menuLinks = this.$element.find('.has-submenu');
this.$menuLinks.each(function(){
var linkId = this.id || Foundation.GetYoDigits(6, 'acc-menu-link'),
$elem = $(this),
$sub = $elem.children('[data-submenu]'),
subId = $sub[0].id || Foundation.GetYoDigits(6, 'acc-menu'),
isActive = $sub.hasClass('is-active');
$elem.attr({
'aria-controls': subId,
'aria-expanded': isActive,
'aria-selected': false,
'role': 'tab',
'id': linkId
});
$sub.attr({
'aria-labelledby': linkId,
'aria-hidden': !isActive,
'role': 'tabpanel',
'id': subId
});
});
var initPanes = this.$element.find('.is-active');
if(initPanes.length){
var _this = this;
initPanes.each(function(){
_this.down($(this));
});
}
this._events();
};
/**
* Adds event handlers for items within the menu.
* @private
*/
AccordionMenu.prototype._events = function() {
var _this = this;
this.$element.find('li').each(function() {
var $submenu = $(this).children('[data-submenu]');
if ($submenu.length) {
$(this).children('a').off('click.zf.accordionmenu').on('click.zf.accordionmenu', function(e) {
e.preventDefault();
_this.toggle($submenu);
});
}
}).on('keydown.zf.accordionmenu', function(e){
var $element = $(this),
$elements = $element.parent('ul').children('li'),
$prevElement,
$nextElement,
$target = $element.children('[data-submenu]');
$elements.each(function(i) {
if ($(this).is($element)) {
$prevElement = $elements.eq(Math.max(0, i-1));
$nextElement = $elements.eq(Math.min(i+1, $elements.length-1));
if ($(this).children('[data-submenu]:visible').length) { // has open sub menu
$nextElement = $element.find('li:first-child');
}
if ($(this).is(':first-child')) { // is first element of sub menu
$prevElement = $element.parents('li').first();
} else if ($prevElement.children('[data-submenu]:visible').length) { // if previous element has open sub menu
$prevElement = $prevElement.find('li:last-child');
}
if ($(this).is(':last-child')) { // is last element of sub menu
$nextElement = $element.parents('li').first().next('li');
}
return;
}
});
Foundation.Keyboard.handleKey(e, 'AccordionMenu', {
open: function() {
if ($target.is(':hidden')) {
_this.down($target);
$target.find('li').first().focus();
}
},
close: function() {
if ($target.length && !$target.is(':hidden')) { // close active sub of this item
_this.up($target);
} else if ($element.parent('[data-submenu]').length) { // close currently open sub
_this.up($element.parent('[data-submenu]'));
$element.parents('li').first().focus();
}
},
up: function() {
$prevElement.focus();
},
down: function() {
$nextElement.focus();
},
toggle: function() {
if ($element.children('[data-submenu]').length) {
_this.toggle($element.children('[data-submenu]'));
}
},
closeAll: function() {
_this.hideAll();
},
handled: function() {
e.preventDefault();
e.stopImmediatePropagation();
}
});
});//.attr('tabindex', 0);
};
/**
* Closes all panes of the menu.
* @function
*/
AccordionMenu.prototype.hideAll = function(){
this.$element.find('[data-submenu]').slideUp(this.options.slideSpeed);
};
/**
* Toggles the open/close state of a submenu.
* @function
* @param {jQuery} $target - the submenu to toggle
*/
AccordionMenu.prototype.toggle = function($target){
if(!$target.is(':animated')) {
if (!$target.is(':hidden')) {
this.up($target);
}
else {
this.down($target);
}
}
};
/**
* Opens the sub-menu defined by `$target`.
* @param {jQuery} $target - Sub-menu to open.
* @fires AccordionMenu#down
*/
AccordionMenu.prototype.down = function($target) {
var _this = this;
if(!this.options.multiOpen){
this.up(this.$element.find('.is-active').not($target.parentsUntil(this.$element).add($target)));
}
$target.addClass('is-active').attr({'aria-hidden': false})
.parent('.has-submenu').attr({'aria-expanded': true, 'aria-selected': true});
Foundation.Move(this.options.slideSpeed, $target, function(){
$target.slideDown(_this.options.slideSpeed);
});
/**
* Fires when the menu is done collapsing up.
* @event AccordionMenu#down
*/
this.$element.trigger('down.zf.accordionMenu', [$target]);
};
/**
* Closes the sub-menu defined by `$target`. All sub-menus inside the target will be closed as well.
* @param {jQuery} $target - Sub-menu to close.
* @fires AccordionMenu#up
*/
AccordionMenu.prototype.up = function($target) {
var _this = this;
Foundation.Move(this.options.slideSpeed, $target, function(){
$target.slideUp(_this.options.slideSpeed);
});
$target.attr('aria-hidden', true)
.find('[data-submenu]').slideUp(0).attr('aria-hidden', true).end()
.parent('.has-submenu')
.attr({'aria-expanded': false, 'aria-selected': false});
// $target.slideUp(this.options.slideSpeed, function() {
// $target.find('[data-submenu]').slideUp(0).attr('aria-hidden', true);
// }).attr('aria-hidden', true).parent('.has-submenu').attr({'aria-expanded': false, 'aria-selected': false});
/**
* Fires when the menu is done collapsing up.
* @event AccordionMenu#up
*/
this.$element.trigger('up.zf.accordionMenu', [$target]);
};
/**
* Destroys an instance of accordion menu.
* @fires AccordionMenu#destroyed
*/
AccordionMenu.prototype.destroy = function(){
this.$element.find('[data-submenu]').slideDown(0).css('display', '');
this.$element.find('a').off('click.zf.accordionMenu');
Foundation.Nest.Burn(this.$element, 'accordion');
Foundation.unregisterPlugin(this);
};
Foundation.plugin(AccordionMenu, 'AccordionMenu');
}(jQuery, window.Foundation);