core/modules/toolbar/js/toolbar.js | 112 +++++++++++++++++-------------- core/modules/toolbar/js/toolbar.menu.js | 4 +- core/modules/toolbar/toolbar.module | 2 +- 3 files changed, 63 insertions(+), 55 deletions(-) diff --git a/core/modules/toolbar/js/toolbar.js b/core/modules/toolbar/js/toolbar.js index 196f6d4..8cbd9c5 100644 --- a/core/modules/toolbar/js/toolbar.js +++ b/core/modules/toolbar/js/toolbar.js @@ -1,18 +1,14 @@ /** - * @file toolbar.js - * + * @file * Defines the behavior of the Drupal administration toolbar. */ -(function ($, Drupal, drupalSettings) { -"use strict"; +(function ($, Backbone, Drupal, drupalSettings, document, window) { -// Closure variables. -var model; // The state model for the toolbar. -var view; // The view of the toolbar. +"use strict"; /** - * Register tabs with the toolbar. + * Registers tabs with the toolbar. * * The Drupal toolbar allows modules to register top-level tabs. These may point * directly to a resource or toggle the visibility of a tray. @@ -20,36 +16,38 @@ var view; // The view of the toolbar. * Modules register tabs with hook_toolbar(). */ Drupal.behaviors.toolbar = { - attach: function(context) { - var options = $.extend(this.options, drupalSettings.toolbar); + attach: function (context) { $(context).find('#toolbar-administration').once('toolbar', function () { + var options = $.extend(Drupal.behaviors.toolbar.defaults, drupalSettings.toolbar); + // Set up switching between the vertical and horizontal presentation // of the toolbar trays based on a breakpoint. var mql = window.matchMedia(options.breakpoints['module.toolbar.wide']); - // Build the model and assign it to the closure variable reference. - model = new StateModel({ + var model = new StateModel({ locked: JSON.parse(localStorage.getItem('Drupal.toolbar.trayVerticalLocked')) || false, activeTab: document.getElementById(JSON.parse(localStorage.getItem('Drupal.toolbar.activeTabID'))), orientation: JSON.parse(localStorage.getItem('Drupal.toolbar.trayOrientation')) || 'vertical', - mql: mql + mqMatches: mql.matches }); + // Respond to external events. $(window) .on('resize.toolbar', Drupal.debounce(function (event) { model.set('dimensionsAreValid', false); }, 150)); + // Update the model when matchMedia fires. mql.addListener(function (mql) { - model.set('mql', mql); - // Setting the mql won't trigger a change in the model. It needs to be - // triggered manually. - model.trigger('change:mql'); + model.set('mqMatches', mql.matches); }); + // Respond to toolbar events. + Drupal.toolbar.model = model; $(document) .on('drupalToolbarDimensionsChanged.toolbar', Drupal.toolbar.dimensionsChangeHandler) .on('drupalToolbarOrientationChanged.toolbar', Drupal.toolbar.orientationChangeHandler) .on('drupalToolbarTrayChanged.toolbar', Drupal.toolbar.trayChangeHandler); + // Broadcast model changes to other modules. model .on('change:height', function (model, height) { @@ -64,12 +62,14 @@ Drupal.behaviors.toolbar = { .on('change:activeTray', function (model, tray) { $(document).trigger('drupalToolbarTrayChanged', tray); }); + // Build the toolbar view and assign it to the closure variable reference. - view = new ToolbarView({ + var view = new ToolbarView({ el: this, model: model, strings: options.strings }); + // Render collapsible menus. var menuModel = new MenuStateModel(); var menuView = new MenuView({ @@ -82,11 +82,10 @@ Drupal.behaviors.toolbar = { Drupal.toolbar.setSubtrees.done(function (subtrees) { menuModel.set('subtrees', subtrees); }); - // Expose a set of methods for }); }, // Default options. - options: { + defaults: { breakpoints: { 'module.toolbar.wide': '' }, @@ -98,10 +97,13 @@ Drupal.behaviors.toolbar = { } }; -// Expose a limited API to outside modules. +// Expose a limited public API. Drupal.toolbar = Drupal.toolbar || { + + model: null, + /** - * Accpets a list of subtree menu elements. + * Accepts a list of subtree menu elements. * * A deferred object that is resolved by an inlined JavaScript callback. * @@ -128,10 +130,11 @@ Drupal.toolbar = Drupal.toolbar || { else if (!element) { item = element; } - if (model && item) { - model.set('activeTab', item); + if (this.model && item) { + this.model.set('activeTab', item); } }, + /** * */ @@ -170,7 +173,8 @@ var StateModel = Backbone.Model.extend({ // normal circumstances. It will remain active across page loads. The active // item is stored as a DOM element, not a jQuery set. activeTab: null, - // Represents whether a tray is open or not. + // Represents whether a tray is open or not. Stored as a DOM element, not a + // jQuery set. activeTray: null, // The orientation of the active tray. orientation: 'horizontal', @@ -179,8 +183,8 @@ var StateModel = Backbone.Model.extend({ // configured breakpoints. The locked state will be maintained across page // loads. locked: false, - // Holds the mediaQueryList object. - mql: null, + // Indicates whether the media query matches or not. + mqMatches: null, // The height of the toolbar. height: null, // Whether the current dimensions are valid. Dimensions can be invalidated @@ -201,12 +205,12 @@ var ToolbarView = Backbone.View.extend({ /** * Implements Backbone.View.prototype.initialize(). */ - initialize: function() { + initialize: function () { this.strings = this.options.strings; this.model.on('change:activeTab', this.render, this); this.model.on('change:orientation', this.render, this); - this.model.on('change:mql', this.onMediaQueryChange, this); + this.model.on('change:mqMatches', this.onMediaQueryChange, this); this.model.on('change:dimensionsAreValid', this.setHeight, this); // Add the tray orientation toggles. @@ -214,14 +218,12 @@ var ToolbarView = Backbone.View.extend({ .find('.lining') .append(Drupal.theme('toolbarOrientationToggle')); - // Update the orientation according to the mql value. - // This model change is silent. The change will be reflect when render() - // is called. - this.model.set({ - orientation: this.mqlHandler() - }, { - silent: true - }); + // Update the tray orientation. + if (!this.model.get('locked')) { + var orientation = this._getTrayOrientation(this.model.get('mqMatches')); + // This model change is silent because this is the initialization phase. + this.model.set('orientation', orientation, { silent: true }); + } // Trigger an activeTab change so that listening scripts can respond on // page load. This will call render. @@ -243,7 +245,7 @@ var ToolbarView = Backbone.View.extend({ }, /** - * + * Toolbar tab click event handler; sets activeTab. */ onTabClick: function (event) { var tab = this.model.get('activeTab'); @@ -256,7 +258,7 @@ var ToolbarView = Backbone.View.extend({ }, /** - * + * Orientation toggle click event handler; toggles orientation. */ onOrientationToggleClick: function (event) { var orientation = this.model.get('orientation'); @@ -265,7 +267,7 @@ var ToolbarView = Backbone.View.extend({ var locked = (antiOrientation === 'vertical') ? true : false; // Remember the locked state. if (locked) { - localStorage.setItem('Drupal.toolbar.trayVerticalLocked', JSON.stringify(locked)); + localStorage.setItem('Drupal.toolbar.trayVerticalLocked', 'true'); } else { localStorage.removeItem('Drupal.toolbar.trayVerticalLocked'); @@ -281,22 +283,27 @@ var ToolbarView = Backbone.View.extend({ }, /** - * Respond to configured media query applicability changes. + * Model change handler; updates tray orientation. */ - onMediaQueryChange: function (model, mql) { - this.model.set('orientation', this.mqlHandler()); + onMediaQueryChange: function (model, mqMatches) { + this.model.set('orientation', this._getTrayOrientation(mqMatches)); }, /** + * Gets the tray orientation depending on whether the media query matches. * + * @param Boolean mqMatches + * Indicates whether the media query matches. + * + * @return String + * The orientation, either 'horizontal' or 'vertical'. */ - mqlHandler: function () { - var mql = this.model.get('mql'); - return (mql.matches) ? 'horizontal' : 'vertical'; + _getTrayOrientation: function (mqMatches) { + return mqMatches ? 'horizontal' : 'vertical'; }, /** - * Toggle a toolbar tab and the associated tray. + * Toggles a toolbar tab and the associated tray. */ refreshTabs: function (event) { var $tab = $(this.model.get('activeTab')); @@ -313,7 +320,7 @@ var ToolbarView = Backbone.View.extend({ localStorage.setItem('Drupal.toolbar.activeTabID', JSON.stringify(id)); } // Activate the associated tray. - var $tray = this.$el.find('[data-toolbar-tray="' + name + '"].tray'); + $tray = this.$el.find('[data-toolbar-tray="' + name + '"].tray'); if ($tray.length) { $tray.addClass('active'); this.model.set('activeTray', $tray.get(0)); @@ -461,13 +468,14 @@ var MenuView = Backbone.View.extend({ .toolbarMenu(); } } + }); /** - * A toggle is an interactive element often bound to a click handler. + * Theme function for the toolbar orientation toggle. * - * @return {String} - * A string representing a DOM fragment. + * @return + * The corresponding HTML. */ Drupal.theme.toolbarOrientationToggle = function () { return '
' + @@ -475,4 +483,4 @@ Drupal.theme.toolbarOrientationToggle = function () { '
'; }; -}(jQuery, Drupal, drupalSettings)); +}(jQuery, Backbone, Drupal, drupalSettings, document, window)); diff --git a/core/modules/toolbar/js/toolbar.menu.js b/core/modules/toolbar/js/toolbar.menu.js index 9b8473a..aba195d 100644 --- a/core/modules/toolbar/js/toolbar.menu.js +++ b/core/modules/toolbar/js/toolbar.menu.js @@ -5,7 +5,7 @@ * - For example, $('.menu').toolbarMenu(); */ -(function ($, Drupal) { +(function ($, Drupal, drupalSettings) { "use strict"; @@ -156,4 +156,4 @@ var activeItem = drupalSettings.basePath + Drupal.encodePath(drupalSettings.curr return ''; }; -}(jQuery, Drupal)); +}(jQuery, Drupal, drupalSettings)); diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module index 47cf740..63a061e 100644 --- a/core/modules/toolbar/toolbar.module +++ b/core/modules/toolbar/toolbar.module @@ -443,7 +443,7 @@ function toolbar_toolbar() { '#options' => array( 'attributes' => array( 'title' => t('Home page'), - 'class' => array('icon', 'icon-home',), + 'class' => array('icon', 'icon-home'), ), ), ),