-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Add hx-swap-oob modifier support #3352
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
base: dev
Are you sure you want to change the base?
Add hx-swap-oob modifier support #3352
Conversation
7d955e5
to
152ebf4
Compare
I can't spot a test for this. |
There are no tests for idiomorph in htmx for me to add tests for this. all the tests are inside the idiomorph repo which I can't test till the htmx change ships and is made available there. Edit: the test for this I had already added in idiomorph a while ago in here https://github.com/bigskysoftware/idiomorph/blob/main/test/htmx-integration.js but it is disabled as it does not work right now. |
I more just meant a more general test that proves the parsing works, rather than an end-to-end test that's specific to idiomorph, even if that's the scenario driving the change. |
Most of the other modifiers for |
I feel like there are already maybe too many config items in htmx and so I would not recommend adding more. right now it defaults to strip on for oob inner swaps and off for all normal swaps and oob outerHTML swaps which matches existing htmx behaviour. To me turning strip on by default would over complicate the feature as it is something you only need for very specific situations and the user should be aware of when they are using it to override the default documented behaviour. and how would the default even work here as in theory each swap situation could have a different strip default like a user will want all their oob afterend ones not strip but may expect to default their oob innerHTML to still strip. And defaulting all normal swaps for all swap styles would cause a lot of confusion and issues and bugs for the user. If we did it we would have to have a oobSwapStyleNoStrip that defaults to ['outerHTML'] and a SwapStyleStrip that defaults to [] Edit: looking at the code it seems complex to handle this as we already have a isInlineSwap function that does the defaulting for oob swaps already and this is used by extensions as well making it messy to implement. And another note is that most of the defaults above you list i explicitly had to exclude them from applying to oob swaps as it would cause a major change in behaviour and do things the users wouldn't expect. The only one i left in place for oob was focus-scroll as this kind of made sense. |
… : in ext swap style
Added some tests for the new : handling in custom extension swap styles. Note that what doesn't work if you do hx-swap-oob="morph:innerHTML" as this has to be treated as morph with target selector innerHTML. But it is designed to work with "morph:innerHTML:#foo" which is the format i'm testing and also "morph:innerHTML target:#foo strip:false" etc |
I am unsure about this logic and if it impacts existing code. |
…it can't parse the new swap specification
Yeah it is a pain with However you are no longer able to do things like The only other issue is that if you mis spell or mis type one of the modifiers and create an invalid swap specification like |
Managed to add simple error logging that detects when the swap style is invalid as in situations where modifers fail the swap style will contain whitespace which we can check for. Found it falls back to a innerHTML swap if the invalid swap spec has no : but if it contains a : it fails with a invalid css selector error. |
@scrhartley how do you feel about this change? Comfortable that it is backwards compatible? |
I want to be convinced, but it's a significant change and so I'll try to spend time at the weekend to really understand its implications and that the parsing doesn't cause any surprises. |
Some initial findings for Bug
<div class="my-div"></div>
<button hx-get="/foo" hx-swap="none" hx-select-oob=".my-div">Button</button> Regression <div id="my-div2"></div>
<button hx-get="/foo" hx-swap="none" hx-select-oob="#my-div2:"
hx-on::oob-after-swap="console.log(event.detail.swapSpec.swapStyle)">
Button
</button> Docs (or perhaps implementation) |
Thanks @scrhartley! Yeah the bug existed in the existing implementation as well as it always forced you to use # in frount and threw selector errors on any non id value before. But I was trying to improve it to make it more generic as the documentation does not point out this existing implementation detail. But my design had issues here so i've pushed a change to address the two things you found and added a note about the limitation of hx-select-oob being only the first item. I could revert my hx-select-oob improvements so it just only supports id based selectors only like before maybe. But it would be nice to have proper CSS selector support if we can as the old limitation was never documented. Adding support for querySelectorAll may be possible but it could cause a minor change in behavior if the user had two of the same id in the response and it now swaps both but this edge case seems unlikely. But I don't know if we really need the added complexity of this feature. I had to use a simple regex to identify valid ID's that are safe to add # to like the old implementation. This does mean that someone could have had an invalidly formated ID with weird characters and found that they had to escape the ID and also found that they could remove the # from this complex id selector and it would still work and this would not catch this edge case and be a regression. But This seems too unlikely to cause issues as all users should have a # in such cases and won't have any issues. |
I don't understand this test. Since it's |
yeah that is a confusing one sorry. When I was testing broken and bad selector handling if you miss type something like hx-swap-oob="outerHTML swapp:10s" it will parse it as "outerHTML swapp" and selector "10s" because it does not detect valid modifiers. This will throw a invalid selector error to the user because 10s is not valid CSS. I also added user error logging for this case when the selector contains a space because if they miss key a modifer with no : or the value after the : happens to be a valid selector like "true" then we need to let them know its broken so i log an error. But I found it can sometimes still follow though and do a swap in this situation and because it has a manual swap specifier it does not use the true/outerHTML fallback and instead falls back to innerHTML. The only reason I added this test was because without it the error logging was never being fired properly so i needed a test just for code coverage but it seems the test above covers this anyway so probable easier just to remove this edge case test as it is not something we something we are trying to enforce |
@scrhartley I've re-looked at the querySelectorAll missing feature for hx-select-oob. I've just added support for this as it was not as hard as I thought to add. it could impact users with duplicate id's and now swap both but they would just overwrite the same one anyway so I think this is not really a problem we need to worry about. There are some good use cases for hx-select-oob=".myclass" so that if you had multiple elements in your response and you don't know their unique id's but just some class or other property you could hx-select-oob them all in one go which is cool. and using hx-select-oob="[id]" would find every single id element in the response and swap these all in which is cool. One issue with this is that if you combine it with a normal or hx-select swap it is possible to cause problems if you hx-select-oob all id'ed elements and your main or select swap contains these same id's as they will be removed from the response data during oobSwap() and then when the main swap hits it removes them from the DOM at the end. But this is already an existing issue you could cause anyway so no real regression here. It would be nice to take out the remove element call at the end of oobSwap() and move it to the findAndSwapOobElements() function instead as then hx-select-oob would never destroy the response data like it does now. But that may be a feature for another day if it was a real issue maybe. Edit also note the multi selector feature does have a limitation that if the elements you are selecting don't have unique id's then it is going to be hard to target them anywhere. You could probably do fancy things with ".myclass:beforeend target:#myclassdiv strip:false" to place them all one by one at the end of a div but its getty pretty complex! |
Regression <div id="my.div3"></div>
<button hx-get="/foo" hx-swap="none" hx-select-oob="my\.div3">
Button 1
</button> (I assume you need something like |
Testing |
The target modifier is designed so that once it finds it all remaining words that don't match exactly with another modifier are included in the target selector. this means you can do any target modifier including spaces after "target:" and still have "swap:10s" or whatever valid modifier after it. So this works for example: <div hx-swap-oob='outerHTML target:#table tbody swap:10s'/>
Yeah id's can contain |
Also just fixed up swapping class handling so oob swaps add and remove swapping class in case a user wants to add a settleDelay for an animation trigger. Just moving the add swapping class point is an easy fix here. and this same fix was actually already done in #2845 that was merged but then I reverted this when I redid swap feature. |
One thing I haven't tested yet is how stripping works with template elements for troublesome tables. Not sure if hx-swap-oob vs. hx-select-oob would make a difference. But I'm too tired for that, so I'm off for bed. |
Shower thought 1: Shower thought 2: |
thanks scrhartley! just tested quickly with the <!-- starting table content -->
<tbody id="tbody">
<tr id="r1">
<td>foo</td>
</tr>
<tr>
<td id="td1">Bar</td>
</tr>
</tbody>
<template> <!-- with this template -->
<tr hx-swap-oob="outerHTML strip:true" id="r1"><td>bar</td></tr>
</template>
<!--This replaces tr[r1] with the inner td as i would expect fine. its not valid table but you get what you asked for. the strip:false version worked like normal when i tested-->
<tbody id="tbody">
<td class="">bar</td>
<tr>
<td id="td1">hey</td>
</tr>
</tbody>
<template> <!-- with this template -->
<tr hx-swap-oob="innerHTML strip:false" id="r1"><td>bar</td></tr>
</template>
<!--This places tr[r1] inside the old tr[r1] as i would expect fine. the strip:true version worked like normal-->
<tbody id="tbody">
<tr id="r1" class=""><tr id="r1"><td>bar</td></tr></tr>
<tr>
<td id="td1">hey</td>
</tr>
</tbody> As for hx-swap-oob vs hx-select-oob the strip function seems to work the same from the tests I've added and tried.
I want to keep the changes we need to make to a minimum to reduce the minified size of the library as much as possible. getSwapApecifications is really already a pretty internal function and there is no clear documentation on the arguments the extensions should use for functions like this and I think adding additional optional parameters is fine and I have seen several examples in other internalApi functions where optional parameters have been added before. It just has a new defaults param that extension users could now use to pass in their own defaults to set settle/swap delays they need and the extra function to skip boosted show defaults and not report errors by default I think are fine. should be zero behaviour change for existing extension users otherwise
I think we have a good running list of the changes documented in this 30 comment thread now and Carson can ask for us to revert any individual changes he is worried about any of them. I've updated the PR description with the main changes we have made. |
Description
This Change re-implements hx-swap-oob swapping to use the full htmx swap function and all of its various modifiers and adds two additional modifiers for additional oob functionality. The recent refactors of the swap function to support history, delay and transitions have now enabled us to reuse swap() much easier than before!
Core to this change is the full backwards compatibility with existing hx-swap-oob use cases where the attribute value can be a
<swapStyle>:<selector>
format. To allow proper support for all the swap modifers it now checks if the value is a valid full swap specifier with more than just a single thing in it and if so it uses the new full complex swap specification. Otherwise it falls back to the old style handling.target:
modifierTo support a custom target selector I've added a
target:
modifier that only works with oob swaps and allows parsing of multiple words aftertarget:
to be used as a complex extended selector.strip:
modifierThe oob tags are set to be stripped off by default in all but outerHTML swap styles to make oob behaviour simple for inner style swaps but it is helpful sometimes to override this default to allow various other kinds of swap behaviors like doing an outerHTML style swap of an oob tags inner contents to place multiple items easily over top of a single target element. So I've added a
strip:true
andstrip:false
options that allows you to easily override it with this modifier.template handling
This change also includes support for oob tags to be template tags themselves instead of just wrapping template tags around them which simplifies tag choice when dealing with tables and other element types. This just required using fragment.content if the oob element happened to be a template element
hx-select/hx-select-oob
hx-select tested working with strip:true and hx-select-oob updated to be fully backwards compatible with its old undocumented id only format and now supports css selectors properly as this was documented but not working properly and it now supports full hx-swap-oob syntax as well. hx-select-oob now supports querying multiple response elements now that it supports more than just an id only selector so you can do "[id]" for example to swap in all id'ed
idiomroph advanced oob swap support
This change now allows hx-oob-swap to support
:
in the swap style which allows morph:innerHTML swap styles that were not usable before. Note that to support this users will have to add modifers like target: to make it render with the new method as it now has code to detect if there are valid swap modifiers and if there are use the new format which supports : in the swap style. But if it detects invalid modifiers then it falls back to just splitting on the first:
for backwards compatibility. It also now detects and warns users when it detects swap styles that contain spaces which happens when an invalid value is provided in error and can lead to default swap style fallback.Corresponding issue:
#2308
#3316
Testing
Performed some manual testing of various hx-swap-oob actions and added many tests for the various core functions.
Checklist
master
for website changes,dev
forsource changes)
approved via an issue
npm run test
) and verified that it succeeded