-
-
Save marka2g/9557b21f0db0098205925ebd9d9b408e to your computer and use it in GitHub Desktop.
Phoenix LiveView main/nested setup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"/> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"/> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/> | |
</head> | |
<body> | |
<div id="phx_root"> | |
<%= render @view_module, @view_template, assigns %> | |
</div> | |
<script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule MyAppWeb.AppState do | |
require Logger | |
use GenServer | |
defmodule State do | |
defstruct [:root_pid, :session, :current_user] | |
end | |
def start_link(socket) do | |
name = via_tuple(socket) | |
state = %State{root_pid: root_pid(socket)} | |
GenServer.start_link(__MODULE__, state, name: name) | |
end | |
@impl true | |
def init(%State{} = state) do | |
Process.flag(:trap_exit, true) | |
{:ok, state} | |
end | |
defp via_tuple(socket), | |
do: {:via, Registry, {MyAppWeb.AppStateRegistry, root_pid(socket)}} | |
@impl true | |
def terminate(reason, _state) do | |
Logger.debug("AppState terminated: #{inspect(reason)}") | |
:ok | |
end | |
defp root_pid(socket) do | |
socket.root_pid || self() | |
end | |
def session(socket) do | |
GenServer.call(via_tuple(socket), :session) | |
end | |
def set_session(socket, session) do | |
GenServer.call(via_tuple(socket), {:set_session, session}) | |
end | |
def current_user(socket) do | |
GenServer.call(via_tuple(socket), :current_user) | |
end | |
# @todo change to send_log_out, return socket | |
def log_out(socket) do | |
send(root_pid(socket), :log_out) | |
end | |
def log_in(socket, user_id) when user_id != nil do | |
send(root_pid(socket), {:log_in, user_id: user_id}) | |
end | |
@impl true | |
def handle_call(:session, _from, state) do | |
{:reply, state.session, state} | |
end | |
@impl true | |
def handle_call({:set_session, session}, _from, state) do | |
{:reply, :ok, %{state | session: session}} | |
end | |
@impl true | |
def handle_call(:current_user, _from, state) do | |
state = | |
state | |
|> put_new_lazy(:current_user, fn -> | |
case state.session do | |
%{user_id: user_id} when user_id != nil -> | |
Accounts.get_user_by_id(user_id) | |
_ -> | |
nil | |
end | |
end) | |
{:reply, state.current_user, state} | |
end | |
defp put_new_lazy(%State{} = state, key, fun) do | |
case Map.get(state, key) do | |
nil -> Map.put(state, key, fun.()) | |
_ -> state | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# add to supervisor children: | |
{Registry, keys: :unique, name: MyAppWeb.AppStateRegistry} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%= live_render(@socket, MyAppWeb.NavbarLive, id: "nav_#{@socket.view}") %> | |
<%= render @view_module, @view_template, assigns %> | |
<%= render MyAppWeb.LayoutView, "footer.html", assigns %> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule MyAppWeb.HomePageLive do | |
use MyAppWeb, :live_view | |
use MyAppWeb, :main_live_view | |
def mount(session, socket) do | |
mount_as_main(session, socket, __MODULE__) | |
end | |
def render(assigns) do | |
Phoenix.View.render(MyAppWeb.PageView, "index.html", assigns) | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule MyAppWeb.LiveHelpers do | |
require Logger | |
import Phoenix.LiveView, only: [redirect: 2, assign: 3] | |
alias MyAppWeb.AppState | |
def put_body_layout(socket) do | |
socket | |
|> assign(:layout, {MyAppWeb.LayoutView, "body.html"}) | |
end | |
def log_mount(socket, module) do | |
Logger.debug( | |
"MOUNT: #{inspect(module)} connected?=#{inspect(Phoenix.LiveView.connected?(socket))} root_pid=#{ | |
inspect(socket.root_pid) | |
} self=#{inspect(self())}" | |
) | |
socket | |
end | |
def mount_as_main(session, socket, module) do | |
AppState.start_link(socket) | |
AppState.set_session(socket, session) | |
current_user = AppState.current_user(socket) | |
socket = | |
socket | |
|> assign(:session, session) | |
|> assign(:current_user, current_user) | |
|> assign(:current_user_id, if(current_user, do: current_user.id, else: nil)) | |
|> put_body_layout() | |
|> log_mount(module) | |
{:ok, socket} | |
end | |
def redirect_if_no_user(%{assigns: %{current_user_id: current_user_id}} = socket) | |
when current_user_id != nil do | |
socket | |
end | |
def redirect_if_no_user(socket) do | |
socket | |
|> redirect(to: "/") | |
end | |
def handle_params_reply(socket) do | |
case socket do | |
%{redirected: nil} -> {:noreply, socket} | |
_ -> {:stop, socket} | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule MyAppWeb.LiveView.Session do | |
@moduledoc """ | |
Joe Noon on 9/3/2019 | |
LiveView does not have a direct way to change the session, but does provide a way to set flash. | |
The application can use `put_flash(socket, :session, %{...})`, and this plug will merge that | |
map when present into the persisted session. | |
""" | |
@behaviour Plug | |
def init(opts), do: opts | |
def call(conn, _) do | |
conn | |
|> handle_session() | |
|> handle_reset_session() | |
end | |
def handle_session( | |
%{ | |
private: %{ | |
plug_session: %{"phoenix_flash" => %{"session" => %{} = session}} = plug_session | |
} | |
} = conn | |
) do | |
conn | |
|> Plug.Conn.put_private(:plug_session, Map.merge(plug_session, session)) | |
end | |
def handle_session(conn), do: conn | |
def handle_reset_session( | |
%{ | |
private: %{ | |
plug_session: %{"phoenix_flash" => %{"reset_session" => %{}} = phoenix_flash} | |
} | |
} = conn | |
) do | |
conn | |
|> Plug.Conn.put_private(:plug_session, %{"phoenix_flash" => phoenix_flash}) | |
end | |
def handle_reset_session(conn), do: conn | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule MyAppWeb do | |
#... controller, view, router use helpers etc. | |
# add: | |
def live_view do | |
quote do | |
use Phoenix.LiveView | |
import MyAppWeb.LiveHelpers | |
alias MyAppWeb.Router.Helpers, as: Routes | |
alias MyAppWeb.AppState | |
end | |
end | |
def main_live_view do | |
quote do | |
alias MyAppWeb.Router.Helpers, as: Routes | |
def handle_info(:log_out, socket) do | |
socket = | |
socket | |
|> put_flash(:info, "You are now logged out.") | |
|> put_flash(:reset_session, %{}) | |
|> redirect(to: "/") | |
{:noreply, socket} | |
end | |
def handle_info({:log_in, user_id: user_id}, socket) do | |
socket = | |
socket | |
|> put_flash(:info, "You are now logged in.") | |
|> put_flash(:session, %{user_id: user_id}) | |
|> redirect(to: "/") | |
{:noreply, socket} | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment