Chris@0
|
1 /**
|
Chris@0
|
2 * @file
|
Chris@0
|
3 * Defines the behavior of the Drupal administration toolbar.
|
Chris@0
|
4 */
|
Chris@0
|
5
|
Chris@17
|
6 (function($, Drupal, drupalSettings) {
|
Chris@0
|
7 // Merge run-time settings with the defaults.
|
Chris@0
|
8 const options = $.extend(
|
Chris@0
|
9 {
|
Chris@0
|
10 breakpoints: {
|
Chris@0
|
11 'toolbar.narrow': '',
|
Chris@0
|
12 'toolbar.standard': '',
|
Chris@0
|
13 'toolbar.wide': '',
|
Chris@0
|
14 },
|
Chris@0
|
15 },
|
Chris@0
|
16 drupalSettings.toolbar,
|
Chris@0
|
17 // Merge strings on top of drupalSettings so that they are not mutable.
|
Chris@0
|
18 {
|
Chris@0
|
19 strings: {
|
Chris@0
|
20 horizontal: Drupal.t('Horizontal orientation'),
|
Chris@0
|
21 vertical: Drupal.t('Vertical orientation'),
|
Chris@0
|
22 },
|
Chris@0
|
23 },
|
Chris@0
|
24 );
|
Chris@0
|
25
|
Chris@0
|
26 /**
|
Chris@0
|
27 * Registers tabs with the toolbar.
|
Chris@0
|
28 *
|
Chris@0
|
29 * The Drupal toolbar allows modules to register top-level tabs. These may
|
Chris@0
|
30 * point directly to a resource or toggle the visibility of a tray.
|
Chris@0
|
31 *
|
Chris@0
|
32 * Modules register tabs with hook_toolbar().
|
Chris@0
|
33 *
|
Chris@0
|
34 * @type {Drupal~behavior}
|
Chris@0
|
35 *
|
Chris@0
|
36 * @prop {Drupal~behaviorAttach} attach
|
Chris@0
|
37 * Attaches the toolbar rendering functionality to the toolbar element.
|
Chris@0
|
38 */
|
Chris@0
|
39 Drupal.behaviors.toolbar = {
|
Chris@0
|
40 attach(context) {
|
Chris@0
|
41 // Verify that the user agent understands media queries. Complex admin
|
Chris@0
|
42 // toolbar layouts require media query support.
|
Chris@0
|
43 if (!window.matchMedia('only screen').matches) {
|
Chris@0
|
44 return;
|
Chris@0
|
45 }
|
Chris@0
|
46 // Process the administrative toolbar.
|
Chris@17
|
47 $(context)
|
Chris@17
|
48 .find('#toolbar-administration')
|
Chris@17
|
49 .once('toolbar')
|
Chris@17
|
50 .each(function() {
|
Chris@17
|
51 // Establish the toolbar models and views.
|
Chris@17
|
52 const model = new Drupal.toolbar.ToolbarModel({
|
Chris@17
|
53 locked: JSON.parse(
|
Chris@17
|
54 localStorage.getItem('Drupal.toolbar.trayVerticalLocked'),
|
Chris@17
|
55 ),
|
Chris@17
|
56 activeTab: document.getElementById(
|
Chris@17
|
57 JSON.parse(localStorage.getItem('Drupal.toolbar.activeTabID')),
|
Chris@17
|
58 ),
|
Chris@17
|
59 height: $('#toolbar-administration').outerHeight(),
|
Chris@0
|
60 });
|
Chris@0
|
61
|
Chris@17
|
62 Drupal.toolbar.models.toolbarModel = model;
|
Chris@17
|
63
|
Chris@17
|
64 // Attach a listener to the configured media query breakpoints.
|
Chris@17
|
65 // Executes it before Drupal.toolbar.views to avoid extra rendering.
|
Chris@17
|
66 Object.keys(options.breakpoints).forEach(label => {
|
Chris@17
|
67 const mq = options.breakpoints[label];
|
Chris@17
|
68 const mql = window.matchMedia(mq);
|
Chris@17
|
69 Drupal.toolbar.mql[label] = mql;
|
Chris@17
|
70 // Curry the model and the label of the media query breakpoint to
|
Chris@17
|
71 // the mediaQueryChangeHandler function.
|
Chris@17
|
72 mql.addListener(
|
Chris@17
|
73 Drupal.toolbar.mediaQueryChangeHandler.bind(null, model, label),
|
Chris@17
|
74 );
|
Chris@17
|
75 // Fire the mediaQueryChangeHandler for each configured breakpoint
|
Chris@17
|
76 // so that they process once.
|
Chris@17
|
77 Drupal.toolbar.mediaQueryChangeHandler.call(
|
Chris@17
|
78 null,
|
Chris@17
|
79 model,
|
Chris@17
|
80 label,
|
Chris@17
|
81 mql,
|
Chris@17
|
82 );
|
Chris@0
|
83 });
|
Chris@0
|
84
|
Chris@17
|
85 Drupal.toolbar.views.toolbarVisualView = new Drupal.toolbar.ToolbarVisualView(
|
Chris@17
|
86 {
|
Chris@17
|
87 el: this,
|
Chris@17
|
88 model,
|
Chris@17
|
89 strings: options.strings,
|
Chris@17
|
90 },
|
Chris@17
|
91 );
|
Chris@17
|
92 Drupal.toolbar.views.toolbarAuralView = new Drupal.toolbar.ToolbarAuralView(
|
Chris@17
|
93 {
|
Chris@17
|
94 el: this,
|
Chris@17
|
95 model,
|
Chris@17
|
96 strings: options.strings,
|
Chris@17
|
97 },
|
Chris@17
|
98 );
|
Chris@17
|
99 Drupal.toolbar.views.bodyVisualView = new Drupal.toolbar.BodyVisualView(
|
Chris@17
|
100 {
|
Chris@17
|
101 el: this,
|
Chris@17
|
102 model,
|
Chris@17
|
103 },
|
Chris@17
|
104 );
|
Chris@17
|
105
|
Chris@17
|
106 // Force layout render to fix mobile view. Only needed on load, not
|
Chris@17
|
107 // for every media query match.
|
Chris@17
|
108 model.trigger('change:isFixed', model, model.get('isFixed'));
|
Chris@17
|
109 model.trigger('change:activeTray', model, model.get('activeTray'));
|
Chris@17
|
110
|
Chris@17
|
111 // Render collapsible menus.
|
Chris@17
|
112 const menuModel = new Drupal.toolbar.MenuModel();
|
Chris@17
|
113 Drupal.toolbar.models.menuModel = menuModel;
|
Chris@17
|
114 Drupal.toolbar.views.menuVisualView = new Drupal.toolbar.MenuVisualView(
|
Chris@17
|
115 {
|
Chris@17
|
116 el: $(this)
|
Chris@17
|
117 .find('.toolbar-menu-administration')
|
Chris@17
|
118 .get(0),
|
Chris@17
|
119 model: menuModel,
|
Chris@17
|
120 strings: options.strings,
|
Chris@17
|
121 },
|
Chris@17
|
122 );
|
Chris@17
|
123
|
Chris@17
|
124 // Handle the resolution of Drupal.toolbar.setSubtrees.
|
Chris@17
|
125 // This is handled with a deferred so that the function may be invoked
|
Chris@17
|
126 // asynchronously.
|
Chris@17
|
127 Drupal.toolbar.setSubtrees.done(subtrees => {
|
Chris@17
|
128 menuModel.set('subtrees', subtrees);
|
Chris@17
|
129 const theme = drupalSettings.ajaxPageState.theme;
|
Chris@17
|
130 localStorage.setItem(
|
Chris@17
|
131 `Drupal.toolbar.subtrees.${theme}`,
|
Chris@17
|
132 JSON.stringify(subtrees),
|
Chris@17
|
133 );
|
Chris@17
|
134 // Indicate on the toolbarModel that subtrees are now loaded.
|
Chris@17
|
135 model.set('areSubtreesLoaded', true);
|
Chris@0
|
136 });
|
Chris@17
|
137
|
Chris@17
|
138 // Trigger an initial attempt to load menu subitems. This first attempt
|
Chris@17
|
139 // is made after the media query handlers have had an opportunity to
|
Chris@17
|
140 // process. The toolbar starts in the vertical orientation by default,
|
Chris@17
|
141 // unless the viewport is wide enough to accommodate a horizontal
|
Chris@17
|
142 // orientation. Thus we give the Toolbar a chance to determine if it
|
Chris@17
|
143 // should be set to horizontal orientation before attempting to load
|
Chris@17
|
144 // menu subtrees.
|
Chris@17
|
145 Drupal.toolbar.views.toolbarVisualView.loadSubtrees();
|
Chris@17
|
146
|
Chris@17
|
147 $(document)
|
Chris@17
|
148 // Update the model when the viewport offset changes.
|
Chris@17
|
149 .on('drupalViewportOffsetChange.toolbar', (event, offsets) => {
|
Chris@17
|
150 model.set('offsets', offsets);
|
Chris@17
|
151 });
|
Chris@17
|
152
|
Chris@17
|
153 // Broadcast model changes to other modules.
|
Chris@17
|
154 model
|
Chris@17
|
155 .on('change:orientation', (model, orientation) => {
|
Chris@17
|
156 $(document).trigger(
|
Chris@17
|
157 'drupalToolbarOrientationChange',
|
Chris@17
|
158 orientation,
|
Chris@17
|
159 );
|
Chris@17
|
160 })
|
Chris@17
|
161 .on('change:activeTab', (model, tab) => {
|
Chris@17
|
162 $(document).trigger('drupalToolbarTabChange', tab);
|
Chris@17
|
163 })
|
Chris@17
|
164 .on('change:activeTray', (model, tray) => {
|
Chris@17
|
165 $(document).trigger('drupalToolbarTrayChange', tray);
|
Chris@17
|
166 });
|
Chris@17
|
167
|
Chris@17
|
168 // If the toolbar's orientation is horizontal and no active tab is
|
Chris@17
|
169 // defined then show the tray of the first toolbar tab by default (but
|
Chris@17
|
170 // not the first 'Home' toolbar tab).
|
Chris@17
|
171 if (
|
Chris@17
|
172 Drupal.toolbar.models.toolbarModel.get('orientation') ===
|
Chris@17
|
173 'horizontal' &&
|
Chris@17
|
174 Drupal.toolbar.models.toolbarModel.get('activeTab') === null
|
Chris@17
|
175 ) {
|
Chris@17
|
176 Drupal.toolbar.models.toolbarModel.set({
|
Chris@17
|
177 activeTab: $(
|
Chris@17
|
178 '.toolbar-bar .toolbar-tab:not(.home-toolbar-tab) a',
|
Chris@17
|
179 ).get(0),
|
Chris@17
|
180 });
|
Chris@17
|
181 }
|
Chris@17
|
182
|
Chris@17
|
183 $(window).on({
|
Chris@17
|
184 'dialog:aftercreate': (event, dialog, $element, settings) => {
|
Chris@17
|
185 const $toolbar = $('#toolbar-bar');
|
Chris@17
|
186 $toolbar.css('margin-top', '0');
|
Chris@17
|
187
|
Chris@17
|
188 // When off-canvas is positioned in top, toolbar has to be moved down.
|
Chris@17
|
189 if (settings.drupalOffCanvasPosition === 'top') {
|
Chris@17
|
190 const height = Drupal.offCanvas
|
Chris@17
|
191 .getContainer($element)
|
Chris@17
|
192 .outerHeight();
|
Chris@17
|
193 $toolbar.css('margin-top', `${height}px`);
|
Chris@17
|
194
|
Chris@17
|
195 $element.on('dialogContentResize.off-canvas', () => {
|
Chris@17
|
196 const newHeight = Drupal.offCanvas
|
Chris@17
|
197 .getContainer($element)
|
Chris@17
|
198 .outerHeight();
|
Chris@17
|
199 $toolbar.css('margin-top', `${newHeight}px`);
|
Chris@17
|
200 });
|
Chris@17
|
201 }
|
Chris@17
|
202 },
|
Chris@17
|
203 'dialog:beforeclose': () => {
|
Chris@17
|
204 $('#toolbar-bar').css('margin-top', '0');
|
Chris@17
|
205 },
|
Chris@17
|
206 });
|
Chris@17
|
207 });
|
Chris@0
|
208 },
|
Chris@0
|
209 };
|
Chris@0
|
210
|
Chris@0
|
211 /**
|
Chris@0
|
212 * Toolbar methods of Backbone objects.
|
Chris@0
|
213 *
|
Chris@0
|
214 * @namespace
|
Chris@0
|
215 */
|
Chris@0
|
216 Drupal.toolbar = {
|
Chris@0
|
217 /**
|
Chris@0
|
218 * A hash of View instances.
|
Chris@0
|
219 *
|
Chris@0
|
220 * @type {object.<string, Backbone.View>}
|
Chris@0
|
221 */
|
Chris@0
|
222 views: {},
|
Chris@0
|
223
|
Chris@0
|
224 /**
|
Chris@0
|
225 * A hash of Model instances.
|
Chris@0
|
226 *
|
Chris@0
|
227 * @type {object.<string, Backbone.Model>}
|
Chris@0
|
228 */
|
Chris@0
|
229 models: {},
|
Chris@0
|
230
|
Chris@0
|
231 /**
|
Chris@0
|
232 * A hash of MediaQueryList objects tracked by the toolbar.
|
Chris@0
|
233 *
|
Chris@0
|
234 * @type {object.<string, object>}
|
Chris@0
|
235 */
|
Chris@0
|
236 mql: {},
|
Chris@0
|
237
|
Chris@0
|
238 /**
|
Chris@0
|
239 * Accepts a list of subtree menu elements.
|
Chris@0
|
240 *
|
Chris@0
|
241 * A deferred object that is resolved by an inlined JavaScript callback.
|
Chris@0
|
242 *
|
Chris@0
|
243 * @type {jQuery.Deferred}
|
Chris@0
|
244 *
|
Chris@0
|
245 * @see toolbar_subtrees_jsonp().
|
Chris@0
|
246 */
|
Chris@0
|
247 setSubtrees: new $.Deferred(),
|
Chris@0
|
248
|
Chris@0
|
249 /**
|
Chris@0
|
250 * Respond to configured narrow media query changes.
|
Chris@0
|
251 *
|
Chris@0
|
252 * @param {Drupal.toolbar.ToolbarModel} model
|
Chris@0
|
253 * A toolbar model
|
Chris@0
|
254 * @param {string} label
|
Chris@0
|
255 * Media query label.
|
Chris@0
|
256 * @param {object} mql
|
Chris@0
|
257 * A MediaQueryList object.
|
Chris@0
|
258 */
|
Chris@0
|
259 mediaQueryChangeHandler(model, label, mql) {
|
Chris@0
|
260 switch (label) {
|
Chris@0
|
261 case 'toolbar.narrow':
|
Chris@0
|
262 model.set({
|
Chris@0
|
263 isOriented: mql.matches,
|
Chris@0
|
264 isTrayToggleVisible: false,
|
Chris@0
|
265 });
|
Chris@0
|
266 // If the toolbar doesn't have an explicit orientation yet, or if the
|
Chris@0
|
267 // narrow media query doesn't match then set the orientation to
|
Chris@0
|
268 // vertical.
|
Chris@0
|
269 if (!mql.matches || !model.get('orientation')) {
|
Chris@0
|
270 model.set({ orientation: 'vertical' }, { validate: true });
|
Chris@0
|
271 }
|
Chris@0
|
272 break;
|
Chris@0
|
273
|
Chris@0
|
274 case 'toolbar.standard':
|
Chris@0
|
275 model.set({
|
Chris@0
|
276 isFixed: mql.matches,
|
Chris@0
|
277 });
|
Chris@0
|
278 break;
|
Chris@0
|
279
|
Chris@0
|
280 case 'toolbar.wide':
|
Chris@17
|
281 model.set(
|
Chris@17
|
282 {
|
Chris@17
|
283 orientation:
|
Chris@17
|
284 mql.matches && !model.get('locked') ? 'horizontal' : 'vertical',
|
Chris@17
|
285 },
|
Chris@17
|
286 { validate: true },
|
Chris@17
|
287 );
|
Chris@0
|
288 // The tray orientation toggle visibility does not need to be
|
Chris@0
|
289 // validated.
|
Chris@0
|
290 model.set({
|
Chris@0
|
291 isTrayToggleVisible: mql.matches,
|
Chris@0
|
292 });
|
Chris@0
|
293 break;
|
Chris@0
|
294
|
Chris@0
|
295 default:
|
Chris@0
|
296 break;
|
Chris@0
|
297 }
|
Chris@0
|
298 },
|
Chris@0
|
299 };
|
Chris@0
|
300
|
Chris@0
|
301 /**
|
Chris@0
|
302 * A toggle is an interactive element often bound to a click handler.
|
Chris@0
|
303 *
|
Chris@0
|
304 * @return {string}
|
Chris@0
|
305 * A string representing a DOM fragment.
|
Chris@0
|
306 */
|
Chris@17
|
307 Drupal.theme.toolbarOrientationToggle = function() {
|
Chris@17
|
308 return (
|
Chris@17
|
309 '<div class="toolbar-toggle-orientation"><div class="toolbar-lining">' +
|
Chris@0
|
310 '<button class="toolbar-icon" type="button"></button>' +
|
Chris@17
|
311 '</div></div>'
|
Chris@17
|
312 );
|
Chris@0
|
313 };
|
Chris@0
|
314
|
Chris@0
|
315 /**
|
Chris@0
|
316 * Ajax command to set the toolbar subtrees.
|
Chris@0
|
317 *
|
Chris@0
|
318 * @param {Drupal.Ajax} ajax
|
Chris@0
|
319 * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
|
Chris@0
|
320 * @param {object} response
|
Chris@0
|
321 * JSON response from the Ajax request.
|
Chris@0
|
322 * @param {number} [status]
|
Chris@0
|
323 * XMLHttpRequest status.
|
Chris@0
|
324 */
|
Chris@17
|
325 Drupal.AjaxCommands.prototype.setToolbarSubtrees = function(
|
Chris@17
|
326 ajax,
|
Chris@17
|
327 response,
|
Chris@17
|
328 status,
|
Chris@17
|
329 ) {
|
Chris@0
|
330 Drupal.toolbar.setSubtrees.resolve(response.subtrees);
|
Chris@0
|
331 };
|
Chris@17
|
332 })(jQuery, Drupal, drupalSettings);
|