annotate core/modules/toolbar/js/views/ToolbarVisualView.es6.js @ 0:4c8ae668cc8c

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