From 087af02f6551e0d8300ce64e41935780b25e47f4 Mon Sep 17 00:00:00 2001 From: prasanna-lmsace <57126778+prasanna-lmsace@users.noreply.github.com> Date: Wed, 15 May 2024 23:32:31 +0530 Subject: [PATCH] Fix: More menu "keep outside of more menu" doesn't show any effect, resolves #461 (#580) --- CHANGES.md | 1 + amd/build/smartmenu.min.js | 2 +- amd/build/smartmenu.min.js.map | 2 +- amd/src/smartmenu.js | 113 +++++++- lang/en/theme_boost_union.php | 2 +- .../smartmenus-cardmenu-children.mustache | 2 +- ...artmenusettings_menus_presentation.feature | 254 ++++++++++++++---- 7 files changed, 317 insertions(+), 59 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7a627f96425..b1afb0af7a1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ Changes ### v4.3-r13 +* 2024-05-13 - Bugfix: Make the "More menu behavior" setting in smart menus more stable, resolves #461. * 2024-05-11 - Improvement: Enhance smart menu restrictions for authenticated user role, guest roles and visitor role, resolves #571 * 2024-05-11 - Improvement: Smart menu "locations" must be filled with a value, resolves #404 * 2024-05-10 - Bugfix: Do not show empty smart menus to users, resolves #405 diff --git a/amd/build/smartmenu.min.js b/amd/build/smartmenu.min.js index 6eca61394f2..f7a25da1bd8 100644 --- a/amd/build/smartmenu.min.js +++ b/amd/build/smartmenu.min.js @@ -5,6 +5,6 @@ * @copyright 2023 bdecent GmbH * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("theme_boost_union/smartmenu",["jquery","core/moremenu"],(function($){const hideSubmenus=target=>{var visibleMenu=document.querySelectorAll("nav.moremenu .dropdown-submenu.show");null!==visibleMenu&&visibleMenu.forEach((el=>{el!=target&&el.classList.remove("show")}))},moveOutMoreMenu=navMenu=>{if(null!==navMenu){var outMenus=navMenu.querySelectorAll(".dropdownmoremenu .force-menu-out"),menuslist=[];if(null!==outMenus){outMenus.forEach((menu=>{menu.querySelector("a").classList.remove("dropdown-item"),menu.querySelector("a").classList.add("nav-link"),menuslist.push(menu),menu.parentNode.removeChild(menu)}));var length=menuslist.length,newPosition=navMenu.children.length-1-length||0;menuslist.forEach((menu=>navMenu.insertBefore(menu,navMenu.children[newPosition]))),window.dispatchEvent(new Event("resize"))}}};return{init:()=>{(()=>{var submenu=document.querySelectorAll("nav.moremenu .dropdown-submenu");null!==submenu&&submenu.forEach((item=>{item.addEventListener("click",(e=>{var target=e.currentTarget;hideSubmenus(target),target.classList.toggle("show"),e.stopPropagation()}))})),$(document).on("hidden.bs.dropdown",(e=>{var submenus=e.relatedTarget.parentNode.querySelectorAll(".dropdown-submenu.show");null!==submenus&&submenus.forEach((e=>e.classList.remove("show")))})),document.addEventListener("click",(e=>{var dropdown=e.target.closest(".dropdownmoremenu"),subMenu=e.target.closest(".dropdown-submenu");dropdown&&null!==subMenu&&(dropdown.querySelectorAll(".dropdown-submenu.show").forEach((menu=>{menu.classList.remove("show")})),subMenu.classList.toggle("show"));var dropdownMenu=e.target.parentNode.classList.contains("dropdown");dropdown&&dropdownMenu&&dropdown.querySelectorAll(".dropdown-menu.show").forEach((menu=>{menu!=e.target.closest(".dropdown-menu")&&menu.classList.remove("show")}))}),!0);var helpIcon=document.querySelectorAll(".moremenu .dropdown .menu-helpicon");null!==helpIcon&&helpIcon.forEach((icon=>{icon.addEventListener("click",(e=>{e.stopPropagation()}))}))})(),(()=>{var cards=document.querySelectorAll(".card-dropdown.card-overflow-no-wrap");if(null!==cards){var scrollStart,scrollMoved;let startPos,scrollPos;cards.forEach((card=>{var scrollElement=card.querySelector(".dropdown-menu");scrollElement.addEventListener("mousedown",(e=>{scrollStart=!0;var target=e.currentTarget.querySelector(".card-block-wrapper");startPos=e.pageX,scrollPos=target.scrollLeft})),scrollElement.addEventListener("mousemove",(e=>{if(e.preventDefault(),!scrollStart)return;scrollMoved=!0;var target=e.currentTarget.querySelector(".card-block-wrapper");const scroll=e.pageX-startPos;target.scrollLeft=scrollPos-scroll})),scrollElement.addEventListener("click",(e=>{scrollMoved&&(e.preventDefault(),scrollMoved=!1),e.stopPropagation()})),scrollElement.addEventListener("mouseleave",(()=>{scrollStart=!1,scrollMoved=!1})),scrollElement.addEventListener("mouseup",(()=>{scrollStart=!1}))}))}})(),(()=>{var primaryNav=document.querySelector(".primary-navigation ul.more-nav");moveOutMoreMenu(primaryNav);var menuBar=document.querySelector("nav.menubar ul.more-nav");moveOutMoreMenu(menuBar)})()}}})); +define("theme_boost_union/smartmenu",["jquery","core/moremenu"],(function($){const Selectors_dropDownMenu="dropdownmoremenu",Selectors_forceOut="force-menu-out",Selectors_navLink="nav-link",Selectors_dropDownItem="dropdown-item",Selectors_classes={dropDownMenuList:".dropdownmoremenu ul.dropdown-menu",forceOut:".dropdownmoremenu .force-menu-out"},hideSubmenus=target=>{var visibleMenu=document.querySelectorAll("nav.moremenu .dropdown-submenu.show");null!==visibleMenu&&visibleMenu.forEach((el=>{el!=target&&el.classList.remove("show")}))},setOutMenuPositions=navMenu=>{if(null!=navMenu){var li=Array.from(navMenu.children).filter((e=>!e.classList.contains(Selectors_dropDownMenu))),position=0;li.forEach((menu=>{position=li.indexOf(menu),menu.dataset.orgposition=position}));var moreMenu=navMenu.querySelector(Selectors_classes.dropDownMenuList);Array.from(moreMenu.children).forEach((menu=>{menu.dataset.orgposition=position++}))}},moveOutMoreMenu=navMenu=>{if(null!==navMenu){var li=Array.from(navMenu.children).reverse().filter((e=>!e.classList.contains(Selectors_forceOut)&&!e.classList.contains(Selectors_dropDownMenu)));if(!(li.length<1)){var outMenus=navMenu.querySelectorAll(Selectors_classes.forceOut),menuslist=[];if(null!==outMenus){outMenus.forEach((menu=>{menu.querySelector("a").classList.remove(Selectors_dropDownItem),menu.querySelector("a").classList.add(Selectors_navLink),menuslist.push(menu),menu.parentNode.removeChild(menu)}));var moveMenus=[];menuslist.forEach((menu=>{navMenu.insertBefore(menu,navMenu.lastElementChild)&&li.length>0&&moveMenus.push(li.shift())})),moveMenus.forEach((menu=>{navMenu.insertBefore(menu,navMenu.lastElementChild)})),window.dispatchEvent(new Event("resize")),(navMenu=>{Array.from(navMenu.children).sort(((a,b)=>a.dataset.orgposition-b.dataset.orgposition)).forEach((menu=>navMenu.appendChild(menu)))})(navMenu)}}}};return{init:()=>{(()=>{var submenu=document.querySelectorAll("nav.moremenu .dropdown-submenu");null!==submenu&&submenu.forEach((item=>{item.addEventListener("click",(e=>{var target=e.currentTarget;hideSubmenus(target),target.classList.toggle("show"),e.stopPropagation()}))})),$(document).on("hidden.bs.dropdown",(e=>{var submenus=e.relatedTarget.parentNode.querySelectorAll(".dropdown-submenu.show");null!==submenus&&submenus.forEach((e=>e.classList.remove("show")))})),document.addEventListener("click",(e=>{var dropdown=e.target.closest(".dropdownmoremenu"),subMenu=e.target.closest(".dropdown-submenu");dropdown&&null!==subMenu&&(dropdown.querySelectorAll(".dropdown-submenu.show").forEach((menu=>{menu.classList.remove("show")})),subMenu.classList.toggle("show"));var dropdownMenu=e.target.parentNode.classList.contains("dropdown");dropdown&&dropdownMenu&&dropdown.querySelectorAll(".dropdown-menu.show").forEach((menu=>{menu!=e.target.closest(".dropdown-menu")&&menu.classList.remove("show")}))}),!0);var helpIcon=document.querySelectorAll(".moremenu .dropdown .menu-helpicon");null!==helpIcon&&helpIcon.forEach((icon=>{icon.addEventListener("click",(e=>{e.stopPropagation()}))}))})(),(()=>{var cards=document.querySelectorAll(".card-dropdown.card-overflow-no-wrap");if(null!==cards){var scrollStart,scrollMoved;let startPos,scrollPos;cards.forEach((card=>{var scrollElement=card.querySelector(".dropdown-menu");scrollElement.addEventListener("mousedown",(e=>{scrollStart=!0;var target=e.currentTarget.querySelector(".card-block-wrapper");startPos=e.pageX,scrollPos=target.scrollLeft})),scrollElement.addEventListener("mousemove",(e=>{if(e.preventDefault(),!scrollStart)return;scrollMoved=!0;var target=e.currentTarget.querySelector(".card-block-wrapper");const scroll=e.pageX-startPos;target.scrollLeft=scrollPos-scroll})),scrollElement.addEventListener("click",(e=>{scrollMoved&&(e.preventDefault(),scrollMoved=!1),e.stopPropagation()})),scrollElement.addEventListener("mouseleave",(()=>{scrollStart=!1,scrollMoved=!1})),scrollElement.addEventListener("mouseup",(()=>{scrollStart=!1}))}))}})(),(()=>{var primaryNav=document.querySelector(".primary-navigation ul.more-nav");null!=primaryNav&&(setOutMenuPositions(primaryNav),moveOutMoreMenu(primaryNav));var menuBar=document.querySelector("nav.menubar ul.more-nav");null!=menuBar&&(setOutMenuPositions(menuBar),moveOutMoreMenu(menuBar)),window.onresize=e=>{e.isTrusted&&(moveOutMoreMenu(primaryNav),moveOutMoreMenu(menuBar))}})()}}})); //# sourceMappingURL=smartmenu.min.js.map \ No newline at end of file diff --git a/amd/build/smartmenu.min.js.map b/amd/build/smartmenu.min.js.map index 6877d2a276f..ae4d37c9fdb 100644 --- a/amd/build/smartmenu.min.js.map +++ b/amd/build/smartmenu.min.js.map @@ -1 +1 @@ -{"version":3,"file":"smartmenu.min.js","sources":["../src/smartmenu.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Theme Boost Union - JS for smart menu to realize the third level submenu support.\n *\n * @module theme_boost_union/smartmenu\n * @copyright 2023 bdecent GmbH \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine([\"jquery\", \"core/moremenu\"], function($) {\n /**\n * Implement the second level of submenu support.\n * Find the submenus inside the dropdown, add an event listener for click event which - on the click - shows the submenu list.\n */\n const addSubmenu = () => {\n // Fetch the list of submenus from moremenu.\n var submenu = document.querySelectorAll('nav.moremenu .dropdown-submenu');\n if (submenu !== null) {\n submenu.forEach((item) => {\n // Add event listener to show the submenu on click.\n item.addEventListener('click', (e) => {\n var target = e.currentTarget;\n // Hide the shown menu.\n hideSubmenus(target);\n target.classList.toggle('show');\n // Prevent hiding the parent menu.\n e.stopPropagation();\n });\n });\n }\n\n // Hide the submenus when its parent dropdown is hidden.\n $(document).on('hidden.bs.dropdown', e => {\n var target = e.relatedTarget.parentNode;\n var submenus = target.querySelectorAll('.dropdown-submenu.show');\n if (submenus !== null) {\n submenus.forEach((e) => e.classList.remove('show'));\n }\n });\n\n // Provide the third level menu support inside the more menu.\n // StopPropagation used in the toggledropdown method on Moremenu.js, It prevents the opening of the third level menus.\n // Used the document delegation method to fetch the click on moremenu and submenu.\n document.addEventListener('click', (e) => {\n var dropdown = e.target.closest('.dropdownmoremenu');\n var subMenu = e.target.closest('.dropdown-submenu');\n if (dropdown && subMenu !== null) {\n // Hide the previously opend submenus. before open the new one.\n dropdown.querySelectorAll('.dropdown-submenu.show').forEach((menu) => {\n menu.classList.remove('show');\n });\n subMenu.classList.toggle('show');\n }\n\n // Hide the opened menus before open the other menus.\n var dropdownMenu = e.target.parentNode.classList.contains('dropdown');\n if (dropdown && dropdownMenu) {\n dropdown.querySelectorAll('.dropdown-menu.show').forEach((menu) => {\n // Hide the opened menus in more menu.\n if (menu != e.target.closest('.dropdown-menu')) {\n menu.classList.remove('show');\n }\n });\n }\n\n }, true);\n\n // Prevent the closing of dropdown during the click on help icon.\n var helpIcon = document.querySelectorAll('.moremenu .dropdown .menu-helpicon');\n if (helpIcon !== null) {\n helpIcon.forEach((icon) => {\n icon.addEventListener('click', (e) => {\n e.stopPropagation();\n });\n });\n }\n };\n\n /**\n * Hide visible submenus before display new submenu.\n *\n * @param {Selector} target\n */\n const hideSubmenus = (target) => {\n var visibleMenu = document.querySelectorAll('nav.moremenu .dropdown-submenu.show');\n if (visibleMenu !== null) {\n visibleMenu.forEach((el) => {\n if (el != target) {\n el.classList.remove('show');\n }\n });\n }\n };\n\n /**\n * Make the no wrapped card menus scroll using swipe or drag.\n */\n const cardScroll = () => {\n var cards = document.querySelectorAll('.card-dropdown.card-overflow-no-wrap');\n if (cards !== null) {\n var scrollStart; // Verify the mouse is clicked and still in click not released.\n var scrollMoved; // Prevent the click on scrolling.\n let startPos, scrollPos;\n\n cards.forEach((card) => {\n var scrollElement = card.querySelector('.dropdown-menu');\n\n scrollElement.addEventListener('mousedown', (e) => {\n scrollStart = true;\n var target = e.currentTarget.querySelector('.card-block-wrapper');\n startPos = e.pageX;\n scrollPos = target.scrollLeft;\n });\n\n scrollElement.addEventListener('mousemove', (e) => {\n e.preventDefault();\n if (!scrollStart) {\n return;\n }\n scrollMoved = true;\n var target = e.currentTarget.querySelector('.card-block-wrapper');\n const scroll = e.pageX - startPos;\n target.scrollLeft = scrollPos - scroll;\n });\n\n scrollElement.addEventListener('click', (e) => {\n if (scrollMoved) {\n e.preventDefault();\n scrollMoved = false;\n }\n e.stopPropagation();\n });\n scrollElement.addEventListener('mouseleave', () => {\n scrollStart = false;\n scrollMoved = false;\n });\n scrollElement.addEventListener('mouseup', () => {\n scrollStart = false;\n });\n });\n }\n };\n\n /**\n * Move the menubar and primary navigation menu items from more menu.\n */\n const autoCollapse = () => {\n var primaryNav = document.querySelector('.primary-navigation ul.more-nav');\n moveOutMoreMenu(primaryNav);\n\n var menuBar = document.querySelector('nav.menubar ul.more-nav');\n moveOutMoreMenu(menuBar);\n };\n\n /**\n * Move the items from more menu, items which is set to force outside more menu.\n * Remove those items from more menu and insert the menu before the last normal item.\n * Find the length and children's length to insert the out menus in that positions.\n * Rerun the more menu it will more the other normal menus into more menu to fix the alignmenu issue.\n *\n * @param {HTMLElement} navMenu The navbar container.\n */\n const moveOutMoreMenu = (navMenu) => {\n\n if (navMenu === null) {\n return;\n }\n\n var outMenus = navMenu.querySelectorAll('.dropdownmoremenu .force-menu-out');\n var menuslist = [];\n\n if (outMenus === null) {\n return;\n }\n\n outMenus.forEach((menu) => {\n menu.querySelector('a').classList.remove('dropdown-item');\n menu.querySelector('a').classList.add('nav-link');\n\n menuslist.push(menu);\n menu.parentNode.removeChild(menu);\n });\n // Find the length and children's length to insert the out menus in that positions.\n var length = menuslist.length;\n var navLength = navMenu.children.length - 1; // Remove more menu.\n var newPosition = navLength - length || 0;\n // Insert the stored menus before the more menu.\n menuslist.forEach((menu) => navMenu.insertBefore(menu, navMenu.children[newPosition]));\n window.dispatchEvent(new Event('resize')); // Dispatch the resize event to create more menu.\n };\n\n return {\n init: () => {\n addSubmenu();\n cardScroll();\n autoCollapse();\n }\n };\n});\n"],"names":["define","$","hideSubmenus","target","visibleMenu","document","querySelectorAll","forEach","el","classList","remove","moveOutMoreMenu","navMenu","outMenus","menuslist","menu","querySelector","add","push","parentNode","removeChild","length","newPosition","children","insertBefore","window","dispatchEvent","Event","init","submenu","item","addEventListener","e","currentTarget","toggle","stopPropagation","on","submenus","relatedTarget","dropdown","closest","subMenu","dropdownMenu","contains","helpIcon","icon","addSubmenu","cards","scrollStart","scrollMoved","startPos","scrollPos","card","scrollElement","pageX","scrollLeft","preventDefault","scroll","cardScroll","primaryNav","menuBar","autoCollapse"],"mappings":";;;;;;;AAuBAA,qCAAO,CAAC,SAAU,kBAAkB,SAASC,SA0EnCC,aAAgBC,aACdC,YAAcC,SAASC,iBAAiB,uCACxB,OAAhBF,aACAA,YAAYG,SAASC,KACbA,IAAML,QACNK,GAAGC,UAAUC,OAAO,YA0E9BC,gBAAmBC,aAEL,OAAZA,aAIAC,SAAWD,QAAQN,iBAAiB,qCACpCQ,UAAY,MAEC,OAAbD,UAIJA,SAASN,SAASQ,OACdA,KAAKC,cAAc,KAAKP,UAAUC,OAAO,iBACzCK,KAAKC,cAAc,KAAKP,UAAUQ,IAAI,YAEtCH,UAAUI,KAAKH,MACfA,KAAKI,WAAWC,YAAYL,aAG5BM,OAASP,UAAUO,OAEnBC,YADYV,QAAQW,SAASF,OAAS,EACZA,QAAU,EAExCP,UAAUP,SAASQ,MAASH,QAAQY,aAAaT,KAAMH,QAAQW,SAASD,gBACxEG,OAAOC,cAAc,IAAIC,MAAM,oBAG5B,CACHC,KAAM,KAlLS,UAEXC,QAAUxB,SAASC,iBAAiB,kCACxB,OAAZuB,SACAA,QAAQtB,SAASuB,OAEbA,KAAKC,iBAAiB,SAAUC,QACxB7B,OAAS6B,EAAEC,cAEf/B,aAAaC,QACbA,OAAOM,UAAUyB,OAAO,QAExBF,EAAEG,wBAMdlC,EAAEI,UAAU+B,GAAG,sBAAsBJ,QAE7BK,SADSL,EAAEM,cAAcnB,WACPb,iBAAiB,0BACtB,OAAb+B,UACAA,SAAS9B,SAASyB,GAAMA,EAAEvB,UAAUC,OAAO,aAOnDL,SAAS0B,iBAAiB,SAAUC,QAC5BO,SAAWP,EAAE7B,OAAOqC,QAAQ,qBAC5BC,QAAUT,EAAE7B,OAAOqC,QAAQ,qBAC3BD,UAAwB,OAAZE,UAEZF,SAASjC,iBAAiB,0BAA0BC,SAASQ,OACzDA,KAAKN,UAAUC,OAAO,WAE1B+B,QAAQhC,UAAUyB,OAAO,aAIzBQ,aAAeV,EAAE7B,OAAOgB,WAAWV,UAAUkC,SAAS,YACtDJ,UAAYG,cACZH,SAASjC,iBAAiB,uBAAuBC,SAASQ,OAElDA,MAAQiB,EAAE7B,OAAOqC,QAAQ,mBACzBzB,KAAKN,UAAUC,OAAO,cAKnC,OAGCkC,SAAWvC,SAASC,iBAAiB,sCACxB,OAAbsC,UACAA,SAASrC,SAASsC,OACdA,KAAKd,iBAAiB,SAAUC,IAC5BA,EAAEG,yBAyHVW,GAhGW,UACXC,MAAQ1C,SAASC,iBAAiB,2CACxB,OAAVyC,MAAgB,KACZC,YACAC,gBACAC,SAAUC,UAEdJ,MAAMxC,SAAS6C,WACPC,cAAgBD,KAAKpC,cAAc,kBAEvCqC,cAActB,iBAAiB,aAAcC,IACzCgB,aAAc,MACV7C,OAAS6B,EAAEC,cAAcjB,cAAc,uBAC3CkC,SAAWlB,EAAEsB,MACbH,UAAYhD,OAAOoD,cAGvBF,cAActB,iBAAiB,aAAcC,OACzCA,EAAEwB,kBACGR,mBAGLC,aAAc,MACV9C,OAAS6B,EAAEC,cAAcjB,cAAc,6BACrCyC,OAASzB,EAAEsB,MAAQJ,SACzB/C,OAAOoD,WAAaJ,UAAYM,UAGpCJ,cAActB,iBAAiB,SAAUC,IACjCiB,cACAjB,EAAEwB,iBACFP,aAAc,GAElBjB,EAAEG,qBAENkB,cAActB,iBAAiB,cAAc,KACzCiB,aAAc,EACdC,aAAc,KAElBI,cAActB,iBAAiB,WAAW,KACtCiB,aAAc,UAyDtBU,GAhDa,UACbC,WAAatD,SAASW,cAAc,mCACxCL,gBAAgBgD,gBAEZC,QAAUvD,SAASW,cAAc,2BACrCL,gBAAgBiD,UA4CZC"} \ No newline at end of file +{"version":3,"file":"smartmenu.min.js","sources":["../src/smartmenu.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Theme Boost Union - JS for smart menu to realize the third level submenu support.\n *\n * @module theme_boost_union/smartmenu\n * @copyright 2023 bdecent GmbH \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine([\"jquery\", \"core/moremenu\"], function($) {\n\n const Selectors = {\n dropDownMenu: \"dropdownmoremenu\",\n forceOut: \"force-menu-out\",\n navLink: \"nav-link\",\n dropDownItem: \"dropdown-item\",\n classes: {\n dropDownMenuList: \".dropdownmoremenu ul.dropdown-menu\",\n forceOut: \".dropdownmoremenu .force-menu-out\"\n }\n };\n\n /**\n * Implement the second level of submenu support.\n * Find the submenus inside the dropdown, add an event listener for click event which - on the click - shows the submenu list.\n */\n const addSubmenu = () => {\n // Fetch the list of submenus from moremenu.\n var submenu = document.querySelectorAll('nav.moremenu .dropdown-submenu');\n if (submenu !== null) {\n submenu.forEach((item) => {\n // Add event listener to show the submenu on click.\n item.addEventListener('click', (e) => {\n var target = e.currentTarget;\n // Hide the shown menu.\n hideSubmenus(target);\n target.classList.toggle('show');\n // Prevent hiding the parent menu.\n e.stopPropagation();\n });\n });\n }\n\n // Hide the submenus when its parent dropdown is hidden.\n $(document).on('hidden.bs.dropdown', e => {\n var target = e.relatedTarget.parentNode;\n var submenus = target.querySelectorAll('.dropdown-submenu.show');\n if (submenus !== null) {\n submenus.forEach((e) => e.classList.remove('show'));\n }\n });\n\n // Provide the third level menu support inside the more menu.\n // StopPropagation used in the toggledropdown method on Moremenu.js, It prevents the opening of the third level menus.\n // Used the document delegation method to fetch the click on moremenu and submenu.\n document.addEventListener('click', (e) => {\n var dropdown = e.target.closest('.dropdownmoremenu');\n var subMenu = e.target.closest('.dropdown-submenu');\n if (dropdown && subMenu !== null) {\n // Hide the previously opend submenus. before open the new one.\n dropdown.querySelectorAll('.dropdown-submenu.show').forEach((menu) => {\n menu.classList.remove('show');\n });\n subMenu.classList.toggle('show');\n }\n\n // Hide the opened menus before open the other menus.\n var dropdownMenu = e.target.parentNode.classList.contains('dropdown');\n if (dropdown && dropdownMenu) {\n dropdown.querySelectorAll('.dropdown-menu.show').forEach((menu) => {\n // Hide the opened menus in more menu.\n if (menu != e.target.closest('.dropdown-menu')) {\n menu.classList.remove('show');\n }\n });\n }\n\n }, true);\n\n // Prevent the closing of dropdown during the click on help icon.\n var helpIcon = document.querySelectorAll('.moremenu .dropdown .menu-helpicon');\n if (helpIcon !== null) {\n helpIcon.forEach((icon) => {\n icon.addEventListener('click', (e) => {\n e.stopPropagation();\n });\n });\n }\n };\n\n /**\n * Hide visible submenus before display new submenu.\n *\n * @param {Selector} target\n */\n const hideSubmenus = (target) => {\n var visibleMenu = document.querySelectorAll('nav.moremenu .dropdown-submenu.show');\n if (visibleMenu !== null) {\n visibleMenu.forEach((el) => {\n if (el != target) {\n el.classList.remove('show');\n }\n });\n }\n };\n\n /**\n * Make the no wrapped card menus scroll using swipe or drag.\n */\n const cardScroll = () => {\n var cards = document.querySelectorAll('.card-dropdown.card-overflow-no-wrap');\n if (cards !== null) {\n var scrollStart; // Verify the mouse is clicked and still in click not released.\n var scrollMoved; // Prevent the click on scrolling.\n let startPos, scrollPos;\n\n cards.forEach((card) => {\n var scrollElement = card.querySelector('.dropdown-menu');\n\n scrollElement.addEventListener('mousedown', (e) => {\n scrollStart = true;\n var target = e.currentTarget.querySelector('.card-block-wrapper');\n startPos = e.pageX;\n scrollPos = target.scrollLeft;\n });\n\n scrollElement.addEventListener('mousemove', (e) => {\n e.preventDefault();\n if (!scrollStart) {\n return;\n }\n scrollMoved = true;\n var target = e.currentTarget.querySelector('.card-block-wrapper');\n const scroll = e.pageX - startPos;\n target.scrollLeft = scrollPos - scroll;\n });\n\n scrollElement.addEventListener('click', (e) => {\n if (scrollMoved) {\n e.preventDefault();\n scrollMoved = false;\n }\n e.stopPropagation();\n });\n scrollElement.addEventListener('mouseleave', () => {\n scrollStart = false;\n scrollMoved = false;\n });\n scrollElement.addEventListener('mouseup', () => {\n scrollStart = false;\n });\n });\n }\n };\n\n /**\n * Move the menubar and primary navigation menu items from more menu.\n */\n const autoCollapse = () => {\n var primaryNav = document.querySelector('.primary-navigation ul.more-nav');\n if (primaryNav != undefined) {\n setOutMenuPositions(primaryNav); // Create a data flag to maintain the original position of the menus.\n moveOutMoreMenu(primaryNav);\n }\n\n\n var menuBar = document.querySelector('nav.menubar ul.more-nav');\n if (menuBar != undefined) {\n setOutMenuPositions(menuBar);\n moveOutMoreMenu(menuBar);\n }\n\n window.onresize = (e) => {\n // Verify the event is original by browser resize.\n if (e.isTrusted) {\n moveOutMoreMenu(primaryNav);\n moveOutMoreMenu(menuBar);\n }\n };\n };\n\n /**\n * Finds and sets the positions of all menus before moving them,\n * helping to maintain the positions of the menus after being moved out from the moremenu.\n *\n * @param {HTMLElement} navMenu The navbar container.\n */\n const setOutMenuPositions = (navMenu) => {\n\n if (navMenu === undefined || navMenu === null) {\n return;\n }\n\n // Find all menu items excluding the dropdownmoremenu class.\n var li = Array.from(navMenu.children).filter((e) => !e.classList.contains(Selectors.dropDownMenu));\n\n // Initialize the position variable.\n var position = 0;\n\n // Loop through each menu item and set its original position.\n li.forEach((menu) => {\n position = li.indexOf(menu);\n menu.dataset.orgposition = position; // Store the original position in the menu's dataset.\n });\n\n // Maintain the positions of the menus inside the moremenu from the last position of the outside menus.\n var moreMenu = navMenu.querySelector(Selectors.classes.dropDownMenuList);\n Array.from(moreMenu.children).forEach((menu) => {\n menu.dataset.orgposition = position++;\n });\n };\n\n /**\n * Rearranges the menus placed outside the more menu based on their original positions.\n *\n * @param {HTMLElement} navMenu The navbar container.\n */\n const reArrangeMenuOrgPositions = (navMenu) => {\n // Retrieve all menu items and sort them based on their original positions.\n var li = Array.from(navMenu.children).sort((a, b) => a.dataset.orgposition - b.dataset.orgposition);\n // Append the sorted menu items back to the navbar container.\n li.forEach((menu) => navMenu.appendChild(menu));\n };\n\n /**\n * Move the items from more menu, items which is set to force outside more menu.\n * Remove those items from more menu and insert the menu before the last normal item.\n * Find the length and children's length to insert the out menus in that positions.\n * Move the non forced more menu to moremenu to make the menu alignment.\n * Rerun the more menu it will more the other normal menus into more menu to fix the alignmenu issue.\n * After the menus are move out, rearrange menus to its original positions.\n *\n * @param {HTMLElement} navMenu The navbar container.\n */\n const moveOutMoreMenu = (navMenu) => {\n\n if (navMenu === null) {\n return;\n }\n\n // Filter the available menus to move inside of more menu.\n var li = Array.from(navMenu.children).reverse().filter(\n (e) => !e.classList.contains(Selectors.forceOut) && !e.classList.contains(Selectors.dropDownMenu));\n\n // Alternate menus are not available for move to moremenu, stop make the menus move to outside.\n if (li.length < 1) {\n return;\n }\n\n var outMenus = navMenu.querySelectorAll(Selectors.classes.forceOut);\n var menuslist = [];\n\n if (outMenus === null) {\n return;\n }\n\n outMenus.forEach((menu) => {\n menu.querySelector('a').classList.remove(Selectors.dropDownItem);\n menu.querySelector('a').classList.add(Selectors.navLink);\n\n menuslist.push(menu);\n menu.parentNode.removeChild(menu);\n });\n\n // Insert the stored menus before the more menu.\n var moveMenus = [];\n menuslist.forEach((menu) => {\n if (navMenu.insertBefore(menu, navMenu.lastElementChild) && li.length > 0) {\n // Instead of move into moremenu, place the menus before the moremenu will moved to moremenu by moremenu.js.\n moveMenus.push(li.shift());\n }\n });\n\n // Move the non forced more menu before the moremenu to make the menu alignment.\n moveMenus.forEach((menu) => {\n navMenu.insertBefore(menu, navMenu.lastElementChild);\n });\n\n window.dispatchEvent(new Event('resize')); // Dispatch the resize event to create more menu.\n\n // After the menus are move out, rearrange menus to its original positions.\n reArrangeMenuOrgPositions(navMenu);\n };\n\n return {\n init: () => {\n addSubmenu();\n cardScroll();\n autoCollapse();\n }\n };\n});\n"],"names":["define","$","Selectors","dropDownMenuList","forceOut","hideSubmenus","target","visibleMenu","document","querySelectorAll","forEach","el","classList","remove","setOutMenuPositions","navMenu","li","Array","from","children","filter","e","contains","position","menu","indexOf","dataset","orgposition","moreMenu","querySelector","moveOutMoreMenu","reverse","length","outMenus","menuslist","add","push","parentNode","removeChild","moveMenus","insertBefore","lastElementChild","shift","window","dispatchEvent","Event","sort","a","b","appendChild","reArrangeMenuOrgPositions","init","submenu","item","addEventListener","currentTarget","toggle","stopPropagation","on","submenus","relatedTarget","dropdown","closest","subMenu","dropdownMenu","helpIcon","icon","addSubmenu","cards","scrollStart","scrollMoved","startPos","scrollPos","card","scrollElement","pageX","scrollLeft","preventDefault","scroll","cardScroll","primaryNav","undefined","menuBar","onresize","isTrusted","autoCollapse"],"mappings":"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"} \ No newline at end of file diff --git a/amd/src/smartmenu.js b/amd/src/smartmenu.js index 892b105dc9b..3c1489ffe65 100644 --- a/amd/src/smartmenu.js +++ b/amd/src/smartmenu.js @@ -22,6 +22,18 @@ */ define(["jquery", "core/moremenu"], function($) { + + const Selectors = { + dropDownMenu: "dropdownmoremenu", + forceOut: "force-menu-out", + navLink: "nav-link", + dropDownItem: "dropdown-item", + classes: { + dropDownMenuList: ".dropdownmoremenu ul.dropdown-menu", + forceOut: ".dropdownmoremenu .force-menu-out" + } + }; + /** * Implement the second level of submenu support. * Find the submenus inside the dropdown, add an event listener for click event which - on the click - shows the submenu list. @@ -160,17 +172,77 @@ define(["jquery", "core/moremenu"], function($) { */ const autoCollapse = () => { var primaryNav = document.querySelector('.primary-navigation ul.more-nav'); - moveOutMoreMenu(primaryNav); + if (primaryNav != undefined) { + setOutMenuPositions(primaryNav); // Create a data flag to maintain the original position of the menus. + moveOutMoreMenu(primaryNav); + } + var menuBar = document.querySelector('nav.menubar ul.more-nav'); - moveOutMoreMenu(menuBar); + if (menuBar != undefined) { + setOutMenuPositions(menuBar); + moveOutMoreMenu(menuBar); + } + + window.onresize = (e) => { + // Verify the event is original by browser resize. + if (e.isTrusted) { + moveOutMoreMenu(primaryNav); + moveOutMoreMenu(menuBar); + } + }; + }; + + /** + * Finds and sets the positions of all menus before moving them, + * helping to maintain the positions of the menus after being moved out from the moremenu. + * + * @param {HTMLElement} navMenu The navbar container. + */ + const setOutMenuPositions = (navMenu) => { + + if (navMenu === undefined || navMenu === null) { + return; + } + + // Find all menu items excluding the dropdownmoremenu class. + var li = Array.from(navMenu.children).filter((e) => !e.classList.contains(Selectors.dropDownMenu)); + + // Initialize the position variable. + var position = 0; + + // Loop through each menu item and set its original position. + li.forEach((menu) => { + position = li.indexOf(menu); + menu.dataset.orgposition = position; // Store the original position in the menu's dataset. + }); + + // Maintain the positions of the menus inside the moremenu from the last position of the outside menus. + var moreMenu = navMenu.querySelector(Selectors.classes.dropDownMenuList); + Array.from(moreMenu.children).forEach((menu) => { + menu.dataset.orgposition = position++; + }); + }; + + /** + * Rearranges the menus placed outside the more menu based on their original positions. + * + * @param {HTMLElement} navMenu The navbar container. + */ + const reArrangeMenuOrgPositions = (navMenu) => { + // Retrieve all menu items and sort them based on their original positions. + var li = Array.from(navMenu.children).sort((a, b) => a.dataset.orgposition - b.dataset.orgposition); + // Append the sorted menu items back to the navbar container. + li.forEach((menu) => navMenu.appendChild(menu)); }; /** * Move the items from more menu, items which is set to force outside more menu. * Remove those items from more menu and insert the menu before the last normal item. * Find the length and children's length to insert the out menus in that positions. + * Move the non forced more menu to moremenu to make the menu alignment. * Rerun the more menu it will more the other normal menus into more menu to fix the alignmenu issue. + * After the menus are move out, rearrange menus to its original positions. * * @param {HTMLElement} navMenu The navbar container. */ @@ -180,7 +252,16 @@ define(["jquery", "core/moremenu"], function($) { return; } - var outMenus = navMenu.querySelectorAll('.dropdownmoremenu .force-menu-out'); + // Filter the available menus to move inside of more menu. + var li = Array.from(navMenu.children).reverse().filter( + (e) => !e.classList.contains(Selectors.forceOut) && !e.classList.contains(Selectors.dropDownMenu)); + + // Alternate menus are not available for move to moremenu, stop make the menus move to outside. + if (li.length < 1) { + return; + } + + var outMenus = navMenu.querySelectorAll(Selectors.classes.forceOut); var menuslist = []; if (outMenus === null) { @@ -188,19 +269,31 @@ define(["jquery", "core/moremenu"], function($) { } outMenus.forEach((menu) => { - menu.querySelector('a').classList.remove('dropdown-item'); - menu.querySelector('a').classList.add('nav-link'); + menu.querySelector('a').classList.remove(Selectors.dropDownItem); + menu.querySelector('a').classList.add(Selectors.navLink); menuslist.push(menu); menu.parentNode.removeChild(menu); }); - // Find the length and children's length to insert the out menus in that positions. - var length = menuslist.length; - var navLength = navMenu.children.length - 1; // Remove more menu. - var newPosition = navLength - length || 0; + // Insert the stored menus before the more menu. - menuslist.forEach((menu) => navMenu.insertBefore(menu, navMenu.children[newPosition])); + var moveMenus = []; + menuslist.forEach((menu) => { + if (navMenu.insertBefore(menu, navMenu.lastElementChild) && li.length > 0) { + // Instead of move into moremenu, place the menus before the moremenu will moved to moremenu by moremenu.js. + moveMenus.push(li.shift()); + } + }); + + // Move the non forced more menu before the moremenu to make the menu alignment. + moveMenus.forEach((menu) => { + navMenu.insertBefore(menu, navMenu.lastElementChild); + }); + window.dispatchEvent(new Event('resize')); // Dispatch the resize event to create more menu. + + // After the menus are move out, rearrange menus to its original positions. + reArrangeMenuOrgPositions(navMenu); }; return { diff --git a/lang/en/theme_boost_union.php b/lang/en/theme_boost_union.php index c75e002a02e..57ee7435e0f 100644 --- a/lang/en/theme_boost_union.php +++ b/lang/en/theme_boost_union.php @@ -1163,7 +1163,7 @@ $string['smartmenusmenumode'] = 'Menu mode'; $string['smartmenusmenumode_help'] = '

Select the mode how the menu\'s items should be displayed.

  • Submenu: The menu items is displayed as a submenu with the menu\'s title as parent node. This is the default option.
  • Inline: The menu\'s items are displayed directly in the navigation, one after another. Please note that this option is not supported for card type menus.
'; $string['smartmenusmenumoremenubehavior'] = 'More menu behavior'; -$string['smartmenusmenumoremenubehavior_help'] = '

Select what should happen if there are too many menus to fit in the menu location.

  • Do not change anything: No particular behaviour will be enforced, excess menus will be moved into the \'More\' menu automatically.
  • Force into more menu: This mode moves the menu directly into the \'More\' menu even if there would still be space.
  • Keep outside of more menu: This mode keeps the menu outside of the \'More\' menu as long as possible.
'; +$string['smartmenusmenumoremenubehavior_help'] = '

Select what should happen if there are too many menus to fit in the menu location.

  • Do not change anything: No particular behaviour will be enforced, excess menus will be moved into the \'More\' menu automatically.
  • Force into more menu: This mode moves the menu directly into the \'More\' menu even if there would still be space.
  • Keep outside of more menu: This mode keeps the menu outside of the \'More\' menu as long as possible – moving other subsequent menus to the more menu instead if needed.

Please note that this setting only affects menus which are located in the main navigation or in the menu bar area.

'; $string['smartmenusmenumoremenubehaviorforceinto'] = 'Force into more menu'; $string['smartmenusmenumoremenubehaviorkeepoutside'] = 'Keep outside of more menu'; $string['smartmenusmenunothingtodisplay'] = 'There aren\'t any smart menus created yet. Please create your first smart menu to get things going.'; diff --git a/templates/smartmenus-cardmenu-children.mustache b/templates/smartmenus-cardmenu-children.mustache index cbfd42bc969..eee721104e5 100644 --- a/templates/smartmenus-cardmenu-children.mustache +++ b/templates/smartmenus-cardmenu-children.mustache @@ -83,7 +83,7 @@ {{/haschildren}} {{^haschildren}} -