405 lines
14 KiB
JavaScript
405 lines
14 KiB
JavaScript
/**
|
|
* @file toolbar.js
|
|
*
|
|
* Defines the behavior of the Drupal administration toolbar.
|
|
*/
|
|
(function ($, Drupal, drupalSettings) {
|
|
|
|
"use strict";
|
|
|
|
Drupal.toolbar = Drupal.toolbar || {};
|
|
|
|
/**
|
|
* Store the state of the active tab so it will remain active across page loads.
|
|
*/
|
|
var activeTab = JSON.parse(localStorage.getItem('Drupal.toolbar.activeTab'));
|
|
|
|
/**
|
|
* Store the state of the trays to maintain them across page loads.
|
|
*/
|
|
var locked = JSON.parse(localStorage.getItem('Drupal.toolbar.trayVerticalLocked')) || false;
|
|
var orientation = (locked) ? 'vertical' : 'horizontal';
|
|
|
|
/**
|
|
* Holds the jQuery objects of the toolbar DOM element, the trays and messages.
|
|
*/
|
|
var $toolbar;
|
|
var $trays;
|
|
var $messages;
|
|
|
|
/**
|
|
* Holds the mediaQueryList object.
|
|
*/
|
|
var mql = {
|
|
narrow: null,
|
|
standard: null,
|
|
wide: null
|
|
};
|
|
|
|
/**
|
|
* Register 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.
|
|
*
|
|
* Modules register tabs with hook_toolbar().
|
|
*/
|
|
Drupal.behaviors.toolbar = {
|
|
attach: function(context) {
|
|
// Verify that the sser agent understands media queries. Complex admin
|
|
// toolbar layouts require media query support.
|
|
if (!window.matchMedia('only screen').matches) {
|
|
return;
|
|
};
|
|
var options = $.extend(this.options, drupalSettings.toolbar);
|
|
var $toolbarOnce = $(context).find('#toolbar-administration').once('toolbar');
|
|
if ($toolbarOnce.length) {
|
|
// Assign the $toolbar variable in the closure.
|
|
$toolbar = $toolbarOnce;
|
|
// Add subtrees.
|
|
// @todo Optimize this to delay adding each subtree to the DOM until it is
|
|
// needed; however, take into account screen readers for determining
|
|
// when the DOM elements are needed.
|
|
var subtrees = Drupal.toolbar.subtrees;
|
|
if (subtrees) {
|
|
for (var id in subtrees) {
|
|
if (subtrees.hasOwnProperty(id)) {
|
|
$('#toolbar-link-' + id).after(subtrees[id]);
|
|
}
|
|
}
|
|
}
|
|
// Append a messages element for appending interaction updates for screen
|
|
// readers.
|
|
$messages = $(Drupal.theme('toolbarMessageBox')).appendTo($toolbar);
|
|
// Store the trays in a scoped variable.
|
|
$trays = $toolbar.find('.toolbar-tray');
|
|
$trays
|
|
// Add the tray orientation toggles.
|
|
.find('.toolbar-lining')
|
|
.append(Drupal.theme('toolbarOrientationToggle'));
|
|
// Store media queries and attach a handler.
|
|
mql.narrow = window.matchMedia(options.breakpoints['module.toolbar.narrow']);
|
|
mql.narrow.addListener(Drupal.toolbar.narrowMediaQueryChangeHandler);
|
|
mql.standard = window.matchMedia(options.breakpoints['module.toolbar.standard']);
|
|
mql.standard.addListener(Drupal.toolbar.standardMediaQueryChangeHandler);
|
|
mql.wide = window.matchMedia(options.breakpoints['module.toolbar.wide']);
|
|
mql.wide.addListener(Drupal.toolbar.wideMediaQueryChangeHandler);
|
|
// Fire each MediaQuery change handler so they process once.
|
|
Drupal.toolbar.narrowMediaQueryChangeHandler.call(this, mql.narrow);
|
|
Drupal.toolbar.standardMediaQueryChangeHandler.call(this, mql.standard);
|
|
Drupal.toolbar.wideMediaQueryChangeHandler.call(this, mql.wide);
|
|
// Set the orientation of the tray.
|
|
// If the tray is set to vertical in localStorage, persist the vertical
|
|
// presentation. If the tray is not locked to vertical, let the media
|
|
// query application decide the orientation.
|
|
changeOrientation((locked) ? 'vertical' : ((mql.wide.matches) ? 'horizontal' : 'vertical'), locked);
|
|
// Render the main menu as a nested, collapsible accordion.
|
|
$toolbar.find('.toolbar-menu-administration > .menu').toolbarMenu();
|
|
// Attach behaviors to the document.
|
|
$(document)
|
|
.on('drupalViewportOffsetChange.toolbar', Drupal.toolbar.adjustPlacement);
|
|
// Attach behaviors to the toolbar.
|
|
$toolbar
|
|
.on('click.toolbar', '.toolbar-bar a', Drupal.toolbar.toggleTray)
|
|
.on('click.toolbar', '.toolbar-toggle-orientation button', Drupal.toolbar.orientationChangeHandler);
|
|
// Restore the open tab. Only open the tab on wide screens.
|
|
if (activeTab && window.matchMedia(options.breakpoints['module.toolbar.standard']).matches) {
|
|
$toolbar.find('[data-toolbar-tray="' + activeTab + '"]').trigger('click.toolbar');
|
|
}
|
|
else {
|
|
// Update the page and toolbar dimension indicators.
|
|
updatePeripherals();
|
|
}
|
|
// Call displace to get the initial placement of offset elements.
|
|
Drupal.displace();
|
|
}
|
|
},
|
|
// Default options.
|
|
options: {
|
|
breakpoints: {
|
|
'module.toolbar.narrow': '',
|
|
'module.toolbar.standard': '',
|
|
'module.toolbar.wide': ''
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Set subtrees.
|
|
*
|
|
* JSONP callback.
|
|
* @see toolbar_subtrees_jsonp().
|
|
*/
|
|
Drupal.toolbar.setSubtrees = function(subtrees) {
|
|
Drupal.toolbar.subtrees = subtrees;
|
|
};
|
|
|
|
/**
|
|
* Toggle a toolbar tab and the associated tray.
|
|
*/
|
|
Drupal.toolbar.toggleTray = function (event) {
|
|
var strings = {
|
|
opened: Drupal.t('opened'),
|
|
closed: Drupal.t('closed')
|
|
};
|
|
var $tab = $(event.target);
|
|
var name = $tab.attr('data-toolbar-tray');
|
|
// Activate the selected tab and associated tray.
|
|
var $activateTray = $toolbar.find('[data-toolbar-tray="' + name + '"].toolbar-tray').toggleClass('active');
|
|
if ($activateTray.length) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
$tab.toggleClass('active');
|
|
// Toggle aria-pressed.
|
|
var value = $tab.prop('aria-pressed');
|
|
$tab.prop('aria-pressed', (value === 'false') ? 'true' : 'false');
|
|
// Append a message that a tray has been opened.
|
|
setMessage(Drupal.t('@tray tray @state.', {
|
|
'@tray': name,
|
|
'@state': (value === 'true') ? strings.closed : strings.opened
|
|
}));
|
|
// Store the active tab name or remove the setting.
|
|
if ($tab.hasClass('active')) {
|
|
localStorage.setItem('Drupal.toolbar.activeTab', JSON.stringify(name));
|
|
}
|
|
else {
|
|
localStorage.removeItem('Drupal.toolbar.activeTab');
|
|
}
|
|
// Disable non-selected tabs and trays.
|
|
$toolbar.find('.toolbar-bar .trigger')
|
|
.not($tab)
|
|
.removeClass('active')
|
|
// Set aria-pressed to false.
|
|
.prop('aria-pressed', false);
|
|
$toolbar.find('.toolbar-tray').not($activateTray).removeClass('active');
|
|
}
|
|
// Update the page and toolbar dimension indicators.
|
|
updatePeripherals();
|
|
};
|
|
|
|
/**
|
|
* Repositions trays and sets body padding according to the height of the bar.
|
|
*
|
|
* @param {Event} event
|
|
* - jQuery Event object.
|
|
*
|
|
* @param {Object} offsets
|
|
* - Contains for keys -- top, right, bottom and left -- that indicate the
|
|
* viewport offset distances calculated by Drupal.displace().
|
|
*/
|
|
Drupal.toolbar.adjustPlacement = function (event, offsets) {
|
|
if (!mql.narrow.matches) {
|
|
var $body = $('body');
|
|
var $trays = $toolbar.find('.toolbar-tray');
|
|
// Alter the padding on the top of the body element.
|
|
$body.css('padding-top', 0);
|
|
$trays.css('padding-top', 0);
|
|
// Remove any orientation classes. Make vertical the default for trays.
|
|
$body.removeClass('toolbar-vertical toolbar-horizontal');
|
|
$trays.removeClass('toolbar-tray-horizontal').addClass('toolbar-tray-vertical');
|
|
}
|
|
else {
|
|
// Alter the padding on the top of the body element.
|
|
$('body').css('padding-top', offsets.top);
|
|
// The navbar container is invisible. Its placement is used to determine the
|
|
// container for the trays.
|
|
$toolbar.find('.toolbar-tray').css('padding-top', $toolbar.find('.toolbar-bar').outerHeight());
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Sets the width of a vertical tray in a data attribute.
|
|
*
|
|
* If the width of the tray changed, Drupal.displace is called so that elements
|
|
* can adjust to the placement of the tray.
|
|
*/
|
|
Drupal.toolbar.setTrayWidth = function () {
|
|
var dir = document.documentElement.dir;
|
|
var edge = (dir === 'rtl') ? 'right' : 'left';
|
|
// Remove the side offset from the trays.
|
|
$toolbar.find('.toolbar-tray').removeAttr('data-offset-' + edge + ' data-offset-top');
|
|
// If the page is wider than the narrow media query, apply offset attributes.
|
|
if (mql.narrow.matches) {
|
|
// If an active vertical tray exists, mark it as an offset element.
|
|
$toolbar.find('.toolbar-tray.toolbar-tray-vertical.active').attr('data-offset-' + edge, '');
|
|
// If an active horizontal tray exists, mark it as an offset element.
|
|
$toolbar.find('.toolbar-tray.toolbar-tray-horizontal.active').attr('data-offset-top', '');
|
|
}
|
|
// Trigger a recalculation of viewport displacing elements.
|
|
Drupal.displace();
|
|
};
|
|
|
|
/**
|
|
* Respond to configured narrow media query changes.
|
|
*/
|
|
Drupal.toolbar.narrowMediaQueryChangeHandler = function (mql) {
|
|
var $bar = $toolbar.find('.toolbar-bar');
|
|
if (mql.matches) {
|
|
$bar.attr('data-offset-top', '');
|
|
}
|
|
else {
|
|
$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.
|
|
$toolbar.toggleClass('toolbar-oriented', mql.matches);
|
|
if (mql.matches) {
|
|
changeOrientation('vertical');
|
|
}
|
|
// Update the page and toolbar dimension indicators.
|
|
updatePeripherals();
|
|
};
|
|
|
|
/**
|
|
* Respond to configured standard media query changes.
|
|
*/
|
|
Drupal.toolbar.standardMediaQueryChangeHandler = function (mql) {
|
|
$('body').toggleClass('toolbar-paneled', mql.matches);
|
|
// Update the page and toolbar dimension indicators.
|
|
updatePeripherals();
|
|
};
|
|
|
|
/**
|
|
* Respond to configured wide media query changes.
|
|
*/
|
|
Drupal.toolbar.wideMediaQueryChangeHandler = function (mql) {
|
|
var orientation = (mql.matches) ? 'horizontal' : 'vertical';
|
|
changeOrientation(orientation);
|
|
// Update the page and toolbar dimension indicators.
|
|
updatePeripherals();
|
|
};
|
|
|
|
/**
|
|
* Respond to the toggling of the tray orientation.
|
|
*/
|
|
Drupal.toolbar.orientationChangeHandler = function (event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
var orientation = event.target.value;
|
|
changeOrientation(orientation, true);
|
|
// Update the page and toolbar dimension indicators.
|
|
updatePeripherals();
|
|
};
|
|
|
|
/**
|
|
* Change the orientation of the tray between vertical and horizontal.
|
|
*
|
|
* @param {String} newOrientation
|
|
* Either 'vertical' or 'horizontal'. The orientation to change the tray to.
|
|
*
|
|
* @param {Boolean} isLock
|
|
* Whether the orientation of the tray should be locked if it is being toggled
|
|
* to vertical.
|
|
*/
|
|
function changeOrientation (newOrientation, isLock) {
|
|
if (isLock) {
|
|
locked = (newOrientation === 'vertical');
|
|
if (locked) {
|
|
localStorage.setItem('Drupal.toolbar.trayVerticalLocked', JSON.stringify(locked));
|
|
}
|
|
else {
|
|
localStorage.removeItem('Drupal.toolbar.trayVerticalLocked');
|
|
}
|
|
}
|
|
if ((!locked && newOrientation === 'horizontal') || newOrientation === 'vertical') {
|
|
$trays
|
|
.removeClass('toolbar-tray-horizontal toolbar-tray-vertical')
|
|
.addClass('toolbar-tray-' + newOrientation);
|
|
orientation = newOrientation;
|
|
toggleOrientationToggle((newOrientation === 'vertical') ? 'horizontal' : 'vertical');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mark up the body tag to reflect the current state of the toolbar.
|
|
*/
|
|
function setBodyState () {
|
|
var $activeTray = $trays.filter('.active');
|
|
$('body')
|
|
.toggleClass('toolbar-tray-open', !!$activeTray.length)
|
|
.toggleClass('toolbar-vertical', (!!$activeTray.length && orientation === 'vertical'))
|
|
.toggleClass('toolbar-horizontal', (!!$activeTray.length && orientation === 'horizontal'));
|
|
}
|
|
|
|
/**
|
|
* Change the orientation toggle active state.
|
|
*/
|
|
function toggleOrientationToggle (orientation) {
|
|
var strings = {
|
|
horizontal: Drupal.t('Horizontal orientation'),
|
|
vertical: Drupal.t('Vertical orientation')
|
|
};
|
|
var antiOrientation = (orientation === 'vertical') ? 'horizontal' : 'vertical';
|
|
var iconClass = 'toolbar-icon-toggle-' + orientation;
|
|
var iconAntiClass = 'toolbar-icon-toggle-' + antiOrientation;
|
|
// Append a message that the tray orientation has been changed.
|
|
setMessage(Drupal.t('Tray orientation changed to @orientation.', {
|
|
'@orientation': antiOrientation
|
|
}));
|
|
// Change the tray orientation.
|
|
$trays.find('.toolbar-toggle-orientation button')
|
|
.val(orientation)
|
|
.text(strings[orientation])
|
|
.removeClass(iconAntiClass)
|
|
.addClass(iconClass);
|
|
}
|
|
|
|
/**
|
|
* Updates elements peripheral to the toolbar.
|
|
*
|
|
* When the dimensions and orientation of the toolbar change, elements on the
|
|
* page must either be changed or informed of the changes.
|
|
*/
|
|
function updatePeripherals () {
|
|
// Adjust the body to accommodate trays.
|
|
setBodyState();
|
|
// Adjust the tray width for vertical trays.
|
|
Drupal.toolbar.setTrayWidth();
|
|
}
|
|
|
|
/**
|
|
* Places the message in the toolbar's ARIA live message area.
|
|
*
|
|
* The message will be read by speaking User Agents.
|
|
*
|
|
* @param {String} message
|
|
* A string to be inserted into the message area.
|
|
*/
|
|
function setMessage (message) {
|
|
$messages.html(Drupal.theme('toolbarTrayMessage', message));
|
|
}
|
|
|
|
/**
|
|
* A toggle is an interactive element often bound to a click handler.
|
|
*
|
|
* @return {String}
|
|
* A string representing a DOM fragment.
|
|
*/
|
|
Drupal.theme.toolbarOrientationToggle = function () {
|
|
return '<div class="toolbar-toggle-orientation"><div class="toolbar-lining">' +
|
|
'<button class="toolbar-icon" type="button"></button>' +
|
|
'</div></div>';
|
|
};
|
|
|
|
/**
|
|
* A region to post messages that a screen reading UA will announce.
|
|
*
|
|
* @return {String}
|
|
* A string representing a DOM fragment.
|
|
*/
|
|
Drupal.theme.toolbarMessageBox = function () {
|
|
return '<div id="toolbar-messages" class="visually-hidden" role="region" aria-live="polite"></div>';
|
|
};
|
|
|
|
/**
|
|
* Wrap a message string in a p tag.
|
|
*
|
|
* @return {String}
|
|
* A string representing a DOM fragment.
|
|
*/
|
|
Drupal.theme.toolbarTrayMessage = function (message) {
|
|
return '<p>' + message + '</p>';
|
|
};
|
|
|
|
}(jQuery, Drupal, drupalSettings));
|