Files
gantry5/assets/common/application/menu/index.js
T

380 lines
14 KiB
JavaScript

"use strict";
var ready = require('domready'),
prime = require('prime'),
$ = require('../utils/dollar-extras'),
zen = require('elements/zen'),
bind = require('mout/function/bind'),
timeout = require('mout/function/timeout'),
Bound = require('prime-util/prime/bound'),
Options = require('prime-util/prime/options');
var hasTouchEvents = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
var Menu = new prime({
mixin: [Bound, Options],
options: {
selectors: {
mainContainer: '.g-main-nav',
mobileContainer: '#g-mobilemenu-container',
topLevel: '.g-toplevel',
rootItems: '> ul > li',
parent: '.g-parent',
item: '.g-menu-item',
dropdown: '.g-dropdown',
overlay: '.g-menu-overlay',
touchIndicator: '.g-menu-parent-indicator',
linkedParent: '[data-g-menuparent]',
mobileTarget: '[data-g-mobile-target]'
},
states: {
active: 'g-active',
inactive: 'g-inactive',
selected: 'g-selected',
touchEvents: 'g-menu-hastouch'
}
},
constructor: function(options) {
this.setOptions(options);
this.selectors = this.options.selectors;
this.states = this.options.states;
this.overlay = zen('div' + this.selectors.overlay).top('#g-page-surround');
this.active = null;
this.location = [];
var mainContainer = $(this.selectors.mainContainer);
if (!mainContainer) { return; }
var gHoverExpand = mainContainer.data('g-hover-expand');
this.hoverExpand = gHoverExpand === null || gHoverExpand === 'true';
if (hasTouchEvents || !this.hoverExpand) {
mainContainer.addClass(this.states.touchEvents);
}
this.attach();
},
attach: function() {
var selectors = this.selectors,
main = $(selectors.mainContainer + ' ' + selectors.item),
mobileContainer = $(selectors.mobileContainer),
body = $('body');
if (!main) { return; }
if (this.hoverExpand) {
main.on('mouseenter', this.bound('mouseenter'));
main.on('mouseleave', this.bound('mouseleave'));
}
body.delegate('click', ':not(' + selectors.mainContainer + ') ' + selectors.linkedParent + ', .g-fullwidth .g-sublevel ' + selectors.linkedParent, this.bound('click'));
body.delegate('click', ':not(' + selectors.mainContainer + ') a[href]', this.bound('resetAfterClick'));
if (hasTouchEvents || !this.hoverExpand) {
var linkedParent = $(selectors.linkedParent);
if (linkedParent) {
linkedParent.on('touchmove', this.bound('touchmove'));
linkedParent.on('touchend', this.bound('touchend'));
}
this.overlay.on('touchend', this.bound('closeAllDropdowns'));
}
if (mobileContainer) {
var query = 'only all and (max-width: ' + this._calculateBreakpoint((mobileContainer.data('g-menu-breakpoint') || '48rem')) + ')',
match = matchMedia(query);
match.addListener(this.bound('_checkQuery'));
this._checkQuery(match);
}
},
detach: function() {},
click: function(event) {
this.touchend(event);
},
resetAfterClick: function(event) {
var target = $(event.target);
if (target.data('g-menuparent') !== null) {
return true;
}
this.closeDropdown(event);
if (global.G5 && global.G5.offcanvas) {
G5.offcanvas.close();
}
},
mouseenter: function(event) {
var element = $(event.target);
if (!element.parent(this.options.selectors.mainContainer)) { return; }
if (element.parent(this.options.selectors.item) && !element.parent('.g-standard')) { return; }
this.openDropdown(element);
},
mouseleave: function(event) {
var element = $(event.target);
if (!element.parent(this.options.selectors.mainContainer)) { return; }
if (element.parent(this.options.selectors.item) && !element.parent('.g-standard')) { return; }
this.closeDropdown(element);
},
touchmove: function(event) {
var target = $(event.target);
target.isMoving = true;
},
touchend: function(event) {
var selectors = this.selectors,
states = this.states;
var target = $(event.target),
indicator = target.parent(selectors.item).find(selectors.touchIndicator),
menuType = target.parent('.g-standard') ? 'standard' : 'megamenu',
isGoingBack = target.parent('.g-go-back'),
parent, isSelected;
if (target.isMoving) {
target.isMoving = false;
return false;
}
target.off('touchmove', this.bound('touchmove'));
target.isMoving = false;
if (indicator) {
target = indicator;
}
parent = target.matches(selectors.item) ? target : target.parent(selectors.item);
isSelected = parent.hasClass(states.selected);
if (!parent.find(selectors.dropdown) && !indicator) { return true; }
event.stopPropagation();
if (!indicator || target.matches(selectors.touchIndicator)) {
event.preventDefault();
}
if (!isSelected) {
var siblings = parent.siblings();
if (siblings) {
var currentlyOpen = siblings.search(selectors.touchIndicator + ' !> * !> ' + selectors.item + '.' + states.selected);
(currentlyOpen || []).forEach(bind(function(open) {
this.closeDropdown(open);
}, this));
}
}
if ((menuType == 'megamenu' || !parent.parent(selectors.mainContainer)) && (parent.find(' > ' + selectors.dropdown + ', > * > ' + selectors.dropdown) || isGoingBack)) {
var sublevel = target.parent('.g-sublevel') || target.parent('.g-toplevel'),
slideout = parent.find('.g-sublevel'),
columns = parent.parent('.g-dropdown-column'),
blocks;
if (sublevel) {
var isNavMenu = target.parent(selectors.mainContainer);
if (!isNavMenu || (isNavMenu && !sublevel.matches('.g-toplevel'))) { this._fixHeights(sublevel, slideout, isGoingBack, isNavMenu); }
if (!isNavMenu && columns && (blocks = columns.search('> .g-grid > .g-block'))) {
if (blocks.length > 1) { sublevel = blocks.search('> .g-sublevel'); }
}
sublevel[!isSelected ? 'addClass' : 'removeClass']('g-slide-out');
}
}
this[!isSelected ? 'openDropdown' : 'closeDropdown'](parent);
if (event.type !== 'click') { this.toggleOverlay(target.parent(selectors.mainContainer)); }
},
openDropdown: function(element) {
element = $(element.target || element);
var dropdown = element.find(this.selectors.dropdown);
element.addClass(this.states.selected);
if (dropdown) {
dropdown.removeClass(this.states.inactive).addClass(this.states.active);
}
},
closeDropdown: function(element) {
element = $(element.target || element);
var dropdown = element.find(this.selectors.dropdown);
element.removeClass(this.states.selected);
if (dropdown) {
var sublevels = dropdown.search('.g-sublevel'),
slideouts = dropdown.search('.g-slide-out, .' + this.states.selected),
actives = dropdown.search('.' + this.states.active);
if (sublevels) { sublevels.attribute('style', null); }
if (slideouts) { slideouts.removeClass('g-slide-out').removeClass(this.states.selected); }
if (actives) { actives.removeClass(this.states.active).addClass(this.states.inactive); }
dropdown.removeClass(this.states.active).addClass(this.states.inactive);
}
},
closeAllDropdowns: function() {
var selectors = this.selectors,
states = this.states,
topLevel = $(selectors.mainContainer + ' > .g-toplevel'),
roots = topLevel.search(' >' + selectors.item);
if (roots) { roots.removeClass(states.selected); }
if (topLevel) {
var allRoots = topLevel.search('> ' + this.options.selectors.item);
if (allRoots) { allRoots.forEach(this.closeDropdown.bind(this)); }
this.closeDropdown(topLevel);
}
this.toggleOverlay(topLevel);
},
resetStates: function(menu) {
if (!menu) { return; }
var items = menu.search('.g-toplevel, .g-dropdown-column, .g-dropdown, .g-selected, .g-active, .g-slide-out'),
actives = menu.search('.g-active');
if (!items) { return; }
menu.attribute('style', null).removeClass('g-selected').removeClass('g-slide-out');
items.attribute('style', null).removeClass('g-selected').removeClass('g-slide-out');
if (actives) { actives.removeClass('g-active').addClass('g-inactive'); }
},
toggleOverlay: function(menu) {
if (!menu) { return; }
var shouldOpen = !!menu.find('.g-active, .g-selected');
this.overlay[shouldOpen ? 'addClass' : 'removeClass']('g-menu-overlay-open');
this.overlay[0].style.opacity = shouldOpen ? 1 : 0;
},
_fixHeights: function(parent, sublevel, isGoingBack, isNavMenu) {
if (parent == sublevel) { return; }
if (isGoingBack) {
parent.attribute('style', null);
}
var parents, heights = {
from: parent[0].getBoundingClientRect(),
to: (!isNavMenu ? sublevel.parent('.g-dropdown')[0] : sublevel[0]).getBoundingClientRect()
},
height = Math.max(heights.from.height, heights.to.height);
if (isGoingBack) {
parents = parent.parents('[style^="height"]');
(parents || []).forEach(function(element) {
element = $(element);
if (element.parent('.g-toplevel')) {
element[0].style.height = heights.from.height + 'px';
}
});
}
if (!isGoingBack) {
// if from height is < than to height set the parent height else, set the target
if (heights.from.height < heights.to.height) {
parent[0].style.height = height + 'px';
parents = parent.parents('[style^="height"]');
(parents || []).forEach(function(element) {
element = $(element);
if (element.parent('.g-toplevel')) {
element[0].style.height = height + 'px';
}
});
} else if (isNavMenu) {
sublevel[0].style.height = height + 'px';
}
// fix sublevels heights in side menu (offcanvas etc)
if (!isNavMenu) {
var maxHeight = height,
block = $(sublevel).parent('.g-block:not(.size-100)'),
column = block ? block.parent('.g-dropdown-column') : null;
(sublevel.parents('.g-slide-out, .g-dropdown-column') || parent).forEach(function(slideout) {
maxHeight = Math.max(height, parseInt(slideout.style.height || 0, 10));
});
if (column) {
column[0].style.height = maxHeight + 'px';
var blocks = column.search('> .g-grid > .g-block'),
diff = maxHeight;
blocks.forEach(function(block, i) {
if ((i + 1) != blocks.length) {
diff -= block.getBoundingClientRect().height;
} else {
$(block).find('.g-sublevel')[0].style.height = diff + 'px';
}
});
} else {
sublevel[0].style.height = maxHeight + 'px';
}
}
}
},
_calculateBreakpoint: function(value) {
var digit = parseFloat(value.match(/^\d{1,}/).shift()),
unit = value.match(/[a-z]{1,}$/i).shift(),
tolerance = unit.match(/r?em/) ? -0.062 : -1;
return (digit + tolerance) + unit;
},
_checkQuery: function(mq) {
var selectors = this.options.selectors,
mobileContainer = $(selectors.mobileContainer),
mainContainer = $(selectors.mainContainer + selectors.mobileTarget) || $(selectors.mainContainer),
find, dropdowns;
if (mq.matches) {
// move to Mobile Container
find = mainContainer.find(selectors.topLevel);
if (find) {
mainContainer.parent('.g-block').addClass('hidden');
mobileContainer.parent('.g-block').removeClass('hidden');
find.top(mobileContainer);
}
} else {
// move back to Original Location
find = mobileContainer.find(selectors.topLevel);
if (find) {
mobileContainer.parent('.g-block').addClass('hidden');
mainContainer.parent('.g-block').removeClass('hidden');
find.top(mainContainer);
}
}
this.resetStates(find);
// we need to reintroduce fixed widths for those dropdowns that come with it
if (!mq.matches && (find && (dropdowns = find.search('[data-g-item-width]')))) {
dropdowns.forEach(function(dropdown) {
dropdown = $(dropdown);
dropdown[0].style.width = dropdown.data('g-item-width');
});
}
},
_debug: function() {}
});
module.exports = Menu;