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