5 (function ($, Drupal, Bootstrap, Attributes) {
9 * Document jQuery object.
13 var $document = $(document);
16 * Finds the first available and visible focusable input element.
18 * This is abstracted from the main code below so sub-themes can override
19 * this method to return their own element if desired.
21 * @param {Modal} modal
22 * The Bootstrap modal instance.
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
29 Bootstrap.modalFindFocusableElement = function (modal) {
30 return modal.$dialogBody.find(':input,:button,.btn');
33 $document.on('shown.bs.modal', function (e) {
34 var $modal = $(e.target);
35 var modal = $modal.data('bs.modal');
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();
43 // Select text if input is text.
44 if (modal.options.selectText && $input.is(':text')) {
45 $input[0].setSelectionRange(0, $input[0].value.length)
52 * Only process this once.
54 Bootstrap.once('modal', function (settings) {
57 * Replace the Bootstrap Modal jQuery plugin definition.
59 * This adds a little bit of functionality so it works better with Drupal.
61 Bootstrap.replacePlugin('modal', function () {
62 var BootstrapModal = this;
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;
77 this.originalBodyPad = null;
78 this.scrollbarWidth = 0;
79 this.ignoreBackdropClick = false;
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
93 // Copy over the original prototype methods.
94 Modal.prototype = BootstrapModal.prototype;
97 * Handler for $.fn.modal('destroy').
99 Modal.prototype.destroy = function () {
101 Drupal.detachBehaviors(this.$element[0]);
102 this.$element.removeData('bs.modal').remove();
106 * Initialize the modal.
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');
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();
122 if ($.isPlainObject(method)) {
127 this.each(function () {
129 var data = $this.data('bs.modal');
130 var initialize = false;
132 // Immediately return if there's no instance to invoke a valid method.
133 if (!data && method && method !== 'open') {
137 options = $.extend({}, Modal.DEFAULTS, data && data.options, $this.data(), options);
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))));
147 // If no method or arguments, treat it like it's initializing the modal.
148 if (!method && !args.length) {
149 data.option(options);
153 // Initialize the modal.
159 if (typeof data[method] === 'function') {
161 ret = data[method].apply(data, args);
164 Drupal.throwError(e);
168 Bootstrap.unsupported('method', method);
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;
178 // Replace the plugin constructor with the new Modal constructor.
179 Plugin.Constructor = Modal;
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.
185 .off('click.bs.modal.data-api')
186 .on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
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());
193 if ($this.is('a')) e.preventDefault();
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');
202 $target.modal(option, this);
209 * Extend Drupal theming functions.
211 $.extend(Drupal.theme, /** @lend Drupal.theme */ {
213 * Theme function for a Bootstrap Modal.
215 * @param {Object} [variables]
216 * An object containing key/value pairs of variables.
219 * The HTML for the modal.
221 bootstrapModal: function (variables) {
223 var settings = drupalSettings.bootstrap || {};
234 class: ['help-block']
241 size: settings.modal_size ? settings.modal_size : '',
244 class: ['modal-title']
246 content: Drupal.t('Loading...'),
251 variables = $.extend(true, {}, defaults, variables);
253 var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
254 attributes.set('id', attributes.get('id', variables.id));
256 if (settings.modal_animation) {
257 attributes.addClass('fade');
260 // Build the modal wrapper.
261 output += '<div' + attributes + '>';
263 // Build the modal-dialog wrapper.
264 output += Drupal.theme('bootstrapModalDialog', _.omit(variables, 'attributes'));
266 // Close the modal wrapper.
269 // Return the constructed modal.
274 * Theme function for a Bootstrap Modal dialog markup.
276 * @param {Object} [variables]
277 * An object containing key/value pairs of variables.
280 * The HTML for the modal close button.
282 bootstrapModalDialog: function (variables) {
287 class: ['modal-dialog'],
292 variables = $.extend(true, {}, defaults, variables);
294 var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
295 attributes.set('id', attributes.get('id', variables.id + '--dialog'));
297 if (variables.size) {
298 attributes.addClass(variables.size);
300 output += '<div' + attributes + '>';
302 // Build the modal-content wrapper.
303 output += Drupal.theme('bootstrapModalContent', _.omit(variables, 'attributes'));
305 // Close the modal-dialog wrapper.
311 * Theme function for a Bootstrap Modal content markup.
313 * @param {Object} [variables]
314 * An object containing key/value pairs of variables.
317 * The HTML for the modal close button.
319 bootstrapModalContent: function (variables) {
324 class: ['modal-content']
328 variables = $.extend(true, {}, defaults, variables);
330 var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
331 attributes.set('id', attributes.get('id', variables.id + '--content'));
333 // Build the modal-content wrapper.
334 output += '<div' + attributes + '>';
335 variables = _.omit(variables, 'attributes');
337 // Build the header wrapper and title.
338 output += Drupal.theme('bootstrapModalHeader', variables);
341 output += Drupal.theme('bootstrapModalBody', variables);
344 output += Drupal.theme('bootstrapModalFooter', variables);
346 // Close the modal-content wrapper.
353 * Theme function for a Bootstrap Modal body markup.
355 * @param {Object} [variables]
356 * An object containing key/value pairs of variables.
359 * The HTML for the modal close button.
361 bootstrapModalBody: function (variables) {
366 class: ['modal-body']
371 class: ['help-block']
378 variables = $.extend(true, {}, defaults, variables);
380 var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
381 attributes.set('id', attributes.get('id', variables.id + '--body'));
383 output += '<div' + attributes + '>';
385 if (typeof variables.description === 'string') {
386 variables.description = $.extend({}, defaults.description, { content: variables.description });
389 var description = variables.description;
390 description.attributes = Attributes.create(defaults.description.attributes).merge(description.attributes);
392 if (description.content && description.position === 'invisible') {
393 description.attributes.addClass('sr-only');
396 if (description.content && description.position === 'before') {
397 output += '<p' + description.attributes + '>' + description.content + '</p>';
400 output += variables.body;
402 if (description.content && (description.position === 'after' || description.position === 'invisible')) {
403 output += '<p' + description.attributes + '>' + description.content + '</p>';
412 * Theme function for a Bootstrap Modal close button.
414 * @param {Object} [variables]
415 * An object containing key/value pairs of variables.
418 * The HTML for the modal close button.
420 bootstrapModalClose: function (variables) {
423 'aria-label': Drupal.t('Close'),
425 'data-dismiss': 'modal',
429 variables = $.extend(true, {}, defaults, variables);
430 var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
431 return '<button' + attributes + '><span aria-hidden="true">×</span></button>';
435 * Theme function for a Bootstrap Modal footer.
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.
443 * The HTML for the modal footer.
445 bootstrapModalFooter: function (variables, force) {
449 class: ['modal-footer']
455 variables = $.extend(true, {}, defaults, variables);
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;
469 * Theme function for a Bootstrap Modal header.
471 * @param {Object} [variables]
472 * An object containing key/value pairs of variables.
475 * The HTML for the modal header.
477 bootstrapModalHeader: function (variables) {
482 class: ['modal-header']
488 class: ['modal-title']
490 content: Drupal.t('Loading...'),
495 variables = $.extend(true, {}, defaults, variables);
497 var title = variables.title;
499 var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
500 attributes.set('id', attributes.get('id', variables.id + '--header'));
502 if (typeof title === 'string') {
503 title = $.extend({}, defaults.title, { content: title });
506 output += '<div' + attributes + '>';
508 if (variables.closeButton) {
509 output += Drupal.theme('bootstrapModalClose', _.omit(variables, 'attributes'));
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) + '>';
523 })(window.jQuery, window.Drupal, window.Drupal.bootstrap, window.Attributes);