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