Is there a way to handle events outside live view modules?

Hi,

I have something like this in router, different LiveViews using the same layout:

live_session :kanban,
      on_mount: TodoWeb.Kanban,
      layout: {TodoWeb.Layouts, :kanban} do
  live "/boards/:id", BoardLive
  live "/boards/:id/settings", BoardSettingsLive
end

The shared layout has a sidebar that is toggled based on the sidebar_open assign, and it is initialized by the on_mount function inside the TodoWeb.Kanban module.

defmodule TodoWeb.Kanban do
  def on_mount(:default, _params, _session, socket) do
    socket =
      socket
      |> assign(sidebar_open: true)

    {:cont, socket}
  end
end

This way I do not have to assign sidebar_open in every LiveView that uses the layout, but I still have to define the below handle_event callback inside every LiveView that uses the layout (BoardLive and BoardSettingsLive in this case), is there a way to avoid this repetition?

  def handle_event("toggle_sidebar", _unsigned_params, socket) do
    {:noreply, assign(socket, sidebar_open: !socket.assigns.sidebar_open)}
  end

See attach_hook/4 for one way to do it. You could also make your sidebar a LiveComponent and render it in the layout, though I personally use attach_hook.

Though to be clear, I use this for the current path. You’re generally better off using JS commands for managing UI-only state like if a sidebar is open or not.

1 Like

Thanks, really JS commands seem to make more sense in my case, but it was good to know about attach_hook/4 for future needs.

1 Like

If you generated your project with phx.new then you probably have a filed called todo_web.ex that has code that looks like this:

  def live_view do
    quote do
      use Phoenix.LiveView, layout: MomoWeb.live_view_layout()
    end
  end

which is used in your LiveView with use, like this:

use TodoWeb, :live_view

So you could just add your handle_event function inside the quote block, if you wanted.

If that starts getting too long, you could make a new file lib/todo_web/sidebar_toggling.ex or something (your choice of name), that looks like this:

defmodule TodoWeb.SidebarToggling do
    defmacro __using__(_) do
      quote do
        def handle_event("toggle-sidebar", _, socket) do
          socket
          |> update(:sidebar_open, &Kernel.!/1)
          |> then(&{:noreply, &1})
        end
      end
    end
end

and then back in the live_view quote block, just add 1 line:

  def live_view do
    quote do
      # ....
      use TodoWeb.SidebarToggling 
    end
  end

I am not saying you should do this. Using attach_hook is often best practice for stuff like this. I’m just making you aware of this using trick, which can be a nice way of splitting up large Elixir modules, or of sharing common code among many modules, even outside of LiveView/Phoenix world.

1 Like