PageSourceSearch

https://www.dodsonandross.com/core/misc/dialog/dialog.ajax.js?v=11.4.6

js dodsonandross.com collected 2026-09-24 19:25:08 UTC 11,092 bytes, 337 lines download raw bytes

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