toolbar.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. /**
  2. * @file
  3. * Defines the behavior of the Drupal administration toolbar.
  4. */
  5. (function ($, Drupal, drupalSettings) {
  6. 'use strict';
  7. // Merge run-time settings with the defaults.
  8. var options = $.extend(
  9. {
  10. breakpoints: {
  11. 'toolbar.narrow': '',
  12. 'toolbar.standard': '',
  13. 'toolbar.wide': ''
  14. }
  15. },
  16. drupalSettings.toolbar,
  17. // Merge strings on top of drupalSettings so that they are not mutable.
  18. {
  19. strings: {
  20. horizontal: Drupal.t('Horizontal orientation'),
  21. vertical: Drupal.t('Vertical orientation')
  22. }
  23. }
  24. );
  25. /**
  26. * Registers tabs with the toolbar.
  27. *
  28. * The Drupal toolbar allows modules to register top-level tabs. These may
  29. * point directly to a resource or toggle the visibility of a tray.
  30. *
  31. * Modules register tabs with hook_toolbar().
  32. *
  33. * @type {Drupal~behavior}
  34. *
  35. * @prop {Drupal~behaviorAttach} attach
  36. * Attaches the toolbar rendering functionality to the toolbar element.
  37. */
  38. Drupal.behaviors.toolbar = {
  39. attach: function (context) {
  40. // Verify that the user agent understands media queries. Complex admin
  41. // toolbar layouts require media query support.
  42. if (!window.matchMedia('only screen').matches) {
  43. return;
  44. }
  45. // Process the administrative toolbar.
  46. $(context).find('#toolbar-administration').once('toolbar').each(function () {
  47. // Establish the toolbar models and views.
  48. var model = Drupal.toolbar.models.toolbarModel = new Drupal.toolbar.ToolbarModel({
  49. locked: JSON.parse(localStorage.getItem('Drupal.toolbar.trayVerticalLocked')) || false,
  50. activeTab: document.getElementById(JSON.parse(localStorage.getItem('Drupal.toolbar.activeTabID')))
  51. });
  52. Drupal.toolbar.views.toolbarVisualView = new Drupal.toolbar.ToolbarVisualView({
  53. el: this,
  54. model: model,
  55. strings: options.strings
  56. });
  57. Drupal.toolbar.views.toolbarAuralView = new Drupal.toolbar.ToolbarAuralView({
  58. el: this,
  59. model: model,
  60. strings: options.strings
  61. });
  62. Drupal.toolbar.views.bodyVisualView = new Drupal.toolbar.BodyVisualView({
  63. el: this,
  64. model: model
  65. });
  66. // Render collapsible menus.
  67. var menuModel = Drupal.toolbar.models.menuModel = new Drupal.toolbar.MenuModel();
  68. Drupal.toolbar.views.menuVisualView = new Drupal.toolbar.MenuVisualView({
  69. el: $(this).find('.toolbar-menu-administration').get(0),
  70. model: menuModel,
  71. strings: options.strings
  72. });
  73. // Handle the resolution of Drupal.toolbar.setSubtrees.
  74. // This is handled with a deferred so that the function may be invoked
  75. // asynchronously.
  76. Drupal.toolbar.setSubtrees.done(function (subtrees) {
  77. menuModel.set('subtrees', subtrees);
  78. var theme = drupalSettings.ajaxPageState.theme;
  79. localStorage.setItem('Drupal.toolbar.subtrees.' + theme, JSON.stringify(subtrees));
  80. // Indicate on the toolbarModel that subtrees are now loaded.
  81. model.set('areSubtreesLoaded', true);
  82. });
  83. // Attach a listener to the configured media query breakpoints.
  84. for (var label in options.breakpoints) {
  85. if (options.breakpoints.hasOwnProperty(label)) {
  86. var mq = options.breakpoints[label];
  87. var mql = Drupal.toolbar.mql[label] = window.matchMedia(mq);
  88. // Curry the model and the label of the media query breakpoint to
  89. // the mediaQueryChangeHandler function.
  90. mql.addListener(Drupal.toolbar.mediaQueryChangeHandler.bind(null, model, label));
  91. // Fire the mediaQueryChangeHandler for each configured breakpoint
  92. // so that they process once.
  93. Drupal.toolbar.mediaQueryChangeHandler.call(null, model, label, mql);
  94. }
  95. }
  96. // Trigger an initial attempt to load menu subitems. This first attempt
  97. // is made after the media query handlers have had an opportunity to
  98. // process. The toolbar starts in the vertical orientation by default,
  99. // unless the viewport is wide enough to accommodate a horizontal
  100. // orientation. Thus we give the Toolbar a chance to determine if it
  101. // should be set to horizontal orientation before attempting to load
  102. // menu subtrees.
  103. Drupal.toolbar.views.toolbarVisualView.loadSubtrees();
  104. $(document)
  105. // Update the model when the viewport offset changes.
  106. .on('drupalViewportOffsetChange.toolbar', function (event, offsets) {
  107. model.set('offsets', offsets);
  108. });
  109. // Broadcast model changes to other modules.
  110. model
  111. .on('change:orientation', function (model, orientation) {
  112. $(document).trigger('drupalToolbarOrientationChange', orientation);
  113. })
  114. .on('change:activeTab', function (model, tab) {
  115. $(document).trigger('drupalToolbarTabChange', tab);
  116. })
  117. .on('change:activeTray', function (model, tray) {
  118. $(document).trigger('drupalToolbarTrayChange', tray);
  119. });
  120. // If the toolbar's orientation is horizontal and no active tab is
  121. // defined then show the tray of the first toolbar tab by default (but
  122. // not the first 'Home' toolbar tab).
  123. if (Drupal.toolbar.models.toolbarModel.get('orientation') === 'horizontal' && Drupal.toolbar.models.toolbarModel.get('activeTab') === null) {
  124. Drupal.toolbar.models.toolbarModel.set({
  125. activeTab: $('.toolbar-bar .toolbar-tab:not(.home-toolbar-tab) a').get(0)
  126. });
  127. }
  128. });
  129. }
  130. };
  131. /**
  132. * Toolbar methods of Backbone objects.
  133. *
  134. * @namespace
  135. */
  136. Drupal.toolbar = {
  137. /**
  138. * A hash of View instances.
  139. *
  140. * @type {object.<string, Backbone.View>}
  141. */
  142. views: {},
  143. /**
  144. * A hash of Model instances.
  145. *
  146. * @type {object.<string, Backbone.Model>}
  147. */
  148. models: {},
  149. /**
  150. * A hash of MediaQueryList objects tracked by the toolbar.
  151. *
  152. * @type {object.<string, object>}
  153. */
  154. mql: {},
  155. /**
  156. * Accepts a list of subtree menu elements.
  157. *
  158. * A deferred object that is resolved by an inlined JavaScript callback.
  159. *
  160. * @type {jQuery.Deferred}
  161. *
  162. * @see toolbar_subtrees_jsonp().
  163. */
  164. setSubtrees: new $.Deferred(),
  165. /**
  166. * Respond to configured narrow media query changes.
  167. *
  168. * @param {Drupal.toolbar.ToolbarModel} model
  169. * A toolbar model
  170. * @param {string} label
  171. * Media query label.
  172. * @param {object} mql
  173. * A MediaQueryList object.
  174. */
  175. mediaQueryChangeHandler: function (model, label, mql) {
  176. switch (label) {
  177. case 'toolbar.narrow':
  178. model.set({
  179. isOriented: mql.matches,
  180. isTrayToggleVisible: false
  181. });
  182. // If the toolbar doesn't have an explicit orientation yet, or if the
  183. // narrow media query doesn't match then set the orientation to
  184. // vertical.
  185. if (!mql.matches || !model.get('orientation')) {
  186. model.set({orientation: 'vertical'}, {validate: true});
  187. }
  188. break;
  189. case 'toolbar.standard':
  190. model.set({
  191. isFixed: mql.matches
  192. });
  193. break;
  194. case 'toolbar.wide':
  195. model.set({
  196. orientation: ((mql.matches) ? 'horizontal' : 'vertical')
  197. }, {validate: true});
  198. // The tray orientation toggle visibility does not need to be
  199. // validated.
  200. model.set({
  201. isTrayToggleVisible: mql.matches
  202. });
  203. break;
  204. default:
  205. break;
  206. }
  207. }
  208. };
  209. /**
  210. * A toggle is an interactive element often bound to a click handler.
  211. *
  212. * @return {string}
  213. * A string representing a DOM fragment.
  214. */
  215. Drupal.theme.toolbarOrientationToggle = function () {
  216. return '<div class="toolbar-toggle-orientation"><div class="toolbar-lining">' +
  217. '<button class="toolbar-icon" type="button"></button>' +
  218. '</div></div>';
  219. };
  220. /**
  221. * Ajax command to set the toolbar subtrees.
  222. *
  223. * @param {Drupal.Ajax} ajax
  224. * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
  225. * @param {object} response
  226. * JSON response from the Ajax request.
  227. * @param {number} [status]
  228. * XMLHttpRequest status.
  229. */
  230. Drupal.AjaxCommands.prototype.setToolbarSubtrees = function (ajax, response, status) {
  231. Drupal.toolbar.setSubtrees.resolve(response.subtrees);
  232. };
  233. }(jQuery, Drupal, drupalSettings));