annotate core/misc/ajax.es6.js @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 /**
Chris@0 2 * @file
Chris@0 3 * Provides Ajax page updating via jQuery $.ajax.
Chris@0 4 *
Chris@0 5 * Ajax is a method of making a request via JavaScript while viewing an HTML
Chris@0 6 * page. The request returns an array of commands encoded in JSON, which is
Chris@0 7 * then executed to make any changes that are necessary to the page.
Chris@0 8 *
Chris@0 9 * Drupal uses this file to enhance form elements with `#ajax['url']` and
Chris@0 10 * `#ajax['wrapper']` properties. If set, this file will automatically be
Chris@0 11 * included to provide Ajax capabilities.
Chris@0 12 */
Chris@0 13
Chris@17 14 (function($, window, Drupal, drupalSettings) {
Chris@0 15 /**
Chris@0 16 * Attaches the Ajax behavior to each Ajax form element.
Chris@0 17 *
Chris@0 18 * @type {Drupal~behavior}
Chris@0 19 *
Chris@0 20 * @prop {Drupal~behaviorAttach} attach
Chris@0 21 * Initialize all {@link Drupal.Ajax} objects declared in
Chris@0 22 * `drupalSettings.ajax` or initialize {@link Drupal.Ajax} objects from
Chris@0 23 * DOM elements having the `use-ajax-submit` or `use-ajax` css class.
Chris@0 24 * @prop {Drupal~behaviorDetach} detach
Chris@0 25 * During `unload` remove all {@link Drupal.Ajax} objects related to
Chris@0 26 * the removed content.
Chris@0 27 */
Chris@0 28 Drupal.behaviors.AJAX = {
Chris@0 29 attach(context, settings) {
Chris@0 30 function loadAjaxBehavior(base) {
Chris@14 31 const elementSettings = settings.ajax[base];
Chris@14 32 if (typeof elementSettings.selector === 'undefined') {
Chris@14 33 elementSettings.selector = `#${base}`;
Chris@0 34 }
Chris@17 35 $(elementSettings.selector)
Chris@17 36 .once('drupal-ajax')
Chris@17 37 .each(function() {
Chris@17 38 elementSettings.element = this;
Chris@17 39 elementSettings.base = base;
Chris@17 40 Drupal.ajax(elementSettings);
Chris@17 41 });
Chris@0 42 }
Chris@0 43
Chris@0 44 // Load all Ajax behaviors specified in the settings.
Chris@14 45 Object.keys(settings.ajax || {}).forEach(base => loadAjaxBehavior(base));
Chris@0 46
Chris@14 47 Drupal.ajax.bindAjaxLinks(document.body);
Chris@0 48
Chris@0 49 // This class means to submit the form to the action using Ajax.
Chris@17 50 $('.use-ajax-submit')
Chris@17 51 .once('ajax')
Chris@17 52 .each(function() {
Chris@17 53 const elementSettings = {};
Chris@0 54
Chris@17 55 // Ajax submits specified in this manner automatically submit to the
Chris@17 56 // normal form action.
Chris@17 57 elementSettings.url = $(this.form).attr('action');
Chris@17 58 // Form submit button clicks need to tell the form what was clicked so
Chris@17 59 // it gets passed in the POST request.
Chris@17 60 elementSettings.setClick = true;
Chris@17 61 // Form buttons use the 'click' event rather than mousedown.
Chris@17 62 elementSettings.event = 'click';
Chris@17 63 // Clicked form buttons look better with the throbber than the progress
Chris@17 64 // bar.
Chris@17 65 elementSettings.progress = { type: 'throbber' };
Chris@17 66 elementSettings.base = $(this).attr('id');
Chris@17 67 elementSettings.element = this;
Chris@0 68
Chris@17 69 Drupal.ajax(elementSettings);
Chris@17 70 });
Chris@0 71 },
Chris@0 72
Chris@0 73 detach(context, settings, trigger) {
Chris@0 74 if (trigger === 'unload') {
Chris@17 75 Drupal.ajax.expired().forEach(instance => {
Chris@0 76 // Set this to null and allow garbage collection to reclaim
Chris@0 77 // the memory.
Chris@0 78 Drupal.ajax.instances[instance.instanceIndex] = null;
Chris@0 79 });
Chris@0 80 }
Chris@0 81 },
Chris@0 82 };
Chris@0 83
Chris@0 84 /**
Chris@0 85 * Extends Error to provide handling for Errors in Ajax.
Chris@0 86 *
Chris@0 87 * @constructor
Chris@0 88 *
Chris@0 89 * @augments Error
Chris@0 90 *
Chris@0 91 * @param {XMLHttpRequest} xmlhttp
Chris@0 92 * XMLHttpRequest object used for the failed request.
Chris@0 93 * @param {string} uri
Chris@0 94 * The URI where the error occurred.
Chris@0 95 * @param {string} customMessage
Chris@0 96 * The custom message.
Chris@0 97 */
Chris@17 98 Drupal.AjaxError = function(xmlhttp, uri, customMessage) {
Chris@0 99 let statusCode;
Chris@0 100 let statusText;
Chris@0 101 let responseText;
Chris@0 102 if (xmlhttp.status) {
Chris@17 103 statusCode = `\n${Drupal.t('An AJAX HTTP error occurred.')}\n${Drupal.t(
Chris@17 104 'HTTP Result Code: !status',
Chris@17 105 { '!status': xmlhttp.status },
Chris@17 106 )}`;
Chris@17 107 } else {
Chris@17 108 statusCode = `\n${Drupal.t(
Chris@17 109 'An AJAX HTTP request terminated abnormally.',
Chris@17 110 )}`;
Chris@0 111 }
Chris@0 112 statusCode += `\n${Drupal.t('Debugging information follows.')}`;
Chris@14 113 const pathText = `\n${Drupal.t('Path: !uri', { '!uri': uri })}`;
Chris@0 114 statusText = '';
Chris@0 115 // In some cases, when statusCode === 0, xmlhttp.statusText may not be
Chris@0 116 // defined. Unfortunately, testing for it with typeof, etc, doesn't seem to
Chris@0 117 // catch that and the test causes an exception. So we need to catch the
Chris@0 118 // exception here.
Chris@0 119 try {
Chris@17 120 statusText = `\n${Drupal.t('StatusText: !statusText', {
Chris@17 121 '!statusText': $.trim(xmlhttp.statusText),
Chris@17 122 })}`;
Chris@17 123 } catch (e) {
Chris@0 124 // Empty.
Chris@0 125 }
Chris@0 126
Chris@0 127 responseText = '';
Chris@0 128 // Again, we don't have a way to know for sure whether accessing
Chris@0 129 // xmlhttp.responseText is going to throw an exception. So we'll catch it.
Chris@0 130 try {
Chris@17 131 responseText = `\n${Drupal.t('ResponseText: !responseText', {
Chris@17 132 '!responseText': $.trim(xmlhttp.responseText),
Chris@17 133 })}`;
Chris@17 134 } catch (e) {
Chris@0 135 // Empty.
Chris@0 136 }
Chris@0 137
Chris@0 138 // Make the responseText more readable by stripping HTML tags and newlines.
Chris@0 139 responseText = responseText.replace(/<("[^"]*"|'[^']*'|[^'">])*>/gi, '');
Chris@0 140 responseText = responseText.replace(/[\n]+\s+/g, '\n');
Chris@0 141
Chris@0 142 // We don't need readyState except for status == 0.
Chris@17 143 const readyStateText =
Chris@17 144 xmlhttp.status === 0
Chris@17 145 ? `\n${Drupal.t('ReadyState: !readyState', {
Chris@17 146 '!readyState': xmlhttp.readyState,
Chris@17 147 })}`
Chris@17 148 : '';
Chris@0 149
Chris@17 150 customMessage = customMessage
Chris@17 151 ? `\n${Drupal.t('CustomMessage: !customMessage', {
Chris@17 152 '!customMessage': customMessage,
Chris@17 153 })}`
Chris@17 154 : '';
Chris@0 155
Chris@0 156 /**
Chris@0 157 * Formatted and translated error message.
Chris@0 158 *
Chris@0 159 * @type {string}
Chris@0 160 */
Chris@17 161 this.message =
Chris@17 162 statusCode +
Chris@17 163 pathText +
Chris@17 164 statusText +
Chris@17 165 customMessage +
Chris@17 166 responseText +
Chris@17 167 readyStateText;
Chris@0 168
Chris@0 169 /**
Chris@0 170 * Used by some browsers to display a more accurate stack trace.
Chris@0 171 *
Chris@0 172 * @type {string}
Chris@0 173 */
Chris@0 174 this.name = 'AjaxError';
Chris@0 175 };
Chris@0 176
Chris@0 177 Drupal.AjaxError.prototype = new Error();
Chris@0 178 Drupal.AjaxError.prototype.constructor = Drupal.AjaxError;
Chris@0 179
Chris@0 180 /**
Chris@0 181 * Provides Ajax page updating via jQuery $.ajax.
Chris@0 182 *
Chris@0 183 * This function is designed to improve developer experience by wrapping the
Chris@0 184 * initialization of {@link Drupal.Ajax} objects and storing all created
Chris@0 185 * objects in the {@link Drupal.ajax.instances} array.
Chris@0 186 *
Chris@0 187 * @example
Chris@0 188 * Drupal.behaviors.myCustomAJAXStuff = {
Chris@0 189 * attach: function (context, settings) {
Chris@0 190 *
Chris@0 191 * var ajaxSettings = {
Chris@0 192 * url: 'my/url/path',
Chris@0 193 * // If the old version of Drupal.ajax() needs to be used those
Chris@0 194 * // properties can be added
Chris@0 195 * base: 'myBase',
Chris@0 196 * element: $(context).find('.someElement')
Chris@0 197 * };
Chris@0 198 *
Chris@0 199 * var myAjaxObject = Drupal.ajax(ajaxSettings);
Chris@0 200 *
Chris@0 201 * // Declare a new Ajax command specifically for this Ajax object.
Chris@0 202 * myAjaxObject.commands.insert = function (ajax, response, status) {
Chris@0 203 * $('#my-wrapper').append(response.data);
Chris@0 204 * alert('New content was appended to #my-wrapper');
Chris@0 205 * };
Chris@0 206 *
Chris@0 207 * // This command will remove this Ajax object from the page.
Chris@0 208 * myAjaxObject.commands.destroyObject = function (ajax, response, status) {
Chris@0 209 * Drupal.ajax.instances[this.instanceIndex] = null;
Chris@0 210 * };
Chris@0 211 *
Chris@0 212 * // Programmatically trigger the Ajax request.
Chris@0 213 * myAjaxObject.execute();
Chris@0 214 * }
Chris@0 215 * };
Chris@0 216 *
Chris@0 217 * @param {object} settings
Chris@0 218 * The settings object passed to {@link Drupal.Ajax} constructor.
Chris@0 219 * @param {string} [settings.base]
Chris@0 220 * Base is passed to {@link Drupal.Ajax} constructor as the 'base'
Chris@0 221 * parameter.
Chris@0 222 * @param {HTMLElement} [settings.element]
Chris@0 223 * Element parameter of {@link Drupal.Ajax} constructor, element on which
Chris@0 224 * event listeners will be bound.
Chris@0 225 *
Chris@0 226 * @return {Drupal.Ajax}
Chris@0 227 * The created Ajax object.
Chris@0 228 *
Chris@0 229 * @see Drupal.AjaxCommands
Chris@0 230 */
Chris@17 231 Drupal.ajax = function(settings) {
Chris@0 232 if (arguments.length !== 1) {
Chris@17 233 throw new Error(
Chris@17 234 'Drupal.ajax() function must be called with one configuration object only',
Chris@17 235 );
Chris@0 236 }
Chris@0 237 // Map those config keys to variables for the old Drupal.ajax function.
Chris@0 238 const base = settings.base || false;
Chris@0 239 const element = settings.element || false;
Chris@0 240 delete settings.base;
Chris@0 241 delete settings.element;
Chris@0 242
Chris@0 243 // By default do not display progress for ajax calls without an element.
Chris@0 244 if (!settings.progress && !element) {
Chris@0 245 settings.progress = false;
Chris@0 246 }
Chris@0 247
Chris@0 248 const ajax = new Drupal.Ajax(base, element, settings);
Chris@0 249 ajax.instanceIndex = Drupal.ajax.instances.length;
Chris@0 250 Drupal.ajax.instances.push(ajax);
Chris@0 251
Chris@0 252 return ajax;
Chris@0 253 };
Chris@0 254
Chris@0 255 /**
Chris@0 256 * Contains all created Ajax objects.
Chris@0 257 *
Chris@0 258 * @type {Array.<Drupal.Ajax|null>}
Chris@0 259 */
Chris@0 260 Drupal.ajax.instances = [];
Chris@0 261
Chris@0 262 /**
Chris@0 263 * List all objects where the associated element is not in the DOM
Chris@0 264 *
Chris@0 265 * This method ignores {@link Drupal.Ajax} objects not bound to DOM elements
Chris@0 266 * when created with {@link Drupal.ajax}.
Chris@0 267 *
Chris@0 268 * @return {Array.<Drupal.Ajax>}
Chris@0 269 * The list of expired {@link Drupal.Ajax} objects.
Chris@0 270 */
Chris@17 271 Drupal.ajax.expired = function() {
Chris@17 272 return Drupal.ajax.instances.filter(
Chris@17 273 instance =>
Chris@17 274 instance &&
Chris@17 275 instance.element !== false &&
Chris@17 276 !document.body.contains(instance.element),
Chris@17 277 );
Chris@0 278 };
Chris@0 279
Chris@0 280 /**
Chris@14 281 * Bind Ajax functionality to links that use the 'use-ajax' class.
Chris@14 282 *
Chris@14 283 * @param {HTMLElement} element
Chris@14 284 * Element to enable Ajax functionality for.
Chris@14 285 */
Chris@17 286 Drupal.ajax.bindAjaxLinks = element => {
Chris@14 287 // Bind Ajax behaviors to all items showing the class.
Chris@17 288 $(element)
Chris@17 289 .find('.use-ajax')
Chris@17 290 .once('ajax')
Chris@17 291 .each((i, ajaxLink) => {
Chris@17 292 const $linkElement = $(ajaxLink);
Chris@14 293
Chris@17 294 const elementSettings = {
Chris@17 295 // Clicked links look better with the throbber than the progress bar.
Chris@17 296 progress: { type: 'throbber' },
Chris@17 297 dialogType: $linkElement.data('dialog-type'),
Chris@17 298 dialog: $linkElement.data('dialog-options'),
Chris@17 299 dialogRenderer: $linkElement.data('dialog-renderer'),
Chris@17 300 base: $linkElement.attr('id'),
Chris@17 301 element: ajaxLink,
Chris@17 302 };
Chris@17 303 const href = $linkElement.attr('href');
Chris@17 304 /**
Chris@17 305 * For anchor tags, these will go to the target of the anchor rather
Chris@17 306 * than the usual location.
Chris@17 307 */
Chris@17 308 if (href) {
Chris@17 309 elementSettings.url = href;
Chris@17 310 elementSettings.event = 'click';
Chris@17 311 }
Chris@17 312 Drupal.ajax(elementSettings);
Chris@17 313 });
Chris@14 314 };
Chris@14 315
Chris@14 316 /**
Chris@0 317 * Settings for an Ajax object.
Chris@0 318 *
Chris@14 319 * @typedef {object} Drupal.Ajax~elementSettings
Chris@0 320 *
Chris@0 321 * @prop {string} url
Chris@0 322 * Target of the Ajax request.
Chris@0 323 * @prop {?string} [event]
Chris@0 324 * Event bound to settings.element which will trigger the Ajax request.
Chris@0 325 * @prop {bool} [keypress=true]
Chris@0 326 * Triggers a request on keypress events.
Chris@0 327 * @prop {?string} selector
Chris@0 328 * jQuery selector targeting the element to bind events to or used with
Chris@0 329 * {@link Drupal.AjaxCommands}.
Chris@0 330 * @prop {string} [effect='none']
Chris@0 331 * Name of the jQuery method to use for displaying new Ajax content.
Chris@0 332 * @prop {string|number} [speed='none']
Chris@0 333 * Speed with which to apply the effect.
Chris@0 334 * @prop {string} [method]
Chris@0 335 * Name of the jQuery method used to insert new content in the targeted
Chris@0 336 * element.
Chris@0 337 * @prop {object} [progress]
Chris@0 338 * Settings for the display of a user-friendly loader.
Chris@0 339 * @prop {string} [progress.type='throbber']
Chris@0 340 * Type of progress element, core provides `'bar'`, `'throbber'` and
Chris@0 341 * `'fullscreen'`.
Chris@0 342 * @prop {string} [progress.message=Drupal.t('Please wait...')]
Chris@0 343 * Custom message to be used with the bar indicator.
Chris@0 344 * @prop {object} [submit]
Chris@0 345 * Extra data to be sent with the Ajax request.
Chris@0 346 * @prop {bool} [submit.js=true]
Chris@0 347 * Allows the PHP side to know this comes from an Ajax request.
Chris@0 348 * @prop {object} [dialog]
Chris@0 349 * Options for {@link Drupal.dialog}.
Chris@0 350 * @prop {string} [dialogType]
Chris@0 351 * One of `'modal'` or `'dialog'`.
Chris@0 352 * @prop {string} [prevent]
Chris@0 353 * List of events on which to stop default action and stop propagation.
Chris@0 354 */
Chris@0 355
Chris@0 356 /**
Chris@0 357 * Ajax constructor.
Chris@0 358 *
Chris@0 359 * The Ajax request returns an array of commands encoded in JSON, which is
Chris@0 360 * then executed to make any changes that are necessary to the page.
Chris@0 361 *
Chris@0 362 * Drupal uses this file to enhance form elements with `#ajax['url']` and
Chris@0 363 * `#ajax['wrapper']` properties. If set, this file will automatically be
Chris@0 364 * included to provide Ajax capabilities.
Chris@0 365 *
Chris@0 366 * @constructor
Chris@0 367 *
Chris@0 368 * @param {string} [base]
Chris@0 369 * Base parameter of {@link Drupal.Ajax} constructor
Chris@0 370 * @param {HTMLElement} [element]
Chris@0 371 * Element parameter of {@link Drupal.Ajax} constructor, element on which
Chris@0 372 * event listeners will be bound.
Chris@14 373 * @param {Drupal.Ajax~elementSettings} elementSettings
Chris@0 374 * Settings for this Ajax object.
Chris@0 375 */
Chris@17 376 Drupal.Ajax = function(base, element, elementSettings) {
Chris@0 377 const defaults = {
Chris@0 378 event: element ? 'mousedown' : null,
Chris@0 379 keypress: true,
Chris@0 380 selector: base ? `#${base}` : null,
Chris@0 381 effect: 'none',
Chris@0 382 speed: 'none',
Chris@0 383 method: 'replaceWith',
Chris@0 384 progress: {
Chris@0 385 type: 'throbber',
Chris@0 386 message: Drupal.t('Please wait...'),
Chris@0 387 },
Chris@0 388 submit: {
Chris@0 389 js: true,
Chris@0 390 },
Chris@0 391 };
Chris@0 392
Chris@14 393 $.extend(this, defaults, elementSettings);
Chris@0 394
Chris@0 395 /**
Chris@0 396 * @type {Drupal.AjaxCommands}
Chris@0 397 */
Chris@0 398 this.commands = new Drupal.AjaxCommands();
Chris@0 399
Chris@0 400 /**
Chris@0 401 * @type {bool|number}
Chris@0 402 */
Chris@0 403 this.instanceIndex = false;
Chris@0 404
Chris@0 405 // @todo Remove this after refactoring the PHP code to:
Chris@0 406 // - Call this 'selector'.
Chris@0 407 // - Include the '#' for ID-based selectors.
Chris@0 408 // - Support non-ID-based selectors.
Chris@0 409 if (this.wrapper) {
Chris@0 410 /**
Chris@0 411 * @type {string}
Chris@0 412 */
Chris@0 413 this.wrapper = `#${this.wrapper}`;
Chris@0 414 }
Chris@0 415
Chris@0 416 /**
Chris@0 417 * @type {HTMLElement}
Chris@0 418 */
Chris@0 419 this.element = element;
Chris@0 420
Chris@0 421 /**
Chris@14 422 * @deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0.
Chris@14 423 * Use elementSettings.
Chris@14 424 *
Chris@14 425 * @type {Drupal.Ajax~elementSettings}
Chris@0 426 */
Chris@14 427 this.element_settings = elementSettings;
Chris@14 428
Chris@14 429 /**
Chris@14 430 * @type {Drupal.Ajax~elementSettings}
Chris@14 431 */
Chris@14 432 this.elementSettings = elementSettings;
Chris@0 433
Chris@0 434 // If there isn't a form, jQuery.ajax() will be used instead, allowing us to
Chris@0 435 // bind Ajax to links as well.
Chris@0 436 if (this.element && this.element.form) {
Chris@0 437 /**
Chris@0 438 * @type {jQuery}
Chris@0 439 */
Chris@0 440 this.$form = $(this.element.form);
Chris@0 441 }
Chris@0 442
Chris@0 443 // If no Ajax callback URL was given, use the link href or form action.
Chris@0 444 if (!this.url) {
Chris@0 445 const $element = $(this.element);
Chris@0 446 if ($element.is('a')) {
Chris@0 447 this.url = $element.attr('href');
Chris@17 448 } else if (this.element && element.form) {
Chris@0 449 this.url = this.$form.attr('action');
Chris@0 450 }
Chris@0 451 }
Chris@0 452
Chris@0 453 // Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let
Chris@0 454 // the server detect when it needs to degrade gracefully.
Chris@0 455 // There are four scenarios to check for:
Chris@0 456 // 1. /nojs/
Chris@0 457 // 2. /nojs$ - The end of a URL string.
Chris@0 458 // 3. /nojs? - Followed by a query (e.g. path/nojs?destination=foobar).
Chris@0 459 // 4. /nojs# - Followed by a fragment (e.g.: path/nojs#myfragment).
Chris@0 460 const originalUrl = this.url;
Chris@0 461
Chris@0 462 /**
Chris@0 463 * Processed Ajax URL.
Chris@0 464 *
Chris@0 465 * @type {string}
Chris@0 466 */
Chris@17 467 this.url = this.url.replace(/\/nojs(\/|$|\?|#)/, '/ajax$1');
Chris@0 468 // If the 'nojs' version of the URL is trusted, also trust the 'ajax'
Chris@0 469 // version.
Chris@0 470 if (drupalSettings.ajaxTrustedUrl[originalUrl]) {
Chris@0 471 drupalSettings.ajaxTrustedUrl[this.url] = true;
Chris@0 472 }
Chris@0 473
Chris@0 474 // Set the options for the ajaxSubmit function.
Chris@0 475 // The 'this' variable will not persist inside of the options object.
Chris@0 476 const ajax = this;
Chris@0 477
Chris@0 478 /**
Chris@0 479 * Options for the jQuery.ajax function.
Chris@0 480 *
Chris@0 481 * @name Drupal.Ajax#options
Chris@0 482 *
Chris@0 483 * @type {object}
Chris@0 484 *
Chris@0 485 * @prop {string} url
Chris@0 486 * Ajax URL to be called.
Chris@0 487 * @prop {object} data
Chris@0 488 * Ajax payload.
Chris@0 489 * @prop {function} beforeSerialize
Chris@0 490 * Implement jQuery beforeSerialize function to call
Chris@0 491 * {@link Drupal.Ajax#beforeSerialize}.
Chris@0 492 * @prop {function} beforeSubmit
Chris@0 493 * Implement jQuery beforeSubmit function to call
Chris@0 494 * {@link Drupal.Ajax#beforeSubmit}.
Chris@0 495 * @prop {function} beforeSend
Chris@0 496 * Implement jQuery beforeSend function to call
Chris@0 497 * {@link Drupal.Ajax#beforeSend}.
Chris@0 498 * @prop {function} success
Chris@0 499 * Implement jQuery success function to call
Chris@0 500 * {@link Drupal.Ajax#success}.
Chris@0 501 * @prop {function} complete
Chris@0 502 * Implement jQuery success function to clean up ajax state and trigger an
Chris@0 503 * error if needed.
Chris@0 504 * @prop {string} dataType='json'
Chris@0 505 * Type of the response expected.
Chris@0 506 * @prop {string} type='POST'
Chris@0 507 * HTTP method to use for the Ajax request.
Chris@0 508 */
Chris@0 509 ajax.options = {
Chris@0 510 url: ajax.url,
Chris@0 511 data: ajax.submit,
Chris@14 512 beforeSerialize(elementSettings, options) {
Chris@14 513 return ajax.beforeSerialize(elementSettings, options);
Chris@0 514 },
Chris@14 515 beforeSubmit(formValues, elementSettings, options) {
Chris@0 516 ajax.ajaxing = true;
Chris@14 517 return ajax.beforeSubmit(formValues, elementSettings, options);
Chris@0 518 },
Chris@0 519 beforeSend(xmlhttprequest, options) {
Chris@0 520 ajax.ajaxing = true;
Chris@0 521 return ajax.beforeSend(xmlhttprequest, options);
Chris@0 522 },
Chris@0 523 success(response, status, xmlhttprequest) {
Chris@0 524 // Sanity check for browser support (object expected).
Chris@0 525 // When using iFrame uploads, responses must be returned as a string.
Chris@0 526 if (typeof response === 'string') {
Chris@0 527 response = $.parseJSON(response);
Chris@0 528 }
Chris@0 529
Chris@0 530 // Prior to invoking the response's commands, verify that they can be
Chris@0 531 // trusted by checking for a response header. See
Chris@0 532 // \Drupal\Core\EventSubscriber\AjaxResponseSubscriber for details.
Chris@0 533 // - Empty responses are harmless so can bypass verification. This
Chris@0 534 // avoids an alert message for server-generated no-op responses that
Chris@0 535 // skip Ajax rendering.
Chris@0 536 // - Ajax objects with trusted URLs (e.g., ones defined server-side via
Chris@0 537 // #ajax) can bypass header verification. This is especially useful
Chris@0 538 // for Ajax with multipart forms. Because IFRAME transport is used,
Chris@0 539 // the response headers cannot be accessed for verification.
Chris@0 540 if (response !== null && !drupalSettings.ajaxTrustedUrl[ajax.url]) {
Chris@0 541 if (xmlhttprequest.getResponseHeader('X-Drupal-Ajax-Token') !== '1') {
Chris@17 542 const customMessage = Drupal.t(
Chris@17 543 'The response failed verification so will not be processed.',
Chris@17 544 );
Chris@0 545 return ajax.error(xmlhttprequest, ajax.url, customMessage);
Chris@0 546 }
Chris@0 547 }
Chris@0 548
Chris@0 549 return ajax.success(response, status);
Chris@0 550 },
Chris@0 551 complete(xmlhttprequest, status) {
Chris@0 552 ajax.ajaxing = false;
Chris@0 553 if (status === 'error' || status === 'parsererror') {
Chris@0 554 return ajax.error(xmlhttprequest, ajax.url);
Chris@0 555 }
Chris@0 556 },
Chris@0 557 dataType: 'json',
Chris@0 558 type: 'POST',
Chris@0 559 };
Chris@0 560
Chris@14 561 if (elementSettings.dialog) {
Chris@14 562 ajax.options.data.dialogOptions = elementSettings.dialog;
Chris@0 563 }
Chris@0 564
Chris@0 565 // Ensure that we have a valid URL by adding ? when no query parameter is
Chris@0 566 // yet available, otherwise append using &.
Chris@0 567 if (ajax.options.url.indexOf('?') === -1) {
Chris@0 568 ajax.options.url += '?';
Chris@17 569 } else {
Chris@0 570 ajax.options.url += '&';
Chris@0 571 }
Chris@0 572 // If this element has a dialog type use if for the wrapper if not use 'ajax'.
Chris@17 573 let wrapper = `drupal_${elementSettings.dialogType || 'ajax'}`;
Chris@14 574 if (elementSettings.dialogRenderer) {
Chris@14 575 wrapper += `.${elementSettings.dialogRenderer}`;
Chris@0 576 }
Chris@0 577 ajax.options.url += `${Drupal.ajax.WRAPPER_FORMAT}=${wrapper}`;
Chris@0 578
Chris@0 579 // Bind the ajaxSubmit function to the element event.
Chris@17 580 $(ajax.element).on(elementSettings.event, function(event) {
Chris@17 581 if (
Chris@17 582 !drupalSettings.ajaxTrustedUrl[ajax.url] &&
Chris@17 583 !Drupal.url.isLocal(ajax.url)
Chris@17 584 ) {
Chris@17 585 throw new Error(
Chris@17 586 Drupal.t('The callback URL is not local and not trusted: !url', {
Chris@17 587 '!url': ajax.url,
Chris@17 588 }),
Chris@17 589 );
Chris@0 590 }
Chris@0 591 return ajax.eventResponse(this, event);
Chris@0 592 });
Chris@0 593
Chris@0 594 // If necessary, enable keyboard submission so that Ajax behaviors
Chris@0 595 // can be triggered through keyboard input as well as e.g. a mousedown
Chris@0 596 // action.
Chris@14 597 if (elementSettings.keypress) {
Chris@17 598 $(ajax.element).on('keypress', function(event) {
Chris@0 599 return ajax.keypressResponse(this, event);
Chris@0 600 });
Chris@0 601 }
Chris@0 602
Chris@0 603 // If necessary, prevent the browser default action of an additional event.
Chris@0 604 // For example, prevent the browser default action of a click, even if the
Chris@0 605 // Ajax behavior binds to mousedown.
Chris@14 606 if (elementSettings.prevent) {
Chris@14 607 $(ajax.element).on(elementSettings.prevent, false);
Chris@0 608 }
Chris@0 609 };
Chris@0 610
Chris@0 611 /**
Chris@0 612 * URL query attribute to indicate the wrapper used to render a request.
Chris@0 613 *
Chris@0 614 * The wrapper format determines how the HTML is wrapped, for example in a
Chris@0 615 * modal dialog.
Chris@0 616 *
Chris@0 617 * @const {string}
Chris@0 618 *
Chris@0 619 * @default
Chris@0 620 */
Chris@0 621 Drupal.ajax.WRAPPER_FORMAT = '_wrapper_format';
Chris@0 622
Chris@0 623 /**
Chris@0 624 * Request parameter to indicate that a request is a Drupal Ajax request.
Chris@0 625 *
Chris@0 626 * @const {string}
Chris@0 627 *
Chris@0 628 * @default
Chris@0 629 */
Chris@0 630 Drupal.Ajax.AJAX_REQUEST_PARAMETER = '_drupal_ajax';
Chris@0 631
Chris@0 632 /**
Chris@0 633 * Execute the ajax request.
Chris@0 634 *
Chris@0 635 * Allows developers to execute an Ajax request manually without specifying
Chris@0 636 * an event to respond to.
Chris@0 637 *
Chris@0 638 * @return {object}
Chris@0 639 * Returns the jQuery.Deferred object underlying the Ajax request. If
Chris@0 640 * pre-serialization fails, the Deferred will be returned in the rejected
Chris@0 641 * state.
Chris@0 642 */
Chris@17 643 Drupal.Ajax.prototype.execute = function() {
Chris@0 644 // Do not perform another ajax command if one is already in progress.
Chris@0 645 if (this.ajaxing) {
Chris@0 646 return;
Chris@0 647 }
Chris@0 648
Chris@0 649 try {
Chris@0 650 this.beforeSerialize(this.element, this.options);
Chris@0 651 // Return the jqXHR so that external code can hook into the Deferred API.
Chris@0 652 return $.ajax(this.options);
Chris@17 653 } catch (e) {
Chris@0 654 // Unset the ajax.ajaxing flag here because it won't be unset during
Chris@0 655 // the complete response.
Chris@0 656 this.ajaxing = false;
Chris@17 657 window.alert(
Chris@17 658 `An error occurred while attempting to process ${this.options.url}: ${
Chris@17 659 e.message
Chris@17 660 }`,
Chris@17 661 );
Chris@0 662 // For consistency, return a rejected Deferred (i.e., jqXHR's superclass)
Chris@0 663 // so that calling code can take appropriate action.
Chris@0 664 return $.Deferred().reject();
Chris@0 665 }
Chris@0 666 };
Chris@0 667
Chris@0 668 /**
Chris@0 669 * Handle a key press.
Chris@0 670 *
Chris@0 671 * The Ajax object will, if instructed, bind to a key press response. This
Chris@0 672 * will test to see if the key press is valid to trigger this event and
Chris@0 673 * if it is, trigger it for us and prevent other keypresses from triggering.
Chris@0 674 * In this case we're handling RETURN and SPACEBAR keypresses (event codes 13
Chris@0 675 * and 32. RETURN is often used to submit a form when in a textfield, and
Chris@0 676 * SPACE is often used to activate an element without submitting.
Chris@0 677 *
Chris@0 678 * @param {HTMLElement} element
Chris@0 679 * Element the event was triggered on.
Chris@0 680 * @param {jQuery.Event} event
Chris@0 681 * Triggered event.
Chris@0 682 */
Chris@17 683 Drupal.Ajax.prototype.keypressResponse = function(element, event) {
Chris@0 684 // Create a synonym for this to reduce code confusion.
Chris@0 685 const ajax = this;
Chris@0 686
Chris@0 687 // Detect enter key and space bar and allow the standard response for them,
Chris@0 688 // except for form elements of type 'text', 'tel', 'number' and 'textarea',
Chris@0 689 // where the spacebar activation causes inappropriate activation if
Chris@0 690 // #ajax['keypress'] is TRUE. On a text-type widget a space should always
Chris@0 691 // be a space.
Chris@17 692 if (
Chris@17 693 event.which === 13 ||
Chris@17 694 (event.which === 32 &&
Chris@17 695 element.type !== 'text' &&
Chris@17 696 element.type !== 'textarea' &&
Chris@17 697 element.type !== 'tel' &&
Chris@17 698 element.type !== 'number')
Chris@17 699 ) {
Chris@0 700 event.preventDefault();
Chris@0 701 event.stopPropagation();
Chris@14 702 $(element).trigger(ajax.elementSettings.event);
Chris@0 703 }
Chris@0 704 };
Chris@0 705
Chris@0 706 /**
Chris@0 707 * Handle an event that triggers an Ajax response.
Chris@0 708 *
Chris@0 709 * When an event that triggers an Ajax response happens, this method will
Chris@0 710 * perform the actual Ajax call. It is bound to the event using
Chris@0 711 * bind() in the constructor, and it uses the options specified on the
Chris@0 712 * Ajax object.
Chris@0 713 *
Chris@0 714 * @param {HTMLElement} element
Chris@0 715 * Element the event was triggered on.
Chris@0 716 * @param {jQuery.Event} event
Chris@0 717 * Triggered event.
Chris@0 718 */
Chris@17 719 Drupal.Ajax.prototype.eventResponse = function(element, event) {
Chris@0 720 event.preventDefault();
Chris@0 721 event.stopPropagation();
Chris@0 722
Chris@0 723 // Create a synonym for this to reduce code confusion.
Chris@0 724 const ajax = this;
Chris@0 725
Chris@0 726 // Do not perform another Ajax command if one is already in progress.
Chris@0 727 if (ajax.ajaxing) {
Chris@0 728 return;
Chris@0 729 }
Chris@0 730
Chris@0 731 try {
Chris@0 732 if (ajax.$form) {
Chris@0 733 // If setClick is set, we must set this to ensure that the button's
Chris@0 734 // value is passed.
Chris@0 735 if (ajax.setClick) {
Chris@0 736 // Mark the clicked button. 'form.clk' is a special variable for
Chris@0 737 // ajaxSubmit that tells the system which element got clicked to
Chris@0 738 // trigger the submit. Without it there would be no 'op' or
Chris@0 739 // equivalent.
Chris@0 740 element.form.clk = element;
Chris@0 741 }
Chris@0 742
Chris@0 743 ajax.$form.ajaxSubmit(ajax.options);
Chris@17 744 } else {
Chris@0 745 ajax.beforeSerialize(ajax.element, ajax.options);
Chris@0 746 $.ajax(ajax.options);
Chris@0 747 }
Chris@17 748 } catch (e) {
Chris@0 749 // Unset the ajax.ajaxing flag here because it won't be unset during
Chris@0 750 // the complete response.
Chris@0 751 ajax.ajaxing = false;
Chris@17 752 window.alert(
Chris@17 753 `An error occurred while attempting to process ${ajax.options.url}: ${
Chris@17 754 e.message
Chris@17 755 }`,
Chris@17 756 );
Chris@0 757 }
Chris@0 758 };
Chris@0 759
Chris@0 760 /**
Chris@0 761 * Handler for the form serialization.
Chris@0 762 *
Chris@0 763 * Runs before the beforeSend() handler (see below), and unlike that one, runs
Chris@0 764 * before field data is collected.
Chris@0 765 *
Chris@0 766 * @param {object} [element]
Chris@14 767 * Ajax object's `elementSettings`.
Chris@0 768 * @param {object} options
Chris@0 769 * jQuery.ajax options.
Chris@0 770 */
Chris@17 771 Drupal.Ajax.prototype.beforeSerialize = function(element, options) {
Chris@0 772 // Allow detaching behaviors to update field values before collecting them.
Chris@0 773 // This is only needed when field values are added to the POST data, so only
Chris@0 774 // when there is a form such that this.$form.ajaxSubmit() is used instead of
Chris@0 775 // $.ajax(). When there is no form and $.ajax() is used, beforeSerialize()
Chris@0 776 // isn't called, but don't rely on that: explicitly check this.$form.
Chris@17 777 if (this.$form && document.body.contains(this.$form.get(0))) {
Chris@0 778 const settings = this.settings || drupalSettings;
Chris@0 779 Drupal.detachBehaviors(this.$form.get(0), settings, 'serialize');
Chris@0 780 }
Chris@0 781
Chris@0 782 // Inform Drupal that this is an AJAX request.
Chris@0 783 options.data[Drupal.Ajax.AJAX_REQUEST_PARAMETER] = 1;
Chris@0 784
Chris@0 785 // Allow Drupal to return new JavaScript and CSS files to load without
Chris@0 786 // returning the ones already loaded.
Chris@0 787 // @see \Drupal\Core\Theme\AjaxBasePageNegotiator
Chris@0 788 // @see \Drupal\Core\Asset\LibraryDependencyResolverInterface::getMinimalRepresentativeSubset()
Chris@0 789 // @see system_js_settings_alter()
Chris@0 790 const pageState = drupalSettings.ajaxPageState;
Chris@0 791 options.data['ajax_page_state[theme]'] = pageState.theme;
Chris@0 792 options.data['ajax_page_state[theme_token]'] = pageState.theme_token;
Chris@0 793 options.data['ajax_page_state[libraries]'] = pageState.libraries;
Chris@0 794 };
Chris@0 795
Chris@0 796 /**
Chris@0 797 * Modify form values prior to form submission.
Chris@0 798 *
Chris@14 799 * @param {Array.<object>} formValues
Chris@0 800 * Processed form values.
Chris@0 801 * @param {jQuery} element
Chris@0 802 * The form node as a jQuery object.
Chris@0 803 * @param {object} options
Chris@0 804 * jQuery.ajax options.
Chris@0 805 */
Chris@17 806 Drupal.Ajax.prototype.beforeSubmit = function(formValues, element, options) {
Chris@0 807 // This function is left empty to make it simple to override for modules
Chris@0 808 // that wish to add functionality here.
Chris@0 809 };
Chris@0 810
Chris@0 811 /**
Chris@0 812 * Prepare the Ajax request before it is sent.
Chris@0 813 *
Chris@0 814 * @param {XMLHttpRequest} xmlhttprequest
Chris@0 815 * Native Ajax object.
Chris@0 816 * @param {object} options
Chris@0 817 * jQuery.ajax options.
Chris@0 818 */
Chris@17 819 Drupal.Ajax.prototype.beforeSend = function(xmlhttprequest, options) {
Chris@0 820 // For forms without file inputs, the jQuery Form plugin serializes the
Chris@0 821 // form values, and then calls jQuery's $.ajax() function, which invokes
Chris@0 822 // this handler. In this circumstance, options.extraData is never used. For
Chris@0 823 // forms with file inputs, the jQuery Form plugin uses the browser's normal
Chris@0 824 // form submission mechanism, but captures the response in a hidden IFRAME.
Chris@0 825 // In this circumstance, it calls this handler first, and then appends
Chris@0 826 // hidden fields to the form to submit the values in options.extraData.
Chris@0 827 // There is no simple way to know which submission mechanism will be used,
Chris@0 828 // so we add to extraData regardless, and allow it to be ignored in the
Chris@0 829 // former case.
Chris@0 830 if (this.$form) {
Chris@0 831 options.extraData = options.extraData || {};
Chris@0 832
Chris@0 833 // Let the server know when the IFRAME submission mechanism is used. The
Chris@0 834 // server can use this information to wrap the JSON response in a
Chris@0 835 // TEXTAREA, as per http://jquery.malsup.com/form/#file-upload.
Chris@0 836 options.extraData.ajax_iframe_upload = '1';
Chris@0 837
Chris@0 838 // The triggering element is about to be disabled (see below), but if it
Chris@0 839 // contains a value (e.g., a checkbox, textfield, select, etc.), ensure
Chris@0 840 // that value is included in the submission. As per above, submissions
Chris@0 841 // that use $.ajax() are already serialized prior to the element being
Chris@0 842 // disabled, so this is only needed for IFRAME submissions.
Chris@0 843 const v = $.fieldValue(this.element);
Chris@0 844 if (v !== null) {
Chris@0 845 options.extraData[this.element.name] = v;
Chris@0 846 }
Chris@0 847 }
Chris@0 848
Chris@0 849 // Disable the element that received the change to prevent user interface
Chris@0 850 // interaction while the Ajax request is in progress. ajax.ajaxing prevents
Chris@0 851 // the element from triggering a new request, but does not prevent the user
Chris@0 852 // from changing its value.
Chris@0 853 $(this.element).prop('disabled', true);
Chris@0 854
Chris@0 855 if (!this.progress || !this.progress.type) {
Chris@0 856 return;
Chris@0 857 }
Chris@0 858
Chris@0 859 // Insert progress indicator.
Chris@17 860 const progressIndicatorMethod = `setProgressIndicator${this.progress.type
Chris@17 861 .slice(0, 1)
Chris@17 862 .toUpperCase()}${this.progress.type.slice(1).toLowerCase()}`;
Chris@17 863 if (
Chris@17 864 progressIndicatorMethod in this &&
Chris@17 865 typeof this[progressIndicatorMethod] === 'function'
Chris@17 866 ) {
Chris@0 867 this[progressIndicatorMethod].call(this);
Chris@0 868 }
Chris@0 869 };
Chris@0 870
Chris@0 871 /**
Chris@17 872 * An animated progress throbber and container element for AJAX operations.
Chris@17 873 *
Chris@17 874 * @param {string} [message]
Chris@17 875 * (optional) The message shown on the UI.
Chris@17 876 * @return {string}
Chris@17 877 * The HTML markup for the throbber.
Chris@17 878 */
Chris@17 879 Drupal.theme.ajaxProgressThrobber = message => {
Chris@17 880 // Build markup without adding extra white space since it affects rendering.
Chris@17 881 const messageMarkup =
Chris@17 882 typeof message === 'string'
Chris@17 883 ? Drupal.theme('ajaxProgressMessage', message)
Chris@17 884 : '';
Chris@17 885 const throbber = '<div class="throbber">&nbsp;</div>';
Chris@17 886
Chris@17 887 return `<div class="ajax-progress ajax-progress-throbber">${throbber}${messageMarkup}</div>`;
Chris@17 888 };
Chris@17 889
Chris@17 890 /**
Chris@17 891 * An animated progress throbber and container element for AJAX operations.
Chris@17 892 *
Chris@17 893 * @return {string}
Chris@17 894 * The HTML markup for the throbber.
Chris@17 895 */
Chris@17 896 Drupal.theme.ajaxProgressIndicatorFullscreen = () =>
Chris@17 897 '<div class="ajax-progress ajax-progress-fullscreen">&nbsp;</div>';
Chris@17 898
Chris@17 899 /**
Chris@17 900 * Formats text accompanying the AJAX progress throbber.
Chris@17 901 *
Chris@17 902 * @param {string} message
Chris@17 903 * The message shown on the UI.
Chris@17 904 * @return {string}
Chris@17 905 * The HTML markup for the throbber.
Chris@17 906 */
Chris@17 907 Drupal.theme.ajaxProgressMessage = message =>
Chris@17 908 `<div class="message">${message}</div>`;
Chris@17 909
Chris@17 910 /**
Chris@0 911 * Sets the progress bar progress indicator.
Chris@0 912 */
Chris@17 913 Drupal.Ajax.prototype.setProgressIndicatorBar = function() {
Chris@17 914 const progressBar = new Drupal.ProgressBar(
Chris@17 915 `ajax-progress-${this.element.id}`,
Chris@17 916 $.noop,
Chris@17 917 this.progress.method,
Chris@17 918 $.noop,
Chris@17 919 );
Chris@0 920 if (this.progress.message) {
Chris@0 921 progressBar.setProgress(-1, this.progress.message);
Chris@0 922 }
Chris@0 923 if (this.progress.url) {
Chris@17 924 progressBar.startMonitoring(
Chris@17 925 this.progress.url,
Chris@17 926 this.progress.interval || 1500,
Chris@17 927 );
Chris@0 928 }
Chris@17 929 this.progress.element = $(progressBar.element).addClass(
Chris@17 930 'ajax-progress ajax-progress-bar',
Chris@17 931 );
Chris@0 932 this.progress.object = progressBar;
Chris@0 933 $(this.element).after(this.progress.element);
Chris@0 934 };
Chris@0 935
Chris@0 936 /**
Chris@0 937 * Sets the throbber progress indicator.
Chris@0 938 */
Chris@17 939 Drupal.Ajax.prototype.setProgressIndicatorThrobber = function() {
Chris@17 940 this.progress.element = $(
Chris@17 941 Drupal.theme('ajaxProgressThrobber', this.progress.message),
Chris@17 942 );
Chris@0 943 $(this.element).after(this.progress.element);
Chris@0 944 };
Chris@0 945
Chris@0 946 /**
Chris@0 947 * Sets the fullscreen progress indicator.
Chris@0 948 */
Chris@17 949 Drupal.Ajax.prototype.setProgressIndicatorFullscreen = function() {
Chris@17 950 this.progress.element = $(Drupal.theme('ajaxProgressIndicatorFullscreen'));
Chris@0 951 $('body').after(this.progress.element);
Chris@0 952 };
Chris@0 953
Chris@0 954 /**
Chris@0 955 * Handler for the form redirection completion.
Chris@0 956 *
Chris@0 957 * @param {Array.<Drupal.AjaxCommands~commandDefinition>} response
Chris@0 958 * Drupal Ajax response.
Chris@0 959 * @param {number} status
Chris@0 960 * XMLHttpRequest status.
Chris@0 961 */
Chris@17 962 Drupal.Ajax.prototype.success = function(response, status) {
Chris@0 963 // Remove the progress element.
Chris@0 964 if (this.progress.element) {
Chris@0 965 $(this.progress.element).remove();
Chris@0 966 }
Chris@0 967 if (this.progress.object) {
Chris@0 968 this.progress.object.stopMonitoring();
Chris@0 969 }
Chris@0 970 $(this.element).prop('disabled', false);
Chris@0 971
Chris@0 972 // Save element's ancestors tree so if the element is removed from the dom
Chris@0 973 // we can try to refocus one of its parents. Using addBack reverse the
Chris@0 974 // result array, meaning that index 0 is the highest parent in the hierarchy
Chris@0 975 // in this situation it is usually a <form> element.
Chris@17 976 const elementParents = $(this.element)
Chris@17 977 .parents('[data-drupal-selector]')
Chris@17 978 .addBack()
Chris@17 979 .toArray();
Chris@0 980
Chris@0 981 // Track if any command is altering the focus so we can avoid changing the
Chris@0 982 // focus set by the Ajax command.
Chris@0 983 let focusChanged = false;
Chris@17 984 Object.keys(response || {}).forEach(i => {
Chris@14 985 if (response[i].command && this.commands[response[i].command]) {
Chris@0 986 this.commands[response[i].command](this, response[i], status);
Chris@17 987 if (
Chris@17 988 response[i].command === 'invoke' &&
Chris@17 989 response[i].method === 'focus'
Chris@17 990 ) {
Chris@0 991 focusChanged = true;
Chris@0 992 }
Chris@0 993 }
Chris@14 994 });
Chris@0 995
Chris@0 996 // If the focus hasn't be changed by the ajax commands, try to refocus the
Chris@0 997 // triggering element or one of its parents if that element does not exist
Chris@0 998 // anymore.
Chris@17 999 if (
Chris@17 1000 !focusChanged &&
Chris@17 1001 this.element &&
Chris@17 1002 !$(this.element).data('disable-refocus')
Chris@17 1003 ) {
Chris@0 1004 let target = false;
Chris@0 1005
Chris@17 1006 for (let n = elementParents.length - 1; !target && n >= 0; n--) {
Chris@17 1007 target = document.querySelector(
Chris@17 1008 `[data-drupal-selector="${elementParents[n].getAttribute(
Chris@17 1009 'data-drupal-selector',
Chris@17 1010 )}"]`,
Chris@17 1011 );
Chris@0 1012 }
Chris@0 1013
Chris@0 1014 if (target) {
Chris@0 1015 $(target).trigger('focus');
Chris@0 1016 }
Chris@0 1017 }
Chris@0 1018
Chris@0 1019 // Reattach behaviors, if they were detached in beforeSerialize(). The
Chris@0 1020 // attachBehaviors() called on the new content from processing the response
Chris@0 1021 // commands is not sufficient, because behaviors from the entire form need
Chris@0 1022 // to be reattached.
Chris@17 1023 if (this.$form && document.body.contains(this.$form.get(0))) {
Chris@0 1024 const settings = this.settings || drupalSettings;
Chris@0 1025 Drupal.attachBehaviors(this.$form.get(0), settings);
Chris@0 1026 }
Chris@0 1027
Chris@0 1028 // Remove any response-specific settings so they don't get used on the next
Chris@0 1029 // call by mistake.
Chris@0 1030 this.settings = null;
Chris@0 1031 };
Chris@0 1032
Chris@0 1033 /**
Chris@0 1034 * Build an effect object to apply an effect when adding new HTML.
Chris@0 1035 *
Chris@0 1036 * @param {object} response
Chris@0 1037 * Drupal Ajax response.
Chris@0 1038 * @param {string} [response.effect]
Chris@14 1039 * Override the default value of {@link Drupal.Ajax#elementSettings}.
Chris@0 1040 * @param {string|number} [response.speed]
Chris@14 1041 * Override the default value of {@link Drupal.Ajax#elementSettings}.
Chris@0 1042 *
Chris@0 1043 * @return {object}
Chris@0 1044 * Returns an object with `showEffect`, `hideEffect` and `showSpeed`
Chris@0 1045 * properties.
Chris@0 1046 */
Chris@17 1047 Drupal.Ajax.prototype.getEffect = function(response) {
Chris@0 1048 const type = response.effect || this.effect;
Chris@0 1049 const speed = response.speed || this.speed;
Chris@0 1050
Chris@0 1051 const effect = {};
Chris@0 1052 if (type === 'none') {
Chris@0 1053 effect.showEffect = 'show';
Chris@0 1054 effect.hideEffect = 'hide';
Chris@0 1055 effect.showSpeed = '';
Chris@17 1056 } else if (type === 'fade') {
Chris@0 1057 effect.showEffect = 'fadeIn';
Chris@0 1058 effect.hideEffect = 'fadeOut';
Chris@0 1059 effect.showSpeed = speed;
Chris@17 1060 } else {
Chris@0 1061 effect.showEffect = `${type}Toggle`;
Chris@0 1062 effect.hideEffect = `${type}Toggle`;
Chris@0 1063 effect.showSpeed = speed;
Chris@0 1064 }
Chris@0 1065
Chris@0 1066 return effect;
Chris@0 1067 };
Chris@0 1068
Chris@0 1069 /**
Chris@0 1070 * Handler for the form redirection error.
Chris@0 1071 *
Chris@0 1072 * @param {object} xmlhttprequest
Chris@0 1073 * Native XMLHttpRequest object.
Chris@0 1074 * @param {string} uri
Chris@0 1075 * Ajax Request URI.
Chris@0 1076 * @param {string} [customMessage]
Chris@0 1077 * Extra message to print with the Ajax error.
Chris@0 1078 */
Chris@17 1079 Drupal.Ajax.prototype.error = function(xmlhttprequest, uri, customMessage) {
Chris@0 1080 // Remove the progress element.
Chris@0 1081 if (this.progress.element) {
Chris@0 1082 $(this.progress.element).remove();
Chris@0 1083 }
Chris@0 1084 if (this.progress.object) {
Chris@0 1085 this.progress.object.stopMonitoring();
Chris@0 1086 }
Chris@0 1087 // Undo hide.
Chris@0 1088 $(this.wrapper).show();
Chris@0 1089 // Re-enable the element.
Chris@0 1090 $(this.element).prop('disabled', false);
Chris@17 1091 // Reattach behaviors, if they were detached in beforeSerialize(), and the
Chris@17 1092 // form is still part of the document.
Chris@17 1093 if (this.$form && document.body.contains(this.$form.get(0))) {
Chris@0 1094 const settings = this.settings || drupalSettings;
Chris@0 1095 Drupal.attachBehaviors(this.$form.get(0), settings);
Chris@0 1096 }
Chris@0 1097 throw new Drupal.AjaxError(xmlhttprequest, uri, customMessage);
Chris@0 1098 };
Chris@0 1099
Chris@0 1100 /**
Chris@17 1101 * Provide a wrapper for new content via Ajax.
Chris@17 1102 *
Chris@17 1103 * Wrap the inserted markup when inserting multiple root elements with an
Chris@17 1104 * ajax effect.
Chris@17 1105 *
Chris@17 1106 * @param {jQuery} $newContent
Chris@17 1107 * Response elements after parsing.
Chris@17 1108 * @param {Drupal.Ajax} ajax
Chris@17 1109 * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
Chris@17 1110 * @param {object} response
Chris@17 1111 * The response from the Ajax request.
Chris@17 1112 *
Chris@17 1113 * @deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0.
Chris@17 1114 * Use data with desired wrapper. See https://www.drupal.org/node/2974880.
Chris@17 1115 *
Chris@17 1116 * @todo Add deprecation warning after it is possible. For more information
Chris@17 1117 * see: https://www.drupal.org/project/drupal/issues/2973400
Chris@17 1118 *
Chris@17 1119 * @see https://www.drupal.org/node/2940704
Chris@17 1120 */
Chris@17 1121 Drupal.theme.ajaxWrapperNewContent = ($newContent, ajax, response) =>
Chris@17 1122 (response.effect || ajax.effect) !== 'none' &&
Chris@17 1123 $newContent.filter(
Chris@17 1124 i =>
Chris@17 1125 !// We can not consider HTML comments or whitespace text as separate
Chris@17 1126 // roots, since they do not cause visual regression with effect.
Chris@17 1127 (
Chris@17 1128 $newContent[i].nodeName === '#comment' ||
Chris@17 1129 ($newContent[i].nodeName === '#text' &&
Chris@17 1130 /^(\s|\n|\r)*$/.test($newContent[i].textContent))
Chris@17 1131 ),
Chris@17 1132 ).length > 1
Chris@17 1133 ? Drupal.theme('ajaxWrapperMultipleRootElements', $newContent)
Chris@17 1134 : $newContent;
Chris@17 1135
Chris@17 1136 /**
Chris@17 1137 * Provide a wrapper for multiple root elements via Ajax.
Chris@17 1138 *
Chris@17 1139 * @param {jQuery} $elements
Chris@17 1140 * Response elements after parsing.
Chris@17 1141 *
Chris@17 1142 * @deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0.
Chris@17 1143 * Use data with desired wrapper. See https://www.drupal.org/node/2974880.
Chris@17 1144 *
Chris@17 1145 * @todo Add deprecation warning after it is possible. For more information
Chris@17 1146 * see: https://www.drupal.org/project/drupal/issues/2973400
Chris@17 1147 *
Chris@17 1148 * @see https://www.drupal.org/node/2940704
Chris@17 1149 */
Chris@17 1150 Drupal.theme.ajaxWrapperMultipleRootElements = $elements =>
Chris@17 1151 $('<div></div>').append($elements);
Chris@17 1152
Chris@17 1153 /**
Chris@0 1154 * @typedef {object} Drupal.AjaxCommands~commandDefinition
Chris@0 1155 *
Chris@0 1156 * @prop {string} command
Chris@0 1157 * @prop {string} [method]
Chris@0 1158 * @prop {string} [selector]
Chris@0 1159 * @prop {string} [data]
Chris@0 1160 * @prop {object} [settings]
Chris@0 1161 * @prop {bool} [asterisk]
Chris@0 1162 * @prop {string} [text]
Chris@0 1163 * @prop {string} [title]
Chris@0 1164 * @prop {string} [url]
Chris@0 1165 * @prop {object} [argument]
Chris@0 1166 * @prop {string} [name]
Chris@0 1167 * @prop {string} [value]
Chris@0 1168 * @prop {string} [old]
Chris@0 1169 * @prop {string} [new]
Chris@0 1170 * @prop {bool} [merge]
Chris@0 1171 * @prop {Array} [args]
Chris@0 1172 *
Chris@0 1173 * @see Drupal.AjaxCommands
Chris@0 1174 */
Chris@0 1175
Chris@0 1176 /**
Chris@0 1177 * Provide a series of commands that the client will perform.
Chris@0 1178 *
Chris@0 1179 * @constructor
Chris@0 1180 */
Chris@17 1181 Drupal.AjaxCommands = function() {};
Chris@0 1182 Drupal.AjaxCommands.prototype = {
Chris@0 1183 /**
Chris@0 1184 * Command to insert new content into the DOM.
Chris@0 1185 *
Chris@0 1186 * @param {Drupal.Ajax} ajax
Chris@0 1187 * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
Chris@0 1188 * @param {object} response
Chris@0 1189 * The response from the Ajax request.
Chris@0 1190 * @param {string} response.data
Chris@0 1191 * The data to use with the jQuery method.
Chris@0 1192 * @param {string} [response.method]
Chris@0 1193 * The jQuery DOM manipulation method to be used.
Chris@0 1194 * @param {string} [response.selector]
Chris@0 1195 * A optional jQuery selector string.
Chris@0 1196 * @param {object} [response.settings]
Chris@0 1197 * An optional array of settings that will be used.
Chris@0 1198 */
Chris@17 1199 insert(ajax, response) {
Chris@0 1200 // Get information from the response. If it is not there, default to
Chris@0 1201 // our presets.
Chris@17 1202 const $wrapper = response.selector
Chris@17 1203 ? $(response.selector)
Chris@17 1204 : $(ajax.wrapper);
Chris@0 1205 const method = response.method || ajax.method;
Chris@0 1206 const effect = ajax.getEffect(response);
Chris@0 1207
Chris@17 1208 // Apply any settings from the returned JSON if available.
Chris@17 1209 const settings = response.settings || ajax.settings || drupalSettings;
Chris@0 1210
Chris@17 1211 // Parse response.data into an element collection.
Chris@17 1212 let $newContent = $($.parseHTML(response.data, document, true));
Chris@17 1213 // For backward compatibility, in some cases a wrapper will be added. This
Chris@17 1214 // behavior will be removed before Drupal 9.0.0. If different behavior is
Chris@17 1215 // needed, the theme functions can be overriden.
Chris@17 1216 // @see https://www.drupal.org/node/2940704
Chris@17 1217 $newContent = Drupal.theme(
Chris@17 1218 'ajaxWrapperNewContent',
Chris@17 1219 $newContent,
Chris@17 1220 ajax,
Chris@17 1221 response,
Chris@17 1222 );
Chris@0 1223
Chris@0 1224 // If removing content from the wrapper, detach behaviors first.
Chris@0 1225 switch (method) {
Chris@0 1226 case 'html':
Chris@0 1227 case 'replaceWith':
Chris@0 1228 case 'replaceAll':
Chris@0 1229 case 'empty':
Chris@0 1230 case 'remove':
Chris@0 1231 Drupal.detachBehaviors($wrapper.get(0), settings);
Chris@17 1232 break;
Chris@17 1233 default:
Chris@17 1234 break;
Chris@0 1235 }
Chris@0 1236
Chris@0 1237 // Add the new content to the page.
Chris@14 1238 $wrapper[method]($newContent);
Chris@0 1239
Chris@0 1240 // Immediately hide the new content if we're using any effects.
Chris@0 1241 if (effect.showEffect !== 'show') {
Chris@14 1242 $newContent.hide();
Chris@0 1243 }
Chris@0 1244
Chris@0 1245 // Determine which effect to use and what content will receive the
Chris@0 1246 // effect, then show the new content.
Chris@17 1247 const $ajaxNewContent = $newContent.find('.ajax-new-content');
Chris@17 1248 if ($ajaxNewContent.length) {
Chris@17 1249 $ajaxNewContent.hide();
Chris@14 1250 $newContent.show();
Chris@17 1251 $ajaxNewContent[effect.showEffect](effect.showSpeed);
Chris@17 1252 } else if (effect.showEffect !== 'show') {
Chris@14 1253 $newContent[effect.showEffect](effect.showSpeed);
Chris@0 1254 }
Chris@0 1255
Chris@0 1256 // Attach all JavaScript behaviors to the new content, if it was
Chris@0 1257 // successfully added to the page, this if statement allows
Chris@0 1258 // `#ajax['wrapper']` to be optional.
Chris@17 1259 if ($newContent.parents('html').length) {
Chris@17 1260 // Attach behaviors to all element nodes.
Chris@17 1261 $newContent.each((index, element) => {
Chris@17 1262 if (element.nodeType === Node.ELEMENT_NODE) {
Chris@17 1263 Drupal.attachBehaviors(element, settings);
Chris@17 1264 }
Chris@17 1265 });
Chris@0 1266 }
Chris@0 1267 },
Chris@0 1268
Chris@0 1269 /**
Chris@0 1270 * Command to remove a chunk from the page.
Chris@0 1271 *
Chris@0 1272 * @param {Drupal.Ajax} [ajax]
Chris@0 1273 * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
Chris@0 1274 * @param {object} response
Chris@0 1275 * The response from the Ajax request.
Chris@0 1276 * @param {string} response.selector
Chris@0 1277 * A jQuery selector string.
Chris@0 1278 * @param {object} [response.settings]
Chris@0 1279 * An optional array of settings that will be used.
Chris@0 1280 * @param {number} [status]
Chris@0 1281 * The XMLHttpRequest status.
Chris@0 1282 */
Chris@0 1283 remove(ajax, response, status) {
Chris@0 1284 const settings = response.settings || ajax.settings || drupalSettings;
Chris@17 1285 $(response.selector)
Chris@17 1286 .each(function() {
Chris@17 1287 Drupal.detachBehaviors(this, settings);
Chris@17 1288 })
Chris@0 1289 .remove();
Chris@0 1290 },
Chris@0 1291
Chris@0 1292 /**
Chris@0 1293 * Command to mark a chunk changed.
Chris@0 1294 *
Chris@0 1295 * @param {Drupal.Ajax} [ajax]
Chris@0 1296 * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
Chris@0 1297 * @param {object} response
Chris@0 1298 * The JSON response object from the Ajax request.
Chris@0 1299 * @param {string} response.selector
Chris@0 1300 * A jQuery selector string.
Chris@0 1301 * @param {bool} [response.asterisk]
Chris@0 1302 * An optional CSS selector. If specified, an asterisk will be
Chris@0 1303 * appended to the HTML inside the provided selector.
Chris@0 1304 * @param {number} [status]
Chris@0 1305 * The request status.
Chris@0 1306 */
Chris@0 1307 changed(ajax, response, status) {
Chris@0 1308 const $element = $(response.selector);
Chris@0 1309 if (!$element.hasClass('ajax-changed')) {
Chris@0 1310 $element.addClass('ajax-changed');
Chris@0 1311 if (response.asterisk) {
Chris@17 1312 $element
Chris@17 1313 .find(response.asterisk)
Chris@17 1314 .append(
Chris@17 1315 ` <abbr class="ajax-changed" title="${Drupal.t(
Chris@17 1316 'Changed',
Chris@17 1317 )}">*</abbr> `,
Chris@17 1318 );
Chris@0 1319 }
Chris@0 1320 }
Chris@0 1321 },
Chris@0 1322
Chris@0 1323 /**
Chris@0 1324 * Command to provide an alert.
Chris@0 1325 *
Chris@0 1326 * @param {Drupal.Ajax} [ajax]
Chris@0 1327 * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
Chris@0 1328 * @param {object} response
Chris@0 1329 * The JSON response from the Ajax request.
Chris@0 1330 * @param {string} response.text
Chris@0 1331 * The text that will be displayed in an alert dialog.
Chris@0 1332 * @param {number} [status]
Chris@0 1333 * The XMLHttpRequest status.
Chris@0 1334 */
Chris@0 1335 alert(ajax, response, status) {
Chris@0 1336 window.alert(response.text, response.title);
Chris@0 1337 },
Chris@0 1338
Chris@0 1339 /**
Chris@18 1340 * Command to provide triggers audio UAs to read the supplied text.
Chris@18 1341 *
Chris@18 1342 * @param {Drupal.Ajax} [ajax]
Chris@18 1343 * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
Chris@18 1344 * @param {object} response
Chris@18 1345 * The JSON response from the Ajax request.
Chris@18 1346 * @param {string} [response.text]
Chris@18 1347 * The text that will be read.
Chris@18 1348 * @param {string} [response.priority]
Chris@18 1349 * An optional priority that will be used for the announcement.
Chris@18 1350 */
Chris@18 1351 announce(ajax, response) {
Chris@18 1352 if (response.priority) {
Chris@18 1353 Drupal.announce(response.text, response.priority);
Chris@18 1354 } else {
Chris@18 1355 Drupal.announce(response.text);
Chris@18 1356 }
Chris@18 1357 },
Chris@18 1358
Chris@18 1359 /**
Chris@0 1360 * Command to set the window.location, redirecting the browser.
Chris@0 1361 *
Chris@0 1362 * @param {Drupal.Ajax} [ajax]
Chris@0 1363 * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
Chris@0 1364 * @param {object} response
Chris@0 1365 * The response from the Ajax request.
Chris@0 1366 * @param {string} response.url
Chris@0 1367 * The URL to redirect to.
Chris@0 1368 * @param {number} [status]
Chris@0 1369 * The XMLHttpRequest status.
Chris@0 1370 */
Chris@0 1371 redirect(ajax, response, status) {
Chris@0 1372 window.location = response.url;
Chris@0 1373 },
Chris@0 1374
Chris@0 1375 /**
Chris@0 1376 * Command to provide the jQuery css() function.
Chris@0 1377 *
Chris@0 1378 * @param {Drupal.Ajax} [ajax]
Chris@0 1379 * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
Chris@0 1380 * @param {object} response
Chris@0 1381 * The response from the Ajax request.
Chris@0 1382 * @param {string} response.selector
Chris@0 1383 * A jQuery selector string.
Chris@0 1384 * @param {object} response.argument
Chris@0 1385 * An array of key/value pairs to set in the CSS for the selector.
Chris@0 1386 * @param {number} [status]
Chris@0 1387 * The XMLHttpRequest status.
Chris@0 1388 */
Chris@0 1389 css(ajax, response, status) {
Chris@0 1390 $(response.selector).css(response.argument);
Chris@0 1391 },
Chris@0 1392
Chris@0 1393 /**
Chris@0 1394 * Command to set the settings used for other commands in this response.
Chris@0 1395 *
Chris@0 1396 * This method will also remove expired `drupalSettings.ajax` settings.
Chris@0 1397 *
Chris@0 1398 * @param {Drupal.Ajax} [ajax]
Chris@0 1399 * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
Chris@0 1400 * @param {object} response
Chris@0 1401 * The response from the Ajax request.
Chris@0 1402 * @param {bool} response.merge
Chris@0 1403 * Determines whether the additional settings should be merged to the
Chris@0 1404 * global settings.
Chris@0 1405 * @param {object} response.settings
Chris@0 1406 * Contains additional settings to add to the global settings.
Chris@0 1407 * @param {number} [status]
Chris@0 1408 * The XMLHttpRequest status.
Chris@0 1409 */
Chris@0 1410 settings(ajax, response, status) {
Chris@0 1411 const ajaxSettings = drupalSettings.ajax;
Chris@0 1412
Chris@0 1413 // Clean up drupalSettings.ajax.
Chris@0 1414 if (ajaxSettings) {
Chris@17 1415 Drupal.ajax.expired().forEach(instance => {
Chris@0 1416 // If the Ajax object has been created through drupalSettings.ajax
Chris@0 1417 // it will have a selector. When there is no selector the object
Chris@0 1418 // has been initialized with a special class name picked up by the
Chris@0 1419 // Ajax behavior.
Chris@0 1420
Chris@0 1421 if (instance.selector) {
Chris@0 1422 const selector = instance.selector.replace('#', '');
Chris@0 1423 if (selector in ajaxSettings) {
Chris@0 1424 delete ajaxSettings[selector];
Chris@0 1425 }
Chris@0 1426 }
Chris@0 1427 });
Chris@0 1428 }
Chris@0 1429
Chris@0 1430 if (response.merge) {
Chris@0 1431 $.extend(true, drupalSettings, response.settings);
Chris@17 1432 } else {
Chris@0 1433 ajax.settings = response.settings;
Chris@0 1434 }
Chris@0 1435 },
Chris@0 1436
Chris@0 1437 /**
Chris@0 1438 * Command to attach data using jQuery's data API.
Chris@0 1439 *
Chris@0 1440 * @param {Drupal.Ajax} [ajax]
Chris@0 1441 * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
Chris@0 1442 * @param {object} response
Chris@0 1443 * The response from the Ajax request.
Chris@0 1444 * @param {string} response.name
Chris@0 1445 * The name or key (in the key value pair) of the data attached to this
Chris@0 1446 * selector.
Chris@0 1447 * @param {string} response.selector
Chris@0 1448 * A jQuery selector string.
Chris@0 1449 * @param {string|object} response.value
Chris@0 1450 * The value of to be attached.
Chris@0 1451 * @param {number} [status]
Chris@0 1452 * The XMLHttpRequest status.
Chris@0 1453 */
Chris@0 1454 data(ajax, response, status) {
Chris@0 1455 $(response.selector).data(response.name, response.value);
Chris@0 1456 },
Chris@0 1457
Chris@0 1458 /**
Chris@0 1459 * Command to apply a jQuery method.
Chris@0 1460 *
Chris@0 1461 * @param {Drupal.Ajax} [ajax]
Chris@0 1462 * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
Chris@0 1463 * @param {object} response
Chris@0 1464 * The response from the Ajax request.
Chris@0 1465 * @param {Array} response.args
Chris@0 1466 * An array of arguments to the jQuery method, if any.
Chris@0 1467 * @param {string} response.method
Chris@0 1468 * The jQuery method to invoke.
Chris@0 1469 * @param {string} response.selector
Chris@0 1470 * A jQuery selector string.
Chris@0 1471 * @param {number} [status]
Chris@0 1472 * The XMLHttpRequest status.
Chris@0 1473 */
Chris@0 1474 invoke(ajax, response, status) {
Chris@0 1475 const $element = $(response.selector);
Chris@0 1476 $element[response.method](...response.args);
Chris@0 1477 },
Chris@0 1478
Chris@0 1479 /**
Chris@0 1480 * Command to restripe a table.
Chris@0 1481 *
Chris@0 1482 * @param {Drupal.Ajax} [ajax]
Chris@0 1483 * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
Chris@0 1484 * @param {object} response
Chris@0 1485 * The response from the Ajax request.
Chris@0 1486 * @param {string} response.selector
Chris@0 1487 * A jQuery selector string.
Chris@0 1488 * @param {number} [status]
Chris@0 1489 * The XMLHttpRequest status.
Chris@0 1490 */
Chris@0 1491 restripe(ajax, response, status) {
Chris@0 1492 // :even and :odd are reversed because jQuery counts from 0 and
Chris@0 1493 // we count from 1, so we're out of sync.
Chris@0 1494 // Match immediate children of the parent element to allow nesting.
Chris@14 1495 $(response.selector)
Chris@14 1496 .find('> tbody > tr:visible, > tr:visible')
Chris@0 1497 .removeClass('odd even')
Chris@14 1498 .filter(':even')
Chris@14 1499 .addClass('odd')
Chris@14 1500 .end()
Chris@14 1501 .filter(':odd')
Chris@14 1502 .addClass('even');
Chris@0 1503 },
Chris@0 1504
Chris@0 1505 /**
Chris@0 1506 * Command to update a form's build ID.
Chris@0 1507 *
Chris@0 1508 * @param {Drupal.Ajax} [ajax]
Chris@0 1509 * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
Chris@0 1510 * @param {object} response
Chris@0 1511 * The response from the Ajax request.
Chris@0 1512 * @param {string} response.old
Chris@0 1513 * The old form build ID.
Chris@0 1514 * @param {string} response.new
Chris@0 1515 * The new form build ID.
Chris@0 1516 * @param {number} [status]
Chris@0 1517 * The XMLHttpRequest status.
Chris@0 1518 */
Chris@0 1519 update_build_id(ajax, response, status) {
Chris@17 1520 $(`input[name="form_build_id"][value="${response.old}"]`).val(
Chris@17 1521 response.new,
Chris@17 1522 );
Chris@0 1523 },
Chris@0 1524
Chris@0 1525 /**
Chris@0 1526 * Command to add css.
Chris@0 1527 *
Chris@0 1528 * Uses the proprietary addImport method if available as browsers which
Chris@0 1529 * support that method ignore @import statements in dynamically added
Chris@0 1530 * stylesheets.
Chris@0 1531 *
Chris@0 1532 * @param {Drupal.Ajax} [ajax]
Chris@0 1533 * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
Chris@0 1534 * @param {object} response
Chris@0 1535 * The response from the Ajax request.
Chris@0 1536 * @param {string} response.data
Chris@0 1537 * A string that contains the styles to be added.
Chris@0 1538 * @param {number} [status]
Chris@0 1539 * The XMLHttpRequest status.
Chris@0 1540 */
Chris@0 1541 add_css(ajax, response, status) {
Chris@0 1542 // Add the styles in the normal way.
Chris@0 1543 $('head').prepend(response.data);
Chris@0 1544 // Add imports in the styles using the addImport method if available.
Chris@0 1545 let match;
Chris@17 1546 const importMatch = /^@import url\("(.*)"\);$/gim;
Chris@17 1547 if (
Chris@17 1548 document.styleSheets[0].addImport &&
Chris@17 1549 importMatch.test(response.data)
Chris@17 1550 ) {
Chris@0 1551 importMatch.lastIndex = 0;
Chris@0 1552 do {
Chris@0 1553 match = importMatch.exec(response.data);
Chris@0 1554 document.styleSheets[0].addImport(match[1]);
Chris@0 1555 } while (match);
Chris@0 1556 }
Chris@0 1557 },
Chris@0 1558 };
Chris@17 1559 })(jQuery, window, Drupal, drupalSettings);