Skip to content
Snippets Groups Projects

Resolve #3526267 "ajax commands with htmx"

Files
10
@@ -7,7 +7,7 @@
* page.
*/
(function (Drupal, drupalSettings, loadjs, htmx) {
(function (Drupal, drupalSettings, htmx) {
// Disable htmx loading of script tags since we're handling it.
htmx.config.allowScriptTags = false;
@@ -21,47 +21,6 @@
*/
const requestAssetsLoaded = new WeakMap();
/**
* Helper function to merge two objects recursively.
*
* @param current
* The object to receive the merged values.
* @param sources
* The objects to merge into current.
*
* @return object
* The merged object.
*
* @see https://youmightnotneedjquery.com/#deep_extend
*/
function mergeSettings(current, ...sources) {
if (!current) {
return {};
}
sources
.filter((obj) => Boolean(obj))
.forEach((obj) => {
Object.entries(obj).forEach(([key, value]) => {
switch (Object.prototype.toString.call(value)) {
case '[object Object]':
current[key] = current[key] || {};
current[key] = mergeSettings(current[key], value);
break;
case '[object Array]':
current[key] = mergeSettings(new Array(value.length), value);
break;
default:
current[key] = value;
}
});
});
return current;
}
/**
* Send the current ajax page state with each request.
*
@@ -94,6 +53,10 @@
// Custom event to detach behaviors.
htmx.trigger(detail.elt, 'htmx:drupal:unload');
if (!detail.xhr) {
return;
}
// We need to parse the response to find all the assets to load.
// htmx cleans up too many things to be able to rely on their dom fragment.
let responseHTML = Document.parseHTMLUnsafe(detail.serverResponse);
@@ -104,71 +67,38 @@
':is(head, body) > script[type="application/json"][data-drupal-selector="drupal-settings-json"]',
);
if (settingsElement !== null) {
mergeSettings(drupalSettings, JSON.parse(settingsElement.textContent));
Drupal.htmx.mergeSettings(
drupalSettings,
JSON.parse(settingsElement.textContent),
);
}
// Load all assets files. We sent ajax_page_state in the request so this is only the diff with the current page.
const assetsTags = responseHTML.querySelectorAll(
'link[rel="stylesheet"][href], script[src]',
);
const bundleIds = Array.from(assetsTags)
.filter(({ href, src }) => !loadjs.isDefined(href ?? src))
.map(({ href, src, type, attributes }) => {
const bundleId = href ?? src;
let prefix = 'css!';
if (src) {
prefix = type === 'module' ? 'module!' : 'js!';
}
loadjs(prefix + bundleId, bundleId, {
// JS files are loaded in order, so this needs to be false when 'src'
// is defined.
async: !src,
// Copy asset tag attributes to the new element.
before(path, element) {
// This allows all attributes to be added, like defer, async and
// crossorigin.
Object.values(attributes).forEach((attr) => {
element.setAttribute(attr.name, attr.value);
});
},
});
return bundleId;
// Transform the data from the DOM into an ajax command like format.
const data = Array.from(assetsTags).map(({ attributes }) => {
const attrs = {};
Object.values(attributes).forEach(({ name, value }) => {
attrs[name] = value;
});
return attrs;
});
// Helps with memory management.
responseHTML = null;
// Nothing to load, we resolve the promise right away.
let assetsLoaded = Promise.resolve();
// If there are assets to load, use loadjs to manage this process.
if (bundleIds.length) {
// Trigger the event once all the dependencies have loaded.
assetsLoaded = new Promise((resolve, reject) => {
loadjs.ready(bundleIds, {
success: resolve,
error(depsNotFound) {
const message = Drupal.t(
`The following files could not be loaded: @dependencies`,
{ '@dependencies': depsNotFound.join(', ') },
);
reject(message);
},
});
});
}
requestAssetsLoaded.set(detail.xhr, assetsLoaded);
requestAssetsLoaded.set(detail.xhr, Drupal.htmx.addAssets(data));
});
// Trigger the Drupal processing once all assets have been loaded.
// @see https://htmx.org/events/#htmx:afterSettle
htmx.on('htmx:afterSettle', ({ detail }) => {
requestAssetsLoaded.get(detail.xhr).then(() => {
(requestAssetsLoaded.get(detail.xhr) || Promise.resolve()).then(() => {
htmx.trigger(detail.elt, 'htmx:drupal:load');
// This should be automatic but don't wait for the garbage collector.
requestAssetsLoaded.delete(detail.xhr);
});
});
})(Drupal, drupalSettings, loadjs, htmx);
})(Drupal, drupalSettings, htmx);
Loading