346 lines
11 KiB
JavaScript
346 lines
11 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 = {
|
|
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) {
|
|
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.
|
|
if (Drupal.toolbar.subtrees) {
|
|
for (var id in Drupal.toolbar.subtrees) {
|
|
$('#toolbar-link-' + id).after(Drupal.toolbar.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('.tray');
|
|
$trays
|
|
// Add the tray orientation toggles.
|
|
.find('.lining')
|
|
.append(Drupal.theme('toolbarOrientationToggle'));
|
|
// Store media queries.
|
|
mql.standard = window.matchMedia(options.breakpoints['module.toolbar.standard']);
|
|
// Set up switching between the vertical and horizontal presentation
|
|
// of the toolbar trays based on a breakpoint.
|
|
mql.wide = window.matchMedia(options.breakpoints['module.toolbar.wide']);
|
|
mql.wide.addListener(Drupal.toolbar.mediaQueryChangeHandler);
|
|
// 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();
|
|
// Call setHeight on screen resize. Wrap it in debounce to prevent
|
|
// setHeight from being called too frequently.
|
|
var setHeight = Drupal.debounce(Drupal.toolbar.setHeight, 200);
|
|
// Attach behavior to the window.
|
|
$(window)
|
|
.on('resize.toolbar', setHeight);
|
|
// Attach behaviors to the toolbar.
|
|
$toolbar
|
|
.on('click.toolbar', '.bar a', Drupal.toolbar.toggleTray)
|
|
.on('click.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();
|
|
}
|
|
}
|
|
},
|
|
// Default options.
|
|
options: {
|
|
breakpoints: {
|
|
'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 + '"].tray').toggleClass('active');
|
|
if ($activateTray.length) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
$tab.toggleClass('active');
|
|
// Toggle aria-pressed.
|
|
var value = $tab.attr('aria-pressed');
|
|
$tab.attr('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('.bar .trigger')
|
|
.not($tab)
|
|
.removeClass('active')
|
|
// Set aria-pressed to false.
|
|
.attr('aria-pressed', 'false');
|
|
$toolbar.find('.tray').not($activateTray).removeClass('active');
|
|
// Update the page and toolbar dimension indicators.
|
|
updatePeripherals();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The height of the toolbar offsets the top of the page content.
|
|
*
|
|
* Page components can register with the offsettopchange event to know when
|
|
* the height of the toolbar changes.
|
|
*/
|
|
Drupal.toolbar.setHeight = function () {
|
|
// Set the top of the all the trays to the height of the bar.
|
|
var barHeight = $toolbar.find('.bar').outerHeight();
|
|
var height = barHeight;
|
|
var bhpx = barHeight + 'px';
|
|
var tray;
|
|
for (var i = 0, il = $trays.length; i < il; i++) {
|
|
tray = $trays[i];
|
|
if (!tray.style.top.length || (tray.style.top !== bhpx)) {
|
|
tray.style.top = bhpx;
|
|
}
|
|
}
|
|
/**
|
|
* Get the height of the active tray and include it in the total
|
|
* height of the toolbar.
|
|
*/
|
|
height += $trays.filter('.active.horizontal').outerHeight() || 0;
|
|
// Indicate the height of the toolbar in the attribute data-offset-top.
|
|
var offset = parseInt($toolbar.attr('data-offset-top'), 10);
|
|
if (offset !== height) {
|
|
$toolbar.attr('data-offset-top', height);
|
|
// Alter the padding on the top of the body element.
|
|
$('body').css('padding-top', height);
|
|
$(document).trigger('offsettopchange', height);
|
|
$(window).trigger('resize');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Respond to configured media query applicability changes.
|
|
*/
|
|
Drupal.toolbar.mediaQueryChangeHandler = 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 $button = $(event.target);
|
|
var orientation = event.target.value;
|
|
var $tray = $button.closest('.tray');
|
|
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) {
|
|
var oldOrientation = orientation;
|
|
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('horizontal vertical')
|
|
.addClass(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 = 'icon-toggle-' + orientation;
|
|
var iconAntiClass = '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('.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 height of the toolbar.
|
|
Drupal.toolbar.setHeight();
|
|
}
|
|
|
|
/**
|
|
* 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="toggle-orientation"><div class="lining">' +
|
|
'<button class="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="element-invisible" 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));
|