Changing iframe src in LiveView adds to the page history

Say you have this LiveView:

defmodule AppWeb.IframeLive do
  use Phoenix.LiveView

  def render(assigns) do
    ~L"""
    <div>
      <iframe id="wp" src="<%= @src %>"></iframe>
      <button phx-click="switch" phx-value-src="//en.wikipedia.org">en</button>
      <button phx-click="switch" phx-value-src="//ru.wikipedia.org">ru</button>
    </div>
    """
  end

  def mount(_params, _session, socket) do
    {:ok, assign(socket, src: "//wikipedia.org")}
  end

  def handle_event("switch", %{"src" => src}, socket) do
    {:noreply, assign(socket, :src, src)}
  end
end

Every time you change the iframe source by clicking ‘en’ or ‘ru’, the current page url gets pushed to the history. Is there a way to avoid this?

Welcome @Proporus – this is not directly related to LiveView. In fact, there is currently an open issue for the HTML spec: whether to add a new mode for iframes where they don’t add to the joint session history.

Is there a way to avoid this?

Yes, I found two different methods to solve this problem and both will use a small client-side Javascript hook.

First, let’s modify the template:

<div id="wiki-frame" phx-hook="IframeSwitch" phx-update="ignore">
  <button data-src="//en.wikipedia.org">en</button>
  <button data-src="//ru.wikipedia.org">ru</button>
  <iframe id="wp" src="//wikipedia.org"></iframe>
</div>

A few things to note:

  • The wrapper div received a phx-hook and therefore it also received a required id.

  • The buttons lost their phx-click attribute.

  • Although not strictly necessary, for clarity the src attribute changed from phx-value-src to data-src.

Next, let’s setup IframeSwitch:

let IframeSwitch = {
  buttons() { return Array.from(this.el.querySelectorAll("button")); },
  iframe() { return this.el.querySelector("iframe"); },
  mounted() {
    this.buttons().forEach(btn => {
      btn.addEventListener("click", (e) => {
        // Invoke location.replace(src) on the iframe's window.
        // This approach will not update the joint session history,
        // however, it may not work for all sources.
        //
        this.iframe().contentWindow.location.replace(e.target.dataset.src);

        // Remove the iframe from the DOM, set the src attribute, and re-add it.
        // This will not update the joint session history.
        // Requires a phx-update=ignore wrapper element.
        //
        // let iframe = this.iframe();
        // iframe.remove();
        // iframe.setAttribute("src", e.target.dataset.src);
        // this.el.append(iframe);

        // BROKEN: Note, this version exhibits the bad behaviour of updating
        // the joint session history. I am including it here to show that this
        // problem is not specific to LiveView.
        //
        // this.iframe().src = e.target.dataset.src;
      });
    });
  }
};

You could also check out Alpine.js and apply the same concept using some lightweight, inline expressions.

5 Likes

Thanks in advance to anyone who replies with an Alpine.js version of IframeSwitch! That’d be neat to see.

1 Like

AlpineJS version:

<div id="wiki-frame" phx-update="ignore" x-data="{src: '//wikipedia.org'}">
  <button @click="$refs.iframe.contentWindow.location.replace('//en.wikipedia.org')">en</button>
  <button @click="$refs.iframe.contentWindow.location.replace('//ru.wikipedia.org')">ru</button>
  <iframe id="wp" :src="src" x-ref="iframe" />
</div>
4 Likes