htmx icon indicating copy to clipboard operation
htmx copied to clipboard

OOB swap stripping containing element in some cases

Open gone opened this issue 3 years ago • 7 comments

Have a repro at https://github.com/gone/oobtest/commit/8e6c2f31bb90f2b21dcc4759eb8f8eff764621cd

the html <li hx-swap-oob="beforeend:#messages"><div>message</message></li> once swapped in only results in <div>message</div> with the li removed

gone avatar Sep 13 '22 18:09 gone

I noticed the same thing. the docs say that you can do something like

<table>
<tbody id="contacts-table">
    ...
  </tbody>
</table>

And in the POST response

<tr hx-swap-oob="beforeend:#contacts-table">
    <td>Joe Smith</td>
    <td>[email protected]</td>
</tr>

But what you end up getting is

<tbody id="contacts-table">
    <td>Jo Smith</td>
    <td>[email protected]</td>
</tbody>

with no<tr> tags at all. So any additional requests just add columns to the table on a single row. The correct (or at least currently functional) way to do it is to add the hx-swap-oob on the outer element. Like this:

<tbody hx-swap-oob="beforeend:#contacts-table">
    <tr>
        <td>Joe Smith</td>
        <td>[email protected]</td>
    </tr>
</tbody>

loveaphair avatar Sep 21 '22 21:09 loveaphair

This seems to happen whenever the additional content is wrapped in a <tr> or <td> (maybe others too but these two definitely don't work, whereas additional content in a <div> or <p> do work).

For example:

<div id="outofbounds">
    Some out of bounds content.
</div>

<table>
    <tr>
        <td>
            <div>
                A table row to swap out.
            </div>
        </td>
        <td>
            <button hx-get="url" hx-target="closest tr">Swap Row</button>   
        </td>
    </tr>
</table>

and responding to the htmx get with:

<tr>
    <td>
        <div>
            Table row was swapped out.
        </div>
    </td>
    <td>
        <button hx-get="url" hx-target="closest tr">Swap Row</button>   
    </td>
</tr>
<div id="outofbounds" hx-swap-oob="true">Out of bounds content swapped</div>

does not work as the tags are getting stripped. The outofbounds swap on its own in the response works and the table row swap on its own also works, but not together.

sean-reed avatar Mar 26 '23 11:03 sean-reed

Have you experimented with the htmx.config.useTemplateFragments configuration setting?

HTMX works by taking the text http response, turning it into a brand new document, then inspecting it to pull nodes out of the new DOM to place them into the actual DOM of the page. But this strategy doesn't work very well for tables, since tables have a bunch of special rules around when table-related node types are allowed and valid. The useTemplateFragments changes the strategy to wrap the http response in <template></template> tags, which has looser rules about table-related nodes.

I believe some people having trouble with table nodes found this setting to be helpful.

See:

  • function parseHTML(): https://github.com/bigskysoftware/htmx/blob/master/src/htmx.js#L250
  • function makeFragment(): https://github.com/bigskysoftware/htmx/blob/master/src/htmx.js#L273

infogulch avatar Mar 26 '23 16:03 infogulch

Hi @infogulch

I've just tried this and it didn't fix the problem unfortunately. Inserting a row in a table is working fine for me, it's just when out of bounds content is included in the response that the problem occurs. I'd guess that the htmx code that is pulling out the out of bounds part of the response is mangling the other content wrapped in <tr> tags.

sean-reed avatar Mar 27 '23 11:03 sean-reed

I've just tried this and it didn't fix the problem unfortunately. Inserting a row in a table is working fine for me, it's just when out of bounds content is included in the response that the problem occurs. I'd guess that the htmx code that is pulling out the out of bounds part of the response is mangling the other content wrapped in <tr> tags.

I just encountered the same problem. I have a server response containing the regular content (in my case a tbody) plus an out-of-bounds element (a section). As a workaround I solved it by wrapping an additional <table> around the <tbody> and added hx-select="tbody" to the triggering element.

RoToRa avatar Apr 14 '23 13:04 RoToRa

Ok, I got around to do some debugging. It seems that even when using template fragments the DOMParser can't handle some combinations of elements that normally can't be siblings, which can happen when using oob swaps. I only tested with Firefox, but I wouldn't be surprised if it is different from browser to browser.

hx-select (and possibly hx-select-oob - I haven't checked that out properly yet) can be used as workaround in some cases, but in cases where HX-Retarget is used this would require implementation of HX-Reselect (and maybe HX-Reselect-oob).

Another thing that could be a solution, could be to supported hx-oob-swap on non-top-level elements.

However in my opinion, a better solution could be to implement a separator between the elements, for example in form of an HTML comment such as <!--oob-->, which would allow splitting the fragments before passing them separately to the DOMParser.

RoToRa avatar Apr 16 '23 15:04 RoToRa

This may be resolved with the release of 2.0 which includes a new config option: set the htmx.config.allowNestedOobSwaps config option to false, and template fragments with the hx-swap-oob attribute will no longer be swallowed.

See also the docs: https://htmx.org/attributes/hx-swap-oob/#nested-oob-swaps

The issue and PR that raises and implements the feature: #1133 #1235

infogulch avatar Jun 19 '24 19:06 infogulch