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@14
|
16
|
Chris@14
|
17 /**
|
Chris@14
|
18 * The minimum width to use body displace needs to match the width at which
|
Chris@14
|
19 * the tray will be 100% width. @see core/misc/dialog/off-canvas.css
|
Chris@14
|
20 *
|
Chris@14
|
21 * @type {Number}
|
Chris@14
|
22 */
|
Chris@14
|
23 minDisplaceWidth: 768,
|
Chris@14
|
24
|
Chris@14
|
25 /**
|
Chris@14
|
26 * Wrapper used to position off-canvas dialog.
|
Chris@14
|
27 *
|
Chris@14
|
28 * @type {jQuery}
|
Chris@14
|
29 */
|
Chris@14
|
30 $mainCanvasWrapper: $('[data-off-canvas-main-canvas]'),
|
Chris@14
|
31
|
Chris@14
|
32 /**
|
Chris@14
|
33 * Determines if an element is an off-canvas dialog.
|
Chris@14
|
34 *
|
Chris@14
|
35 * @param {jQuery} $element
|
Chris@14
|
36 * The dialog element.
|
Chris@14
|
37 *
|
Chris@14
|
38 * @return {bool}
|
Chris@14
|
39 * True this is currently an off-canvas dialog.
|
Chris@14
|
40 */
|
Chris@14
|
41 isOffCanvas($element) {
|
Chris@14
|
42 return $element.is('#drupal-off-canvas');
|
Chris@14
|
43 },
|
Chris@14
|
44
|
Chris@14
|
45 /**
|
Chris@14
|
46 * Remove off-canvas dialog events.
|
Chris@14
|
47 *
|
Chris@14
|
48 * @param {jQuery} $element
|
Chris@14
|
49 * The target element.
|
Chris@14
|
50 */
|
Chris@14
|
51 removeOffCanvasEvents($element) {
|
Chris@14
|
52 $element.off('.off-canvas');
|
Chris@14
|
53 $(document).off('.off-canvas');
|
Chris@14
|
54 $(window).off('.off-canvas');
|
Chris@14
|
55 },
|
Chris@14
|
56
|
Chris@14
|
57 /**
|
Chris@14
|
58 * Handler fired before an off-canvas dialog has been opened.
|
Chris@14
|
59 *
|
Chris@14
|
60 * @param {Object} settings
|
Chris@14
|
61 * Settings related to the composition of the dialog.
|
Chris@14
|
62 *
|
Chris@14
|
63 * @return {undefined}
|
Chris@14
|
64 */
|
Chris@14
|
65 beforeCreate({ settings, $element }) {
|
Chris@14
|
66 // Clean up previous dialog event handlers.
|
Chris@14
|
67 Drupal.offCanvas.removeOffCanvasEvents($element);
|
Chris@14
|
68
|
Chris@14
|
69 $('body').addClass('js-off-canvas-dialog-open');
|
Chris@14
|
70 // @see http://api.jqueryui.com/position/
|
Chris@14
|
71 settings.position = {
|
Chris@14
|
72 my: 'left top',
|
Chris@14
|
73 at: `${Drupal.offCanvas.getEdge()} top`,
|
Chris@14
|
74 of: window,
|
Chris@14
|
75 };
|
Chris@14
|
76
|
Chris@14
|
77 /**
|
Chris@14
|
78 * Applies initial height to dialog based on window height.
|
Chris@14
|
79 * @see http://api.jqueryui.com/dialog for all dialog options.
|
Chris@14
|
80 */
|
Chris@14
|
81 settings.height = $(window).height();
|
Chris@14
|
82 },
|
Chris@14
|
83
|
Chris@14
|
84 /**
|
Chris@14
|
85 * Handler fired after an off-canvas dialog has been closed.
|
Chris@14
|
86 *
|
Chris@14
|
87 * @return {undefined}
|
Chris@14
|
88 */
|
Chris@14
|
89 beforeClose({ $element }) {
|
Chris@14
|
90 $('body').removeClass('js-off-canvas-dialog-open');
|
Chris@14
|
91 // Remove all *.off-canvas events
|
Chris@14
|
92 Drupal.offCanvas.removeOffCanvasEvents($element);
|
Chris@14
|
93
|
Chris@14
|
94 Drupal.offCanvas.$mainCanvasWrapper.css(`padding-${Drupal.offCanvas.getEdge()}`, 0);
|
Chris@14
|
95 },
|
Chris@14
|
96
|
Chris@14
|
97 /**
|
Chris@14
|
98 * Handler fired when an off-canvas dialog has been opened.
|
Chris@14
|
99 *
|
Chris@14
|
100 * @param {jQuery} $element
|
Chris@14
|
101 * The off-canvas dialog element.
|
Chris@14
|
102 * @param {Object} settings
|
Chris@14
|
103 * Settings related to the composition of the dialog.
|
Chris@14
|
104 *
|
Chris@14
|
105 * @return {undefined}
|
Chris@14
|
106 */
|
Chris@14
|
107 afterCreate({ $element, settings }) {
|
Chris@14
|
108 const eventData = { settings, $element, offCanvasDialog: this };
|
Chris@14
|
109
|
Chris@14
|
110 $element
|
Chris@14
|
111 .on('dialogContentResize.off-canvas', eventData, Drupal.offCanvas.handleDialogResize)
|
Chris@14
|
112 .on('dialogContentResize.off-canvas', eventData, Drupal.offCanvas.bodyPadding);
|
Chris@14
|
113
|
Chris@14
|
114 Drupal.offCanvas.getContainer($element).attr(`data-offset-${Drupal.offCanvas.getEdge()}`, '');
|
Chris@14
|
115
|
Chris@14
|
116 $(window)
|
Chris@14
|
117 .on('resize.off-canvas', eventData, debounce(Drupal.offCanvas.resetSize, 100))
|
Chris@14
|
118 .trigger('resize.off-canvas');
|
Chris@14
|
119 },
|
Chris@14
|
120
|
Chris@14
|
121 /**
|
Chris@14
|
122 * Toggle classes based on title existence.
|
Chris@14
|
123 * Called with Drupal.offCanvas.afterCreate.
|
Chris@14
|
124 *
|
Chris@14
|
125 * @param {Object} settings
|
Chris@14
|
126 * Settings related to the composition of the dialog.
|
Chris@14
|
127 *
|
Chris@14
|
128 * @return {undefined}
|
Chris@14
|
129 */
|
Chris@14
|
130 render({ settings }) {
|
Chris@14
|
131 $('.ui-dialog-off-canvas, .ui-dialog-off-canvas .ui-dialog-titlebar').toggleClass('ui-dialog-empty-title', !settings.title);
|
Chris@14
|
132 },
|
Chris@14
|
133
|
Chris@14
|
134 /**
|
Chris@14
|
135 * Adjusts the dialog on resize.
|
Chris@14
|
136 *
|
Chris@14
|
137 * @param {jQuery.Event} event
|
Chris@14
|
138 * The event triggered.
|
Chris@14
|
139 * @param {object} event.data
|
Chris@14
|
140 * Data attached to the event.
|
Chris@14
|
141 */
|
Chris@14
|
142 handleDialogResize(event) {
|
Chris@14
|
143 const $element = event.data.$element;
|
Chris@14
|
144 const $container = Drupal.offCanvas.getContainer($element);
|
Chris@14
|
145
|
Chris@14
|
146 const $offsets = $container.find('> :not(#drupal-off-canvas, .ui-resizable-handle)');
|
Chris@14
|
147 let offset = 0;
|
Chris@14
|
148
|
Chris@14
|
149 // Let scroll element take all the height available.
|
Chris@14
|
150 $element.css({ height: 'auto' });
|
Chris@14
|
151 const modalHeight = $container.height();
|
Chris@14
|
152
|
Chris@14
|
153 $offsets.each((i, e) => {
|
Chris@14
|
154 offset += $(e).outerHeight();
|
Chris@14
|
155 });
|
Chris@14
|
156
|
Chris@14
|
157 // Take internal padding into account.
|
Chris@14
|
158 const scrollOffset = $element.outerHeight() - $element.height();
|
Chris@14
|
159 $element.height(modalHeight - offset - scrollOffset);
|
Chris@14
|
160 },
|
Chris@14
|
161
|
Chris@14
|
162 /**
|
Chris@14
|
163 * Resets the size of the dialog.
|
Chris@14
|
164 *
|
Chris@14
|
165 * @param {jQuery.Event} event
|
Chris@14
|
166 * The event triggered.
|
Chris@14
|
167 * @param {object} event.data
|
Chris@14
|
168 * Data attached to the event.
|
Chris@14
|
169 */
|
Chris@14
|
170 resetSize(event) {
|
Chris@14
|
171 const offsets = displace.offsets;
|
Chris@14
|
172 const $element = event.data.$element;
|
Chris@14
|
173 const container = Drupal.offCanvas.getContainer($element);
|
Chris@14
|
174
|
Chris@14
|
175 const topPosition = (offsets.top !== 0 ? `+${offsets.top}` : '');
|
Chris@14
|
176 const adjustedOptions = {
|
Chris@14
|
177 // @see http://api.jqueryui.com/position/
|
Chris@14
|
178 position: {
|
Chris@14
|
179 my: `${Drupal.offCanvas.getEdge()} top`,
|
Chris@14
|
180 at: `${Drupal.offCanvas.getEdge()} top${topPosition}`,
|
Chris@14
|
181 of: window,
|
Chris@14
|
182 },
|
Chris@14
|
183 };
|
Chris@14
|
184
|
Chris@14
|
185 container.css({
|
Chris@14
|
186 position: 'fixed',
|
Chris@14
|
187 height: `${$(window).height() - (offsets.top + offsets.bottom)}px`,
|
Chris@14
|
188 });
|
Chris@14
|
189
|
Chris@14
|
190 $element
|
Chris@14
|
191 .dialog('option', adjustedOptions)
|
Chris@14
|
192 .trigger('dialogContentResize.off-canvas');
|
Chris@14
|
193 },
|
Chris@14
|
194
|
Chris@14
|
195 /**
|
Chris@14
|
196 * Adjusts the body padding when the dialog is resized.
|
Chris@14
|
197 *
|
Chris@14
|
198 * @param {jQuery.Event} event
|
Chris@14
|
199 * The event triggered.
|
Chris@14
|
200 * @param {object} event.data
|
Chris@14
|
201 * Data attached to the event.
|
Chris@14
|
202 */
|
Chris@14
|
203 bodyPadding(event) {
|
Chris@14
|
204 if ($('body').outerWidth() < Drupal.offCanvas.minDisplaceWidth) {
|
Chris@14
|
205 return;
|
Chris@14
|
206 }
|
Chris@14
|
207 const $element = event.data.$element;
|
Chris@14
|
208 const $container = Drupal.offCanvas.getContainer($element);
|
Chris@14
|
209 const $mainCanvasWrapper = Drupal.offCanvas.$mainCanvasWrapper;
|
Chris@14
|
210
|
Chris@14
|
211 const width = $container.outerWidth();
|
Chris@14
|
212 const mainCanvasPadding = $mainCanvasWrapper.css(`padding-${Drupal.offCanvas.getEdge()}`);
|
Chris@14
|
213 if (width !== mainCanvasPadding) {
|
Chris@14
|
214 $mainCanvasWrapper.css(`padding-${Drupal.offCanvas.getEdge()}`, `${width}px`);
|
Chris@14
|
215 $container.attr(`data-offset-${Drupal.offCanvas.getEdge()}`, width);
|
Chris@14
|
216 displace();
|
Chris@14
|
217 }
|
Chris@14
|
218 },
|
Chris@14
|
219
|
Chris@14
|
220 /**
|
Chris@14
|
221 * The HTML element that surrounds the dialog.
|
Chris@14
|
222 * @param {HTMLElement} $element
|
Chris@14
|
223 * The dialog element.
|
Chris@14
|
224 *
|
Chris@14
|
225 * @return {HTMLElement}
|
Chris@14
|
226 * The containing element.
|
Chris@14
|
227 */
|
Chris@14
|
228 getContainer($element) {
|
Chris@14
|
229 return $element.dialog('widget');
|
Chris@14
|
230 },
|
Chris@14
|
231
|
Chris@14
|
232 /**
|
Chris@14
|
233 * The edge of the screen that the dialog should appear on.
|
Chris@14
|
234 *
|
Chris@14
|
235 * @return {string}
|
Chris@14
|
236 * The edge the tray will be shown on, left or right.
|
Chris@14
|
237 */
|
Chris@14
|
238 getEdge() {
|
Chris@14
|
239 return document.documentElement.dir === 'rtl' ? 'left' : 'right';
|
Chris@14
|
240 },
|
Chris@14
|
241 };
|
Chris@14
|
242
|
Chris@14
|
243 /**
|
Chris@14
|
244 * Attaches off-canvas dialog behaviors.
|
Chris@14
|
245 *
|
Chris@14
|
246 * @type {Drupal~behavior}
|
Chris@14
|
247 *
|
Chris@14
|
248 * @prop {Drupal~behaviorAttach} attach
|
Chris@14
|
249 * Attaches event listeners for off-canvas dialogs.
|
Chris@14
|
250 */
|
Chris@14
|
251 Drupal.behaviors.offCanvasEvents = {
|
Chris@14
|
252 attach: () => {
|
Chris@14
|
253 $(window).once('off-canvas').on({
|
Chris@14
|
254 'dialog:beforecreate': (event, dialog, $element, settings) => {
|
Chris@14
|
255 if (Drupal.offCanvas.isOffCanvas($element)) {
|
Chris@14
|
256 Drupal.offCanvas.beforeCreate({ dialog, $element, settings });
|
Chris@14
|
257 }
|
Chris@14
|
258 },
|
Chris@14
|
259 'dialog:aftercreate': (event, dialog, $element, settings) => {
|
Chris@14
|
260 if (Drupal.offCanvas.isOffCanvas($element)) {
|
Chris@14
|
261 Drupal.offCanvas.render({ dialog, $element, settings });
|
Chris@14
|
262 Drupal.offCanvas.afterCreate({ $element, settings });
|
Chris@14
|
263 }
|
Chris@14
|
264 },
|
Chris@14
|
265 'dialog:beforeclose': (event, dialog, $element) => {
|
Chris@14
|
266 if (Drupal.offCanvas.isOffCanvas($element)) {
|
Chris@14
|
267 Drupal.offCanvas.beforeClose({ dialog, $element });
|
Chris@14
|
268 }
|
Chris@14
|
269 },
|
Chris@14
|
270 });
|
Chris@14
|
271 },
|
Chris@14
|
272 };
|
Chris@14
|
273 })(jQuery, Drupal, Drupal.debounce, Drupal.displace);
|