navigation-menu.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. /**
  2. * Copyright © Magento, Inc. All rights reserved.
  3. * See COPYING.txt for license details.
  4. */
  5. /**
  6. * @deprecated
  7. * @see lib/web/mage/menu.js
  8. */
  9. define([
  10. 'jquery',
  11. 'matchMedia',
  12. 'mage/template',
  13. 'mage/dropdowns',
  14. 'mage/terms'
  15. ], function ($, mediaCheck, mageTemplate) {
  16. 'use strict';
  17. $.widget('mage.navigationMenu', {
  18. options: {
  19. itemsContainer: '> ul',
  20. topLevel: 'li.level0',
  21. topLevelSubmenu: '> .submenu',
  22. topLevelHoverClass: 'hover',
  23. expandedTopLevel: '.more',
  24. hoverInTimeout: 300,
  25. hoverOutTimeout: 500,
  26. submenuAnimationSpeed: 200,
  27. collapsable: true,
  28. collapsableDropdownTemplate:
  29. '<script type="text/x-magento-template">' +
  30. '<li class="level0 level-top more parent">' +
  31. '<div class="submenu">' +
  32. '<ul><%= elems %></ul>' +
  33. '</div>' +
  34. '</li>' +
  35. '</script>'
  36. },
  37. /** @inheritdoc */
  38. _create: function () {
  39. this.itemsContainer = $(this.options.itemsContainer, this.element);
  40. this.topLevel = $(this.options.topLevel, this.element);
  41. this.topLevelSubmenu = $(this.options.topLevelSubmenu, this.topLevel);
  42. this._bind();
  43. },
  44. /**
  45. * @private
  46. */
  47. _init: function () {
  48. if (this.options.collapsable) {
  49. setTimeout($.proxy(function () {
  50. this._checkToCollapseOrExpand();
  51. }, this), 100);
  52. }
  53. },
  54. /**
  55. * @private
  56. */
  57. _bind: function () {
  58. this._on({
  59. /**
  60. * @param {jQuery.Event} e
  61. */
  62. 'mouseenter > ul > li.level0': function (e) {
  63. if (!this.entered) { // fix IE bug with 'mouseenter' event
  64. this.timeoutId && clearTimeout(this.timeoutId);
  65. this.timeoutId = setTimeout($.proxy(function () {
  66. this._openSubmenu(e);
  67. }, this), this.options.hoverInTimeout);
  68. this.entered = true;
  69. }
  70. },
  71. /**
  72. * @param {jQuery.Event} e
  73. */
  74. 'mouseleave > ul > li.level0': function (e) {
  75. this.entered = null;
  76. this.timeoutId && clearTimeout(this.timeoutId);
  77. this.timeoutId = setTimeout($.proxy(function () {
  78. this._closeSubmenu(e.currentTarget);
  79. }, this), this.options.hoverOutTimeout);
  80. },
  81. /**
  82. * @param {jQuert.Event} e
  83. */
  84. 'click': function (e) {
  85. e.stopPropagation();
  86. }
  87. });
  88. $(document)
  89. .on('click.hideMenu', $.proxy(function () {
  90. var isOpened = this.topLevel.filter(function () {
  91. return $(this).data('opened');
  92. });
  93. if (isOpened) {
  94. this._closeSubmenu(null, false);
  95. }
  96. }, this));
  97. $(window)
  98. .on('resize', $.proxy(function () {
  99. this.timeoutOnResize && clearTimeout(this.timeoutOnResize);
  100. this.timeoutOnResize = setTimeout($.proxy(function () {
  101. if (this.options.collapsable) {
  102. if ($(this.options.expandedTopLevel, this.element).length) {
  103. this._expandMenu();
  104. }
  105. this._checkToCollapseOrExpand();
  106. }
  107. }, this), 300);
  108. }, this));
  109. },
  110. /**
  111. * @param {jQuery.Event} e
  112. * @private
  113. */
  114. _openSubmenu: function (e) {
  115. var menuItem = e.currentTarget;
  116. if (!$(menuItem).data('opened')) {
  117. this._closeSubmenu(menuItem, true, true);
  118. $(this.options.topLevelSubmenu, menuItem)
  119. .slideDown(this.options.submenuAnimationSpeed, $.proxy(function () {
  120. $(menuItem).addClass(this.options.topLevelHoverClass);
  121. $(menuItem).data('opened', true);
  122. }, this));
  123. } else if ($(e.target).closest(this.options.topLevel)) {
  124. $(e.target)
  125. .addClass(this.options.topLevelHoverClass)
  126. .siblings(this.options.topLevel)
  127. .removeClass(this.options.topLevelHoverClass);
  128. }
  129. },
  130. /**
  131. * @param {*} menuItem
  132. * @param {*} excludeCurrent
  133. * @param {*} fast
  134. * @private
  135. */
  136. _closeSubmenu: function (menuItem, excludeCurrent, fast) {
  137. var topLevel = $(this.options.topLevel, this.element),
  138. activeSubmenu = $(this.options.topLevelSubmenu, menuItem || null);
  139. $(this.options.topLevelSubmenu, topLevel)
  140. .filter(function () {
  141. return excludeCurrent ? $(this).not(activeSubmenu) : true;
  142. })
  143. .slideUp(fast ? 0 : this.options.submenuAnimationSpeed);
  144. topLevel
  145. .removeClass(this.options.topLevelHoverClass)
  146. .data('opened', false);
  147. },
  148. /**
  149. * @private
  150. */
  151. _checkToCollapseOrExpand: function () {
  152. var navWidth, totalWidth, startCollapseIndex;
  153. if ($('html').hasClass('lt-640') || $('html').hasClass('w-640')) {
  154. return;
  155. }
  156. navWidth = this.itemsContainer.width();
  157. totalWidth = 0;
  158. startCollapseIndex = 0;
  159. $.each($(this.options.topLevel, this.element), function (index, item) {
  160. totalWidth += $(item).outerWidth(true);
  161. if (totalWidth > navWidth && !startCollapseIndex) {
  162. startCollapseIndex = index - 2;
  163. }
  164. });
  165. this[startCollapseIndex ? '_collapseMenu' : '_expandMenu'](startCollapseIndex);
  166. },
  167. /**
  168. * @param {*} startCollapseIndex
  169. * @private
  170. */
  171. _collapseMenu: function (startCollapseIndex) {
  172. this.elemsToCollapse = this.topLevel.filter(function (index) {
  173. return index > startCollapseIndex;
  174. });
  175. this.elemsToCollapseClone = $('<div></div>').append(this.elemsToCollapse.clone()).html();
  176. this.collapsableDropdown = $(
  177. mageTemplate(
  178. this.options.collapsableDropdownTemplate,
  179. {
  180. elems: this.elemsToCollapseClone
  181. }
  182. )
  183. );
  184. this.itemsContainer.append(this.collapsableDropdown);
  185. this.elemsToCollapse.detach();
  186. },
  187. /**
  188. * @private
  189. */
  190. _expandMenu: function () {
  191. this.elemsToCollapse && this.elemsToCollapse.appendTo(this.itemsContainer);
  192. this.collapsableDropdown && this.collapsableDropdown.remove();
  193. },
  194. /**
  195. * @private
  196. */
  197. _destroy: function () {
  198. this._expandMenu();
  199. }
  200. });
  201. /*
  202. * Provides "Continium" effect for submenu
  203. * */
  204. $.widget('mage.navigationMenu', $.mage.navigationMenu, {
  205. options: {
  206. parentLevel: '> ul > li.level0',
  207. submenuAnimationSpeed: 150,
  208. submenuContiniumEffect: false
  209. },
  210. /**
  211. * @private
  212. */
  213. _init: function () {
  214. this._super();
  215. this._applySubmenuStyles();
  216. },
  217. /**
  218. * @private
  219. */
  220. _applySubmenuStyles: function () {
  221. $(this.options.topLevelSubmenu, $(this.options.topLevel, this.element))
  222. .removeAttr('style');
  223. $(this.options.topLevelSubmenu, $(this.options.parentLevel, this.element))
  224. .css({
  225. display: 'block',
  226. height: 0,
  227. overflow: 'hidden'
  228. });
  229. },
  230. /**
  231. * @param {jQuery.Event} e
  232. * @private
  233. */
  234. _openSubmenu: function (e) {
  235. var menuItem = e.currentTarget,
  236. submenu = $(this.options.topLevelSubmenu, menuItem),
  237. openedItems = $(this.options.topLevel, this.element).filter(function () {
  238. return $(this).data('opened');
  239. });
  240. if (submenu.length) {
  241. this.heightToAnimate = $(this.options.itemsContainer, submenu).outerHeight(true);
  242. if (openedItems.length) {
  243. this._closeSubmenu(menuItem, true, this.heightToAnimate, $.proxy(function () {
  244. submenu.css({
  245. height: 'auto'
  246. });
  247. $(menuItem)
  248. .addClass(this.options.topLevelHoverClass);
  249. }, this), e);
  250. } else {
  251. submenu.animate({
  252. height: this.heightToAnimate
  253. }, this.options.submenuAnimationSpeed, $.proxy(function () {
  254. $(menuItem)
  255. .addClass(this.options.topLevelHoverClass);
  256. }, this));
  257. }
  258. $(menuItem)
  259. .data('opened', true);
  260. } else {
  261. this._closeSubmenu(menuItem);
  262. }
  263. },
  264. /**
  265. * @param {*} menuItem
  266. * @param {*} excludeCurrent
  267. * @param {*} heightToAnimate
  268. * @param {Function} callback
  269. * @private
  270. */
  271. _closeSubmenu: function (menuItem, excludeCurrent, heightToAnimate, callback) {
  272. var topLevel = $(this.options.topLevel, this.itemsContainer),
  273. prevOpenedItem, prevOpenedSubmenu;
  274. if (!excludeCurrent) {
  275. $(this.options.topLevelSubmenu, $(this.options.parentLevel, this.element))
  276. .animate({
  277. height: 0
  278. });
  279. topLevel
  280. .data('opened', false)
  281. .removeClass(this.options.topLevelHoverClass);
  282. } else {
  283. prevOpenedItem = topLevel.filter(function () {
  284. return $(this).data('opened');
  285. });
  286. prevOpenedSubmenu = $(this.options.topLevelSubmenu, prevOpenedItem);
  287. prevOpenedSubmenu.animate({
  288. height: heightToAnimate
  289. }, this.options.submenuAnimationSpeed, 'linear', function () {
  290. $(this).css({
  291. height: 0
  292. });
  293. callback && callback();
  294. });
  295. prevOpenedItem
  296. .data('opened', false)
  297. .removeClass(this.options.topLevelHoverClass);
  298. }
  299. },
  300. /**
  301. * @private
  302. */
  303. _collapseMenu: function () {
  304. this._superApply(arguments);
  305. this._applySubmenuStyles();
  306. }
  307. });
  308. // Responsive menu
  309. $.widget('mage.navigationMenu', $.mage.navigationMenu, {
  310. options: {
  311. responsive: false,
  312. origNavPlaceholder: '.page-header',
  313. mainContainer: 'body',
  314. pageWrapper: '.page-wrapper',
  315. openedMenuClass: 'opened',
  316. toggleActionPlaceholder: '.block-search',
  317. itemWithSubmenu: 'li.parent',
  318. titleWithSubmenu: 'li.parent > a',
  319. submenu: 'li.parent > .submenu',
  320. toggleActionTemplate:
  321. '<script type="text/x-magento-template">' +
  322. '<span data-action="toggle-nav" class="action toggle nav">Toggle Nav</span>' +
  323. '</script>',
  324. submenuActionsTemplate:
  325. '<script type="text/x-magento-template">' +
  326. '<li class="action all">' +
  327. '<a href="<%= categoryURL %>"><span>All <%= category %></span></a>' +
  328. '</li>' +
  329. '</script>',
  330. navigationSectionsWrapperTemplate:
  331. '<script type="text/x-magento-template">' +
  332. '<dl class="navigation-tabs" data-sections="tabs">' +
  333. '</dl>' +
  334. '</script>',
  335. navigationItemWrapperTemplate:
  336. '<script type="text/x-magento-template">' +
  337. '<dt class="item title <% if (active) { %>active<% } %>" data-section="title">' +
  338. '<a class="switch" data-toggle="switch" href="#TODO"><%= title %></a>' +
  339. '</dt>' +
  340. '<dd class="item content <% if (active) { %>active<%}%>" data-section="content">' +
  341. '</dd>' +
  342. '</script>'
  343. },
  344. /**
  345. * @private
  346. */
  347. _init: function () {
  348. this._super();
  349. this.mainContainer = $(this.options.mainContainer);
  350. this.pageWrapper = $(this.options.pageWrapper);
  351. this.toggleAction = $(mageTemplate(this.options.toggleActionTemplate, {}));
  352. if (this.options.responsive) {
  353. mediaCheck({
  354. media: '(min-width: 768px)',
  355. entry: $.proxy(function () {
  356. this._toggleDesktopMode();
  357. }, this),
  358. exit: $.proxy(function () {
  359. this._toggleMobileMode();
  360. }, this)
  361. });
  362. }
  363. },
  364. /**
  365. * @private
  366. */
  367. _bind: function () {
  368. this._super();
  369. this._bindDocumentEvents();
  370. },
  371. /**
  372. * @private
  373. */
  374. _bindDocumentEvents: function () {
  375. if (!this.eventsBound) {
  376. $(document)
  377. .on('click.toggleMenu', '.action.toggle.nav', $.proxy(function (e) {
  378. if ($(this.element).data('opened')) {
  379. this._hideMenu();
  380. } else {
  381. this._showMenu();
  382. }
  383. e.stopPropagation();
  384. this.mobileNav.scrollTop(0);
  385. this._fixedBackLink();
  386. }, this))
  387. .on('click.hideMenu', this.options.pageWrapper, $.proxy(function () {
  388. if ($(this.element).data('opened')) {
  389. this._hideMenu();
  390. this.mobileNav.scrollTop(0);
  391. this._fixedBackLink();
  392. }
  393. }, this))
  394. .on('click.showSubmenu', this.options.titleWithSubmenu, $.proxy(function (e) {
  395. this._showSubmenu(e);
  396. e.preventDefault();
  397. this.mobileNav.scrollTop(0);
  398. this._fixedBackLink();
  399. }, this))
  400. .on('click.hideSubmenu', '.action.back', $.proxy(function (e) {
  401. this._hideSubmenu(e);
  402. this.mobileNav.scrollTop(0);
  403. this._fixedBackLink();
  404. }, this));
  405. this.eventsBound = true;
  406. }
  407. },
  408. /**
  409. * @private
  410. */
  411. _showMenu: function () {
  412. $(this.element).data('opened', true);
  413. this.mainContainer.add('html').addClass(this.options.openedMenuClass);
  414. },
  415. /**
  416. * @private
  417. */
  418. _hideMenu: function () {
  419. $(this.element).data('opened', false);
  420. this.mainContainer.add('html').removeClass(this.options.openedMenuClass);
  421. },
  422. /**
  423. * @param {jQuery.Event} e
  424. * @private
  425. */
  426. _showSubmenu: function (e) {
  427. var submenu;
  428. $(e.currentTarget).addClass('action back');
  429. submenu = $(e.currentTarget).siblings('.submenu');
  430. submenu.addClass('opened');
  431. },
  432. /**
  433. * @param {jQuery.Event} e
  434. * @private
  435. */
  436. _hideSubmenu: function (e) {
  437. var submenuSelector = '.submenu',
  438. submenu = $(e.currentTarget).next(submenuSelector);
  439. $(e.currentTarget).removeClass('action back');
  440. submenu.removeClass('opened');
  441. },
  442. /**
  443. * @private
  444. */
  445. _renderSubmenuActions: function () {
  446. $.each(
  447. $(this.options.itemWithSubmenu),
  448. $.proxy(function (index, item) {
  449. var actions = $(mageTemplate(
  450. this.options.submenuActionsTemplate,
  451. {
  452. category: $('> a > span', item).text(),
  453. categoryURL: $('> a', item).attr('href')
  454. }
  455. )),
  456. submenu = $('> .submenu', item),
  457. items = $('> ul', submenu);
  458. items.prepend(actions);
  459. }, this)
  460. );
  461. },
  462. /**
  463. * @private
  464. */
  465. _toggleMobileMode: function () {
  466. this._expandMenu();
  467. $(this.options.topLevelSubmenu, $(this.options.topLevel, this.element))
  468. .removeAttr('style');
  469. this.toggleAction.insertBefore(this.options.toggleActionPlaceholder);
  470. this.mobileNav = $(this.element).detach().clone();
  471. this.mainContainer.prepend(this.mobileNav);
  472. this.mobileNav.find('> ul').addClass('nav');
  473. this._insertExtraItems();
  474. this._wrapItemsInSections();
  475. this.mobileNav.scroll($.proxy(function () {
  476. this._fixedBackLink();
  477. }, this));
  478. this._renderSubmenuActions();
  479. this._bindDocumentEvents();
  480. },
  481. /**
  482. * @private
  483. */
  484. _toggleDesktopMode: function () {
  485. this.mobileNav && this.mobileNav.remove();
  486. this.toggleAction.detach();
  487. $(this.element).insertAfter(this.options.origNavPlaceholder);
  488. $(document)
  489. .off('click.toggleMenu', '.action.toggle.nav')
  490. .off('click.hideMenu', this.options.pageWrapper)
  491. .off('click.showSubmenu', this.options.titleWithSubmenu)
  492. .off('click.hideSubmenu', '.action.back');
  493. this.eventsBound = false;
  494. this._applySubmenuStyles();
  495. },
  496. /**
  497. * @private
  498. */
  499. _insertExtraItems: function () {
  500. var settings, footerSettings, account;
  501. if ($('.header.panel .switcher').length) {
  502. settings = $('.header.panel .switcher')
  503. .clone()
  504. .addClass('settings');
  505. this.mobileNav.prepend(settings);
  506. }
  507. if ($('.footer .switcher').length) {
  508. footerSettings = $('.footer .switcher')
  509. .clone()
  510. .addClass('settings');
  511. this.mobileNav.prepend(footerSettings);
  512. }
  513. if ($('.header.panel .header.links li').length) {
  514. account = $('.header.panel > .header.links')
  515. .clone()
  516. .addClass('account');
  517. this.mobileNav.prepend(account);
  518. }
  519. },
  520. /**
  521. * @private
  522. */
  523. _wrapItemsInSections: function () {
  524. var account = $('> .account', this.mobileNav),
  525. settings = $('> .settings', this.mobileNav),
  526. nav = $('> .nav', this.mobileNav),
  527. navigationSectionsWrapper = $(mageTemplate(this.options.navigationSectionsWrapperTemplate, {})),
  528. navigationItemWrapper;
  529. this.mobileNav.append(navigationSectionsWrapper);
  530. if (nav.length) {
  531. navigationItemWrapper = $(mageTemplate(this.options.navigationItemWrapperTemplate, {
  532. title: 'Menu'
  533. }));
  534. navigationSectionsWrapper.append(navigationItemWrapper);
  535. navigationItemWrapper.eq(1).append(nav);
  536. }
  537. if (account.length) {
  538. navigationItemWrapper = $(mageTemplate(this.options.navigationItemWrapperTemplate, {
  539. title: 'Account'
  540. }));
  541. navigationSectionsWrapper.append(navigationItemWrapper);
  542. navigationItemWrapper.eq(1).append(account);
  543. }
  544. if (settings.length) {
  545. navigationItemWrapper = $(
  546. mageTemplate(this.options.navigationItemWrapperTemplate, {
  547. title: 'Settings'
  548. })
  549. );
  550. navigationSectionsWrapper.append(navigationItemWrapper);
  551. navigationItemWrapper.eq(1).append(settings);
  552. }
  553. navigationSectionsWrapper.addClass(
  554. 'navigation-tabs-' + navigationSectionsWrapper.find('[data-section="title"]').length
  555. );
  556. navigationSectionsWrapper.terms();
  557. },
  558. /**
  559. * @private
  560. */
  561. _fixedBackLink: function () {
  562. var linksBack = this.mobileNav.find('.submenu .action.back'),
  563. linkBack = this.mobileNav.find('.submenu.opened > ul > .action.back').last(),
  564. subMenu, navOffset, linkBackHeight;
  565. linksBack.removeClass('fixed');
  566. if (linkBack.length) {
  567. subMenu = linkBack.parent();
  568. navOffset = this.mobileNav.find('.nav').position().top;
  569. linkBackHeight = linkBack.height();
  570. if (navOffset <= 0) {
  571. linkBack.addClass('fixed');
  572. subMenu.css({
  573. paddingTop: linkBackHeight
  574. });
  575. } else {
  576. linkBack.removeClass('fixed');
  577. subMenu.css({
  578. paddingTop: 0
  579. });
  580. }
  581. }
  582. }
  583. });
  584. return $.mage.navigationMenu;
  585. });