ajax.es6.js 47 KB

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