PageSourceSearch

https://queenslibrary.org/themes/bootstrap/js/modal.js?ss73o2

js queenslibrary.org collected 2026-09-24 08:48:02 UTC 19,379 bytes, 606 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.