tb_megamenu-8.x-1.0-beta2/js/tb-megamenu-frontend.js
js/tb-megamenu-frontend.js
/**
* @file
* Defines Javascript behaviors for MegaMenu frontend.
*/
(function ($, Drupal, drupalSettings, once) {
"use strict";
Drupal.TBMegaMenu = Drupal.TBMegaMenu || {};
Drupal.TBMegaMenu.oldWindowWidth = 0;
Drupal.TBMegaMenu.displayedMenuMobile = false;
Drupal.TBMegaMenu.supportedScreens = [980];
Drupal.TBMegaMenu.focusableElements = 'a:not([disabled]), button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), details:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])';
Drupal.TBMegaMenu.menuResponsive = function () {
var windowWidth = window.innerWidth ? window.innerWidth : $(window).width();
var navCollapse = $('.tb-megamenu').children('.nav-collapse');
if (windowWidth < Drupal.TBMegaMenu.supportedScreens[0]) {
navCollapse.addClass('collapse');
if (Drupal.TBMegaMenu.displayedMenuMobile) {
navCollapse.css({height: 'auto', overflow: 'visible'});
}
else {
navCollapse.css({height: 0, overflow: 'hidden'});
}
}
else {
// If width of window is greater than 980 (supported screen).
navCollapse.removeClass('collapse');
if (navCollapse.height() <= 0) {
navCollapse.css({height: 'auto', overflow: 'visible'});
}
}
};
Drupal.TBMegaMenu.focusNextPrevElement = function (direction) {
// Add all the elements we want to include in our selection
var $current = $(document.activeElement);
if ($current.length) {
var $focusable = $(Drupal.TBMegaMenu.focusableElements).filter(function () {
var $this = $(this);
return $this.closest('.tb-megamenu-subnav').length === 0 && $this.is(':visible');
})
var index = $focusable.index($current);
if (index > -1) {
if (direction === 'next') {
var nextElement = $focusable[index + 1] || $focusable[0];
}
else {
var nextElement = $focusable[index - 1] || $focusable[0];
}
nextElement.focus();
}
}
}
Drupal.behaviors.tbMegaMenuAction = {
attach: function (context, settings) {
$(once('tb-megamenu', '.tb-megamenu', context)).each(function () {
/* Keyboard Control Setup */
// Semi-Global Variables
var navParent = document.querySelector('.tb-megamenu'),
linkArray = new Array(),
curPos = new Array(-1,-1,-1);
// Each Top-Level Link
$(this).find('.level-1').children('a, span').not('.mobile-only').each(function (i, toplink) {
linkArray[i] = new Array();
// Add Link to Array
linkArray[i][-1] = toplink;
// Determine Coordinates
$(toplink).data({ coordinate: [i, -1] });
// Each Column
$(toplink).next().children().children().children('.mega-col-nav').each(function (j, column) {
// Only add to the linkArray if menu items exist.
// TODO - this does not allow for tabbing to links in blocks, only menu item links.
if ($(column).find(Drupal.TBMegaMenu.focusableElements).length > 0) {
linkArray[i][j] = new Array();
// Each Link
$(column).find(Drupal.TBMegaMenu.focusableElements).each(function (k, sublink) {
// Add Link to Array
linkArray[i][j][k] = sublink;
// Determine Coordinates
$(sublink).data({ coordinate: [i, j, k] });
}); // each link
}
}); // each column
}); // each top-level link
// Update Position on Focus
$(this).find(Drupal.TBMegaMenu.focusableElements).focus(function () {
curPos = $(this).data('coordinate');
});
// Key Pressed
function keydownEvent(k) {
// Determine Key
switch(k.keyCode) {
// TAB
case 9:
k.preventDefault();
nav_tab(k);
break;
// RETURN
case 13:
nav_open_link();
break;
// ESC
case 27:
nav_esc();
break;
// LEFT
case 37:
k.preventDefault();
nav_left();
break;
// UP
case 38:
k.preventDefault();
nav_up();
break;
// RIGHT
case 39:
k.preventDefault();
nav_right();
break;
// DOWN
case 40:
k.preventDefault();
nav_down();
break;
// HOME
case 36:
nav_home();
break;
// END
case 35:
nav_end();
break;
// Else
default:
// Do nothing
} // determine key
} // keydownEvent
/* Keypress Functions */
// Tab
function nav_tab(k) {
if (nav_is_toplink()) {
if (k.shiftKey) {
nav_prev_toplink();
}
else {
nav_next_toplink();
}
}
else {
if (k.shiftKey) {
nav_up();
}
else {
nav_down();
}
}
}
// Open Link
function nav_open_link() {
linkArray[curPos[0]][curPos[1]][curPos[2]].click();
}
// Escape
function nav_esc() {
nav_close_megamenu();
}
// Left
function nav_left() {
if (nav_is_toplink()) {
nav_prev_toplink();
}
else {
nav_prev_column();
}
}
// Right
function nav_right() {
if (nav_is_toplink()) {
nav_next_toplink();
}
else {
nav_next_column();
}
}
// Up
function nav_up() {
if (nav_is_toplink()) {
nav_prev_toplink();
}
else {
if (linkArray[curPos[0]][curPos[1]][curPos[2] - 1]) {
// If the previous link in the array is hidden (ie, it's in a
// submenu that is not currently expanded), then skip to the next
// item in the array until we find one that's visible.
if ($(linkArray[curPos[0]][curPos[1]][curPos[2] - 1]).is(':visible')) {
linkArray[curPos[0]][curPos[1]][curPos[2] - 1].focus();
}
else {
curPos = [curPos[0], curPos[1], curPos[2] - 1];
nav_up();
}
}
else {
nav_prev_column();
}
}
}
// Down
function nav_down() {
if (nav_is_toplink()) {
nav_next_column();
}
else {
if (linkArray[curPos[0]][curPos[1]][curPos[2] + 1]) {
linkArray[curPos[0]][curPos[1]][curPos[2] + 1].focus();
}
else {
nav_next_column();
}
}
}
// Home Button
function nav_home() {
if (nav_is_toplink()) {
linkArray[0][-1].focus();
}
else {
linkArray[curPos[0]][0][0].focus();
}
}
// End Button
function nav_end() {
if (nav_is_toplink()) {
linkArray.slice(-1)[0][-1].focus();
}
else {
linkArray[curPos[0]].slice(-1)[0].slice(-1)[0].focus();
}
}
/* Helper Functions */
// Determine Link Level
function nav_is_toplink() {
return (curPos[1] < 0);
}
// Close Mega Menu
function nav_close_megamenu() {
$('.tb-megamenu .open').removeClass('open');
ariaCheck();
}
// Next Toplink
function nav_next_toplink() {
if (linkArray[curPos[0] + 1]) {
linkArray[curPos[0] + 1][-1].focus();
}
else {
nav_close_megamenu();
// Focus on the next element.
Drupal.TBMegaMenu.focusNextPrevElement('next');
}
}
// Previous Toplink
function nav_prev_toplink() {
if (linkArray[curPos[0] - 1]) {
linkArray[curPos[0] - 1][-1].focus();
}
else {
// Focus on the previous element.
Drupal.TBMegaMenu.focusNextPrevElement('prev');
}
}
// Previous Column
function nav_prev_column() {
if (linkArray[curPos[0]][curPos[1] - 1][0]) {
linkArray[curPos[0]][curPos[1] - 1][0].focus();
}
else {
nav_parent_toplink();
}
}
// Next Column
function nav_next_column() {
if (linkArray[curPos[0]][curPos[1] + 1]) {
linkArray[curPos[0]][curPos[1] + 1][0].focus();
}
else {
nav_parent_toplink();
}
}
// Go to Parent Toplink
function nav_parent_toplink() {
linkArray[curPos[0]][-1].focus();
}
var ariaCheck = function () {
$("li.tb-megamenu-item", this).each(function () {
if ($(this).is('.mega-group')) {
// Mega menu item has mega class (it's a true mega menu)
if (!$(this).parents().is('.open')) {
// Mega menu item has mega class and its ancestor is closed, so apply appropriate ARIA attributes
$(this).children().attr('aria-expanded', 'false');
}
else if ($(this).parents().is('.open')) {
// Mega menu item has mega class and its ancestor is open, so apply appropriate ARIA attributes
$(this).children().attr('aria-expanded', 'true');
}
}
else if ($(this).is('.dropdown') || $(this).is('.dropdown-submenu')) {
// Mega menu item has dropdown (it's a flyout menu)
if (!$(this).is('.open')) {
// Mega menu item has dropdown class and is closed, so apply appropriate ARIA attributes
$(this).children().attr('aria-expanded', 'false');
}
else if ($(this).is('.open')) {
// Mega menu item has dropdown class and is open, so apply appropriate ARIA attributes
$(this).children().attr('aria-expanded', 'true');
}
}
else {
// Mega menu item is neither a mega or dropdown class, so remove ARIA attributes (it doesn't have children)
$(this).children().removeAttr('aria-expanded');
}
});
};
var showMenu = function ($subMenu, mm_timeout) {
if ($subMenu.hasClass('mega')) {
$subMenu.addClass('animating');
clearTimeout($subMenu.data('animatingTimeout'));
$subMenu.data('animatingTimeout', setTimeout(function () {
$subMenu.removeClass('animating')
}, mm_timeout));
clearTimeout($subMenu.data('hoverTimeout'));
$subMenu.data('hoverTimeout', setTimeout(function () {
$subMenu.addClass('open');
ariaCheck();
}, 100));
}
else {
clearTimeout($subMenu.data('hoverTimeout'));
$subMenu.data('hoverTimeout',
setTimeout(function () {
$subMenu.addClass('open');
ariaCheck();
}, 100));
}
};
var hideMenu = function ($subMenu, mm_timeout) {
$subMenu.children('.dropdown-toggle').attr('aria-expanded', 'false');
if ($subMenu.hasClass('mega')) {
$subMenu.addClass('animating');
clearTimeout($subMenu.data('animatingTimeout'));
$subMenu.data('animatingTimeout', setTimeout(function () {
$subMenu.removeClass('animating')
}, mm_timeout));
clearTimeout($subMenu.data('hoverTimeout'));
$subMenu.data('hoverTimeout', setTimeout(function () {
$subMenu.removeClass('open');
ariaCheck();
}, 100));
}
else {
clearTimeout($subMenu.data('hoverTimeout'));
$subMenu.data('hoverTimeout', setTimeout(function () {
$subMenu.removeClass('open');
ariaCheck();
}, 100));
}
};
$('.tb-megamenu-button', this).click(function () {
if (parseInt($(this).parent().children('.nav-collapse').height())) {
$(this).parent().children('.nav-collapse').css({height: 0, overflow: 'hidden'});
Drupal.TBMegaMenu.displayedMenuMobile = false;
}
else {
$(this).parent().children('.nav-collapse').css({height: 'auto', overflow: 'visible'});
Drupal.TBMegaMenu.displayedMenuMobile = true;
}
});
var isTouch = window.matchMedia('(pointer: coarse)').matches;
if (!isTouch) {
var mm_duration = 0;
$('.tb-megamenu', context).each(function () {
if ($(this).data('duration')) {
mm_duration = $(this).data('duration');
}
});
var mm_timeout = mm_duration ? 100 + mm_duration : 500;
$('.nav > li, li.mega', context).bind('mouseenter', function (event) {
showMenu($(this), mm_timeout);
});
$('.nav > li > .dropdown-toggle, li.mega > .dropdown-toggle', context).bind('focus', function (event) {
var $this = $(this);
var $subMenu = $this.closest('li');
showMenu($subMenu, mm_timeout);
// If the focus moves outside of the subMenu, close it.
$(document).bind('focusin', function (event) {
if ($subMenu.has(event.target).length) {
return;
}
$(document).unbind(event);
hideMenu($subMenu, mm_timeout);
});
});
$('.nav > li, li.mega', context).bind('mouseleave', function (event) {
hideMenu($(this), mm_timeout);
});
/**
* Allow tabbing by appending the open class.
* Works in tandem with CSS changes that utilize opacity rather than
* display none
*/
// If the selected anchor is not in the TB Megamenu, remove all "open"
// class occurrences
$('a, span').focus(function (event) {
if (!$(this).parent().hasClass('tb-megamenu-item') && !$(this).parents('.tb-megamenu-block').length) {
nav_close_megamenu();
}
});
$('.nav > li > a, li.mega > a').focus(function (event) {
// Remove all occurrences of "open" from other menu trees
var siblings = $(this).parents('.tb-megamenu-item').siblings();
// var siblings = $(this).closest('.tb-megamenu-item.level-1').siblings();
$.each(siblings, function (i, v) {
var cousins = $(v).find('.open');
$.each(cousins, function (index, value) {
$(value).removeClass('open');
ariaCheck($(this));
});
$(v).removeClass('open');
ariaCheck();
});
// Open the submenu if the selected item has one
if ($(this).next(".tb-megamenu-submenu").length > 0) {
if (!$(this).parent().hasClass("open")) {
$(this).parent().addClass("open");
}
}
// If the anchor's top-level parent is not open, open it
if (!$(this).closest('.tb-megamenu-item.dropdown').hasClass('open') && $(this).closest('.tb-megamenu-item.dropdown').find('.tb-megamenu-submenu').length > 0) {
$(this).closest('.tb-megamenu-item.dropdown').addClass('open');
ariaCheck();
}
// If anchor's parent submenus are not open, open them
var parents = $(this).parents('.tb-megamenu-item.dropdown-submenu');
$.each(parents, function (i, v) {
if (!$(v).hasClass('open')) {
$(v).addClass('open');
ariaCheck();
}
});
});
}
// Define actions for touch devices.
var createTouchMenu = function (items) {
items.children("a, span").each(function () {
var $item = $(this);
var tbitem = $(this).parent();
$item.click(function (event) {
// If the menu link has already been clicked once...
if ($item.hasClass("tb-megamenu-clicked")) {
var $uri = $item.attr("href");
// If the menu link has a URI, go to the link.
// <nolink> menu items will not have a URI.
if ($uri) {
window.location.href = $uri;
}
else {
$item.removeClass("tb-megamenu-clicked");
hideMenu(tbitem, mm_timeout);
}
}
else {
event.preventDefault();
// Hide any already open menus.
nav_close_megamenu();
$(".tb-megamenu").find(".tb-megamenu-clicked").removeClass("tb-megamenu-clicked");
// Open the submenu.
$item.addClass("tb-megamenu-clicked");
showMenu(tbitem, mm_timeout);
}
});
});
// Anytime there's a click outside the menu, close the menu.
$(document).on('click', function (event) {
if ($(event.target).closest('.tb-megamenu-nav').length === 0) {
nav_close_megamenu();
$(".tb-megamenu").find(".tb-megamenu-clicked").removeClass("tb-megamenu-clicked");
};
})
};
if (isTouch) {
createTouchMenu($(".tb-megamenu ul.nav li.mega", context).has(".dropdown-menu"));
};
$(window).on('load resize', function () {
var windowWidth = window.innerWidth ? window.innerWidth : $(window).width();
if (windowWidth != Drupal.TBMegaMenu.oldWindowWidth) {
Drupal.TBMegaMenu.oldWindowWidth = windowWidth;
Drupal.TBMegaMenu.menuResponsive();
if (windowWidth >= Drupal.TBMegaMenu.supportedScreens[0]) {
navParent.addEventListener('keydown',keydownEvent);
}
else {
navParent.removeEventListener('keydown',keydownEvent);
}
}
});
});
}
}
})(jQuery, Drupal, drupalSettings, once);
