-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Unable to disable inherit for hx-ext with hx-disinherit or htmx.config.disableInheritance #3016
Comments
Yeah inheritance in htmx can be a bit confusing and could do with improvements in documentation. There are three unique kinds of inheritance that I can find inside htmx. There is attribute inheritance which applies to a subset of the common htmx attributes where inheritance would be useful like hx-target, hx-swap, hx-select etc. Attribute inheritance is the feature that is currently turned off with disableInheritance config. There is also a special merge-key-inheritance used for attributes hx-vars, hx-vals, hx-headers and hx-request. These attributes do not work like the normal attributes as they apply a dynamic list of keys/values to all their children elements where it allows you to set multiple keys in multiple parent elements and the values are all merged together with most specific key overriding. This type does not respect the attribute inheritance disable config setting or the hx-inherit/hx-disinherit attributes as this does not fit the merge-keys system well and instead you have to use the not well documented hx-vals="unset" option on a child element to disable inheritance. see #2847 And finally we have hx-ext inheritance which is currently implemented with its own style with "ignore:XXX" as the only way to remove inheritance currently. I don't think we can reuse the attribute inheritance based disableInheritance config as this would be a breaking change that would impact any deployed sites that use this config to disable attribute inheritance but also want to use extensions. Many of the extensions work best by placing the hx-ext on the body element as they make very global changes to how htmx works and moving these to apply only to the one element would be a big change in expected behavior. hx-ext just like how the merge-key-inheritance doesn't work well with the hx-inherit/hx-disinherit pattern. The hx-ext is actually very similar where each hx-ext attribute can set a list of extensions that apply to this element and all children like hx-ext="ext1,ext2,ext3". Which is why I'm guessing the hx-ext="ignore:ext2" option was implemented so that you have fine grained control to remove just the extensions you need to disable but leave the other global extensions. In theory hx-disinherit attribute could be overloaded with new code to enable disinheritance for hx-ext like in your last example but it does add some more complexity to the htmx source to do this when there is already a documented way of doing it. Doing this would have a small performance impact as well as scanning for extension attributes happens many many times for every request and now it has to scan twice as many attributes every time. You could also make it work the same for the merge-key-inheritance attributes case as well to make it more universal. It makes no sense to do hx-inherit here though. Another simpler option could be to implement and document better the "unset" pattern used for merge-key-inheritance as this would be a much simpler code change with no performance impact where you do: <form action="/user.html" method="post" hx-ext="my-form-extension">
<a hx-get="/details.html" hx-ext="unset">Details</a>
</form> Which disables ALL extensions on the element and its children. Also right now you can simplify your initial example right now down to: <form action="/user.html" method="post" hx-ext="my-form-extension">
<a hx-get="/details.html" hx-ext="ignore:my-form-extension">Details</a>
</form> |
Examples of how you could add one if check for 'unset' and return early so you can stop all extension inheritance. needs 3 lines of code: function getExtensions(elt, extensionsToReturn, extensionsToIgnore) {
if (extensionsToReturn == undefined) {
extensionsToReturn = []
}
if (elt == undefined) {
return extensionsToReturn
}
if (extensionsToIgnore == undefined) {
extensionsToIgnore = []
}
const extensionsForElement = getAttributeValue(elt, 'hx-ext')
if (extensionsForElement == 'unset') { // <--------- Just this one if statement added to return early when it finds unset so it won't continue search up the parent tree from here. you can't set any extensions on the same element you use unset on.
return extensionsToReturn
}
if (extensionsForElement) {
forEach(extensionsForElement.split(','), function(extensionName) {
extensionName = extensionName.replace(/ /g, '')
if (extensionName.slice(0, 7) == 'ignore:') {
extensionsToIgnore.push(extensionName.slice(7))
return
}
if (extensionsToIgnore.indexOf(extensionName) < 0) {
const extension = extensions[extensionName]
if (extension && extensionsToReturn.indexOf(extension) < 0) {
extensionsToReturn.push(extension)
}
}
})
}
return getExtensions(asElement(parentElt(elt)), extensionsToReturn, extensionsToIgnore)
} Examples of how to allow hx-ext="my-custom-ext,unset" so you could also enable one or more extensions while disabling all parent inheritance at the same time. this takes 3 bits added and 8 lines of code: function getExtensions(elt, extensionsToReturn, extensionsToIgnore) {
if (extensionsToReturn == undefined) {
extensionsToReturn = []
}
if (elt == undefined) {
return extensionsToReturn
}
if (extensionsToIgnore == undefined) {
extensionsToIgnore = []
}
const extensionsForElement = getAttributeValue(elt, 'hx-ext')
let unset = false // define unset boolean to false initially
if (extensionsForElement) {
forEach(extensionsForElement.split(','), function(extensionName) {
if (extensionName == 'unset) { // set unset variable true if found
unset = true
return
}
extensionName = extensionName.replace(/ /g, '')
if (extensionName.slice(0, 7) == 'ignore:') {
extensionsToIgnore.push(extensionName.slice(7))
return
}
if (extensionsToIgnore.indexOf(extensionName) < 0) {
const extension = extensions[extensionName]
if (extension && extensionsToReturn.indexOf(extension) < 0) {
extensionsToReturn.push(extension)
}
}
})
}
if(unset) { // add a return here if any unset found to stop it scanning parents
return extensionsToReturn
}
return getExtensions(asElement(parentElt(elt)), extensionsToReturn, extensionsToIgnore)
} And finally an option to have a hx-ext="local:my-custom-ext" option to apply an extension to the local element only without applying it to children via inheritance or having to ignore: on all children. requires 3 additions and 9 more lines of code function getExtensions(elt, extensionsToReturn, extensionsToIgnore) {
let parentScan = true // add a variable for if we are scanning parents
if (extensionsToReturn == undefined) {
parentScan = false // set the parent scan false on first call where extensionsToReturn are undefined
extensionsToReturn = []
}
if (elt == undefined) {
return extensionsToReturn
}
if (extensionsToIgnore == undefined) {
extensionsToIgnore = []
}
const extensionsForElement = getAttributeValue(elt, 'hx-ext')
if (extensionsForElement) {
forEach(extensionsForElement.split(','), function(extensionName) {
extensionName = extensionName.replace(/ /g, '')
if (extensionName.slice(0, 7) == 'ignore:') {
extensionsToIgnore.push(extensionName.slice(7))
return
}
if (extensionName.slice(0, 6) == 'local:') { // for local: prefix return if its a recursive parent scan to ignore this extensions inheritance and otherwise strip the local: prefix
if (parentScan) {
return
}
extensionName = extensionName.slice(6)
}
if (extensionsToIgnore.indexOf(extensionName) < 0) {
const extension = extensions[extensionName]
if (extension && extensionsToReturn.indexOf(extension) < 0) {
extensionsToReturn.push(extension)
}
}
})
}
return getExtensions(asElement(parentElt(elt)), extensionsToReturn, extensionsToIgnore)
} |
Thank you for the detailed answer. For me, it would be good if there is a way to disable inheritance globally. BTW, I don't like the idea of using |
@Telroshan mind taking a look? |
TL;DR
Wall of text
I agree with that As Michael pointed out, we have the existing system of If I make up an example, I can imagine someone wanting to enable say
So, to me, |
Yeah the Just Did a quick PR with the local: idea to try and see how the change looks when done properly. Also optimized readability and performance a little to counter the added complexity being added. I can add disableExtensionInheritance config option if there is a real need for this as well. |
Most of our extensions are element specific and should not be inherited by default. I'm not a big fan of having different approaches for the same functionality. For attribute inheritance you have to use |
Given that the Probably a matter of taste to decide between those 2, but I don't think any of them perfectly solves the consistency-with-other-features issue. I personally like the
|
What are the next steps here? |
@xhaggi hopefully 1cg will have some time available to review pending PRs & cut a new release by the end of the year. While Michael's PR is pending review with its |
I wonder why the inheritance of
hx-ext
is not done in the same way as the inheritance of all other attributes in htmx? If you disable the inheritance globally, this has no effect onhx-ext
. Instead, you have to write the following to "ignore" the extension in a subtree, which is a bit annoying.IMO it would be better to do the following:
or
The text was updated successfully, but these errors were encountered: