toolbar.es6.js 8.9 KB

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