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">×</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.