* A Backbone view for the toolbar element. Listens to mouse & touch.
*/
-(function ($, Drupal, drupalSettings, Backbone) {
- Drupal.toolbar.ToolbarVisualView = Backbone.View.extend(/** @lends Drupal.toolbar.ToolbarVisualView# */{
+(function($, Drupal, drupalSettings, Backbone) {
+ Drupal.toolbar.ToolbarVisualView = Backbone.View.extend(
+ /** @lends Drupal.toolbar.ToolbarVisualView# */ {
+ /**
+ * Event map for the `ToolbarVisualView`.
+ *
+ * @return {object}
+ * A map of events.
+ */
+ events() {
+ // Prevents delay and simulated mouse events.
+ const touchEndToClick = function(event) {
+ event.preventDefault();
+ event.target.click();
+ };
- /**
- * Event map for the `ToolbarVisualView`.
- *
- * @return {object}
- * A map of events.
- */
- events() {
- // Prevents delay and simulated mouse events.
- const touchEndToClick = function (event) {
- event.preventDefault();
- event.target.click();
- };
+ return {
+ 'click .toolbar-bar .toolbar-tab .trigger': 'onTabClick',
+ 'click .toolbar-toggle-orientation button':
+ 'onOrientationToggleClick',
+ 'touchend .toolbar-bar .toolbar-tab .trigger': touchEndToClick,
+ 'touchend .toolbar-toggle-orientation button': touchEndToClick,
+ };
+ },
- return {
- 'click .toolbar-bar .toolbar-tab .trigger': 'onTabClick',
- 'click .toolbar-toggle-orientation button': 'onOrientationToggleClick',
- 'touchend .toolbar-bar .toolbar-tab .trigger': touchEndToClick,
- 'touchend .toolbar-toggle-orientation button': touchEndToClick,
- };
- },
+ /**
+ * Backbone view for the toolbar element. Listens to mouse & touch.
+ *
+ * @constructs
+ *
+ * @augments Backbone.View
+ *
+ * @param {object} options
+ * Options for the view object.
+ * @param {object} options.strings
+ * Various strings to use in the view.
+ */
+ initialize(options) {
+ this.strings = options.strings;
- /**
- * Backbone view for the toolbar element. Listens to mouse & touch.
- *
- * @constructs
- *
- * @augments Backbone.View
- *
- * @param {object} options
- * Options for the view object.
- * @param {object} options.strings
- * Various strings to use in the view.
- */
- initialize(options) {
- this.strings = options.strings;
+ this.listenTo(
+ this.model,
+ 'change:activeTab change:orientation change:isOriented change:isTrayToggleVisible',
+ this.render,
+ );
+ this.listenTo(this.model, 'change:mqMatches', this.onMediaQueryChange);
+ this.listenTo(this.model, 'change:offsets', this.adjustPlacement);
+ this.listenTo(
+ this.model,
+ 'change:activeTab change:orientation change:isOriented',
+ this.updateToolbarHeight,
+ );
- this.listenTo(this.model, 'change:activeTab change:orientation change:isOriented change:isTrayToggleVisible', this.render);
- this.listenTo(this.model, 'change:mqMatches', this.onMediaQueryChange);
- this.listenTo(this.model, 'change:offsets', this.adjustPlacement);
- this.listenTo(this.model, 'change:activeTab change:orientation change:isOriented', this.updateToolbarHeight);
+ // Add the tray orientation toggles.
+ this.$el
+ .find('.toolbar-tray .toolbar-lining')
+ .append(Drupal.theme('toolbarOrientationToggle'));
- // Add the tray orientation toggles.
- this.$el
- .find('.toolbar-tray .toolbar-lining')
- .append(Drupal.theme('toolbarOrientationToggle'));
+ // Trigger an activeTab change so that listening scripts can respond on
+ // page load. This will call render.
+ this.model.trigger('change:activeTab');
+ },
- // Trigger an activeTab change so that listening scripts can respond on
- // page load. This will call render.
- this.model.trigger('change:activeTab');
- },
+ /**
+ * Update the toolbar element height.
+ *
+ * @constructs
+ *
+ * @augments Backbone.View
+ */
+ updateToolbarHeight() {
+ const toolbarTabOuterHeight =
+ $('#toolbar-bar')
+ .find('.toolbar-tab')
+ .outerHeight() || 0;
+ const toolbarTrayHorizontalOuterHeight =
+ $('.is-active.toolbar-tray-horizontal').outerHeight() || 0;
+ this.model.set(
+ 'height',
+ toolbarTabOuterHeight + toolbarTrayHorizontalOuterHeight,
+ );
- /**
- * Update the toolbar element height.
- *
- * @constructs
- *
- * @augments Backbone.View
- */
- updateToolbarHeight() {
- const toolbarTabOuterHeight = $('#toolbar-bar').find('.toolbar-tab').outerHeight() || 0;
- const toolbarTrayHorizontalOuterHeight = $('.is-active.toolbar-tray-horizontal').outerHeight() || 0;
- this.model.set('height', toolbarTabOuterHeight + toolbarTrayHorizontalOuterHeight);
+ $('body').css({
+ 'padding-top': this.model.get('height'),
+ });
- $('body').css({
- 'padding-top': this.model.get('height'),
- });
+ this.triggerDisplace();
+ },
- this.triggerDisplace();
- },
+ // Trigger a recalculation of viewport displacing elements. Use setTimeout
+ // to ensure this recalculation happens after changes to visual elements
+ // have processed.
+ triggerDisplace() {
+ _.defer(() => {
+ Drupal.displace(true);
+ });
+ },
- // Trigger a recalculation of viewport displacing elements. Use setTimeout
- // to ensure this recalculation happens after changes to visual elements
- // have processed.
- triggerDisplace() {
- _.defer(() => {
- Drupal.displace(true);
- });
- },
+ /**
+ * @inheritdoc
+ *
+ * @return {Drupal.toolbar.ToolbarVisualView}
+ * The `ToolbarVisualView` instance.
+ */
+ render() {
+ this.updateTabs();
+ this.updateTrayOrientation();
+ this.updateBarAttributes();
+
+ $('body').removeClass('toolbar-loading');
- /**
- * @inheritdoc
- *
- * @return {Drupal.toolbar.ToolbarVisualView}
- * The `ToolbarVisualView` instance.
- */
- render() {
- this.updateTabs();
- this.updateTrayOrientation();
- this.updateBarAttributes();
+ // Load the subtrees if the orientation of the toolbar is changed to
+ // vertical. This condition responds to the case that the toolbar switches
+ // from horizontal to vertical orientation. The toolbar starts in a
+ // vertical orientation by default and then switches to horizontal during
+ // initialization if the media query conditions are met. Simply checking
+ // that the orientation is vertical here would result in the subtrees
+ // always being loaded, even when the toolbar initialization ultimately
+ // results in a horizontal orientation.
+ //
+ // @see Drupal.behaviors.toolbar.attach() where admin menu subtrees
+ // loading is invoked during initialization after media query conditions
+ // have been processed.
+ if (
+ this.model.changed.orientation === 'vertical' ||
+ this.model.changed.activeTab
+ ) {
+ this.loadSubtrees();
+ }
- $('body').removeClass('toolbar-loading');
+ return this;
+ },
- // Load the subtrees if the orientation of the toolbar is changed to
- // vertical. This condition responds to the case that the toolbar switches
- // from horizontal to vertical orientation. The toolbar starts in a
- // vertical orientation by default and then switches to horizontal during
- // initialization if the media query conditions are met. Simply checking
- // that the orientation is vertical here would result in the subtrees
- // always being loaded, even when the toolbar initialization ultimately
- // results in a horizontal orientation.
- //
- // @see Drupal.behaviors.toolbar.attach() where admin menu subtrees
- // loading is invoked during initialization after media query conditions
- // have been processed.
- if (this.model.changed.orientation === 'vertical' || this.model.changed.activeTab) {
- this.loadSubtrees();
- }
+ /**
+ * Responds to a toolbar tab click.
+ *
+ * @param {jQuery.Event} event
+ * The event triggered.
+ */
+ onTabClick(event) {
+ // If this tab has a tray associated with it, it is considered an
+ // activatable tab.
+ if (event.target.hasAttribute('data-toolbar-tray')) {
+ const activeTab = this.model.get('activeTab');
+ const clickedTab = event.target;
- return this;
- },
+ // Set the event target as the active item if it is not already.
+ this.model.set(
+ 'activeTab',
+ !activeTab || clickedTab !== activeTab ? clickedTab : null,
+ );
- /**
- * Responds to a toolbar tab click.
- *
- * @param {jQuery.Event} event
- * The event triggered.
- */
- onTabClick(event) {
- // If this tab has a tray associated with it, it is considered an
- // activatable tab.
- if (event.target.hasAttribute('data-toolbar-tray')) {
- const activeTab = this.model.get('activeTab');
- const clickedTab = event.target;
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ },
- // Set the event target as the active item if it is not already.
- this.model.set('activeTab', (!activeTab || clickedTab !== activeTab) ? clickedTab : null);
+ /**
+ * Toggles the orientation of a toolbar tray.
+ *
+ * @param {jQuery.Event} event
+ * The event triggered.
+ */
+ onOrientationToggleClick(event) {
+ const orientation = this.model.get('orientation');
+ // Determine the toggle-to orientation.
+ const antiOrientation =
+ orientation === 'vertical' ? 'horizontal' : 'vertical';
+ const locked = antiOrientation === 'vertical';
+ // Remember the locked state.
+ if (locked) {
+ localStorage.setItem('Drupal.toolbar.trayVerticalLocked', 'true');
+ } else {
+ localStorage.removeItem('Drupal.toolbar.trayVerticalLocked');
+ }
+ // Update the model.
+ this.model.set(
+ {
+ locked,
+ orientation: antiOrientation,
+ },
+ {
+ validate: true,
+ override: true,
+ },
+ );
event.preventDefault();
event.stopPropagation();
- }
- },
+ },
- /**
- * Toggles the orientation of a toolbar tray.
- *
- * @param {jQuery.Event} event
- * The event triggered.
- */
- onOrientationToggleClick(event) {
- const orientation = this.model.get('orientation');
- // Determine the toggle-to orientation.
- const antiOrientation = (orientation === 'vertical') ? 'horizontal' : 'vertical';
- const locked = antiOrientation === 'vertical';
- // Remember the locked state.
- if (locked) {
- localStorage.setItem('Drupal.toolbar.trayVerticalLocked', 'true');
- }
- else {
- localStorage.removeItem('Drupal.toolbar.trayVerticalLocked');
- }
- // Update the model.
- this.model.set({
- locked,
- orientation: antiOrientation,
- }, {
- validate: true,
- override: true,
- });
+ /**
+ * Updates the display of the tabs: toggles a tab and the associated tray.
+ */
+ updateTabs() {
+ const $tab = $(this.model.get('activeTab'));
+ // Deactivate the previous tab.
+ $(this.model.previous('activeTab'))
+ .removeClass('is-active')
+ .prop('aria-pressed', false);
+ // Deactivate the previous tray.
+ $(this.model.previous('activeTray')).removeClass('is-active');
- event.preventDefault();
- event.stopPropagation();
- },
-
- /**
- * Updates the display of the tabs: toggles a tab and the associated tray.
- */
- updateTabs() {
- const $tab = $(this.model.get('activeTab'));
- // Deactivate the previous tab.
- $(this.model.previous('activeTab'))
- .removeClass('is-active')
- .prop('aria-pressed', false);
- // Deactivate the previous tray.
- $(this.model.previous('activeTray'))
- .removeClass('is-active');
-
- // Activate the selected tab.
- if ($tab.length > 0) {
- $tab
- .addClass('is-active')
- // Mark the tab as pressed.
- .prop('aria-pressed', true);
- const name = $tab.attr('data-toolbar-tray');
- // Store the active tab name or remove the setting.
- const id = $tab.get(0).id;
- if (id) {
- localStorage.setItem('Drupal.toolbar.activeTabID', JSON.stringify(id));
- }
- // Activate the associated tray.
- const $tray = this.$el.find(`[data-toolbar-tray="${name}"].toolbar-tray`);
- if ($tray.length) {
- $tray.addClass('is-active');
- this.model.set('activeTray', $tray.get(0));
- }
- else {
+ // Activate the selected tab.
+ if ($tab.length > 0) {
+ $tab
+ .addClass('is-active')
+ // Mark the tab as pressed.
+ .prop('aria-pressed', true);
+ const name = $tab.attr('data-toolbar-tray');
+ // Store the active tab name or remove the setting.
+ const id = $tab.get(0).id;
+ if (id) {
+ localStorage.setItem(
+ 'Drupal.toolbar.activeTabID',
+ JSON.stringify(id),
+ );
+ }
+ // Activate the associated tray.
+ const $tray = this.$el.find(
+ `[data-toolbar-tray="${name}"].toolbar-tray`,
+ );
+ if ($tray.length) {
+ $tray.addClass('is-active');
+ this.model.set('activeTray', $tray.get(0));
+ } else {
+ // There is no active tray.
+ this.model.set('activeTray', null);
+ }
+ } else {
// There is no active tray.
this.model.set('activeTray', null);
+ localStorage.removeItem('Drupal.toolbar.activeTabID');
}
- }
- else {
- // There is no active tray.
- this.model.set('activeTray', null);
- localStorage.removeItem('Drupal.toolbar.activeTabID');
- }
- },
+ },
- /**
- * Update the attributes of the toolbar bar element.
- */
- updateBarAttributes() {
- const isOriented = this.model.get('isOriented');
- if (isOriented) {
- this.$el.find('.toolbar-bar').attr('data-offset-top', '');
- }
- else {
- this.$el.find('.toolbar-bar').removeAttr('data-offset-top');
- }
- // Toggle between a basic vertical view and a more sophisticated
- // horizontal and vertical display of the toolbar bar and trays.
- this.$el.toggleClass('toolbar-oriented', isOriented);
- },
+ /**
+ * Update the attributes of the toolbar bar element.
+ */
+ updateBarAttributes() {
+ const isOriented = this.model.get('isOriented');
+ if (isOriented) {
+ this.$el.find('.toolbar-bar').attr('data-offset-top', '');
+ } else {
+ this.$el.find('.toolbar-bar').removeAttr('data-offset-top');
+ }
+ // Toggle between a basic vertical view and a more sophisticated
+ // horizontal and vertical display of the toolbar bar and trays.
+ this.$el.toggleClass('toolbar-oriented', isOriented);
+ },
- /**
- * Updates the orientation of the active tray if necessary.
- */
- updateTrayOrientation() {
- const orientation = this.model.get('orientation');
+ /**
+ * Updates the orientation of the active tray if necessary.
+ */
+ updateTrayOrientation() {
+ const orientation = this.model.get('orientation');
- // The antiOrientation is used to render the view of action buttons like
- // the tray orientation toggle.
- const antiOrientation = (orientation === 'vertical') ? 'horizontal' : 'vertical';
+ // The antiOrientation is used to render the view of action buttons like
+ // the tray orientation toggle.
+ const antiOrientation =
+ orientation === 'vertical' ? 'horizontal' : 'vertical';
- // Toggle toolbar's parent classes before other toolbar classes to avoid
- // potential flicker and re-rendering.
- $('body')
- .toggleClass('toolbar-vertical', (orientation === 'vertical'))
- .toggleClass('toolbar-horizontal', (orientation === 'horizontal'));
+ // Toggle toolbar's parent classes before other toolbar classes to avoid
+ // potential flicker and re-rendering.
+ $('body')
+ .toggleClass('toolbar-vertical', orientation === 'vertical')
+ .toggleClass('toolbar-horizontal', orientation === 'horizontal');
- const removeClass = (antiOrientation === 'horizontal') ? 'toolbar-tray-horizontal' : 'toolbar-tray-vertical';
- const $trays = this.$el.find('.toolbar-tray')
- .removeClass(removeClass)
- .addClass(`toolbar-tray-${orientation}`);
+ const removeClass =
+ antiOrientation === 'horizontal'
+ ? 'toolbar-tray-horizontal'
+ : 'toolbar-tray-vertical';
+ const $trays = this.$el
+ .find('.toolbar-tray')
+ .removeClass(removeClass)
+ .addClass(`toolbar-tray-${orientation}`);
- // Update the tray orientation toggle button.
- const iconClass = `toolbar-icon-toggle-${orientation}`;
- const iconAntiClass = `toolbar-icon-toggle-${antiOrientation}`;
- const $orientationToggle = this.$el.find('.toolbar-toggle-orientation')
- .toggle(this.model.get('isTrayToggleVisible'));
- $orientationToggle.find('button')
- .val(antiOrientation)
- .attr('title', this.strings[antiOrientation])
- .text(this.strings[antiOrientation])
- .removeClass(iconClass)
- .addClass(iconAntiClass);
+ // Update the tray orientation toggle button.
+ const iconClass = `toolbar-icon-toggle-${orientation}`;
+ const iconAntiClass = `toolbar-icon-toggle-${antiOrientation}`;
+ const $orientationToggle = this.$el
+ .find('.toolbar-toggle-orientation')
+ .toggle(this.model.get('isTrayToggleVisible'));
+ $orientationToggle
+ .find('button')
+ .val(antiOrientation)
+ .attr('title', this.strings[antiOrientation])
+ .text(this.strings[antiOrientation])
+ .removeClass(iconClass)
+ .addClass(iconAntiClass);
- // Update data offset attributes for the trays.
- const dir = document.documentElement.dir;
- const edge = (dir === 'rtl') ? 'right' : 'left';
- // Remove data-offset attributes from the trays so they can be refreshed.
- $trays.removeAttr('data-offset-left data-offset-right data-offset-top');
- // If an active vertical tray exists, mark it as an offset element.
- $trays.filter('.toolbar-tray-vertical.is-active').attr(`data-offset-${edge}`, '');
- // If an active horizontal tray exists, mark it as an offset element.
- $trays.filter('.toolbar-tray-horizontal.is-active').attr('data-offset-top', '');
- },
+ // Update data offset attributes for the trays.
+ const dir = document.documentElement.dir;
+ const edge = dir === 'rtl' ? 'right' : 'left';
+ // Remove data-offset attributes from the trays so they can be refreshed.
+ $trays.removeAttr('data-offset-left data-offset-right data-offset-top');
+ // If an active vertical tray exists, mark it as an offset element.
+ $trays
+ .filter('.toolbar-tray-vertical.is-active')
+ .attr(`data-offset-${edge}`, '');
+ // If an active horizontal tray exists, mark it as an offset element.
+ $trays
+ .filter('.toolbar-tray-horizontal.is-active')
+ .attr('data-offset-top', '');
+ },
- /**
- * Sets the tops of the trays so that they align with the bottom of the bar.
- */
- adjustPlacement() {
- const $trays = this.$el.find('.toolbar-tray');
- if (!this.model.get('isOriented')) {
- $trays.removeClass('toolbar-tray-horizontal').addClass('toolbar-tray-vertical');
- }
- },
-
- /**
- * Calls the endpoint URI that builds an AJAX command with the rendered
- * subtrees.
- *
- * The rendered admin menu subtrees HTML is cached on the client in
- * localStorage until the cache of the admin menu subtrees on the server-
- * side is invalidated. The subtreesHash is stored in localStorage as well
- * and compared to the subtreesHash in drupalSettings to determine when the
- * admin menu subtrees cache has been invalidated.
- */
- loadSubtrees() {
- const $activeTab = $(this.model.get('activeTab'));
- const orientation = this.model.get('orientation');
- // Only load and render the admin menu subtrees if:
- // (1) They have not been loaded yet.
- // (2) The active tab is the administration menu tab, indicated by the
- // presence of the data-drupal-subtrees attribute.
- // (3) The orientation of the tray is vertical.
- if (!this.model.get('areSubtreesLoaded') && typeof $activeTab.data('drupal-subtrees') !== 'undefined' && orientation === 'vertical') {
- const subtreesHash = drupalSettings.toolbar.subtreesHash;
- const theme = drupalSettings.ajaxPageState.theme;
- const endpoint = Drupal.url(`toolbar/subtrees/${subtreesHash}`);
- const cachedSubtreesHash = localStorage.getItem(`Drupal.toolbar.subtreesHash.${theme}`);
- const cachedSubtrees = JSON.parse(localStorage.getItem(`Drupal.toolbar.subtrees.${theme}`));
- const isVertical = this.model.get('orientation') === 'vertical';
- // If we have the subtrees in localStorage and the subtree hash has not
- // changed, then use the cached data.
- if (isVertical && subtreesHash === cachedSubtreesHash && cachedSubtrees) {
- Drupal.toolbar.setSubtrees.resolve(cachedSubtrees);
+ /**
+ * Sets the tops of the trays so that they align with the bottom of the bar.
+ */
+ adjustPlacement() {
+ const $trays = this.$el.find('.toolbar-tray');
+ if (!this.model.get('isOriented')) {
+ $trays
+ .removeClass('toolbar-tray-horizontal')
+ .addClass('toolbar-tray-vertical');
}
- // Only make the call to get the subtrees if the orientation of the
- // toolbar is vertical.
- else if (isVertical) {
- // Remove the cached menu information.
- localStorage.removeItem(`Drupal.toolbar.subtreesHash.${theme}`);
- localStorage.removeItem(`Drupal.toolbar.subtrees.${theme}`);
- // The AJAX response's command will trigger the resolve method of the
- // Drupal.toolbar.setSubtrees Promise.
- Drupal.ajax({ url: endpoint }).execute();
- // Cache the hash for the subtrees locally.
- localStorage.setItem(`Drupal.toolbar.subtreesHash.${theme}`, subtreesHash);
+ },
+
+ /**
+ * Calls the endpoint URI that builds an AJAX command with the rendered
+ * subtrees.
+ *
+ * The rendered admin menu subtrees HTML is cached on the client in
+ * localStorage until the cache of the admin menu subtrees on the server-
+ * side is invalidated. The subtreesHash is stored in localStorage as well
+ * and compared to the subtreesHash in drupalSettings to determine when the
+ * admin menu subtrees cache has been invalidated.
+ */
+ loadSubtrees() {
+ const $activeTab = $(this.model.get('activeTab'));
+ const orientation = this.model.get('orientation');
+ // Only load and render the admin menu subtrees if:
+ // (1) They have not been loaded yet.
+ // (2) The active tab is the administration menu tab, indicated by the
+ // presence of the data-drupal-subtrees attribute.
+ // (3) The orientation of the tray is vertical.
+ if (
+ !this.model.get('areSubtreesLoaded') &&
+ typeof $activeTab.data('drupal-subtrees') !== 'undefined' &&
+ orientation === 'vertical'
+ ) {
+ const subtreesHash = drupalSettings.toolbar.subtreesHash;
+ const theme = drupalSettings.ajaxPageState.theme;
+ const endpoint = Drupal.url(`toolbar/subtrees/${subtreesHash}`);
+ const cachedSubtreesHash = localStorage.getItem(
+ `Drupal.toolbar.subtreesHash.${theme}`,
+ );
+ const cachedSubtrees = JSON.parse(
+ localStorage.getItem(`Drupal.toolbar.subtrees.${theme}`),
+ );
+ const isVertical = this.model.get('orientation') === 'vertical';
+ // If we have the subtrees in localStorage and the subtree hash has not
+ // changed, then use the cached data.
+ if (
+ isVertical &&
+ subtreesHash === cachedSubtreesHash &&
+ cachedSubtrees
+ ) {
+ Drupal.toolbar.setSubtrees.resolve(cachedSubtrees);
+ }
+ // Only make the call to get the subtrees if the orientation of the
+ // toolbar is vertical.
+ else if (isVertical) {
+ // Remove the cached menu information.
+ localStorage.removeItem(`Drupal.toolbar.subtreesHash.${theme}`);
+ localStorage.removeItem(`Drupal.toolbar.subtrees.${theme}`);
+ // The AJAX response's command will trigger the resolve method of the
+ // Drupal.toolbar.setSubtrees Promise.
+ Drupal.ajax({ url: endpoint }).execute();
+ // Cache the hash for the subtrees locally.
+ localStorage.setItem(
+ `Drupal.toolbar.subtreesHash.${theme}`,
+ subtreesHash,
+ );
+ }
}
- }
+ },
},
- });
-}(jQuery, Drupal, drupalSettings, Backbone));
+ );
+})(jQuery, Drupal, drupalSettings, Backbone);