123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- /**
- * @file
- * 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# */{
- /**
- * 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,
- };
- },
- /**
- * 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);
- // 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');
- },
- /**
- * 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'),
- });
- 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);
- });
- },
- /**
- * @inheritdoc
- *
- * @return {Drupal.toolbar.ToolbarVisualView}
- * The `ToolbarVisualView` instance.
- */
- render() {
- this.updateTabs();
- this.updateTrayOrientation();
- this.updateBarAttributes();
- $('body').removeClass('toolbar-loading');
- // 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();
- }
- return this;
- },
- /**
- * 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;
- // Set the event target as the active item if it is not already.
- this.model.set('activeTab', (!activeTab || clickedTab !== activeTab) ? clickedTab : null);
- 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,
- });
- 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 {
- // 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');
- }
- },
- /**
- * 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');
- // 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'));
- 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 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);
- }
- // 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));
|