htmz icon indicating copy to clipboard operation
htmz copied to clipboard

Propagating redirects to the top window

Open kiastorm opened this issue 1 year ago • 6 comments

I have the following situation

I submit a form

  • If there's an error, the server responds with the same form HTML fragment but with invalid inputs and error messages
  • If the request was successful, the server responds with a 302 and intends to redirect to a different page, full page reload

My problem is that in the case of a successful request, the page I intend to redirect to is rendered replacing the form. The redirect does not propagate to the top window.

Has anyone found a way to do this?

kiastorm avatar Sep 30 '24 17:09 kiastorm

You can change the script by including window.top, which refers to the topmost window

Atmos4 avatar Oct 16 '24 04:10 Atmos4

@Atmos4 Did you have an example of how to do this? Thank you 🙏

lpil avatar Jul 20 '25 09:07 lpil

@lpil reading back what I said 9 months ago, I think I misunderstood the issue completely :D I had mistaken it for an issue with nested htmz frames.

To answer the issue properly this time, the limitation of htmz is that you can't change the target from the response, you can only set it in the request.

To bypass this limitation, I believe we could rewrite htmz to use only out-of-band swaps with ids. Here is a very basic example of what it would look like:

<script>
  function htmz(frame) {
    setTimeout(() =>
      frame.contentDocument.body.childNodes.forEach((n) =>
        document.getElementById(n.id)?.replaceWith(n)
      );
    );
  }
</script>
<iframe hidden name=htmz onload="htmz(this)"></iframe>

or minified:

<iframe hidden name=htmz onload="setTimeout(()=>contentDocument.body.childNodes.forEach(n=>document.getElementById(n.id)?.replaceWith(n)))"></iframe>

Hope it helps and sorry for the confusion. Let me know if you want a full example using this technique.

Atmos4 avatar Jul 20 '25 21:07 Atmos4

That's a cool idea, neat!

I think that would not be enough to propagate redirects though. You could replace the whole page but it wouldn't change the URL or unload all the JavaScript.

lpil avatar Jul 21 '25 08:07 lpil

Then you need to attach information to the response, a bit like HTMX headers.

Since we are not in control of the request object with htmz, we could add an attribute to the body of the returned response (for example hz-redirect) that contains the redirect information.

The associated extension would be (based from the extension section of the docs)

<script>
  function htmz(frame) {
    // redirect extension
    let redirect = frame.contentDocument.body.getAttribute("hz-redirect")
    if (redirect) window.top.location.replace(redirect)
   
    setTimeout(() =>
      document
        .querySelector(frame.contentWindow.location.hash || null)
        ?.replaceWith(...frame.contentDocument.body.childNodes)
    );
  }
</script>
<iframe hidden name=htmz onload="htmz(this)"></iframe>

Let me know if that helps

Atmos4 avatar Jul 26 '25 08:07 Atmos4

When I needed to do this. I just returned a custom element which redirects, so like this:

customeElements.define("x-redirect", class extends HTMLElement {
  constructor() {
    super()
    let url = this.dataset.url || "/"
    document.location.href = url
  }
}

And the the element:

<x-redirect data-url="/my/path/i/want/to/go/to"></x-redirect>

jon49 avatar Oct 23 '25 16:10 jon49