614574ef72640d6167b07ab13a9e7976f51220a4
[yaffs-website] / web / core / modules / toolbar / js / toolbar.menu.es6.js
1 /**
2  * @file
3  * Builds a nested accordion widget.
4  *
5  * Invoke on an HTML list element with the jQuery plugin pattern.
6  *
7  * @example
8  * $('.toolbar-menu').drupalToolbarMenu();
9  */
10
11 (function($, Drupal, drupalSettings) {
12   /**
13    * Store the open menu tray.
14    */
15   let activeItem = Drupal.url(drupalSettings.path.currentPath);
16
17   $.fn.drupalToolbarMenu = function() {
18     const ui = {
19       handleOpen: Drupal.t('Extend'),
20       handleClose: Drupal.t('Collapse'),
21     };
22
23     /**
24      * Toggle the open/close state of a list is a menu.
25      *
26      * @param {jQuery} $item
27      *   The li item to be toggled.
28      *
29      * @param {Boolean} switcher
30      *   A flag that forces toggleClass to add or a remove a class, rather than
31      *   simply toggling its presence.
32      */
33     function toggleList($item, switcher) {
34       const $toggle = $item
35         .children('.toolbar-box')
36         .children('.toolbar-handle');
37       switcher =
38         typeof switcher !== 'undefined' ? switcher : !$item.hasClass('open');
39       // Toggle the item open state.
40       $item.toggleClass('open', switcher);
41       // Twist the toggle.
42       $toggle.toggleClass('open', switcher);
43       // Adjust the toggle text.
44       $toggle
45         .find('.action')
46         // Expand Structure, Collapse Structure.
47         .text(switcher ? ui.handleClose : ui.handleOpen);
48     }
49
50     /**
51      * Handle clicks from the disclosure button on an item with sub-items.
52      *
53      * @param {Object} event
54      *   A jQuery Event object.
55      */
56     function toggleClickHandler(event) {
57       const $toggle = $(event.target);
58       const $item = $toggle.closest('li');
59       // Toggle the list item.
60       toggleList($item);
61       // Close open sibling menus.
62       const $openItems = $item.siblings().filter('.open');
63       toggleList($openItems, false);
64     }
65
66     /**
67      * Handle clicks from a menu item link.
68      *
69      * @param {Object} event
70      *   A jQuery Event object.
71      */
72     function linkClickHandler(event) {
73       // If the toolbar is positioned fixed (and therefore hiding content
74       // underneath), then users expect clicks in the administration menu tray
75       // to take them to that destination but for the menu tray to be closed
76       // after clicking: otherwise the toolbar itself is obstructing the view
77       // of the destination they chose.
78       if (!Drupal.toolbar.models.toolbarModel.get('isFixed')) {
79         Drupal.toolbar.models.toolbarModel.set('activeTab', null);
80       }
81       // Stopping propagation to make sure that once a toolbar-box is clicked
82       // (the whitespace part), the page is not redirected anymore.
83       event.stopPropagation();
84     }
85
86     /**
87      * Add markup to the menu elements.
88      *
89      * Items with sub-elements have a list toggle attached to them. Menu item
90      * links and the corresponding list toggle are wrapped with in a div
91      * classed with .toolbar-box. The .toolbar-box div provides a positioning
92      * context for the item list toggle.
93      *
94      * @param {jQuery} $menu
95      *   The root of the menu to be initialized.
96      */
97     function initItems($menu) {
98       const options = {
99         class: 'toolbar-icon toolbar-handle',
100         action: ui.handleOpen,
101         text: '',
102       };
103       // Initialize items and their links.
104       $menu.find('li > a').wrap('<div class="toolbar-box">');
105       // Add a handle to each list item if it has a menu.
106       $menu.find('li').each((index, element) => {
107         const $item = $(element);
108         if ($item.children('ul.toolbar-menu').length) {
109           const $box = $item.children('.toolbar-box');
110           options.text = Drupal.t('@label', {
111             '@label': $box.find('a').text(),
112           });
113           $item
114             .children('.toolbar-box')
115             .append(Drupal.theme('toolbarMenuItemToggle', options));
116         }
117       });
118     }
119
120     /**
121      * Adds a level class to each list based on its depth in the menu.
122      *
123      * This function is called recursively on each sub level of lists elements
124      * until the depth of the menu is exhausted.
125      *
126      * @param {jQuery} $lists
127      *   A jQuery object of ul elements.
128      *
129      * @param {number} level
130      *   The current level number to be assigned to the list elements.
131      */
132     function markListLevels($lists, level) {
133       level = !level ? 1 : level;
134       const $lis = $lists.children('li').addClass(`level-${level}`);
135       $lists = $lis.children('ul');
136       if ($lists.length) {
137         markListLevels($lists, level + 1);
138       }
139     }
140
141     /**
142      * On page load, open the active menu item.
143      *
144      * Marks the trail of the active link in the menu back to the root of the
145      * menu with .menu-item--active-trail.
146      *
147      * @param {jQuery} $menu
148      *   The root of the menu.
149      */
150     function openActiveItem($menu) {
151       const pathItem = $menu.find(`a[href="${window.location.pathname}"]`);
152       if (pathItem.length && !activeItem) {
153         activeItem = window.location.pathname;
154       }
155       if (activeItem) {
156         const $activeItem = $menu
157           .find(`a[href="${activeItem}"]`)
158           .addClass('menu-item--active');
159         const $activeTrail = $activeItem
160           .parentsUntil('.root', 'li')
161           .addClass('menu-item--active-trail');
162         toggleList($activeTrail, true);
163       }
164     }
165
166     // Return the jQuery object.
167     return this.each(function(selector) {
168       const $menu = $(this).once('toolbar-menu');
169       if ($menu.length) {
170         // Bind event handlers.
171         $menu
172           .on('click.toolbar', '.toolbar-box', toggleClickHandler)
173           .on('click.toolbar', '.toolbar-box a', linkClickHandler);
174
175         $menu.addClass('root');
176         initItems($menu);
177         markListLevels($menu);
178         // Restore previous and active states.
179         openActiveItem($menu);
180       }
181     });
182   };
183
184   /**
185    * A toggle is an interactive element often bound to a click handler.
186    *
187    * @param {object} options
188    *   Options for the button.
189    * @param {string} options.class
190    *   Class to set on the button.
191    * @param {string} options.action
192    *   Action for the button.
193    * @param {string} options.text
194    *   Used as label for the button.
195    *
196    * @return {string}
197    *   A string representing a DOM fragment.
198    */
199   Drupal.theme.toolbarMenuItemToggle = function(options) {
200     return `<button class="${options.class}"><span class="action">${
201       options.action
202     }</span> <span class="label">${options.text}</span></button>`;
203   };
204 })(jQuery, Drupal, drupalSettings);