Chris@14
|
1 /**
|
Chris@14
|
2 * @file
|
Chris@14
|
3 * Drupal's off-canvas library.
|
Chris@14
|
4 */
|
Chris@14
|
5
|
Chris@14
|
6 (($, Drupal, debounce, displace) => {
|
Chris@14
|
7 /**
|
Chris@14
|
8 * Off-canvas dialog implementation using jQuery Dialog.
|
Chris@14
|
9 *
|
Chris@14
|
10 * Transforms the regular dialogs created using Drupal.dialog when the dialog
|
Chris@14
|
11 * element equals '#drupal-off-canvas' into an side-loading dialog.
|
Chris@14
|
12 *
|
Chris@14
|
13 * @namespace
|
Chris@14
|
14 */
|
Chris@14
|
15 Drupal.offCanvas = {
|
Chris@17
|
16 /**
|
Chris@17
|
17 * Storage for position information about the tray.
|
Chris@17
|
18 *
|
Chris@17
|
19 * @type {?String}
|
Chris@17
|
20 */
|
Chris@17
|
21 position: null,
|
Chris@17
|
22
|
Chris@17
|
23 /**
|
Chris@17
|
24 * The minimum height of the tray when opened at the top of the page.
|
Chris@17
|
25 *
|
Chris@17
|
26 * @type {Number}
|
Chris@17
|
27 */
|
Chris@17
|
28 minimumHeight: 30,
|
Chris@14
|
29
|
Chris@14
|
30 /**
|
Chris@14
|
31 * The minimum width to use body displace needs to match the width at which
|
Chris@14
|
32 * the tray will be 100% width. @see core/misc/dialog/off-canvas.css
|
Chris@14
|
33 *
|
Chris@14
|
34 * @type {Number}
|
Chris@14
|
35 */
|
Chris@14
|
36 minDisplaceWidth: 768,
|
Chris@14
|
37
|
Chris@14
|
38 /**
|
Chris@14
|
39 * Wrapper used to position off-canvas dialog.
|
Chris@14
|
40 *
|
Chris@14
|
41 * @type {jQuery}
|
Chris@14
|
42 */
|
Chris@14
|
43 $mainCanvasWrapper: $('[data-off-canvas-main-canvas]'),
|
Chris@14
|
44
|
Chris@14
|
45 /**
|
Chris@14
|
46 * Determines if an element is an off-canvas dialog.
|
Chris@14
|
47 *
|
Chris@14
|
48 * @param {jQuery} $element
|
Chris@14
|
49 * The dialog element.
|
Chris@14
|
50 *
|
Chris@14
|
51 * @return {bool}
|
Chris@14
|
52 * True this is currently an off-canvas dialog.
|
Chris@14
|
53 */
|
Chris@14
|
54 isOffCanvas($element) {
|
Chris@14
|
55 return $element.is('#drupal-off-canvas');
|
Chris@14
|
56 },
|
Chris@14
|
57
|
Chris@14
|
58 /**
|
Chris@14
|
59 * Remove off-canvas dialog events.
|
Chris@14
|
60 *
|
Chris@14
|
61 * @param {jQuery} $element
|
Chris@14
|
62 * The target element.
|
Chris@14
|
63 */
|
Chris@14
|
64 removeOffCanvasEvents($element) {
|
Chris@14
|
65 $element.off('.off-canvas');
|
Chris@14
|
66 $(document).off('.off-canvas');
|
Chris@14
|
67 $(window).off('.off-canvas');
|
Chris@14
|
68 },
|
Chris@14
|
69
|
Chris@14
|
70 /**
|
Chris@14
|
71 * Handler fired before an off-canvas dialog has been opened.
|
Chris@14
|
72 *
|
Chris@14
|
73 * @param {Object} settings
|
Chris@14
|
74 * Settings related to the composition of the dialog.
|
Chris@14
|
75 *
|
Chris@14
|
76 * @return {undefined}
|
Chris@14
|
77 */
|
Chris@14
|
78 beforeCreate({ settings, $element }) {
|
Chris@14
|
79 // Clean up previous dialog event handlers.
|
Chris@14
|
80 Drupal.offCanvas.removeOffCanvasEvents($element);
|
Chris@14
|
81
|
Chris@14
|
82 $('body').addClass('js-off-canvas-dialog-open');
|
Chris@14
|
83 // @see http://api.jqueryui.com/position/
|
Chris@14
|
84 settings.position = {
|
Chris@14
|
85 my: 'left top',
|
Chris@14
|
86 at: `${Drupal.offCanvas.getEdge()} top`,
|
Chris@14
|
87 of: window,
|
Chris@14
|
88 };
|
Chris@14
|
89
|
Chris@14
|
90 /**
|
Chris@17
|
91 * Applies initial height and with to dialog based depending on position.
|
Chris@14
|
92 * @see http://api.jqueryui.com/dialog for all dialog options.
|
Chris@14
|
93 */
|
Chris@17
|
94 const position = settings.drupalOffCanvasPosition;
|
Chris@17
|
95 const height = position === 'side' ? $(window).height() : settings.height;
|
Chris@17
|
96 const width = position === 'side' ? settings.width : '100%';
|
Chris@17
|
97 settings.height = height;
|
Chris@17
|
98 settings.width = width;
|
Chris@14
|
99 },
|
Chris@14
|
100
|
Chris@14
|
101 /**
|
Chris@14
|
102 * Handler fired after an off-canvas dialog has been closed.
|
Chris@14
|
103 *
|
Chris@14
|
104 * @return {undefined}
|
Chris@14
|
105 */
|
Chris@14
|
106 beforeClose({ $element }) {
|
Chris@14
|
107 $('body').removeClass('js-off-canvas-dialog-open');
|
Chris@14
|
108 // Remove all *.off-canvas events
|
Chris@14
|
109 Drupal.offCanvas.removeOffCanvasEvents($element);
|
Chris@17
|
110 Drupal.offCanvas.resetPadding();
|
Chris@14
|
111 },
|
Chris@14
|
112
|
Chris@14
|
113 /**
|
Chris@14
|
114 * Handler fired when an off-canvas dialog has been opened.
|
Chris@14
|
115 *
|
Chris@14
|
116 * @param {jQuery} $element
|
Chris@14
|
117 * The off-canvas dialog element.
|
Chris@14
|
118 * @param {Object} settings
|
Chris@14
|
119 * Settings related to the composition of the dialog.
|
Chris@14
|
120 *
|
Chris@14
|
121 * @return {undefined}
|
Chris@14
|
122 */
|
Chris@14
|
123 afterCreate({ $element, settings }) {
|
Chris@14
|
124 const eventData = { settings, $element, offCanvasDialog: this };
|
Chris@14
|
125
|
Chris@14
|
126 $element
|
Chris@17
|
127 .on(
|
Chris@17
|
128 'dialogContentResize.off-canvas',
|
Chris@17
|
129 eventData,
|
Chris@17
|
130 Drupal.offCanvas.handleDialogResize,
|
Chris@17
|
131 )
|
Chris@17
|
132 .on(
|
Chris@17
|
133 'dialogContentResize.off-canvas',
|
Chris@17
|
134 eventData,
|
Chris@17
|
135 Drupal.offCanvas.bodyPadding,
|
Chris@17
|
136 );
|
Chris@14
|
137
|
Chris@17
|
138 Drupal.offCanvas
|
Chris@17
|
139 .getContainer($element)
|
Chris@17
|
140 .attr(`data-offset-${Drupal.offCanvas.getEdge()}`, '');
|
Chris@14
|
141
|
Chris@14
|
142 $(window)
|
Chris@17
|
143 .on(
|
Chris@17
|
144 'resize.off-canvas',
|
Chris@17
|
145 eventData,
|
Chris@17
|
146 debounce(Drupal.offCanvas.resetSize, 100),
|
Chris@17
|
147 )
|
Chris@14
|
148 .trigger('resize.off-canvas');
|
Chris@14
|
149 },
|
Chris@14
|
150
|
Chris@14
|
151 /**
|
Chris@14
|
152 * Toggle classes based on title existence.
|
Chris@14
|
153 * Called with Drupal.offCanvas.afterCreate.
|
Chris@14
|
154 *
|
Chris@14
|
155 * @param {Object} settings
|
Chris@14
|
156 * Settings related to the composition of the dialog.
|
Chris@14
|
157 *
|
Chris@14
|
158 * @return {undefined}
|
Chris@14
|
159 */
|
Chris@14
|
160 render({ settings }) {
|
Chris@17
|
161 $(
|
Chris@17
|
162 '.ui-dialog-off-canvas, .ui-dialog-off-canvas .ui-dialog-titlebar',
|
Chris@17
|
163 ).toggleClass('ui-dialog-empty-title', !settings.title);
|
Chris@14
|
164 },
|
Chris@14
|
165
|
Chris@14
|
166 /**
|
Chris@14
|
167 * Adjusts the dialog on resize.
|
Chris@14
|
168 *
|
Chris@14
|
169 * @param {jQuery.Event} event
|
Chris@14
|
170 * The event triggered.
|
Chris@14
|
171 * @param {object} event.data
|
Chris@14
|
172 * Data attached to the event.
|
Chris@14
|
173 */
|
Chris@14
|
174 handleDialogResize(event) {
|
Chris@14
|
175 const $element = event.data.$element;
|
Chris@14
|
176 const $container = Drupal.offCanvas.getContainer($element);
|
Chris@14
|
177
|
Chris@17
|
178 const $offsets = $container.find(
|
Chris@17
|
179 '> :not(#drupal-off-canvas, .ui-resizable-handle)',
|
Chris@17
|
180 );
|
Chris@14
|
181 let offset = 0;
|
Chris@14
|
182
|
Chris@14
|
183 // Let scroll element take all the height available.
|
Chris@14
|
184 $element.css({ height: 'auto' });
|
Chris@14
|
185 const modalHeight = $container.height();
|
Chris@14
|
186
|
Chris@14
|
187 $offsets.each((i, e) => {
|
Chris@14
|
188 offset += $(e).outerHeight();
|
Chris@14
|
189 });
|
Chris@14
|
190
|
Chris@14
|
191 // Take internal padding into account.
|
Chris@14
|
192 const scrollOffset = $element.outerHeight() - $element.height();
|
Chris@14
|
193 $element.height(modalHeight - offset - scrollOffset);
|
Chris@14
|
194 },
|
Chris@14
|
195
|
Chris@14
|
196 /**
|
Chris@14
|
197 * Resets the size of the dialog.
|
Chris@14
|
198 *
|
Chris@14
|
199 * @param {jQuery.Event} event
|
Chris@14
|
200 * The event triggered.
|
Chris@14
|
201 * @param {object} event.data
|
Chris@14
|
202 * Data attached to the event.
|
Chris@14
|
203 */
|
Chris@14
|
204 resetSize(event) {
|
Chris@14
|
205 const $element = event.data.$element;
|
Chris@14
|
206 const container = Drupal.offCanvas.getContainer($element);
|
Chris@17
|
207 const position = event.data.settings.drupalOffCanvasPosition;
|
Chris@14
|
208
|
Chris@17
|
209 // Only remove the `data-offset-*` attribute if the value previously
|
Chris@17
|
210 // exists and the orientation is changing.
|
Chris@17
|
211 if (Drupal.offCanvas.position && Drupal.offCanvas.position !== position) {
|
Chris@17
|
212 container.removeAttr(`data-offset-${Drupal.offCanvas.position}`);
|
Chris@17
|
213 }
|
Chris@17
|
214 // Set a minimum height on $element
|
Chris@17
|
215 if (position === 'top') {
|
Chris@17
|
216 $element.css('min-height', `${Drupal.offCanvas.minimumHeight}px`);
|
Chris@17
|
217 }
|
Chris@17
|
218
|
Chris@17
|
219 displace();
|
Chris@17
|
220
|
Chris@17
|
221 const offsets = displace.offsets;
|
Chris@17
|
222
|
Chris@17
|
223 const topPosition =
|
Chris@17
|
224 position === 'side' && offsets.top !== 0 ? `+${offsets.top}` : '';
|
Chris@14
|
225 const adjustedOptions = {
|
Chris@14
|
226 // @see http://api.jqueryui.com/position/
|
Chris@14
|
227 position: {
|
Chris@14
|
228 my: `${Drupal.offCanvas.getEdge()} top`,
|
Chris@14
|
229 at: `${Drupal.offCanvas.getEdge()} top${topPosition}`,
|
Chris@14
|
230 of: window,
|
Chris@14
|
231 },
|
Chris@14
|
232 };
|
Chris@14
|
233
|
Chris@17
|
234 const height =
|
Chris@17
|
235 position === 'side'
|
Chris@17
|
236 ? `${$(window).height() - (offsets.top + offsets.bottom)}px`
|
Chris@17
|
237 : event.data.settings.height;
|
Chris@14
|
238 container.css({
|
Chris@14
|
239 position: 'fixed',
|
Chris@17
|
240 height,
|
Chris@14
|
241 });
|
Chris@14
|
242
|
Chris@14
|
243 $element
|
Chris@14
|
244 .dialog('option', adjustedOptions)
|
Chris@14
|
245 .trigger('dialogContentResize.off-canvas');
|
Chris@17
|
246
|
Chris@17
|
247 Drupal.offCanvas.position = position;
|
Chris@14
|
248 },
|
Chris@14
|
249
|
Chris@14
|
250 /**
|
Chris@14
|
251 * Adjusts the body padding when the dialog is resized.
|
Chris@14
|
252 *
|
Chris@14
|
253 * @param {jQuery.Event} event
|
Chris@14
|
254 * The event triggered.
|
Chris@14
|
255 * @param {object} event.data
|
Chris@14
|
256 * Data attached to the event.
|
Chris@14
|
257 */
|
Chris@14
|
258 bodyPadding(event) {
|
Chris@17
|
259 const position = event.data.settings.drupalOffCanvasPosition;
|
Chris@17
|
260 if (
|
Chris@17
|
261 position === 'side' &&
|
Chris@17
|
262 $('body').outerWidth() < Drupal.offCanvas.minDisplaceWidth
|
Chris@17
|
263 ) {
|
Chris@14
|
264 return;
|
Chris@14
|
265 }
|
Chris@17
|
266 Drupal.offCanvas.resetPadding();
|
Chris@14
|
267 const $element = event.data.$element;
|
Chris@14
|
268 const $container = Drupal.offCanvas.getContainer($element);
|
Chris@14
|
269 const $mainCanvasWrapper = Drupal.offCanvas.$mainCanvasWrapper;
|
Chris@14
|
270
|
Chris@14
|
271 const width = $container.outerWidth();
|
Chris@17
|
272 const mainCanvasPadding = $mainCanvasWrapper.css(
|
Chris@17
|
273 `padding-${Drupal.offCanvas.getEdge()}`,
|
Chris@17
|
274 );
|
Chris@17
|
275 if (position === 'side' && width !== mainCanvasPadding) {
|
Chris@17
|
276 $mainCanvasWrapper.css(
|
Chris@17
|
277 `padding-${Drupal.offCanvas.getEdge()}`,
|
Chris@17
|
278 `${width}px`,
|
Chris@17
|
279 );
|
Chris@14
|
280 $container.attr(`data-offset-${Drupal.offCanvas.getEdge()}`, width);
|
Chris@14
|
281 displace();
|
Chris@14
|
282 }
|
Chris@17
|
283
|
Chris@17
|
284 const height = $container.outerHeight();
|
Chris@17
|
285 if (position === 'top') {
|
Chris@17
|
286 $mainCanvasWrapper.css('padding-top', `${height}px`);
|
Chris@17
|
287 $container.attr('data-offset-top', height);
|
Chris@17
|
288 displace();
|
Chris@17
|
289 }
|
Chris@14
|
290 },
|
Chris@14
|
291
|
Chris@14
|
292 /**
|
Chris@14
|
293 * The HTML element that surrounds the dialog.
|
Chris@14
|
294 * @param {HTMLElement} $element
|
Chris@14
|
295 * The dialog element.
|
Chris@14
|
296 *
|
Chris@14
|
297 * @return {HTMLElement}
|
Chris@14
|
298 * The containing element.
|
Chris@14
|
299 */
|
Chris@14
|
300 getContainer($element) {
|
Chris@14
|
301 return $element.dialog('widget');
|
Chris@14
|
302 },
|
Chris@14
|
303
|
Chris@14
|
304 /**
|
Chris@14
|
305 * The edge of the screen that the dialog should appear on.
|
Chris@14
|
306 *
|
Chris@14
|
307 * @return {string}
|
Chris@14
|
308 * The edge the tray will be shown on, left or right.
|
Chris@14
|
309 */
|
Chris@14
|
310 getEdge() {
|
Chris@14
|
311 return document.documentElement.dir === 'rtl' ? 'left' : 'right';
|
Chris@14
|
312 },
|
Chris@17
|
313
|
Chris@17
|
314 /**
|
Chris@17
|
315 * Resets main canvas wrapper and toolbar padding / margin.
|
Chris@17
|
316 */
|
Chris@17
|
317 resetPadding() {
|
Chris@17
|
318 Drupal.offCanvas.$mainCanvasWrapper.css(
|
Chris@17
|
319 `padding-${Drupal.offCanvas.getEdge()}`,
|
Chris@17
|
320 0,
|
Chris@17
|
321 );
|
Chris@17
|
322 Drupal.offCanvas.$mainCanvasWrapper.css('padding-top', 0);
|
Chris@17
|
323 displace();
|
Chris@17
|
324 },
|
Chris@14
|
325 };
|
Chris@14
|
326
|
Chris@14
|
327 /**
|
Chris@14
|
328 * Attaches off-canvas dialog behaviors.
|
Chris@14
|
329 *
|
Chris@14
|
330 * @type {Drupal~behavior}
|
Chris@14
|
331 *
|
Chris@14
|
332 * @prop {Drupal~behaviorAttach} attach
|
Chris@14
|
333 * Attaches event listeners for off-canvas dialogs.
|
Chris@14
|
334 */
|
Chris@14
|
335 Drupal.behaviors.offCanvasEvents = {
|
Chris@14
|
336 attach: () => {
|
Chris@17
|
337 $(window)
|
Chris@17
|
338 .once('off-canvas')
|
Chris@17
|
339 .on({
|
Chris@17
|
340 'dialog:beforecreate': (event, dialog, $element, settings) => {
|
Chris@17
|
341 if (Drupal.offCanvas.isOffCanvas($element)) {
|
Chris@17
|
342 Drupal.offCanvas.beforeCreate({ dialog, $element, settings });
|
Chris@17
|
343 }
|
Chris@17
|
344 },
|
Chris@17
|
345 'dialog:aftercreate': (event, dialog, $element, settings) => {
|
Chris@17
|
346 if (Drupal.offCanvas.isOffCanvas($element)) {
|
Chris@17
|
347 Drupal.offCanvas.render({ dialog, $element, settings });
|
Chris@17
|
348 Drupal.offCanvas.afterCreate({ $element, settings });
|
Chris@17
|
349 }
|
Chris@17
|
350 },
|
Chris@17
|
351 'dialog:beforeclose': (event, dialog, $element) => {
|
Chris@17
|
352 if (Drupal.offCanvas.isOffCanvas($element)) {
|
Chris@17
|
353 Drupal.offCanvas.beforeClose({ dialog, $element });
|
Chris@17
|
354 }
|
Chris@17
|
355 },
|
Chris@17
|
356 });
|
Chris@14
|
357 },
|
Chris@14
|
358 };
|
Chris@14
|
359 })(jQuery, Drupal, Drupal.debounce, Drupal.displace);
|