PageSourceSearch

https://www.ntca.org/themes/contrib/bootstrap/js/modal.js?tlk4xv

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