annotate core/modules/toolbar/js/views/ToolbarVisualView.es6.js @ 5:12f9dff5fda9 tip

Update to Drupal core 8.7.1
author Chris Cannam
date Thu, 09 May 2019 15:34:47 +0100
parents a9cd425dd02b
children
rev   line source
Chris@0 1 /**
Chris@0 2 * @file
Chris@0 3 * A Backbone view for the toolbar element. Listens to mouse & touch.
Chris@0 4 */
Chris@0 5
Chris@4 6 (function($, Drupal, drupalSettings, Backbone) {
Chris@4 7 Drupal.toolbar.ToolbarVisualView = Backbone.View.extend(
Chris@4 8 /** @lends Drupal.toolbar.ToolbarVisualView# */ {
Chris@4 9 /**
Chris@4 10 * Event map for the `ToolbarVisualView`.
Chris@4 11 *
Chris@4 12 * @return {object}
Chris@4 13 * A map of events.
Chris@4 14 */
Chris@4 15 events() {
Chris@4 16 // Prevents delay and simulated mouse events.
Chris@4 17 const touchEndToClick = function(event) {
Chris@4 18 event.preventDefault();
Chris@4 19 event.target.click();
Chris@4 20 };
Chris@0 21
Chris@4 22 return {
Chris@4 23 'click .toolbar-bar .toolbar-tab .trigger': 'onTabClick',
Chris@4 24 'click .toolbar-toggle-orientation button':
Chris@4 25 'onOrientationToggleClick',
Chris@4 26 'touchend .toolbar-bar .toolbar-tab .trigger': touchEndToClick,
Chris@4 27 'touchend .toolbar-toggle-orientation button': touchEndToClick,
Chris@4 28 };
Chris@4 29 },
Chris@0 30
Chris@4 31 /**
Chris@4 32 * Backbone view for the toolbar element. Listens to mouse & touch.
Chris@4 33 *
Chris@4 34 * @constructs
Chris@4 35 *
Chris@4 36 * @augments Backbone.View
Chris@4 37 *
Chris@4 38 * @param {object} options
Chris@4 39 * Options for the view object.
Chris@4 40 * @param {object} options.strings
Chris@4 41 * Various strings to use in the view.
Chris@4 42 */
Chris@4 43 initialize(options) {
Chris@4 44 this.strings = options.strings;
Chris@0 45
Chris@4 46 this.listenTo(
Chris@4 47 this.model,
Chris@4 48 'change:activeTab change:orientation change:isOriented change:isTrayToggleVisible',
Chris@4 49 this.render,
Chris@4 50 );
Chris@4 51 this.listenTo(this.model, 'change:mqMatches', this.onMediaQueryChange);
Chris@4 52 this.listenTo(this.model, 'change:offsets', this.adjustPlacement);
Chris@4 53 this.listenTo(
Chris@4 54 this.model,
Chris@4 55 'change:activeTab change:orientation change:isOriented',
Chris@4 56 this.updateToolbarHeight,
Chris@4 57 );
Chris@0 58
Chris@4 59 // Add the tray orientation toggles.
Chris@4 60 this.$el
Chris@4 61 .find('.toolbar-tray .toolbar-lining')
Chris@4 62 .append(Drupal.theme('toolbarOrientationToggle'));
Chris@0 63
Chris@4 64 // Trigger an activeTab change so that listening scripts can respond on
Chris@4 65 // page load. This will call render.
Chris@4 66 this.model.trigger('change:activeTab');
Chris@4 67 },
Chris@0 68
Chris@4 69 /**
Chris@4 70 * Update the toolbar element height.
Chris@4 71 *
Chris@4 72 * @constructs
Chris@4 73 *
Chris@4 74 * @augments Backbone.View
Chris@4 75 */
Chris@4 76 updateToolbarHeight() {
Chris@4 77 const toolbarTabOuterHeight =
Chris@4 78 $('#toolbar-bar')
Chris@4 79 .find('.toolbar-tab')
Chris@4 80 .outerHeight() || 0;
Chris@4 81 const toolbarTrayHorizontalOuterHeight =
Chris@4 82 $('.is-active.toolbar-tray-horizontal').outerHeight() || 0;
Chris@4 83 this.model.set(
Chris@4 84 'height',
Chris@4 85 toolbarTabOuterHeight + toolbarTrayHorizontalOuterHeight,
Chris@4 86 );
Chris@0 87
Chris@4 88 $('body').css({
Chris@4 89 'padding-top': this.model.get('height'),
Chris@4 90 });
Chris@0 91
Chris@4 92 this.triggerDisplace();
Chris@4 93 },
Chris@0 94
Chris@4 95 // Trigger a recalculation of viewport displacing elements. Use setTimeout
Chris@4 96 // to ensure this recalculation happens after changes to visual elements
Chris@4 97 // have processed.
Chris@4 98 triggerDisplace() {
Chris@4 99 _.defer(() => {
Chris@4 100 Drupal.displace(true);
Chris@4 101 });
Chris@4 102 },
Chris@0 103
Chris@4 104 /**
Chris@4 105 * @inheritdoc
Chris@4 106 *
Chris@4 107 * @return {Drupal.toolbar.ToolbarVisualView}
Chris@4 108 * The `ToolbarVisualView` instance.
Chris@4 109 */
Chris@4 110 render() {
Chris@4 111 this.updateTabs();
Chris@4 112 this.updateTrayOrientation();
Chris@4 113 this.updateBarAttributes();
Chris@0 114
Chris@4 115 $('body').removeClass('toolbar-loading');
Chris@0 116
Chris@4 117 // Load the subtrees if the orientation of the toolbar is changed to
Chris@4 118 // vertical. This condition responds to the case that the toolbar switches
Chris@4 119 // from horizontal to vertical orientation. The toolbar starts in a
Chris@4 120 // vertical orientation by default and then switches to horizontal during
Chris@4 121 // initialization if the media query conditions are met. Simply checking
Chris@4 122 // that the orientation is vertical here would result in the subtrees
Chris@4 123 // always being loaded, even when the toolbar initialization ultimately
Chris@4 124 // results in a horizontal orientation.
Chris@4 125 //
Chris@4 126 // @see Drupal.behaviors.toolbar.attach() where admin menu subtrees
Chris@4 127 // loading is invoked during initialization after media query conditions
Chris@4 128 // have been processed.
Chris@4 129 if (
Chris@4 130 this.model.changed.orientation === 'vertical' ||
Chris@4 131 this.model.changed.activeTab
Chris@4 132 ) {
Chris@4 133 this.loadSubtrees();
Chris@4 134 }
Chris@0 135
Chris@4 136 return this;
Chris@4 137 },
Chris@0 138
Chris@4 139 /**
Chris@4 140 * Responds to a toolbar tab click.
Chris@4 141 *
Chris@4 142 * @param {jQuery.Event} event
Chris@4 143 * The event triggered.
Chris@4 144 */
Chris@4 145 onTabClick(event) {
Chris@4 146 // If this tab has a tray associated with it, it is considered an
Chris@4 147 // activatable tab.
Chris@5 148 if (event.currentTarget.hasAttribute('data-toolbar-tray')) {
Chris@4 149 const activeTab = this.model.get('activeTab');
Chris@5 150 const clickedTab = event.currentTarget;
Chris@0 151
Chris@4 152 // Set the event target as the active item if it is not already.
Chris@4 153 this.model.set(
Chris@4 154 'activeTab',
Chris@4 155 !activeTab || clickedTab !== activeTab ? clickedTab : null,
Chris@4 156 );
Chris@0 157
Chris@4 158 event.preventDefault();
Chris@4 159 event.stopPropagation();
Chris@4 160 }
Chris@4 161 },
Chris@4 162
Chris@4 163 /**
Chris@4 164 * Toggles the orientation of a toolbar tray.
Chris@4 165 *
Chris@4 166 * @param {jQuery.Event} event
Chris@4 167 * The event triggered.
Chris@4 168 */
Chris@4 169 onOrientationToggleClick(event) {
Chris@4 170 const orientation = this.model.get('orientation');
Chris@4 171 // Determine the toggle-to orientation.
Chris@4 172 const antiOrientation =
Chris@4 173 orientation === 'vertical' ? 'horizontal' : 'vertical';
Chris@4 174 const locked = antiOrientation === 'vertical';
Chris@4 175 // Remember the locked state.
Chris@4 176 if (locked) {
Chris@4 177 localStorage.setItem('Drupal.toolbar.trayVerticalLocked', 'true');
Chris@4 178 } else {
Chris@4 179 localStorage.removeItem('Drupal.toolbar.trayVerticalLocked');
Chris@4 180 }
Chris@4 181 // Update the model.
Chris@4 182 this.model.set(
Chris@4 183 {
Chris@4 184 locked,
Chris@4 185 orientation: antiOrientation,
Chris@4 186 },
Chris@4 187 {
Chris@4 188 validate: true,
Chris@4 189 override: true,
Chris@4 190 },
Chris@4 191 );
Chris@0 192
Chris@0 193 event.preventDefault();
Chris@0 194 event.stopPropagation();
Chris@4 195 },
Chris@0 196
Chris@4 197 /**
Chris@4 198 * Updates the display of the tabs: toggles a tab and the associated tray.
Chris@4 199 */
Chris@4 200 updateTabs() {
Chris@4 201 const $tab = $(this.model.get('activeTab'));
Chris@4 202 // Deactivate the previous tab.
Chris@4 203 $(this.model.previous('activeTab'))
Chris@4 204 .removeClass('is-active')
Chris@4 205 .prop('aria-pressed', false);
Chris@4 206 // Deactivate the previous tray.
Chris@4 207 $(this.model.previous('activeTray')).removeClass('is-active');
Chris@0 208
Chris@4 209 // Activate the selected tab.
Chris@4 210 if ($tab.length > 0) {
Chris@4 211 $tab
Chris@4 212 .addClass('is-active')
Chris@4 213 // Mark the tab as pressed.
Chris@4 214 .prop('aria-pressed', true);
Chris@4 215 const name = $tab.attr('data-toolbar-tray');
Chris@4 216 // Store the active tab name or remove the setting.
Chris@4 217 const id = $tab.get(0).id;
Chris@4 218 if (id) {
Chris@4 219 localStorage.setItem(
Chris@4 220 'Drupal.toolbar.activeTabID',
Chris@4 221 JSON.stringify(id),
Chris@4 222 );
Chris@4 223 }
Chris@4 224 // Activate the associated tray.
Chris@4 225 const $tray = this.$el.find(
Chris@4 226 `[data-toolbar-tray="${name}"].toolbar-tray`,
Chris@4 227 );
Chris@4 228 if ($tray.length) {
Chris@4 229 $tray.addClass('is-active');
Chris@4 230 this.model.set('activeTray', $tray.get(0));
Chris@4 231 } else {
Chris@4 232 // There is no active tray.
Chris@4 233 this.model.set('activeTray', null);
Chris@4 234 }
Chris@4 235 } else {
Chris@0 236 // There is no active tray.
Chris@0 237 this.model.set('activeTray', null);
Chris@4 238 localStorage.removeItem('Drupal.toolbar.activeTabID');
Chris@0 239 }
Chris@4 240 },
Chris@4 241
Chris@4 242 /**
Chris@4 243 * Update the attributes of the toolbar bar element.
Chris@4 244 */
Chris@4 245 updateBarAttributes() {
Chris@4 246 const isOriented = this.model.get('isOriented');
Chris@4 247 if (isOriented) {
Chris@4 248 this.$el.find('.toolbar-bar').attr('data-offset-top', '');
Chris@4 249 } else {
Chris@4 250 this.$el.find('.toolbar-bar').removeAttr('data-offset-top');
Chris@4 251 }
Chris@4 252 // Toggle between a basic vertical view and a more sophisticated
Chris@4 253 // horizontal and vertical display of the toolbar bar and trays.
Chris@4 254 this.$el.toggleClass('toolbar-oriented', isOriented);
Chris@4 255 },
Chris@4 256
Chris@4 257 /**
Chris@4 258 * Updates the orientation of the active tray if necessary.
Chris@4 259 */
Chris@4 260 updateTrayOrientation() {
Chris@4 261 const orientation = this.model.get('orientation');
Chris@4 262
Chris@4 263 // The antiOrientation is used to render the view of action buttons like
Chris@4 264 // the tray orientation toggle.
Chris@4 265 const antiOrientation =
Chris@4 266 orientation === 'vertical' ? 'horizontal' : 'vertical';
Chris@4 267
Chris@4 268 // Toggle toolbar's parent classes before other toolbar classes to avoid
Chris@4 269 // potential flicker and re-rendering.
Chris@4 270 $('body')
Chris@4 271 .toggleClass('toolbar-vertical', orientation === 'vertical')
Chris@4 272 .toggleClass('toolbar-horizontal', orientation === 'horizontal');
Chris@4 273
Chris@4 274 const removeClass =
Chris@4 275 antiOrientation === 'horizontal'
Chris@4 276 ? 'toolbar-tray-horizontal'
Chris@4 277 : 'toolbar-tray-vertical';
Chris@4 278 const $trays = this.$el
Chris@4 279 .find('.toolbar-tray')
Chris@4 280 .removeClass(removeClass)
Chris@4 281 .addClass(`toolbar-tray-${orientation}`);
Chris@4 282
Chris@4 283 // Update the tray orientation toggle button.
Chris@4 284 const iconClass = `toolbar-icon-toggle-${orientation}`;
Chris@4 285 const iconAntiClass = `toolbar-icon-toggle-${antiOrientation}`;
Chris@4 286 const $orientationToggle = this.$el
Chris@4 287 .find('.toolbar-toggle-orientation')
Chris@4 288 .toggle(this.model.get('isTrayToggleVisible'));
Chris@4 289 $orientationToggle
Chris@4 290 .find('button')
Chris@4 291 .val(antiOrientation)
Chris@4 292 .attr('title', this.strings[antiOrientation])
Chris@4 293 .text(this.strings[antiOrientation])
Chris@4 294 .removeClass(iconClass)
Chris@4 295 .addClass(iconAntiClass);
Chris@4 296
Chris@4 297 // Update data offset attributes for the trays.
Chris@4 298 const dir = document.documentElement.dir;
Chris@4 299 const edge = dir === 'rtl' ? 'right' : 'left';
Chris@4 300 // Remove data-offset attributes from the trays so they can be refreshed.
Chris@4 301 $trays.removeAttr('data-offset-left data-offset-right data-offset-top');
Chris@4 302 // If an active vertical tray exists, mark it as an offset element.
Chris@4 303 $trays
Chris@4 304 .filter('.toolbar-tray-vertical.is-active')
Chris@4 305 .attr(`data-offset-${edge}`, '');
Chris@4 306 // If an active horizontal tray exists, mark it as an offset element.
Chris@4 307 $trays
Chris@4 308 .filter('.toolbar-tray-horizontal.is-active')
Chris@4 309 .attr('data-offset-top', '');
Chris@4 310 },
Chris@4 311
Chris@4 312 /**
Chris@4 313 * Sets the tops of the trays so that they align with the bottom of the bar.
Chris@4 314 */
Chris@4 315 adjustPlacement() {
Chris@4 316 const $trays = this.$el.find('.toolbar-tray');
Chris@4 317 if (!this.model.get('isOriented')) {
Chris@4 318 $trays
Chris@4 319 .removeClass('toolbar-tray-horizontal')
Chris@4 320 .addClass('toolbar-tray-vertical');
Chris@4 321 }
Chris@4 322 },
Chris@4 323
Chris@4 324 /**
Chris@4 325 * Calls the endpoint URI that builds an AJAX command with the rendered
Chris@4 326 * subtrees.
Chris@4 327 *
Chris@4 328 * The rendered admin menu subtrees HTML is cached on the client in
Chris@4 329 * localStorage until the cache of the admin menu subtrees on the server-
Chris@4 330 * side is invalidated. The subtreesHash is stored in localStorage as well
Chris@4 331 * and compared to the subtreesHash in drupalSettings to determine when the
Chris@4 332 * admin menu subtrees cache has been invalidated.
Chris@4 333 */
Chris@4 334 loadSubtrees() {
Chris@4 335 const $activeTab = $(this.model.get('activeTab'));
Chris@4 336 const orientation = this.model.get('orientation');
Chris@4 337 // Only load and render the admin menu subtrees if:
Chris@4 338 // (1) They have not been loaded yet.
Chris@4 339 // (2) The active tab is the administration menu tab, indicated by the
Chris@4 340 // presence of the data-drupal-subtrees attribute.
Chris@4 341 // (3) The orientation of the tray is vertical.
Chris@4 342 if (
Chris@4 343 !this.model.get('areSubtreesLoaded') &&
Chris@4 344 typeof $activeTab.data('drupal-subtrees') !== 'undefined' &&
Chris@4 345 orientation === 'vertical'
Chris@4 346 ) {
Chris@4 347 const subtreesHash = drupalSettings.toolbar.subtreesHash;
Chris@4 348 const theme = drupalSettings.ajaxPageState.theme;
Chris@4 349 const endpoint = Drupal.url(`toolbar/subtrees/${subtreesHash}`);
Chris@4 350 const cachedSubtreesHash = localStorage.getItem(
Chris@4 351 `Drupal.toolbar.subtreesHash.${theme}`,
Chris@4 352 );
Chris@4 353 const cachedSubtrees = JSON.parse(
Chris@4 354 localStorage.getItem(`Drupal.toolbar.subtrees.${theme}`),
Chris@4 355 );
Chris@4 356 const isVertical = this.model.get('orientation') === 'vertical';
Chris@4 357 // If we have the subtrees in localStorage and the subtree hash has not
Chris@4 358 // changed, then use the cached data.
Chris@4 359 if (
Chris@4 360 isVertical &&
Chris@4 361 subtreesHash === cachedSubtreesHash &&
Chris@4 362 cachedSubtrees
Chris@4 363 ) {
Chris@4 364 Drupal.toolbar.setSubtrees.resolve(cachedSubtrees);
Chris@4 365 }
Chris@4 366 // Only make the call to get the subtrees if the orientation of the
Chris@4 367 // toolbar is vertical.
Chris@4 368 else if (isVertical) {
Chris@4 369 // Remove the cached menu information.
Chris@4 370 localStorage.removeItem(`Drupal.toolbar.subtreesHash.${theme}`);
Chris@4 371 localStorage.removeItem(`Drupal.toolbar.subtrees.${theme}`);
Chris@4 372 // The AJAX response's command will trigger the resolve method of the
Chris@4 373 // Drupal.toolbar.setSubtrees Promise.
Chris@4 374 Drupal.ajax({ url: endpoint }).execute();
Chris@4 375 // Cache the hash for the subtrees locally.
Chris@4 376 localStorage.setItem(
Chris@4 377 `Drupal.toolbar.subtreesHash.${theme}`,
Chris@4 378 subtreesHash,
Chris@4 379 );
Chris@4 380 }
Chris@4 381 }
Chris@4 382 },
Chris@0 383 },
Chris@4 384 );
Chris@4 385 })(jQuery, Drupal, drupalSettings, Backbone);