-
Notifications
You must be signed in to change notification settings - Fork 56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
response-targets: add support for hx-swap-... overrides #87
Comments
Good idea imo! |
I'm not familiar with the internals of HTMX yet, so it would take time for me to learn the necessary things (e.g. what to override in the event details) and implement (and test) this feature (I guess the pattern would be the same as for the target override). I would be happy to contribute this feature, but I can not promise for sure as my time is quite limited for the coming months. I'll leave a comment here if I can start work on this. |
Made some small change 😄 tested on my project. var swapPrefix = "hx-swap-";
// It's the same as getRespCodeTarget but the return value is the swap style string
function getSwapStyle(elt, respCodeNumber, prefix) {
if (!elt || !respCodeNumber) return null;
var respCode = respCodeNumber.toString();
var attrPossibilities = [
respCode,
respCode.substr(0, 2) + "*",
respCode.substr(0, 2) + "x",
respCode.substr(0, 1) + "*",
respCode.substr(0, 1) + "x",
respCode.substr(0, 1) + "**",
respCode.substr(0, 1) + "xx",
"*",
"x",
"***",
"xxx",
];
if (startsWith(respCode, "4") || startsWith(respCode, "5")) {
attrPossibilities.push("error");
}
for (var i = 0; i < attrPossibilities.length; i++) {
var attr = prefix + attrPossibilities[i];
var attrValue = api.getClosestAttributeValue(elt, attr);
if (attrValue) {
return attrValue;
}
// Uncomment this for more specific swap styles
// if (
// attrValue == "innerHTML" ||
// attrValue == "outerHTML" ||
// attrValue == "beforebegin" ||
// attrValue == "afterbegin" ||
// attrValue == "beforeend" ||
// attrValue == "afterend" ||
// attrValue == "delete" ||
// attrValue == "none" ||
// attrValue == string
// ) {
// return attrValue;
// }
}
return null;
}
// Get the swap style inside the onEvent callback
var swapMethod = getSwapStyle(
evt.detail.requestConfig.elt,
evt.detail.xhr.status,
swapPrefix
);
// Add the found swap style to the event detail
if (swapMethod) {
evt.detail.swapStyle = swapMethod;
}
// If the swap style is not found, the default swap style from config will be used Full Source Code(function () {
/** @type {import("../htmx").HtmxInternalApi} */
var api;
var attrPrefix = "hx-target-";
var swapPrefix = "hx-swap-";
// IE11 doesn't support string.startsWith
function startsWith(str, prefix) {
return str.substring(0, prefix.length) === prefix;
}
/**
* @param {HTMLElement} elt
* @param {number} respCode
* @returns {HTMLElement | null}
*/
function getRespCodeTarget(elt, respCodeNumber) {
if (!elt || !respCodeNumber) return null;
var respCode = respCodeNumber.toString();
// '*' is the original syntax, as the obvious character for a wildcard.
// The 'x' alternative was added for maximum compatibility with HTML
// templating engines, due to ambiguity around which characters are
// supported in HTML attributes.
//
// Start with the most specific possible attribute and generalize from
// there.
var attrPossibilities = [
respCode,
respCode.substr(0, 2) + "*",
respCode.substr(0, 2) + "x",
respCode.substr(0, 1) + "*",
respCode.substr(0, 1) + "x",
respCode.substr(0, 1) + "**",
respCode.substr(0, 1) + "xx",
"*",
"x",
"***",
"xxx",
];
if (startsWith(respCode, "4") || startsWith(respCode, "5")) {
attrPossibilities.push("error");
}
for (var i = 0; i < attrPossibilities.length; i++) {
var attr = attrPrefix + attrPossibilities[i];
var attrValue = api.getClosestAttributeValue(elt, attr);
if (attrValue) {
if (attrValue === "this") {
return api.findThisElement(elt, attr);
} else {
return api.querySelectorExt(elt, attrValue);
}
}
}
return null;
}
/**
* @param {HTMLElement} elt
* @param {number} respCode
* @returns {HTMLElement | null}
*/
function getSwapStyle(elt, respCodeNumber, prefix) {
if (!elt || !respCodeNumber) return null;
var respCode = respCodeNumber.toString();
var attrPossibilities = [
respCode,
respCode.substr(0, 2) + "*",
respCode.substr(0, 2) + "x",
respCode.substr(0, 1) + "*",
respCode.substr(0, 1) + "x",
respCode.substr(0, 1) + "**",
respCode.substr(0, 1) + "xx",
"*",
"x",
"***",
"xxx",
];
if (startsWith(respCode, "4") || startsWith(respCode, "5")) {
attrPossibilities.push("error");
}
for (var i = 0; i < attrPossibilities.length; i++) {
var attr = prefix + attrPossibilities[i];
var attrValue = api.getClosestAttributeValue(elt, attr);
if (attrValue) {
return attrValue;
}
// Uncomment this for more specific swap styles
// if (
// attrValue == "innerHTML" ||
// attrValue == "outerHTML" ||
// attrValue == "beforebegin" ||
// attrValue == "afterbegin" ||
// attrValue == "beforeend" ||
// attrValue == "afterend" ||
// attrValue == "delete" ||
// attrValue == "none" ||
// attrValue == string
// ) {
// return attrValue;
// }
}
return null;
}
/** @param {Event} evt */
function handleErrorFlag(evt) {
if (evt.detail.isError) {
if (htmx.config.responseTargetUnsetsError) {
evt.detail.isError = false;
}
} else if (htmx.config.responseTargetSetsError) {
evt.detail.isError = true;
}
}
htmx.defineExtension("response-targets", {
/** @param {import("../htmx").HtmxInternalApi} apiRef */
init: function (apiRef) {
api = apiRef;
if (htmx.config.responseTargetUnsetsError === undefined) {
htmx.config.responseTargetUnsetsError = true;
}
if (htmx.config.responseTargetSetsError === undefined) {
htmx.config.responseTargetSetsError = false;
}
if (htmx.config.responseTargetPrefersExisting === undefined) {
htmx.config.responseTargetPrefersExisting = false;
}
if (htmx.config.responseTargetPrefersRetargetHeader === undefined) {
htmx.config.responseTargetPrefersRetargetHeader = true;
}
},
/**
* @param {string} name
* @param {Event} evt
*/
onEvent: function (name, evt) {
if (
name === "htmx:beforeSwap" &&
evt.detail.xhr &&
evt.detail.xhr.status !== 200
) {
if (evt.detail.target) {
if (htmx.config.responseTargetPrefersExisting) {
evt.detail.shouldSwap = true;
handleErrorFlag(evt);
return true;
}
if (
htmx.config.responseTargetPrefersRetargetHeader &&
evt.detail.xhr.getAllResponseHeaders().match(/HX-Retarget:/i)
) {
evt.detail.shouldSwap = true;
handleErrorFlag(evt);
return true;
}
}
if (!evt.detail.requestConfig) {
return true;
}
var target = getRespCodeTarget(
evt.detail.requestConfig.elt,
evt.detail.xhr.status
);
if (target) {
handleErrorFlag(evt);
evt.detail.shouldSwap = true;
evt.detail.target = target;
}
var swapMethod = getSwapStyle(
evt.detail.requestConfig.elt,
evt.detail.xhr.status,
swapPrefix
);
if (swapMethod) {
evt.detail.swapStyle = swapMethod;
}
return true;
}
},
});
})(); |
I might have a go at this in the near future, because one of my clients might want to have this. Currently I'm hoping there is a clean and universal way to add the response-target functionality to all relevant attributes, at least I'd like hx-select on top of what has been discussed here. But I haven't thought about this in detail. |
Hi guys,
First of all, thanks for the great work on HTMX and its extensions. I just started a new project with it, and I must say I'm surprised how convenient it is to work with.
I ran into one issue though with the
response-targets
extension: it would be really handy if it was possible to override thehx-swap
attribute similarly tohx-target
, because the two are tightly related.In my specific case, I'd need to replace a
delete
swap rule withinnerHTML
. I know I could tweak my components and API to be able to use the same swap rule, but that would really degrade the code (especially the API). My current workaround is to include a custom header in the HX request that triggers a custom "reswap" logic (HX-Reswap
response header) on the backend, effectively implementinghx-swap-<error-code>
manually on the backend, but solving this problem with anhx-swap-<error-code>
attribute in the HTML would be way cleaner and more elegant in my opinion.The text was updated successfully, but these errors were encountered: