LiveView link that patch and perform a JS command

Hey there,

In my application, I have links that need to perform as regular links (live_patch) but that also need to trigger a JS command (to close a sidebar).

Before the JS event, my link looked like that and worked perfectly:

<%= live_patch name, to: entry_path %>

Then to perform both the patch and JS command, it turned like this

<a href="#" phx-click={ patch_to(entry_path) |> close_sidebar()}>
  <%= name %>
</a>
defp close_sidebar(js \\ %JS{}) do
  js |> JS.add_class("lsb-hidden", to: "#sidebar")
end

defp patch_to(js \\ %JS{}, path) do
  js |> JS.push("patch-to", value: %{path: path}, target: "#sidebar")
end

def handle_event("patch-to", %{"path" => path}, socket) do
  {:noreply, push_patch(socket, to: path)}
end

Still working but now my render_click in tests are no longer working :frowning:

I kinda made the tests to pass with the following hack.

<a id={absolute_path} href="#" phx-click={ patch_to(entry_path) |> close_sidebar()} data-path={entry_path} phx-hook="FakeHook" phx-target={@myself}>
  <%= name %>
</a>
defp render_js_click(element, view) do
  [path] = element |> render() |> parse_fragment!() |> attribute("data-path")
  render_hook(element, "patch-to", %{path: path})
  render(view)
end

Do you have a less hack-ish solution?

2 Likes

Can you create a close-sidebar event listener and trigger the event from the handle_params/3 callback? handle_params/3 will be invoked every time there is a push_patch event

Invoked … whenever there is a live patch event.

From:
https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#c:handle_params/3

Push the event from the server: Phoenix.LiveView — Phoenix LiveView v0.17.11

I haven’t tested this approach but you should just be able to use the regular <%= live_patch ...%> after that

2 Likes

I don’t think I can trigger any JS command from handle_params/3. Can I?

My sidebar toggling is only managed on client side so I need to use either a %JS{} command or a javascript Hook.

1 Like

You won’t trigger a JS command from handle params, but you will trigger a client side JS event. Like this

# Inside your template or render/1 callback:
<%= live_patch name, to: entry_path

@impl true
def handle_params(params, uri, socket) do
  # Other work
  {:noreply, push_event(socket, "close-sidebar", %{})}
end

On the client side, add either:

  1. Hook
  2. Window event listener (in app.js)

For a hook, use:

this.handleEvent("close-sidebar", _data => {
  document.querySelector("#sidebar").addClass("lsb-hidden")
});

Or a window event listener:

# send sidebar ID to client
def handle_params(params, uri, socket) do
  {:noreply, push_event(socket, "close-sidebar", %{"id" => "#sidebar})}
end
// events are prefixed with phx:
window.addEventListener("phx:close-sidebar", e => {
  document.getElementById(e.detail.id).addClass("lsb-hidden");
});

I wrote the above code in the forum text editor, not guaranteed to compile. But it should get you close

2 Likes

Thanks! It seems elegant (way less a hack than my solution) but I’m still a bit disappointed because having to write a Hook defeats for me the purpose of LiveView JS commands.

1 Like

@JohnnyCurran your solution is working perfectly, thanks! :+1:

1 Like