Skip to content
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

Incorrect OOB swapping of second table in a two tables page #3203

Open
langdiana opened this issue Feb 21, 2025 · 9 comments
Open

Incorrect OOB swapping of second table in a two tables page #3203

langdiana opened this issue Feb 21, 2025 · 9 comments

Comments

@langdiana
Copy link

langdiana commented Feb 21, 2025

I have two tables, I swap regularly the tbody of the first one and OOB swap the whole second table.
After swap the inner html of the second table (with the new content) is included in the first table and the second table is not touched. Any additional swap will add the inner html of the second table again into the first table.
OOB swapping works normal if I use something else instead of second table.

Additional info (and I think here is the problem): the trigger for swap is inside the first table, more exactly in an input field in one header. I think your function makeFragment is not working correctly in this case.

Before swap:

<table id="myTable">
  <thead> ...</thead>
  <tbody> ...</tbody>
</table>

<table id="myOobTable">
  <thead> ...</thead>
  <tbody> ...</tbody>
</table>

After first swap:

<table id="myTable">
  <thead> ...</thead>
  <tbody> ...</tbody> // new content
  <thead> ...</thead> // thead from myOobTable, new content
  <tbody> ...</tbody> //tbody from myOobTable, new content
</table>

<table id="myOobTable"> // same as before swap
  <thead> ...</thead>
  <tbody> ...</tbody>
</table>

After second swap:

<table id="myTable">
  <thead> ...</thead>
  <tbody> ...</tbody> // new content
  <thead> ...</thead> // thead from myOobTable, new content
  <tbody> ...</tbody> //tbody from myOobTable, new content
  <thead> ...</thead> // thead from myOobTable, content from first swap 
  <tbody> ...</tbody> //tbody from myOobTable, content from first swap
</table>

<table id="myOobTable"> // same as before swap
  <thead> ...</thead>
  <tbody> ...</tbody>
</table>
@Telroshan
Copy link
Collaborator

Hey, table elements such as tbody, tr or td often cause issues when parsed on their own at the top level of a fragment.

See the "troublesome tables" section of the docs about that.

Could you try encapsulating, in your server response's HTML, your second table (the oob swapped one), in a template tag as shown in the docs and tell us how it goes?

Table elements can be problematic when combined with out of band swaps, because, by the HTML spec, many can’t stand on their own in the DOM (e.g. or ).

To avoid this issue you can use a template tag to encapsulate these elements:

<template>
 <tr id="message" hx-swap-oob="true"><td>Joe</td><td>Smith</td></tr>
</template>

Hope this helps!

@langdiana
Copy link
Author

Hi,
I know about that doc section but this is different. In my case, the table itself is oob swapped, not just some table elements, and a table can stand on its own in DOM:

<table id="myOobTable" hx-swap-oob="true">

As a matter of fact, I did try to wrap the entire table in a template but it's not working: the table is not rendered at all.

When I looked inside htmx code, the fragment processed by makeFragment function is something like this:

  <tbody> ...</tbody>
...
</table>

<table id="myOobTable">
  <thead> ...</thead>
  <tbody> ...</tbody>
</table>

It starts from tbody of the first table and continues past the first table's end tag, including the second table. So the DOM is messed up. Somehow the hx-swap-oob attrib is not found, I guess it's stripped along with the table tag of the second table, and the remaining of the second table from response is included as regular swap in the first table while the second table itself in the target page is not touched.

@Telroshan
Copy link
Collaborator

Hey, just tried locally and unfortunately I can't seem to be able to reproduce that issue.

I swap regularly the tbody of the first one and OOB swap the whole second table

Assuming here you mean targeting the tbody and swapping it as outerHTML and the second table as a whole with an outerHTML OOB swap.

Here's what I tried, let me know if I misunderstood something in your setup

Initial HTML:

<table id="myTable">
    <thead><tr><th>Test</th></tr></thead>
    <tbody><tr><td>Initial</td></tr></tbody>
</table>

<table id="myOobTable">
    <thead><tr><th>Test OOB</th></tr></thead>
    <tbody><tr><td>Initial OOB</td></tr></tbody>
</table>

<button type="button" hx-get="/test" hx-target="#myTable tbody" hx-swap="outerHTML">Swap tables</button>

Server response:

<tbody><tr><td>Swapped</td></tr></tbody>

<template>
    <table id="myOobTable" hx-swap-oob="true">
        <thead><tr><th>Test OOB</th></tr></thead>
        <tbody><tr><td>Swapped OOB</td></tr></tbody>
    </table>
</template>

I get the expected result, from Image to Image

Where the DOM after swap looks correct

Image

If your setup is indeed similar to this one, would you mind sharing the htmx version you're using? (and try with the latest, i.e. 2.0.4 as I'm writing this, if you are on an older one, to see if by any chance it's an issue that had been fixed along past releases)

If not, could you please share your server response and triggering element's attributes so I can replicate it and investigate on my side?

Hope this helps!

@langdiana
Copy link
Author

There is a critical difference, the trigger is inside the header of first table:

<table id="myTable">
   <thead>
       <tr>
          <th>
              <input type="text" hx-get="/test" hx-trigger="input changed delay:500ms" hx-target="#tablebody" hx-swap="outerHTML"></input>
          </th>
       </tr>
   </thead> 
   <tbody id="tablebody"><tr><td>Initial</td></tr></tbody>
</table>

<table id="myOobTable">
    <thead><tr><th>Test OOB</th></tr></thead>
    <tbody><tr><td>Initial OOB</td></tr></tbody>
</table>

@Telroshan
Copy link
Collaborator

Thanks for the details.

Even with the following setup, I get the same result as in my previous message:
Initial HTML:

<table id="myTable">
    <thead>
    <tr>
        <th>
            <input type="text" hx-get="/test" hx-trigger="input changed delay:500ms" hx-target="#tablebody" hx-swap="outerHTML"></input>
        </th>
    </tr>
    </thead>
    <tbody id="tablebody"><tr><td>Initial</td></tr></tbody>
</table>

<table id="myOobTable">
    <thead><tr><th>Test OOB</th></tr></thead>
    <tbody><tr><td>Initial OOB</td></tr></tbody>
</table>

Server response: same as before

So there's likely something else going on.

May I ask again:

If your setup is indeed similar to this one, would you mind sharing the htmx version you're using? (and try with the latest, i.e. 2.0.4 as I'm writing this, if you are on an older one, to see if by any chance it's an issue that had been fixed along past releases)

If not, could you please share your server response and triggering element's attributes so I can replicate it and investigate on my side?

Thanks

@langdiana
Copy link
Author

langdiana commented Feb 24, 2025

It doesn't work for me. And yes, I'm using 2.0.4. Also I'm using Edge.
I attached a series of files with the rendered content and server response:

Initial page sent from server:
InitialPageFromServer.txt

Initial page rendered in browser:
InitialPageRender.txt

Server response after first input:
FirstInputResponse.txt

Html rendered after first server response:
FirstInputRender.txt

Server response after second input (fully identical with first server response):
SecondInputResponse.txt

Html rendered after second server response:
SecondInputRender.txt

Also, here is a screenshot with the browser after second input:

Image

@Telroshan
Copy link
Collaborator

Telroshan commented Feb 24, 2025

Thanks, this helps a lot!

So, if I use your initial HTML (InitialPageFromServer.txt), then send the response you provided (FirstInputResponse.txt), I indeed get the same issue.

However, if I wrap the oob table in a template as discussed before in the server response, it works as expected.
i.e, sending back this

<tbody id="tablebody"><tr><td>Test</td></tr></tbody>

<template>
    <table id="myOobTable" hx-swap-oob="true">
        <thead><tr><th> Oob header</th></tr></thead>
        <tbody><tr><td> Oob col</td></tr></tbody>
    </table>
</template>

Notice how I wrapped myOobTable in a template tag in the server response (as per FirstInputResponse.txt above)

Hope this helps!

@langdiana
Copy link
Author

langdiana commented Feb 24, 2025

Yes, that works, thanks.
I think you shd update the docs to make it clear that any table element (not just tr, td etc) shd be embedded in a template

@Telroshan
Copy link
Collaborator

Yeah usually we have people swapping a table element (tr td etc.) as OOB, which then works fine wrapped in a template.
Here it's the opposite ; the main content to swap is a table-child element and the OOB one is not, maybe that's a usecase we should highlight in the hx-swap-oob docs in the "troublesome tables and lists" section?

If you are interested in improving the docs btw, PRs are always welcome 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants