/** * @file * Builds a nested accordion widget. * * Invoke on an HTML list element with the jQuery plugin pattern. * * @example * $('.toolbar-menu').drupalToolbarMenu(); */ (function ($, Drupal, drupalSettings) { /** * Store the open menu tray. */ let activeItem = Drupal.url(drupalSettings.path.currentPath); $.fn.drupalToolbarMenu = function () { const ui = { handleOpen: Drupal.t('Extend'), handleClose: Drupal.t('Collapse'), }; /** * Handle clicks from the disclosure button on an item with sub-items. * * @param {Object} event * A jQuery Event object. */ function toggleClickHandler(event) { const $toggle = $(event.target); const $item = $toggle.closest('li'); // Toggle the list item. toggleList($item); // Close open sibling menus. const $openItems = $item.siblings().filter('.open'); toggleList($openItems, false); } /** * Handle clicks from a menu item link. * * @param {Object} event * A jQuery Event object. */ function linkClickHandler(event) { // If the toolbar is positioned fixed (and therefore hiding content // underneath), then users expect clicks in the administration menu tray // to take them to that destination but for the menu tray to be closed // after clicking: otherwise the toolbar itself is obstructing the view // of the destination they chose. if (!Drupal.toolbar.models.toolbarModel.get('isFixed')) { Drupal.toolbar.models.toolbarModel.set('activeTab', null); } // Stopping propagation to make sure that once a toolbar-box is clicked // (the whitespace part), the page is not redirected anymore. event.stopPropagation(); } /** * Toggle the open/close state of a list is a menu. * * @param {jQuery} $item * The li item to be toggled. * * @param {Boolean} switcher * A flag that forces toggleClass to add or a remove a class, rather than * simply toggling its presence. */ function toggleList($item, switcher) { const $toggle = $item.children('.toolbar-box').children('.toolbar-handle'); switcher = (typeof switcher !== 'undefined') ? switcher : !$item.hasClass('open'); // Toggle the item open state. $item.toggleClass('open', switcher); // Twist the toggle. $toggle.toggleClass('open', switcher); // Adjust the toggle text. $toggle .find('.action') // Expand Structure, Collapse Structure. .text((switcher) ? ui.handleClose : ui.handleOpen); } /** * Add markup to the menu elements. * * Items with sub-elements have a list toggle attached to them. Menu item * links and the corresponding list toggle are wrapped with in a div * classed with .toolbar-box. The .toolbar-box div provides a positioning * context for the item list toggle. * * @param {jQuery} $menu * The root of the menu to be initialized. */ function initItems($menu) { const options = { class: 'toolbar-icon toolbar-handle', action: ui.handleOpen, text: '', }; // Initialize items and their links. $menu.find('li > a').wrap('
'); // Add a handle to each list item if it has a menu. $menu.find('li').each((index, element) => { const $item = $(element); if ($item.children('ul.toolbar-menu').length) { const $box = $item.children('.toolbar-box'); options.text = Drupal.t('@label', { '@label': $box.find('a').text() }); $item.children('.toolbar-box') .append(Drupal.theme('toolbarMenuItemToggle', options)); } }); } /** * Adds a level class to each list based on its depth in the menu. * * This function is called recursively on each sub level of lists elements * until the depth of the menu is exhausted. * * @param {jQuery} $lists * A jQuery object of ul elements. * * @param {number} level * The current level number to be assigned to the list elements. */ function markListLevels($lists, level) { level = (!level) ? 1 : level; const $lis = $lists.children('li').addClass(`level-${level}`); $lists = $lis.children('ul'); if ($lists.length) { markListLevels($lists, level + 1); } } /** * On page load, open the active menu item. * * Marks the trail of the active link in the menu back to the root of the * menu with .menu-item--active-trail. * * @param {jQuery} $menu * The root of the menu. */ function openActiveItem($menu) { const pathItem = $menu.find(`a[href="${location.pathname}"]`); if (pathItem.length && !activeItem) { activeItem = location.pathname; } if (activeItem) { const $activeItem = $menu.find(`a[href="${activeItem}"]`).addClass('menu-item--active'); const $activeTrail = $activeItem.parentsUntil('.root', 'li').addClass('menu-item--active-trail'); toggleList($activeTrail, true); } } // Return the jQuery object. return this.each(function (selector) { const $menu = $(this).once('toolbar-menu'); if ($menu.length) { // Bind event handlers. $menu .on('click.toolbar', '.toolbar-box', toggleClickHandler) .on('click.toolbar', '.toolbar-box a', linkClickHandler); $menu.addClass('root'); initItems($menu); markListLevels($menu); // Restore previous and active states. openActiveItem($menu); } }); }; /** * A toggle is an interactive element often bound to a click handler. * * @param {object} options * Options for the button. * @param {string} options.class * Class to set on the button. * @param {string} options.action * Action for the button. * @param {string} options.text * Used as label for the button. * * @return {string} * A string representing a DOM fragment. */ Drupal.theme.toolbarMenuItemToggle = function (options) { return ``; }; }(jQuery, Drupal, drupalSettings));