360 lines
10 KiB
JavaScript
360 lines
10 KiB
JavaScript
/**
|
|
* @file
|
|
* Drupal's off-canvas library.
|
|
*/
|
|
|
|
(($, Drupal, debounce, displace) => {
|
|
/**
|
|
* Off-canvas dialog implementation using jQuery Dialog.
|
|
*
|
|
* Transforms the regular dialogs created using Drupal.dialog when the dialog
|
|
* element equals '#drupal-off-canvas' into an side-loading dialog.
|
|
*
|
|
* @namespace
|
|
*/
|
|
Drupal.offCanvas = {
|
|
/**
|
|
* Storage for position information about the tray.
|
|
*
|
|
* @type {?String}
|
|
*/
|
|
position: null,
|
|
|
|
/**
|
|
* The minimum height of the tray when opened at the top of the page.
|
|
*
|
|
* @type {Number}
|
|
*/
|
|
minimumHeight: 30,
|
|
|
|
/**
|
|
* The minimum width to use body displace needs to match the width at which
|
|
* the tray will be 100% width. @see core/misc/dialog/off-canvas.css
|
|
*
|
|
* @type {Number}
|
|
*/
|
|
minDisplaceWidth: 768,
|
|
|
|
/**
|
|
* Wrapper used to position off-canvas dialog.
|
|
*
|
|
* @type {jQuery}
|
|
*/
|
|
$mainCanvasWrapper: $('[data-off-canvas-main-canvas]'),
|
|
|
|
/**
|
|
* Determines if an element is an off-canvas dialog.
|
|
*
|
|
* @param {jQuery} $element
|
|
* The dialog element.
|
|
*
|
|
* @return {bool}
|
|
* True this is currently an off-canvas dialog.
|
|
*/
|
|
isOffCanvas($element) {
|
|
return $element.is('#drupal-off-canvas');
|
|
},
|
|
|
|
/**
|
|
* Remove off-canvas dialog events.
|
|
*
|
|
* @param {jQuery} $element
|
|
* The target element.
|
|
*/
|
|
removeOffCanvasEvents($element) {
|
|
$element.off('.off-canvas');
|
|
$(document).off('.off-canvas');
|
|
$(window).off('.off-canvas');
|
|
},
|
|
|
|
/**
|
|
* Handler fired before an off-canvas dialog has been opened.
|
|
*
|
|
* @param {Object} settings
|
|
* Settings related to the composition of the dialog.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
beforeCreate({ settings, $element }) {
|
|
// Clean up previous dialog event handlers.
|
|
Drupal.offCanvas.removeOffCanvasEvents($element);
|
|
|
|
$('body').addClass('js-off-canvas-dialog-open');
|
|
// @see http://api.jqueryui.com/position/
|
|
settings.position = {
|
|
my: 'left top',
|
|
at: `${Drupal.offCanvas.getEdge()} top`,
|
|
of: window,
|
|
};
|
|
|
|
/**
|
|
* Applies initial height and with to dialog based depending on position.
|
|
* @see http://api.jqueryui.com/dialog for all dialog options.
|
|
*/
|
|
const position = settings.drupalOffCanvasPosition;
|
|
const height = position === 'side' ? $(window).height() : settings.height;
|
|
const width = position === 'side' ? settings.width : '100%';
|
|
settings.height = height;
|
|
settings.width = width;
|
|
},
|
|
|
|
/**
|
|
* Handler fired after an off-canvas dialog has been closed.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
beforeClose({ $element }) {
|
|
$('body').removeClass('js-off-canvas-dialog-open');
|
|
// Remove all *.off-canvas events
|
|
Drupal.offCanvas.removeOffCanvasEvents($element);
|
|
Drupal.offCanvas.resetPadding();
|
|
},
|
|
|
|
/**
|
|
* Handler fired when an off-canvas dialog has been opened.
|
|
*
|
|
* @param {jQuery} $element
|
|
* The off-canvas dialog element.
|
|
* @param {Object} settings
|
|
* Settings related to the composition of the dialog.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
afterCreate({ $element, settings }) {
|
|
const eventData = { settings, $element, offCanvasDialog: this };
|
|
|
|
$element
|
|
.on(
|
|
'dialogContentResize.off-canvas',
|
|
eventData,
|
|
Drupal.offCanvas.handleDialogResize,
|
|
)
|
|
.on(
|
|
'dialogContentResize.off-canvas',
|
|
eventData,
|
|
Drupal.offCanvas.bodyPadding,
|
|
);
|
|
|
|
Drupal.offCanvas
|
|
.getContainer($element)
|
|
.attr(`data-offset-${Drupal.offCanvas.getEdge()}`, '');
|
|
|
|
$(window)
|
|
.on(
|
|
'resize.off-canvas',
|
|
eventData,
|
|
debounce(Drupal.offCanvas.resetSize, 100),
|
|
)
|
|
.trigger('resize.off-canvas');
|
|
},
|
|
|
|
/**
|
|
* Toggle classes based on title existence.
|
|
* Called with Drupal.offCanvas.afterCreate.
|
|
*
|
|
* @param {Object} settings
|
|
* Settings related to the composition of the dialog.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
render({ settings }) {
|
|
$(
|
|
'.ui-dialog-off-canvas, .ui-dialog-off-canvas .ui-dialog-titlebar',
|
|
).toggleClass('ui-dialog-empty-title', !settings.title);
|
|
},
|
|
|
|
/**
|
|
* Adjusts the dialog on resize.
|
|
*
|
|
* @param {jQuery.Event} event
|
|
* The event triggered.
|
|
* @param {object} event.data
|
|
* Data attached to the event.
|
|
*/
|
|
handleDialogResize(event) {
|
|
const $element = event.data.$element;
|
|
const $container = Drupal.offCanvas.getContainer($element);
|
|
|
|
const $offsets = $container.find(
|
|
'> :not(#drupal-off-canvas, .ui-resizable-handle)',
|
|
);
|
|
let offset = 0;
|
|
|
|
// Let scroll element take all the height available.
|
|
$element.css({ height: 'auto' });
|
|
const modalHeight = $container.height();
|
|
|
|
$offsets.each((i, e) => {
|
|
offset += $(e).outerHeight();
|
|
});
|
|
|
|
// Take internal padding into account.
|
|
const scrollOffset = $element.outerHeight() - $element.height();
|
|
$element.height(modalHeight - offset - scrollOffset);
|
|
},
|
|
|
|
/**
|
|
* Resets the size of the dialog.
|
|
*
|
|
* @param {jQuery.Event} event
|
|
* The event triggered.
|
|
* @param {object} event.data
|
|
* Data attached to the event.
|
|
*/
|
|
resetSize(event) {
|
|
const $element = event.data.$element;
|
|
const container = Drupal.offCanvas.getContainer($element);
|
|
const position = event.data.settings.drupalOffCanvasPosition;
|
|
|
|
// Only remove the `data-offset-*` attribute if the value previously
|
|
// exists and the orientation is changing.
|
|
if (Drupal.offCanvas.position && Drupal.offCanvas.position !== position) {
|
|
container.removeAttr(`data-offset-${Drupal.offCanvas.position}`);
|
|
}
|
|
// Set a minimum height on $element
|
|
if (position === 'top') {
|
|
$element.css('min-height', `${Drupal.offCanvas.minimumHeight}px`);
|
|
}
|
|
|
|
displace();
|
|
|
|
const offsets = displace.offsets;
|
|
|
|
const topPosition =
|
|
position === 'side' && offsets.top !== 0 ? `+${offsets.top}` : '';
|
|
const adjustedOptions = {
|
|
// @see http://api.jqueryui.com/position/
|
|
position: {
|
|
my: `${Drupal.offCanvas.getEdge()} top`,
|
|
at: `${Drupal.offCanvas.getEdge()} top${topPosition}`,
|
|
of: window,
|
|
},
|
|
};
|
|
|
|
const height =
|
|
position === 'side'
|
|
? `${$(window).height() - (offsets.top + offsets.bottom)}px`
|
|
: event.data.settings.height;
|
|
container.css({
|
|
position: 'fixed',
|
|
height,
|
|
});
|
|
|
|
$element
|
|
.dialog('option', adjustedOptions)
|
|
.trigger('dialogContentResize.off-canvas');
|
|
|
|
Drupal.offCanvas.position = position;
|
|
},
|
|
|
|
/**
|
|
* Adjusts the body padding when the dialog is resized.
|
|
*
|
|
* @param {jQuery.Event} event
|
|
* The event triggered.
|
|
* @param {object} event.data
|
|
* Data attached to the event.
|
|
*/
|
|
bodyPadding(event) {
|
|
const position = event.data.settings.drupalOffCanvasPosition;
|
|
if (
|
|
position === 'side' &&
|
|
$('body').outerWidth() < Drupal.offCanvas.minDisplaceWidth
|
|
) {
|
|
return;
|
|
}
|
|
Drupal.offCanvas.resetPadding();
|
|
const $element = event.data.$element;
|
|
const $container = Drupal.offCanvas.getContainer($element);
|
|
const $mainCanvasWrapper = Drupal.offCanvas.$mainCanvasWrapper;
|
|
|
|
const width = $container.outerWidth();
|
|
const mainCanvasPadding = $mainCanvasWrapper.css(
|
|
`padding-${Drupal.offCanvas.getEdge()}`,
|
|
);
|
|
if (position === 'side' && width !== mainCanvasPadding) {
|
|
$mainCanvasWrapper.css(
|
|
`padding-${Drupal.offCanvas.getEdge()}`,
|
|
`${width}px`,
|
|
);
|
|
$container.attr(`data-offset-${Drupal.offCanvas.getEdge()}`, width);
|
|
displace();
|
|
}
|
|
|
|
const height = $container.outerHeight();
|
|
if (position === 'top') {
|
|
$mainCanvasWrapper.css('padding-top', `${height}px`);
|
|
$container.attr('data-offset-top', height);
|
|
displace();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* The HTML element that surrounds the dialog.
|
|
* @param {HTMLElement} $element
|
|
* The dialog element.
|
|
*
|
|
* @return {HTMLElement}
|
|
* The containing element.
|
|
*/
|
|
getContainer($element) {
|
|
return $element.dialog('widget');
|
|
},
|
|
|
|
/**
|
|
* The edge of the screen that the dialog should appear on.
|
|
*
|
|
* @return {string}
|
|
* The edge the tray will be shown on, left or right.
|
|
*/
|
|
getEdge() {
|
|
return document.documentElement.dir === 'rtl' ? 'left' : 'right';
|
|
},
|
|
|
|
/**
|
|
* Resets main canvas wrapper and toolbar padding / margin.
|
|
*/
|
|
resetPadding() {
|
|
Drupal.offCanvas.$mainCanvasWrapper.css(
|
|
`padding-${Drupal.offCanvas.getEdge()}`,
|
|
0,
|
|
);
|
|
Drupal.offCanvas.$mainCanvasWrapper.css('padding-top', 0);
|
|
displace();
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Attaches off-canvas dialog behaviors.
|
|
*
|
|
* @type {Drupal~behavior}
|
|
*
|
|
* @prop {Drupal~behaviorAttach} attach
|
|
* Attaches event listeners for off-canvas dialogs.
|
|
*/
|
|
Drupal.behaviors.offCanvasEvents = {
|
|
attach: () => {
|
|
$(window)
|
|
.once('off-canvas')
|
|
.on({
|
|
'dialog:beforecreate': (event, dialog, $element, settings) => {
|
|
if (Drupal.offCanvas.isOffCanvas($element)) {
|
|
Drupal.offCanvas.beforeCreate({ dialog, $element, settings });
|
|
}
|
|
},
|
|
'dialog:aftercreate': (event, dialog, $element, settings) => {
|
|
if (Drupal.offCanvas.isOffCanvas($element)) {
|
|
Drupal.offCanvas.render({ dialog, $element, settings });
|
|
Drupal.offCanvas.afterCreate({ $element, settings });
|
|
}
|
|
},
|
|
'dialog:beforeclose': (event, dialog, $element) => {
|
|
if (Drupal.offCanvas.isOffCanvas($element)) {
|
|
Drupal.offCanvas.beforeClose({ dialog, $element });
|
|
}
|
|
},
|
|
});
|
|
},
|
|
};
|
|
})(jQuery, Drupal, Drupal.debounce, Drupal.displace);
|