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