toolbar.es6.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  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)
  46. .find('#toolbar-administration')
  47. .once('toolbar')
  48. .each(function() {
  49. // Establish the toolbar models and views.
  50. const model = new Drupal.toolbar.ToolbarModel({
  51. locked: JSON.parse(
  52. localStorage.getItem('Drupal.toolbar.trayVerticalLocked'),
  53. ),
  54. activeTab: document.getElementById(
  55. JSON.parse(localStorage.getItem('Drupal.toolbar.activeTabID')),
  56. ),
  57. height: $('#toolbar-administration').outerHeight(),
  58. });
  59. Drupal.toolbar.models.toolbarModel = model;
  60. // Attach a listener to the configured media query breakpoints.
  61. // Executes it before Drupal.toolbar.views to avoid extra rendering.
  62. Object.keys(options.breakpoints).forEach(label => {
  63. const mq = options.breakpoints[label];
  64. const mql = window.matchMedia(mq);
  65. Drupal.toolbar.mql[label] = mql;
  66. // Curry the model and the label of the media query breakpoint to
  67. // the mediaQueryChangeHandler function.
  68. mql.addListener(
  69. Drupal.toolbar.mediaQueryChangeHandler.bind(null, model, label),
  70. );
  71. // Fire the mediaQueryChangeHandler for each configured breakpoint
  72. // so that they process once.
  73. Drupal.toolbar.mediaQueryChangeHandler.call(
  74. null,
  75. model,
  76. label,
  77. mql,
  78. );
  79. });
  80. Drupal.toolbar.views.toolbarVisualView = new Drupal.toolbar.ToolbarVisualView(
  81. {
  82. el: this,
  83. model,
  84. strings: options.strings,
  85. },
  86. );
  87. Drupal.toolbar.views.toolbarAuralView = new Drupal.toolbar.ToolbarAuralView(
  88. {
  89. el: this,
  90. model,
  91. strings: options.strings,
  92. },
  93. );
  94. Drupal.toolbar.views.bodyVisualView = new Drupal.toolbar.BodyVisualView(
  95. {
  96. el: this,
  97. model,
  98. },
  99. );
  100. // Force layout render to fix mobile view. Only needed on load, not
  101. // for every media query match.
  102. model.trigger('change:isFixed', model, model.get('isFixed'));
  103. model.trigger('change:activeTray', model, model.get('activeTray'));
  104. // Render collapsible menus.
  105. const menuModel = new Drupal.toolbar.MenuModel();
  106. Drupal.toolbar.models.menuModel = menuModel;
  107. Drupal.toolbar.views.menuVisualView = new Drupal.toolbar.MenuVisualView(
  108. {
  109. el: $(this)
  110. .find('.toolbar-menu-administration')
  111. .get(0),
  112. model: menuModel,
  113. strings: options.strings,
  114. },
  115. );
  116. // Handle the resolution of Drupal.toolbar.setSubtrees.
  117. // This is handled with a deferred so that the function may be invoked
  118. // asynchronously.
  119. Drupal.toolbar.setSubtrees.done(subtrees => {
  120. menuModel.set('subtrees', subtrees);
  121. const theme = drupalSettings.ajaxPageState.theme;
  122. localStorage.setItem(
  123. `Drupal.toolbar.subtrees.${theme}`,
  124. JSON.stringify(subtrees),
  125. );
  126. // Indicate on the toolbarModel that subtrees are now loaded.
  127. model.set('areSubtreesLoaded', true);
  128. });
  129. // Trigger an initial attempt to load menu subitems. This first attempt
  130. // is made after the media query handlers have had an opportunity to
  131. // process. The toolbar starts in the vertical orientation by default,
  132. // unless the viewport is wide enough to accommodate a horizontal
  133. // orientation. Thus we give the Toolbar a chance to determine if it
  134. // should be set to horizontal orientation before attempting to load
  135. // menu subtrees.
  136. Drupal.toolbar.views.toolbarVisualView.loadSubtrees();
  137. $(document)
  138. // Update the model when the viewport offset changes.
  139. .on('drupalViewportOffsetChange.toolbar', (event, offsets) => {
  140. model.set('offsets', offsets);
  141. });
  142. // Broadcast model changes to other modules.
  143. model
  144. .on('change:orientation', (model, orientation) => {
  145. $(document).trigger(
  146. 'drupalToolbarOrientationChange',
  147. orientation,
  148. );
  149. })
  150. .on('change:activeTab', (model, tab) => {
  151. $(document).trigger('drupalToolbarTabChange', tab);
  152. })
  153. .on('change:activeTray', (model, tray) => {
  154. $(document).trigger('drupalToolbarTrayChange', tray);
  155. });
  156. // If the toolbar's orientation is horizontal and no active tab is
  157. // defined then show the tray of the first toolbar tab by default (but
  158. // not the first 'Home' toolbar tab).
  159. if (
  160. Drupal.toolbar.models.toolbarModel.get('orientation') ===
  161. 'horizontal' &&
  162. Drupal.toolbar.models.toolbarModel.get('activeTab') === null
  163. ) {
  164. Drupal.toolbar.models.toolbarModel.set({
  165. activeTab: $(
  166. '.toolbar-bar .toolbar-tab:not(.home-toolbar-tab) a',
  167. ).get(0),
  168. });
  169. }
  170. $(window).on({
  171. 'dialog:aftercreate': (event, dialog, $element, settings) => {
  172. const $toolbar = $('#toolbar-bar');
  173. $toolbar.css('margin-top', '0');
  174. // When off-canvas is positioned in top, toolbar has to be moved down.
  175. if (settings.drupalOffCanvasPosition === 'top') {
  176. const height = Drupal.offCanvas
  177. .getContainer($element)
  178. .outerHeight();
  179. $toolbar.css('margin-top', `${height}px`);
  180. $element.on('dialogContentResize.off-canvas', () => {
  181. const newHeight = Drupal.offCanvas
  182. .getContainer($element)
  183. .outerHeight();
  184. $toolbar.css('margin-top', `${newHeight}px`);
  185. });
  186. }
  187. },
  188. 'dialog:beforeclose': () => {
  189. $('#toolbar-bar').css('margin-top', '0');
  190. },
  191. });
  192. });
  193. },
  194. };
  195. /**
  196. * Toolbar methods of Backbone objects.
  197. *
  198. * @namespace
  199. */
  200. Drupal.toolbar = {
  201. /**
  202. * A hash of View instances.
  203. *
  204. * @type {object.<string, Backbone.View>}
  205. */
  206. views: {},
  207. /**
  208. * A hash of Model instances.
  209. *
  210. * @type {object.<string, Backbone.Model>}
  211. */
  212. models: {},
  213. /**
  214. * A hash of MediaQueryList objects tracked by the toolbar.
  215. *
  216. * @type {object.<string, object>}
  217. */
  218. mql: {},
  219. /**
  220. * Accepts a list of subtree menu elements.
  221. *
  222. * A deferred object that is resolved by an inlined JavaScript callback.
  223. *
  224. * @type {jQuery.Deferred}
  225. *
  226. * @see toolbar_subtrees_jsonp().
  227. */
  228. setSubtrees: new $.Deferred(),
  229. /**
  230. * Respond to configured narrow media query changes.
  231. *
  232. * @param {Drupal.toolbar.ToolbarModel} model
  233. * A toolbar model
  234. * @param {string} label
  235. * Media query label.
  236. * @param {object} mql
  237. * A MediaQueryList object.
  238. */
  239. mediaQueryChangeHandler(model, label, mql) {
  240. switch (label) {
  241. case 'toolbar.narrow':
  242. model.set({
  243. isOriented: mql.matches,
  244. isTrayToggleVisible: false,
  245. });
  246. // If the toolbar doesn't have an explicit orientation yet, or if the
  247. // narrow media query doesn't match then set the orientation to
  248. // vertical.
  249. if (!mql.matches || !model.get('orientation')) {
  250. model.set({ orientation: 'vertical' }, { validate: true });
  251. }
  252. break;
  253. case 'toolbar.standard':
  254. model.set({
  255. isFixed: mql.matches,
  256. });
  257. break;
  258. case 'toolbar.wide':
  259. model.set(
  260. {
  261. orientation:
  262. mql.matches && !model.get('locked') ? 'horizontal' : 'vertical',
  263. },
  264. { validate: true },
  265. );
  266. // The tray orientation toggle visibility does not need to be
  267. // validated.
  268. model.set({
  269. isTrayToggleVisible: mql.matches,
  270. });
  271. break;
  272. default:
  273. break;
  274. }
  275. },
  276. };
  277. /**
  278. * A toggle is an interactive element often bound to a click handler.
  279. *
  280. * @return {string}
  281. * A string representing a DOM fragment.
  282. */
  283. Drupal.theme.toolbarOrientationToggle = function() {
  284. return (
  285. '<div class="toolbar-toggle-orientation"><div class="toolbar-lining">' +
  286. '<button class="toolbar-icon" type="button"></button>' +
  287. '</div></div>'
  288. );
  289. };
  290. /**
  291. * Ajax command to set the toolbar subtrees.
  292. *
  293. * @param {Drupal.Ajax} ajax
  294. * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
  295. * @param {object} response
  296. * JSON response from the Ajax request.
  297. * @param {number} [status]
  298. * XMLHttpRequest status.
  299. */
  300. Drupal.AjaxCommands.prototype.setToolbarSubtrees = function(
  301. ajax,
  302. response,
  303. status,
  304. ) {
  305. Drupal.toolbar.setSubtrees.resolve(response.subtrees);
  306. };
  307. })(jQuery, Drupal, drupalSettings);