1/** 2 * @file 3 * Extends the Drupal AJAX functionality to integrate the dialog API. 4 */ 5 6(function ($, Drupal, { focusable }) { 7 /** 8 * Initialize dialogs for Ajax purposes. 9 * 10 * @type {Drupal~behavior} 11 * 12 * @prop {Drupal~behaviorAttach} attach 13 * Attaches the behaviors for dialog ajax functionality. 14 */ 15 Drupal.behaviors.dialog = { 16 attach(context, settings) { 17 const $context = $(context); 18 19 // Provide a known 'drupal-modal' DOM element for Drupal-based modal 20 // dialogs. Non-modal dialogs are responsible for creating their own 21 // elements, since there can be multiple non-modal dialogs at a time. 22 if (!$('#drupal-modal').length) { 23 // Add 'ui-front' jQuery UI class so jQuery UI widgets like autocomplete 24 // sit on top of dialogs. For more information see 25 // http://api.jqueryui.com/theming/stacking-elements/. 26 $('<div id="drupal-modal" class="ui-front"></div>') 27 .hide() 28 .appendTo('body'); 29 } 30 31 // Special behaviors specific when attaching content within a dialog. 32 // These behaviors usually fire after a validation error inside a dialog. 33 const $dialog = $context.closest('.ui-dialog-content'); 34 if ($dialog.length) { 35 // Remove and replace the dialog buttons with those from the new form. 36 if ($dialog.dialog('option', 'drupalAutoButtons')) { 37 // Trigger an event to detect/sync changes to buttons. 38 $dialog.trigger('dialogButtonsChange'); 39 } 40 41 setTimeout(function () { 42 // Account for pre-existing focus handling that may have already moved 43 // the focus inside the dialog. 44 if (!$dialog[0].contains(document.activeElement)) { 45 // Move focus to the first focusable element in the next event loop 46 // to allow dialog buttons to be changed first. 47 $dialog.dialog('instance')._focusedElement = null; 48 $dialog.dialog('instance')._focusTabbable(); 49 } 50 }, 0); 51 } 52 53 const originalClose = settings.dialog.close; 54 // Overwrite the close method to remove the dialog on closing. 55 settings.dialog.close = function (event, ...args) { 56 originalClose.apply(settings.dialog, [event, ...args]); 57 // Check if the opener element is inside an AJAX container. 58 const $element = $(event.target); 59 const ajaxContainer = $element.data('uiDialog') 60 ? $element 61 .data('uiDialog') 62 .opener.closest('[data-drupal-ajax-container]') 63 : []; 64 65 // If the opener element was in an ajax container, and focus is on the 66 // body element, we can assume focus was lost. To recover, focus is 67 // moved to the first focusable element in the container. 68 if ( 69 ajaxContainer.length && 70 (document.activeElement === document.body || 71 $(document.activeElement).not(':visible')) 72 ) { 73 const focusableChildren = focusable(ajaxContainer[0]); 74 if (focusableChildren.length > 0) { 75 setTimeout(() => { 76 focusableChildren[0].focus(); 77 }, 0); 78 } 79 } 80 81 $(event.target).remove(); 82 }; 83 }, 84 85 /** 86 * Scan a dialog for any primary buttons and move them to the button area. 87 * 88 * @param {jQuery} $dialog 89 * A jQuery object containing the element that is the dialog target. 90 * 91 * @return {Array} 92 * An array of buttons that need to be added to the button area. 93 */ 94 prepareDialogButtons($dialog) { 95 const buttons = []; 96 const $buttons = $dialog.find( 97 '.form-actions input[type=submit], .form-actions a.button, .form-actions a.action-link', 98 ); 99 $buttons.each(function () { 100 const $originalButton = $(this); 101 this.style.display = 'none'; 102 buttons.push({ 103 text: $originalButton.html() || $originalButton.attr('value'), 104 class: $originalButton.attr('class'), 105 'data-once': $originalButton.data('once'), 106 click(e) { 107 // If the original button is an anchor tag, triggering the "click" 108 // event will not simulate a click. Use the click method instead. 109 if ($originalButton[0].tagName === 'A') { 110 $originalButton[0].click(); 111 } else { 112 $originalButton 113 .trigger('mousedown') 114 .trigger('mouseup') 115 .trigger('click'); 116 } 117 e.preventDefault(); 118 }, 119 }); 120 }); 121 return buttons; 122 }, 123 }; 124 125 /** 126 * Command to open a dialog. 127 * 128 * @param {Drupal.Ajax} ajax 129 * The Drupal Ajax object. 130 * @param {object} response 131 * Object holding the server response. 132 * @param {number} [status] 133 * The HTTP status code. 134 * 135 * @return {boolean|undefined} 136 * Returns false if there was no selector property in the response object. 137 */ 138 Drupal.AjaxCommands.prototype.openDialog = function (ajax, response, status) { 139 if (!response.selector) { 140 return false;
141 } 142 let $dialog = $(response.selector); 143 if (!$dialog.length) { 144 // Create the element if needed. 145 $dialog = $( 146 `<div id="${response.selector.replace( 147 /^#/, 148 '', 149 )}" class="ui-front"></div>`, 150 ).appendTo('body'); 151 } 152 // Set up the wrapper, if there isn't one. 153 if (!ajax.wrapper) { 154 ajax.wrapper = $dialog.attr('id'); 155 } 156 157 // Use the ajax.js insert command to populate the dialog contents. 158 response.command = 'insert'; 159 response.method = 'html'; 160 ajax.commands.insert(ajax, response, status); 161 162 // Move the buttons to the jQuery UI dialog buttons area. 163 response.dialogOptions = response.dialogOptions || {}; 164 if (typeof response.dialogOptions.drupalAutoButtons === 'undefined') { 165 response.dialogOptions.drupalAutoButtons = true; 166 } else if (response.dialogOptions.drupalAutoButtons === 'false') { 167 response.dialogOptions.drupalAutoButtons = false; 168 } else { 169 response.dialogOptions.drupalAutoButtons = 170 !!response.dialogOptions.drupalAutoButtons; 171 } 172 if ( 173 !response.dialogOptions.buttons && 174 response.dialogOptions.drupalAutoButtons 175 ) { 176 response.dialogOptions.buttons = 177 Drupal.behaviors.dialog.prepareDialogButtons($dialog); 178 } 179 180 // Bind dialogButtonsChange. 181 $dialog.on('dialogButtonsChange', () => { 182 const buttons = Drupal.behaviors.dialog.prepareDialogButtons($dialog); 183 $dialog.dialog('option', 'buttons', buttons); 184 }); 185 186 // Open the dialog itself. 187 response.dialogOptions = response.dialogOptions || {}; 188 const dialog = Drupal.dialog($dialog.get(0), response.dialogOptions); 189 if (response.dialogOptions.modal) { 190 dialog.showModal(); 191 } else { 192 dialog.show(); 193 } 194 195 // Add the standard Drupal class for buttons for style consistency. 196 $dialog.parent().find('.ui-dialog-buttonset').addClass('form-actions'); 197 }; 198 199 /** 200 * Command to close a dialog. 201 * 202 * If no selector is given, it defaults to trying to close the modal. 203 * 204 * @param {Drupal.Ajax} [ajax] 205 * The ajax object. 206 * @param {object} response 207 * Object holding the server response. 208 * @param {string} response.selector 209 * The selector of the dialog. 210 * @param {boolean} response.persist 211 * Whether to persist the dialog element or not. 212 * @param {number} [status] 213 * The HTTP status code. 214 */ 215 Drupal.AjaxCommands.prototype.closeDialog = function ( 216 ajax, 217 response, 218 status, 219 ) { 220 const $dialog = $(response.selector); 221 if ($dialog.length) { 222 Drupal.dialog($dialog.get(0)).close(); 223 if (!response.persist) { 224 $dialog.remove(); 225 } 226 } 227 228 // Unbind dialogButtonsChange. 229 $dialog.off('dialogButtonsChange'); 230 }; 231 232 /** 233 * Command to set a dialog property. 234 * 235 * JQuery UI specific way of setting dialog options. 236 * 237 * @param {Drupal.Ajax} [ajax] 238 * The Drupal Ajax object. 239 * @param {object} response 240 * Object holding the server response. 241 * @param {string} response.selector 242 * Selector for the dialog element. 243 * @param {string} response.optionsName 244 * Name of a key to set. 245 * @param {string} response.optionValue 246 * Value to set. 247 * @param {number} [status] 248 * The HTTP status code. 249 */ 250 Drupal.AjaxCommands.prototype.setDialogOption = function ( 251 ajax, 252 response, 253 status, 254 ) { 255 const $dialog = $(response.selector); 256 if ($dialog.length) { 257 $dialog.dialog('option', response.optionName, response.optionValue); 258 } 259 }; 260 261 /** 262 * Binds a listener on dialog creation to handle the cancel link. 263 * 264 * @param {jQuery.Event} e 265 * The event triggered. 266 * @param {Drupal.dialog~dialogDefinition} dialog 267 * The dialog instance. 268 * @param {jQuery} $element 269 * The jQuery collection of the dialog element. 270 * @param {object} [settings] 271 * Dialog settings. 272 */ 273 window.addEventListener('dialog:aftercreate', (event) => { 274 const $element = $(event.target); 275 const dialog = event.dialog; 276 $element.on('click.dialog', '.dialog-cancel', (e) => { 277 dialog.close('cancel'); 278 e.preventDefault(); 279 e.stopPropagation(); 280 }); 281 }); 282 283 /** 284 * Removes all 'dialog' listeners. 285 * 286 * @param {jQuery.Event} e 287 * The event triggered. 288 * @param {Drupal.dialog~dialogDefinition} dialog 289 * The dialog instance. 290 * @param {jQuery} $element 291 * jQuery collection of the dialog element. 292 */ 293 window.addEventListener('dialog:beforeclose', (e) => { 294 const $element = $(e.target); 295 $element.off('.dialog'); 296 }); 297 298 /** 299 * Ajax command to open URL in a modal dialog. 300 * 301 * @param {Drupal.Ajax} [ajax] 302 * An Ajax object. 303 * @param {object} response 304 * The Ajax response. 305 */ 306 Drupal.AjaxCommands.prototype.openModalDialogWithUrl = function ( 307 ajax, 308 response, 309 ) { 310 const dialogOptions = response.dialogOptions || {}; 311 const elementSettings = { 312 progress: { type: 'throbber' }, 313 dialogType: 'modal', 314 dialog: dialogOptions, 315 url: response.url, 316 httpMethod: 'GET', 317 }; 318 Drupal.ajax(elementSettings).execute(); 319 }; 320})(jQuery, Drupal, window.tabbable);
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.