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