phoenixframework / phoenix_live_view

Rich, real-time user experiences with server-rendered HTML
https://hex.pm/packages/phoenix_live_view
MIT License
6.18k stars 930 forks source link

custom elements inner content is emptied #2942

Closed toonmaterial closed 10 months ago

toonmaterial commented 10 months ago

I am trying to integrate custom elements into phoenix_live_view.

The dom operations by phoenix would empty the inner content of custom element. I assume it is caused by some morphdom mechanics. Just wondering is there any way to avoid this? Thanks.

Here is an example. <x-client-time /> is supposed to display local time.

Mix.install([
  :bandit,
  :jason,
  :phoenix,
  :phoenix_live_view
])

defmodule App.Live do
  use Phoenix.LiveView

  def mount(_params, _session, socket) do
    {:ok, assign_time(socket)}
  end

  def handle_event("time", _params, socket) do
    {:noreply, assign_time(socket)}
  end

  defp assign_time(socket) do
    assign(socket, :time, :os.system_time(:millisecond))
  end

  def render(assigns) do
    ~H"""
    <p class="m-8">
      <button class="px-2 py-1 border" phx-click="time">time</button>
      <x-client-time time={@time} />
    </p>
    """
  end

  def root(assigns) do
    ~H"""
    <!doctype html>
    <script type="module">
      import { Socket } from 'https://esm.run/phoenix'
      import { LiveSocket } from 'https://esm.run/phoenix_live_view'

      new LiveSocket('/live', Socket).connect()

      class ClientTime extends HTMLElement {
        static observedAttributes = ['time']

        attributeChangedCallback(_name, _v0, v) {
          this.innerHTML = `<span class="text-blue-800">Disappeared? ${new Date(+v).toString()}</span>`
        }
      }

      customElements.define('x-client-time', ClientTime)
    </script>
    <script src="https://cdn.tailwindcss.com"></script>
    <%= @inner_content %>
    """
  end
end

defmodule App.Router do
  use Phoenix.Router
  import Phoenix.LiveView.Router

  pipeline :browser do
    plug(:put_root_layout, html: {App.Live, :root})
  end

  scope "/" do
    pipe_through(:browser)

    live("/", App.Live)
  end
end

defmodule App.Endpoint do
  use Phoenix.Endpoint, otp_app: :app

  socket("/live", Phoenix.LiveView.Socket)
  plug(App.Router)
end

Application.put_env(:app, App.Endpoint,
  adapter: Bandit.PhoenixAdapter,
  http: [ip: {0, 0, 0, 0}, port: 4000],
  server: true,
  live_view: [signing_salt: "signing_salt"],
  secret_key_base: "secret_key_base"
)

{:ok, _} = Supervisor.start_link([App.Endpoint], strategy: :one_for_one)

Process.sleep(:infinity)
josevalim commented 10 months ago

You can use phx-update=“ignore” or phx-hook to control how the element is updated!