123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821 |
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- define([
- 'jquery',
- 'matchMedia',
- 'jquery/ui',
- 'jquery/jquery.mobile.custom',
- 'mage/translate'
- ], function ($, mediaCheck) {
- 'use strict';
- /**
- * Menu Widget - this widget is a wrapper for the jQuery UI Menu
- */
- $.widget('mage.menu', $.ui.menu, {
- options: {
- responsive: false,
- expanded: false,
- showDelay: 42,
- hideDelay: 300,
- delay: 0,
- mediaBreakpoint: '(max-width: 768px)'
- },
- /**
- * @private
- */
- _create: function () {
- var self = this;
- this.delay = this.options.delay;
- this._super();
- $(window).on('resize', function () {
- self.element.find('.submenu-reverse').removeClass('submenu-reverse');
- });
- },
- /**
- * @private
- */
- _init: function () {
- this._super();
- if (this.options.expanded === true) {
- this.isExpanded();
- }
- if (this.options.responsive === true) {
- mediaCheck({
- media: this.options.mediaBreakpoint,
- entry: $.proxy(function () {
- this._toggleMobileMode();
- }, this),
- exit: $.proxy(function () {
- this._toggleDesktopMode();
- }, this)
- });
- }
- this._assignControls()._listen();
- this._setActiveMenu();
- },
- /**
- * @return {Object}
- * @private
- */
- _assignControls: function () {
- this.controls = {
- toggleBtn: $('[data-action="toggle-nav"]'),
- swipeArea: $('.nav-sections')
- };
- return this;
- },
- /**
- * @private
- */
- _listen: function () {
- var controls = this.controls,
- toggle = this.toggle;
- controls.toggleBtn.off('click');
- controls.toggleBtn.on('click', toggle.bind(this));
- controls.swipeArea.off('swipeleft');
- controls.swipeArea.on('swipeleft', toggle.bind(this));
- },
- /**
- * Toggle.
- */
- toggle: function () {
- var html = $('html');
- if (html.hasClass('nav-open')) {
- html.removeClass('nav-open');
- setTimeout(function () {
- html.removeClass('nav-before-open');
- }, this.options.hideDelay);
- } else {
- html.addClass('nav-before-open');
- setTimeout(function () {
- html.addClass('nav-open');
- }, this.options.showDelay);
- }
- },
- /**
- * Tries to figure out the active category for current page and add appropriate classes:
- * - 'active' class for active category
- * - 'has-active' class for all parents of active category
- *
- * First, checks whether current URL is URL of category page,
- * otherwise tries to retrieve category URL in case of current URL is product view page URL
- * which has category tree path in it.
- *
- * @return void
- * @private
- */
- _setActiveMenu: function () {
- var currentUrl = window.location.href.split('?')[0];
- if (!this._setActiveMenuForCategory(currentUrl)) {
- this._setActiveMenuForProduct(currentUrl);
- }
- },
- /**
- * Looks for category with provided URL and adds 'active' CSS class to it if it was not set before.
- * If menu item has parent categories, sets 'has-active' class to all af them.
- *
- * @param {String} url - possible category URL
- * @returns {Boolean} - true if active category was founded by provided URL, otherwise return false
- * @private
- */
- _setActiveMenuForCategory: function (url) {
- var activeCategoryLink = this.element.find('a[href="' + url + '"]'),
- classes,
- classNav;
- if (!activeCategoryLink || !activeCategoryLink.hasClass('ui-corner-all')) {
- //category was not found by provided URL
- return false;
- } else if (!activeCategoryLink.parent().hasClass('active')) {
- activeCategoryLink.parent().addClass('active');
- classes = activeCategoryLink.parent().attr('class');
- classNav = classes.match(/(nav\-)[0-9]+(\-[0-9]+)+/gi);
- if (classNav) {
- this._setActiveParent(classNav[0]);
- }
- }
- return true;
- },
- /**
- * Sets 'has-active' CSS class to all parent categories which have part of provided class in childClassName
- *
- * @example
- * childClassName - 'nav-1-2-3'
- * CSS class 'has-active' will be added to categories have 'nav-1-2' and 'nav-1' classes
- *
- * @param {String} childClassName - Class name of active category <li> element
- * @return void
- * @private
- */
- _setActiveParent: function (childClassName) {
- var parentElement,
- parentClass = childClassName.substr(0, childClassName.lastIndexOf('-'));
- if (parentClass.lastIndexOf('-') !== -1) {
- parentElement = this.element.find('.' + parentClass);
- if (parentElement) {
- parentElement.addClass('has-active');
- }
- this._setActiveParent(parentClass);
- }
- },
- /**
- * Tries to retrieve category URL from current URL and mark this category as active
- * @see _setActiveMenuForCategory(url)
- *
- * @example
- * currentUrl - http://magento.com/category1/category12/product.html,
- * category URLs has extensions .phtml - http://magento.com/category1.phtml
- * method sets active category which has URL http://magento.com/category1/category12.phtml
- *
- * @param {String} currentUrl - current page URL without parameters
- * @return void
- * @private
- */
- _setActiveMenuForProduct: function (currentUrl) {
- var categoryUrlExtension,
- lastUrlSection,
- possibleCategoryUrl,
- //retrieve first category URL to know what extension is used for category URLs
- firstCategoryUrl = this.element.find('> li a').attr('href');
- if (firstCategoryUrl) {
- lastUrlSection = firstCategoryUrl.substr(firstCategoryUrl.lastIndexOf('/'));
- categoryUrlExtension = lastUrlSection.lastIndexOf('.') !== -1 ?
- lastUrlSection.substr(lastUrlSection.lastIndexOf('.')) : '';
- possibleCategoryUrl = currentUrl.substr(0, currentUrl.lastIndexOf('/')) + categoryUrlExtension;
- this._setActiveMenuForCategory(possibleCategoryUrl);
- }
- },
- /**
- * Add class for expanded option.
- */
- isExpanded: function () {
- var subMenus = this.element.find(this.options.menus),
- expandedMenus = subMenus.find(this.options.menus);
- expandedMenus.addClass('expanded');
- },
- /**
- * @param {jQuery.Event} event
- * @private
- */
- _activate: function (event) {
- window.location.href = this.active.find('> a').attr('href');
- this.collapseAll(event);
- },
- /**
- * @param {jQuery.Event} event
- * @private
- */
- _keydown: function (event) {
- var match, prev, character, skip, regex,
- preventDefault = true;
- /* eslint-disable max-depth */
- /**
- * @param {String} value
- */
- function escape(value) {
- return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
- }
- if (this.active.closest(this.options.menus).attr('aria-expanded') != 'true') { //eslint-disable-line eqeqeq
- switch (event.keyCode) {
- case $.ui.keyCode.PAGE_UP:
- this.previousPage(event);
- break;
- case $.ui.keyCode.PAGE_DOWN:
- this.nextPage(event);
- break;
- case $.ui.keyCode.HOME:
- this._move('first', 'first', event);
- break;
- case $.ui.keyCode.END:
- this._move('last', 'last', event);
- break;
- case $.ui.keyCode.UP:
- this.previous(event);
- break;
- case $.ui.keyCode.DOWN:
- if (this.active && !this.active.is('.ui-state-disabled')) {
- this.expand(event);
- }
- break;
- case $.ui.keyCode.LEFT:
- this.previous(event);
- break;
- case $.ui.keyCode.RIGHT:
- this.next(event);
- break;
- case $.ui.keyCode.ENTER:
- case $.ui.keyCode.SPACE:
- this._activate(event);
- break;
- case $.ui.keyCode.ESCAPE:
- this.collapse(event);
- break;
- default:
- preventDefault = false;
- prev = this.previousFilter || '';
- character = String.fromCharCode(event.keyCode);
- skip = false;
- clearTimeout(this.filterTimer);
- if (character === prev) {
- skip = true;
- } else {
- character = prev + character;
- }
- regex = new RegExp('^' + escape(character), 'i');
- match = this.activeMenu.children('.ui-menu-item').filter(function () {
- return regex.test($(this).children('a').text());
- });
- match = skip && match.index(this.active.next()) !== -1 ?
- this.active.nextAll('.ui-menu-item') :
- match;
- // If no matches on the current filter, reset to the last character pressed
- // to move down the menu to the first item that starts with that character
- if (!match.length) {
- character = String.fromCharCode(event.keyCode);
- regex = new RegExp('^' + escape(character), 'i');
- match = this.activeMenu.children('.ui-menu-item').filter(function () {
- return regex.test($(this).children('a').text());
- });
- }
- if (match.length) {
- this.focus(event, match);
- if (match.length > 1) {
- this.previousFilter = character;
- this.filterTimer = this._delay(function () {
- delete this.previousFilter;
- }, 1000);
- } else {
- delete this.previousFilter;
- }
- } else {
- delete this.previousFilter;
- }
- }
- } else {
- switch (event.keyCode) {
- case $.ui.keyCode.DOWN:
- this.next(event);
- break;
- case $.ui.keyCode.UP:
- this.previous(event);
- break;
- case $.ui.keyCode.RIGHT:
- if (this.active && !this.active.is('.ui-state-disabled')) {
- this.expand(event);
- }
- break;
- case $.ui.keyCode.ENTER:
- case $.ui.keyCode.SPACE:
- this._activate(event);
- break;
- case $.ui.keyCode.LEFT:
- case $.ui.keyCode.ESCAPE:
- this.collapse(event);
- break;
- default:
- preventDefault = false;
- prev = this.previousFilter || '';
- character = String.fromCharCode(event.keyCode);
- skip = false;
- clearTimeout(this.filterTimer);
- if (character === prev) {
- skip = true;
- } else {
- character = prev + character;
- }
- regex = new RegExp('^' + escape(character), 'i');
- match = this.activeMenu.children('.ui-menu-item').filter(function () {
- return regex.test($(this).children('a').text());
- });
- match = skip && match.index(this.active.next()) !== -1 ?
- this.active.nextAll('.ui-menu-item') :
- match;
- // If no matches on the current filter, reset to the last character pressed
- // to move down the menu to the first item that starts with that character
- if (!match.length) {
- character = String.fromCharCode(event.keyCode);
- regex = new RegExp('^' + escape(character), 'i');
- match = this.activeMenu.children('.ui-menu-item').filter(function () {
- return regex.test($(this).children('a').text());
- });
- }
- if (match.length) {
- this.focus(event, match);
- if (match.length > 1) {
- this.previousFilter = character;
- this.filterTimer = this._delay(function () {
- delete this.previousFilter;
- }, 1000);
- } else {
- delete this.previousFilter;
- }
- } else {
- delete this.previousFilter;
- }
- }
- }
- /* eslint-enable max-depth */
- if (preventDefault) {
- event.preventDefault();
- }
- },
- /**
- * @private
- */
- _toggleMobileMode: function () {
- var subMenus;
- $(this.element).off('mouseenter mouseleave');
- this._on({
- /**
- * @param {jQuery.Event} event
- */
- 'click .ui-menu-item:has(a)': function (event) {
- var target;
- event.preventDefault();
- target = $(event.target).closest('.ui-menu-item');
- target.get(0).scrollIntoView();
- if (!target.hasClass('level-top') || !target.has('.ui-menu').length) {
- window.location.href = target.find('> a').attr('href');
- }
- },
- /**
- * @param {jQuery.Event} event
- */
- 'click .ui-menu-item:has(.ui-state-active)': function (event) {
- this.collapseAll(event, true);
- }
- });
- subMenus = this.element.find('.level-top');
- $.each(subMenus, $.proxy(function (index, item) {
- var category = $(item).find('> a span').not('.ui-menu-icon').text(),
- categoryUrl = $(item).find('> a').attr('href'),
- menu = $(item).find('> .ui-menu');
- this.categoryLink = $('<a>')
- .attr('href', categoryUrl)
- .text($.mage.__('All ') + category);
- this.categoryParent = $('<li>')
- .addClass('ui-menu-item all-category')
- .html(this.categoryLink);
- if (menu.find('.all-category').length === 0) {
- menu.prepend(this.categoryParent);
- }
- }, this));
- },
- /**
- * @private
- */
- _toggleDesktopMode: function () {
- var categoryParent, html;
- $(this.element).off('click mousedown mouseenter mouseleave');
- this._on({
- /**
- * Prevent focus from sticking to links inside menu after clicking
- * them (focus should always stay on UL during navigation).
- */
- 'mousedown .ui-menu-item > a': function (event) {
- event.preventDefault();
- },
- /**
- * Prevent focus from sticking to links inside menu after clicking
- * them (focus should always stay on UL during navigation).
- */
- 'click .ui-state-disabled > a': function (event) {
- event.preventDefault();
- },
- /**
- * @param {jQuer.Event} event
- */
- 'click .ui-menu-item:has(a)': function (event) {
- var target = $(event.target).closest('.ui-menu-item');
- if (!this.mouseHandled && target.not('.ui-state-disabled').length) {
- this.select(event);
- // Only set the mouseHandled flag if the event will bubble, see #9469.
- if (!event.isPropagationStopped()) {
- this.mouseHandled = true;
- }
- // Open submenu on click
- if (target.has('.ui-menu').length) {
- this.expand(event);
- } else if (!this.element.is(':focus') &&
- $(this.document[0].activeElement).closest('.ui-menu').length
- ) {
- // Redirect focus to the menu
- this.element.trigger('focus', [true]);
- // If the active item is on the top level, let it stay active.
- // Otherwise, blur the active item since it is no longer visible.
- if (this.active && this.active.parents('.ui-menu').length === 1) { //eslint-disable-line
- clearTimeout(this.timer);
- }
- }
- }
- },
- /**
- * @param {jQuery.Event} event
- */
- 'mouseenter .ui-menu-item': function (event) {
- var target = $(event.currentTarget),
- submenu = this.options.menus,
- ulElement,
- ulElementWidth,
- width,
- targetPageX,
- rightBound;
- if (target.has(submenu)) {
- ulElement = target.find(submenu);
- ulElementWidth = ulElement.outerWidth(true);
- width = target.outerWidth() * 2;
- targetPageX = target.offset().left;
- rightBound = $(window).width();
- if (ulElementWidth + width + targetPageX > rightBound) {
- ulElement.addClass('submenu-reverse');
- }
- if (targetPageX - ulElementWidth < 0) {
- ulElement.removeClass('submenu-reverse');
- }
- }
- // Remove ui-state-active class from siblings of the newly focused menu item
- // to avoid a jump caused by adjacent elements both having a class with a border
- target.siblings().children('.ui-state-active').removeClass('ui-state-active');
- this.focus(event, target);
- },
- /**
- * @param {jQuery.Event} event
- */
- 'mouseleave': function (event) {
- this.collapseAll(event, true);
- },
- /**
- * Mouse leave.
- */
- 'mouseleave .ui-menu': 'collapseAll'
- });
- categoryParent = this.element.find('.all-category');
- html = $('html');
- categoryParent.remove();
- if (html.hasClass('nav-open')) {
- html.removeClass('nav-open');
- setTimeout(function () {
- html.removeClass('nav-before-open');
- }, this.options.hideDelay);
- }
- },
- /**
- * @param {*} handler
- * @param {Number} delay
- * @return {Number}
- * @private
- */
- _delay: function (handler, delay) {
- var instance = this,
- /**
- * @return {*}
- */
- handlerProxy = function () {
- return (typeof handler === 'string' ? instance[handler] : handler).apply(instance, arguments);
- };
- return setTimeout(handlerProxy, delay || 0);
- },
- /**
- * @param {jQuery.Event} event
- */
- expand: function (event) {
- var newItem = this.active &&
- this.active
- .children('.ui-menu')
- .children('.ui-menu-item')
- .first();
- if (newItem && newItem.length) {
- if (newItem.closest('.ui-menu').is(':visible') &&
- newItem.closest('.ui-menu').has('.all-categories')
- ) {
- return;
- }
- // remove the active state class from the siblings
- this.active.siblings().children('.ui-state-active').removeClass('ui-state-active');
- this._open(newItem.parent());
- // Delay so Firefox will not hide activedescendant change in expanding submenu from AT
- this._delay(function () {
- this.focus(event, newItem);
- });
- }
- },
- /**
- * @param {jQuery.Event} event
- */
- select: function (event) {
- var ui;
- this.active = this.active || $(event.target).closest('.ui-menu-item');
- if (this.active.is('.all-category')) {
- this.active = $(event.target).closest('.ui-menu-item');
- }
- ui = {
- item: this.active
- };
- if (!this.active.has('.ui-menu').length) {
- this.collapseAll(event, true);
- }
- this._trigger('select', event, ui);
- }
- });
- $.widget('mage.navigation', $.mage.menu, {
- options: {
- responsiveAction: 'wrap', //option for responsive handling
- maxItems: null, //option to set max number of menu items
- container: '#menu', //container to check against navigation length
- moreText: $.mage.__('more'),
- breakpoint: 768
- },
- /**
- * @private
- */
- _init: function () {
- var that, responsive;
- this._super();
- that = this;
- responsive = this.options.responsiveAction;
- this.element
- .addClass('ui-menu-responsive')
- .attr('responsive', 'main');
- this.setupMoreMenu();
- this.setMaxItems();
- //check responsive option
- if (responsive == 'onResize') { //eslint-disable-line eqeqeq
- $(window).on('resize', function () {
- if ($(window).width() > that.options.breakpoint) {
- that._responsive();
- $('[responsive=more]').show();
- } else {
- that.element.children().show();
- $('[responsive=more]').hide();
- }
- });
- } else if (responsive == 'onReload') { //eslint-disable-line eqeqeq
- this._responsive();
- }
- },
- /**
- * Setup more menu.
- */
- setupMoreMenu: function () {
- var moreListItems = this.element.children().clone(),
- moreLink = $('<a>' + this.options.moreText + '</a>');
- moreListItems.hide();
- moreLink.attr('href', '#');
- this.moreItemsList = $('<ul>')
- .append(moreListItems);
- this.moreListContainer = $('<li>')
- .append(moreLink)
- .append(this.moreItemsList);
- this.responsiveMenu = $('<ul>')
- .addClass('ui-menu-more')
- .attr('responsive', 'more')
- .append(this.moreListContainer)
- .menu({
- position: {
- my: 'right top',
- at: 'right bottom'
- }
- })
- .insertAfter(this.element);
- },
- /**
- * @private
- */
- _responsive: function () {
- var container = $(this.options.container),
- containerSize = container.width(),
- width = 0,
- items = this.element.children('li'),
- more = $('.ui-menu-more > li > ul > li a');
- items = items.map(function () {
- var item = {};
- item.item = $(this);
- item.itemSize = $(this).outerWidth();
- return item;
- });
- $.each(items, function (index) {
- var itemText = items[index].item
- .find('a:first')
- .text();
- width += parseInt(items[index].itemSize, null); //eslint-disable-line radix
- if (width < containerSize) {
- items[index].item.show();
- more.each(function () {
- var text = $(this).text();
- if (text === itemText) {
- $(this).parent().hide();
- }
- });
- } else if (width > containerSize) {
- items[index].item.hide();
- more.each(function () {
- var text = $(this).text();
- if (text === itemText) {
- $(this).parent().show();
- }
- });
- }
- });
- },
- /**
- * Set max items.
- */
- setMaxItems: function () {
- var items = this.element.children('li'),
- itemsCount = items.length,
- maxItems = this.options.maxItems,
- overflow = itemsCount - maxItems,
- overflowItems = items.slice(overflow);
- overflowItems.hide();
- overflowItems.each(function () {
- var itemText = $(this).find('a:first').text();
- $(this).hide();
- $('.ui-menu-more > li > ul > li a').each(function () {
- var text = $(this).text();
- if (text === itemText) {
- $(this).parent().show();
- }
- });
- });
- }
- });
- return {
- menu: $.mage.menu,
- navigation: $.mage.navigation
- };
- });
|