I need to persist a value to a user session navigating between liveviews. I have found similar use cases describing some “cart” functionality but no solution fit. Most of the info is pre phoenix 1.7 without sigil_p
, avoiding Phoenix.Router.Helpers
and other goodies.
So I hacked a solution to explain the problem and maybe someone could help me understand what I missed to have this so complicated.
Here is the plain mix phx.new --no-ecto
project, and I will comment the only changes I made.
In the app.html.heex
I have a select
:
<header class="px-4 sm:px-6 lg:px-8">
<ul>
<li><.link href={~p"/"}>Home</.link></li>
<li><.link href={~p"/view1"}>View1</.link></li>
<li><.link href={~p"/view2"}>View2</.link></li>
</ul>
<form phx-change="global-var-change">
<label>Select option</label>
<select name="global_var_select">
<option
:for={{label, value} <- @options}
selected={value == @global_var}
value={value}>
<%= label %>
</option>
</select>
</form>
<div>Current value: <%= @global_var %></div>
</header>
<main class="px-4 py-20 sm:px-6 lg:px-8">
<div class="mx-auto max-w-2xl">
<%= @inner_content %>
</div>
</main>
On the app Web module LiveGlobalVarWeb
I implement handle_event/3
to handle global-var-change
on all liveviews:
Note hardcoded_get_path/2
. I wasn’t able to build the current URL from the socket. I tried enabling the Phoenix.Router.Helpers
but wasn’t able.
I also realised that the socket has a :host_uri
but for some reason the :path
attribute is nil
.
defmodule LiveGlobalVarWeb do
...
def live_view do
quote do
use Phoenix.LiveView,
layout: {LiveGlobalVarWeb.Layouts, :app}
unquote(html_helpers())
defp hardcoded_get_path(LiveGlobalVarWeb.View1Live, params), do: ~p"/view1?#{params}"
defp hardcoded_get_path(LiveGlobalVarWeb.View2Live, params), do: ~p"/view2?#{params}"
defp hardcoded_get_path(_, params), do: ~p"/?#{params}"
def handle_event("global-var-change", %{"global_var_select" => value}, socket) do
path = hardcoded_get_path(socket.view, %{"global_var" => value})
#push_patch/2 and push_navigate/2 requests don't go through the plug pipelines
{:noreply, redirect(socket, to: path)}
end
end
end
...
end
And this is the Router that put it all together:
- Puts the value in the session if it comes in the
params
-
on_mount
to assign the select options and the “global value” from the session
defmodule LiveGlobalVarWeb.Router do
use LiveGlobalVarWeb, :router
@example_options [{"", nil}, {"Opt1", "1"}, {"Opt2", "2"}, {"Opt3", "3"}]
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :put_root_layout, {LiveGlobalVarWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :put_global_var
end
scope "/", LiveGlobalVarWeb do
pipe_through :browser
live_session :global_var, on_mount: [{__MODULE__, :mount_global_var}] do
live "/", HomeLive
live "/view1", View1Live
live "/view2", View2Live
end
end
# Plug to persist the value from them params if exists
def put_global_var(%{params: %{"global_var" => value}} = conn, _) do
put_session(conn, :global_var, value)
end
def put_global_var(conn, _), do: conn
# `on_mount` to assign the select options and the "global value" from the session
def on_mount(:mount_global_var, _params, session, socket) do
global_var = Map.get(session, "global_var", nil)
socket =
socket
|> Phoenix.Component.assign_new(:options, fn -> @example_options end)
|> Phoenix.Component.assign_new(:global_var, fn -> global_var end)
{:cont, socket}
end
end
Is there a way to do this, or achieve this behaviour without having to navigate passing the URL params?
Thanks in advance!