Further Drupal 8.6.4 changes. Some core files were not committed before a commit...
[yaffs-website] / web / themes / contrib / bootstrap / js / modal.js
1 /**
2  * @file
3  * Bootstrap Modals.
4  */
5 (function ($, Drupal, Bootstrap, Attributes) {
6   'use strict';
7
8   /**
9    * Document jQuery object.
10    *
11    * @type {jQuery}
12    */
13   var $document = $(document);
14
15   /**
16    * Finds the first available and visible focusable input element.
17    *
18    * This is abstracted from the main code below so sub-themes can override
19    * this method to return their own element if desired.
20    *
21    * @param {Modal} modal
22    *   The Bootstrap modal instance.
23    *
24    * @return {jQuery}
25    *   A jQuery object containing the element that should be focused. Note: if
26    *   this object contains multiple elements, only the first visible one will
27    *   be used.
28    */
29   Bootstrap.modalFindFocusableElement = function (modal) {
30     return modal.$dialogBody.find(':input,:button,.btn');
31   };
32
33   $document.on('shown.bs.modal', function (e) {
34     var $modal = $(e.target);
35     var modal = $modal.data('bs.modal');
36
37     // Focus the first input element found.
38     if (modal && modal.options.focusInput) {
39       var $focusable = Bootstrap.modalFindFocusableElement(modal);
40       if ($focusable && $focusable[0]) {
41         var $input = $focusable.filter(':visible:first').focus();
42
43         // Select text if input is text.
44         if (modal.options.selectText && $input.is(':text')) {
45           $input[0].setSelectionRange(0, $input[0].value.length)
46         }
47       }
48     }
49   });
50
51   /**
52    * Only process this once.
53    */
54   Bootstrap.once('modal', function (settings) {
55
56     /**
57      * Replace the Bootstrap Modal jQuery plugin definition.
58      *
59      * This adds a little bit of functionality so it works better with Drupal.
60      */
61     Bootstrap.replacePlugin('modal', function () {
62       var BootstrapModal = this;
63
64       // Override the Modal constructor.
65       var Modal = function (element, options) {
66         this.options             = options;
67         this.$body               = $(document.body);
68         this.$element            = $(element);
69         this.$dialog             = this.$element.find('.modal-dialog');
70         this.$header             = this.$dialog.find('.modal-header');
71         this.$close              = this.$header.find('.close');
72         this.$footer             = this.$dialog.find('.modal-footer');
73         this.$content            = this.$dialog.find('.modal-content');
74         this.$dialogBody         = this.$content.find('.modal-body');
75         this.$backdrop           = null;
76         this.isShown             = null;
77         this.originalBodyPad     = null;
78         this.scrollbarWidth      = 0;
79         this.ignoreBackdropClick = false;
80       };
81
82       // Extend defaults to take into account for theme settings.
83       Modal.DEFAULTS = $.extend({}, BootstrapModal.DEFAULTS, {
84         animation: !!settings.modal_animation,
85         backdrop: settings.modal_backdrop === 'static' ? 'static' : !!settings.modal_backdrop,
86         focusInput: !!settings.modal_focus_input,
87         selectText: !!settings.modal_select_text,
88         keyboard: !!settings.modal_keyboard,
89         show: !!settings.modal_show,
90         size: settings.modal_size
91       });
92
93       // Copy over the original prototype methods.
94       Modal.prototype = BootstrapModal.prototype;
95
96       /**
97        * Handler for $.fn.modal('destroy').
98        */
99       Modal.prototype.destroy = function () {
100         this.hide();
101         Drupal.detachBehaviors(this.$element[0]);
102         this.$element.removeData('bs.modal').remove();
103       };
104
105       /**
106        * Initialize the modal.
107        */
108       Modal.prototype.init = function () {
109         if (this.options.remote) {
110           this.$content.load(this.options.remote, $.proxy(function () {
111             this.$element.trigger('loaded.bs.modal');
112           }, this));
113         }
114       };
115
116       // Modal jQuery Plugin Definition.
117       var Plugin = function () {
118         // Extract the arguments.
119         var args = Array.prototype.slice.call(arguments);
120         var method = args.shift();
121         var options = {};
122         if ($.isPlainObject(method)) {
123           options = method;
124           method = null;
125         }
126         var ret = void 0;
127         this.each(function () {
128           var $this   = $(this);
129           var data    = $this.data('bs.modal');
130           var initialize = false;
131
132           // Immediately return if there's no instance to invoke a valid method.
133           if (!data && method && method !== 'open') {
134             return;
135           }
136
137           options = $.extend({}, Modal.DEFAULTS, data && data.options, $this.data(), options);
138           if (!data) {
139             // When initializing the Bootstrap Modal, only pass the "supported"
140             // options by intersecting the default options. This allows plugins
141             // like the jQuery UI bridge to properly detect when options have
142             // changed when they're set below as a global "option" method.
143             $this.data('bs.modal', (data = new Modal(this, Bootstrap.intersectObjects(options, Modal.DEFAULTS))));
144             initialize = true;
145           }
146
147           // If no method or arguments, treat it like it's initializing the modal.
148           if (!method && !args.length) {
149             data.option(options);
150             initialize = true;
151           }
152
153           // Initialize the modal.
154           if (initialize) {
155             data.init();
156           }
157
158           if (method) {
159             if (typeof data[method] === 'function') {
160               try {
161                 ret = data[method].apply(data, args);
162               }
163               catch (e) {
164                 Drupal.throwError(e);
165               }
166             }
167             else {
168               Bootstrap.unsupported('method', method);
169             }
170           }
171         });
172
173         // If just one element and there was a result returned for the option passed,
174         // then return the result. Otherwise, just return the jQuery object.
175         return this.length === 1 && ret !== void 0 ? ret : this;
176       };
177
178       // Replace the plugin constructor with the new Modal constructor.
179       Plugin.Constructor = Modal;
180
181       // Replace the data API so that it calls $.fn.modal rather than Plugin.
182       // This allows sub-themes to replace the jQuery Plugin if they like with
183       // out having to redo all this boilerplate.
184       $document
185         .off('click.bs.modal.data-api')
186         .on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
187           var $this   = $(this);
188           var href    = $this.attr('href');
189           var target  = $this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, '')); // strip for ie7
190           var $target = $document.find(target);
191           var option  = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data());
192
193           if ($this.is('a')) e.preventDefault();
194
195           $target.one('show.bs.modal', function (showEvent) {
196             // Only register focus restorer if modal will actually get shown.
197             if (showEvent.isDefaultPrevented()) return;
198             $target.one('hidden.bs.modal', function () {
199               $this.is(':visible') && $this.trigger('focus');
200             });
201           });
202           $target.modal(option, this);
203         });
204
205       return Plugin;
206     });
207
208     /**
209      * Extend Drupal theming functions.
210      */
211     $.extend(Drupal.theme, /** @lend Drupal.theme */ {
212       /**
213        * Theme function for a Bootstrap Modal.
214        *
215        * @param {Object} [variables]
216        *   An object containing key/value pairs of variables.
217        *
218        * @return {string}
219        *   The HTML for the modal.
220        */
221       bootstrapModal: function (variables) {
222         var output = '';
223         var settings = drupalSettings.bootstrap || {};
224         var defaults = {
225           attributes: {
226             class: ['modal'],
227             tabindex: -1,
228             role: 'dialog'
229           },
230           body: '',
231           closeButton: true,
232           description: {
233             attributes: {
234               class: ['help-block']
235             },
236             content: null,
237             position: 'before'
238           },
239           footer: '',
240           id: 'drupal-modal',
241           size: settings.modal_size ? settings.modal_size : '',
242           title: {
243             attributes: {
244               class: ['modal-title']
245             },
246             content: Drupal.t('Loading...'),
247             html: false,
248             tag: 'h4'
249           }
250         };
251         variables = $.extend(true, {}, defaults, variables);
252
253         var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
254         attributes.set('id', attributes.get('id', variables.id));
255
256         if (settings.modal_animation) {
257           attributes.addClass('fade');
258         }
259
260         // Build the modal wrapper.
261         output += '<div' + attributes + '>';
262
263         // Build the modal-dialog wrapper.
264         output += Drupal.theme('bootstrapModalDialog', _.omit(variables, 'attributes'));
265
266         // Close the modal wrapper.
267         output += '</div>';
268
269         // Return the constructed modal.
270         return output;
271       },
272
273       /**
274        * Theme function for a Bootstrap Modal dialog markup.
275        *
276        * @param {Object} [variables]
277        *   An object containing key/value pairs of variables.
278        *
279        * @return {string}
280        *   The HTML for the modal close button.
281        */
282       bootstrapModalDialog: function (variables) {
283         var output = '';
284
285         var defaults = {
286           attributes: {
287             class: ['modal-dialog'],
288             role: 'document'
289           },
290           id: 'drupal-modal'
291         };
292         variables = $.extend(true, {}, defaults, variables);
293
294         var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
295         attributes.set('id', attributes.get('id', variables.id + '--dialog'));
296
297         if (variables.size) {
298           attributes.addClass(variables.size);
299         }
300         output += '<div' + attributes + '>';
301
302         // Build the modal-content wrapper.
303         output += Drupal.theme('bootstrapModalContent', _.omit(variables, 'attributes'));
304
305         // Close the modal-dialog wrapper.
306         output += '</div>';
307         return output;
308       },
309
310       /**
311        * Theme function for a Bootstrap Modal content markup.
312        *
313        * @param {Object} [variables]
314        *   An object containing key/value pairs of variables.
315        *
316        * @return {string}
317        *   The HTML for the modal close button.
318        */
319       bootstrapModalContent: function (variables) {
320         var output = '';
321
322         var defaults = {
323           attributes: {
324             class: ['modal-content']
325           },
326           id: 'drupal-modal'
327         };
328         variables = $.extend(true, {}, defaults, variables);
329
330         var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
331         attributes.set('id', attributes.get('id', variables.id + '--content'));
332
333         // Build the modal-content wrapper.
334         output += '<div' + attributes + '>';
335         variables = _.omit(variables, 'attributes');
336
337         // Build the header wrapper and title.
338         output += Drupal.theme('bootstrapModalHeader', variables);
339
340         // Build the body.
341         output += Drupal.theme('bootstrapModalBody', variables);
342
343         // Build the footer.
344         output += Drupal.theme('bootstrapModalFooter', variables);
345
346         // Close the modal-content wrapper.
347         output += '</div>';
348
349         return output;
350       },
351
352       /**
353        * Theme function for a Bootstrap Modal body markup.
354        *
355        * @param {Object} [variables]
356        *   An object containing key/value pairs of variables.
357        *
358        * @return {string}
359        *   The HTML for the modal close button.
360        */
361       bootstrapModalBody: function (variables) {
362         var output = '';
363
364         var defaults = {
365           attributes: {
366             class: ['modal-body']
367           },
368           body: '',
369           description: {
370             attributes: {
371               class: ['help-block']
372             },
373             content: null,
374             position: 'before'
375           },
376           id: 'drupal-modal'
377         };
378         variables = $.extend(true, {}, defaults, variables);
379
380         var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
381         attributes.set('id', attributes.get('id', variables.id + '--body'));
382
383         output += '<div' + attributes + '>';
384
385         if (typeof variables.description === 'string') {
386           variables.description = $.extend({}, defaults.description, { content: variables.description });
387         }
388
389         var description = variables.description;
390         description.attributes = Attributes.create(defaults.description.attributes).merge(description.attributes);
391
392         if (description.content && description.position === 'invisible') {
393           description.attributes.addClass('sr-only');
394         }
395
396         if (description.content && description.position === 'before') {
397           output += '<p' + description.attributes + '>' + description.content + '</p>';
398         }
399
400         output += variables.body;
401
402         if (description.content && (description.position === 'after' || description.position === 'invisible')) {
403           output += '<p' + description.attributes + '>' + description.content + '</p>';
404         }
405
406         output += '</div>';
407
408         return output;
409       },
410
411       /**
412        * Theme function for a Bootstrap Modal close button.
413        *
414        * @param {Object} [variables]
415        *   An object containing key/value pairs of variables.
416        *
417        * @return {string}
418        *   The HTML for the modal close button.
419        */
420       bootstrapModalClose: function (variables) {
421         var defaults = {
422           attributes: {
423             'aria-label': Drupal.t('Close'),
424             class: ['close'],
425             'data-dismiss': 'modal',
426             type: 'button'
427           }
428         };
429         variables = $.extend(true, {}, defaults, variables);
430         var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
431         return '<button' + attributes + '><span aria-hidden="true">&times;</span></button>';
432       },
433
434       /**
435        * Theme function for a Bootstrap Modal footer.
436        *
437        * @param {Object} [variables]
438        *   An object containing key/value pairs of variables.
439        * @param {boolean} [force]
440        *   Flag to force rendering the footer, regardless if there's content.
441        *
442        * @return {string}
443        *   The HTML for the modal footer.
444        */
445       bootstrapModalFooter: function (variables, force) {
446         var output = '';
447         var defaults = {
448           attributes: {
449             class: ['modal-footer']
450           },
451           footer: '',
452           id: 'drupal-modal'
453         };
454
455         variables = $.extend(true, {}, defaults, variables);
456
457         if (force || variables.footer) {
458           var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
459           attributes.set('id', attributes.get('id', variables.id + '--footer'));
460           output += '<div' + attributes + '>';
461           output += variables.footer;
462           output += '</div>';
463         }
464
465         return output;
466       },
467
468       /**
469        * Theme function for a Bootstrap Modal header.
470        *
471        * @param {Object} [variables]
472        *   An object containing key/value pairs of variables.
473        *
474        * @return {string}
475        *   The HTML for the modal header.
476        */
477       bootstrapModalHeader: function (variables) {
478         var output = '';
479
480         var defaults = {
481           attributes: {
482             class: ['modal-header']
483           },
484           closeButton: true,
485           id: 'drupal-modal',
486           title: {
487             attributes: {
488               class: ['modal-title']
489             },
490             content: Drupal.t('Loading...'),
491             html: false,
492             tag: 'h4'
493           }
494         };
495         variables = $.extend(true, {}, defaults, variables);
496
497         var title = variables.title;
498         if (title) {
499           var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
500           attributes.set('id', attributes.get('id', variables.id + '--header'));
501
502           if (typeof title === 'string') {
503             title = $.extend({}, defaults.title, { content: title });
504           }
505
506           output += '<div' + attributes + '>';
507
508           if (variables.closeButton) {
509             output += Drupal.theme('bootstrapModalClose', _.omit(variables, 'attributes'));
510           }
511
512           output += '<' + Drupal.checkPlain(title.tag) + Attributes.create(defaults.title.attributes).merge(title.attributes) + '>' + (title.html ? title.content : Drupal.checkPlain(title.content)) + '</' + Drupal.checkPlain(title.tag) + '>';
513
514           output += '</div>';
515         }
516
517         return output;
518       }
519     })
520
521   });
522
523 })(window.jQuery, window.Drupal, window.Drupal.bootstrap, window.Attributes);