bigskysoftware / htmx

</> htmx - high power tools for HTML
https://htmx.org
Other
35.35k stars 1.18k forks source link

htmx history issue with alpine <template x-teleport="#id"> tag #1081

Open nikalexis opened 1 year ago

nikalexis commented 1 year ago

Using htmx with this config: <meta name="htmx-config" content='{"useTemplateFragments":true}'>

Using alpine V3, let's say that you want to make a teleport inside a htmx get div:

<div id="page_header"></div>
<div id="page">
<!-- This alpine teleport updates the panel header with an appended h1-->
<template x-teleport="#panel_header">
    <h1>Page title of main page</h1>
</template>
Some page content here. Go to <a hx-get="/another_page" hx-target="#page">another page</a>.
</div>

When the <a> object is clicked, htmx removes the template object, alpine removes the teleported <h1>, htmx requests the new content, a new teleport gets in place and alpine places the new <h1> object in #panel_header_left div. Everything works fine with the new get and the new teleport!

But, when you hit the back button, the history of htmx runs and make a copy all DOM objects, including <h1> and <template x-teleport> objects. As a result when the new DOM gets in place, alpine realizes that a new teleported template got in place and you end up with 2 <h1> headers (alpine teleport can only append items). The first placed <h1> was never removed by alpine, because I guess the first <template x-teleport> was never removed also (just replaced by htmx?). Alpine thought the history replaced template as a new one.

I know this a corner case, but htmx and alpine transport feature gives great power, so I thought this worth an opened issue. I don't have a clew, how htmx could handle these case. Maybe by doing a special copy for the template objects, but unfortunately I have no idea how this could be implemented.

PS: I found a workaround for now by refreshing the page on every history back trigger with the following config: <meta name="htmx-config" content='{"useTemplateFragments":true, "refreshOnHistoryMiss": true, "historyCacheSize": 1}'>

JakeCoxon commented 1 year ago

+1 It would be good to be able to override the swap for history restore to use Alpine.morph

croxton commented 1 year ago

This extension can be used to preserve the initial dom state of elements across history restores: https://gist.github.com/croxton/e2c33bd22591f9a5bd8c9d23a56c9edc

In this case you would add the hx-history-preserve attribute to the <template> tag and the teleport target#panel_header.