annotate core/modules/toolbar/js/views/ToolbarVisualView.es6.js @ 19:fa3358dc1485 tip

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