Packages
phoenix_live_view
1.2.4
1.2.7
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.2.0-rc.3
1.2.0-rc.2
1.2.0-rc.1
1.2.0-rc.0
1.1.32
1.1.31
1.1.30
1.1.29
1.1.28
1.1.27
1.1.26
1.1.25
1.1.24
1.1.23
1.1.22
1.1.21
1.1.20
1.1.19
1.1.18
1.1.17
1.1.16
1.1.15
1.1.14
1.1.13
1.1.12
1.1.11
1.1.10
1.1.9
1.1.8
1.1.7
1.1.6
retired
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.1.0-rc.4
1.1.0-rc.3
1.1.0-rc.2
1.1.0-rc.1
1.1.0-rc.0
1.0.18
1.0.17
1.0.16
1.0.15
1.0.14
1.0.13
1.0.12
1.0.11
1.0.10
1.0.9
1.0.8
retired
1.0.7
1.0.6
retired
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
1.0.0-rc.9
1.0.0-rc.8
1.0.0-rc.7
1.0.0-rc.6
1.0.0-rc.5
1.0.0-rc.4
1.0.0-rc.3
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.20.17
0.20.16
0.20.15
0.20.14
0.20.13
0.20.12
0.20.11
0.20.10
0.20.9
0.20.8
0.20.7
0.20.6
0.20.5
0.20.4
0.20.3
0.20.2
0.20.1
0.20.0
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.18
0.18.17
0.18.16
0.18.15
0.18.14
0.18.13
0.18.12
0.18.11
0.18.10
0.18.9
0.18.8
0.18.7
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.14
0.17.13
0.17.12
0.17.11
0.17.10
0.17.9
0.17.8
0.17.7
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.0
0.9.0
0.8.1
0.8.0
0.7.1
0.7.0
0.6.0
0.6.0-dev
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.1
0.3.0
0.2.1
0.2.0
0.1.1
0.1.0
Rich, real-time user experiences with server-rendered HTML
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/phoenix_live_view/lifecycle.ex
defmodule Phoenix.LiveView.Lifecycle do
@moduledoc false
alias Phoenix.LiveView.{Socket, Utils}
@lifecycle :lifecycle
@type hook :: map()
@type t :: %__MODULE__{
after_render: [hook],
handle_async: [hook],
handle_event: [hook],
handle_info: [hook],
handle_params: [hook],
mount: [hook]
}
defstruct after_render: [],
handle_async: [],
handle_event: [],
handle_info: [],
handle_params: [],
mount: []
@doc """
Returns a map of infos about the lifecycle stage for the given `view`.
"""
def stage_info(%Socket{} = socket, view, stage, arity) do
callbacks? = callbacks?(socket, stage)
exported? = function_exported?(view, stage, arity)
%{
any?: callbacks? or exported?,
callbacks?: callbacks?,
exported?: exported?
}
end
defp callbacks?(%Socket{private: %{@lifecycle => lifecycle}}, stage)
when stage in [:handle_async, :handle_event, :handle_info, :handle_params, :mount] do
lifecycle |> Map.fetch!(stage) |> Kernel.!=([])
end
def attach_hook(%Socket{router: nil}, id, :handle_params, _fun) do
raise "cannot attach hook with id #{inspect(id)} on :handle_params because" <>
" the view was not mounted at the router with the live/3 macro"
end
def attach_hook(%Socket{} = socket, id, stage, fun)
when stage in [:handle_async, :handle_event, :handle_info, :handle_params, :after_render] do
lifecycle = lifecycle(socket, stage)
hook = hook!(id, stage, fun)
existing = Enum.find(Map.fetch!(lifecycle, stage), &(&1.id == id))
if existing do
raise ArgumentError, """
existing hook #{inspect(hook.id)} already attached on #{inspect(hook.stage)}.
"""
end
update_lifecycle(socket, stage, fn hooks ->
hooks ++ [hook]
end)
end
def attach_hook(%Socket{}, _id, stage, _fun) do
raise ArgumentError, """
invalid lifecycle event provided to attach_hook.
Expected one of: :handle_async | :handle_event | :handle_info | :handle_params | :after_render
Got: #{inspect(stage)}
"""
end
def detach_hook(%Socket{} = socket, id, stage)
when stage in [:handle_async, :handle_event, :handle_info, :handle_params, :after_render] do
update_lifecycle(socket, stage, fn hooks ->
for hook <- hooks, hook.id != id, do: hook
end)
end
def detach_hook(%Socket{}, _id, stage) do
raise ArgumentError, """
invalid lifecycle event provided to detach_hook.
Expected one of: :handle_async | :handle_event | :handle_info | :handle_params | :after_render
Got: #{inspect(stage)}
"""
end
defp hook!(id, stage, fun) when is_atom(stage) and is_function(fun) do
%{id: id, stage: stage, function: fun}
end
defp lifecycle(socket, stage) do
if Utils.cid(socket) && stage not in [:after_render, :handle_event, :handle_async] do
raise ArgumentError, "lifecycle hooks are not supported on stateful components."
end
Map.fetch!(socket.private, @lifecycle)
end
defp update_lifecycle(socket, stage, fun) do
lifecycle = lifecycle(socket, stage)
new_lifecycle = Map.update!(lifecycle, stage, fun)
put_lifecycle(socket, new_lifecycle)
end
defp put_lifecycle(socket, lifecycle) do
put_private(socket, @lifecycle, lifecycle)
end
defp put_private(%Socket{private: private} = socket, key, value) when is_atom(key) do
%{socket | private: Map.put(private, key, value)}
end
@doc false
def validate_on_mount!(_view, {module, arg}) when is_atom(module) do
{module, arg}
end
def validate_on_mount!(_view, module) when is_atom(module) do
{module, :default}
end
def validate_on_mount!(view, result) do
raise ArgumentError, """
invalid on_mount hook declared in #{inspect(view)}.
Expected one of:
Module
{Module, arg}
Got: #{inspect(result)}
"""
end
@doc false
def prepare_on_mount!(hooks) do
for {module, _fun} = id <- hooks do
hook!(id, :mount, Function.capture(module, :on_mount, 4))
end
end
# Lifecycle Event API
@doc false
def build(mount_hooks) when is_list(mount_hooks) do
%__MODULE__{mount: prepare_on_mount!(mount_hooks)}
end
@doc false
def mount(params, session, %Socket{private: %{@lifecycle => lifecycle}} = socket) do
reduce_socket(lifecycle.mount, socket, fn %{id: {mod, arg}} = hook, acc ->
case hook.function.(arg, params, session, acc) do
{:halt, %Socket{redirected: nil}} ->
raise_halt_without_redirect!(hook)
{:halt, %Socket{redirected: nil}, _opts} ->
raise_halt_without_redirect!(hook)
{:cont, %Socket{redirected: to}} when not is_nil(to) ->
raise_continue_with_redirect!(hook)
{:cont, %Socket{redirected: to}, _opts} when not is_nil(to) ->
raise_continue_with_redirect!(hook)
{:cont, socket, opts} ->
{:cont, Utils.handle_mount_options!(socket, opts, {mod, :on_mount, 4})}
ok ->
ok
end
end)
end
@doc false
def handle_event(event, val, %Socket{private: %{@lifecycle => lifecycle}} = socket) do
reduce_handle_event(lifecycle.handle_event, socket, fn hook, acc ->
hook.function.(event, val, acc)
end)
end
defp reduce_handle_event([hook | hooks], acc, function) do
case function.(hook, acc) do
{:cont, %Socket{} = socket} -> reduce_handle_event(hooks, socket, function)
{:halt, %Socket{} = socket} -> {:halt, socket}
{:halt, reply, %Socket{} = socket} -> {:halt, reply, socket}
other -> bad_lifecycle_response!(other, hook)
end
end
defp reduce_handle_event([], acc, _function), do: {:cont, acc}
@doc false
def handle_params(params, uri, %Socket{private: %{@lifecycle => lifecycle}} = socket) do
reduce_socket(lifecycle.handle_params, socket, fn hook, acc ->
hook.function.(params, uri, acc)
end)
end
@doc false
def handle_info(msg, %Socket{private: %{@lifecycle => lifecycle}} = socket) do
reduce_socket(lifecycle.handle_info, socket, fn hook, acc ->
hook.function.(msg, acc)
end)
end
@doc false
def handle_async(key, result, %Socket{private: %{@lifecycle => lifecycle}} = socket) do
reduce_socket(lifecycle.handle_async, socket, fn hook, acc ->
hook.function.(key, result, acc)
end)
end
@doc false
def after_render(%Socket{private: %{@lifecycle => lifecycle}} = socket) do
{:cont, new_socket} =
reduce_socket(lifecycle.after_render, socket, fn hook, acc ->
case hook.function.(acc) do
%Socket{} = new_socket ->
{:cont, new_socket}
other ->
raise ArgumentError,
"expected after_render hook to return a socket, got: #{inspect(other)}"
end
end)
new_socket
end
defp reduce_socket([hook | hooks], acc, function) do
case function.(hook, acc) do
{:cont, %Socket{} = socket} -> reduce_socket(hooks, socket, function)
{:halt, %Socket{} = socket} -> {:halt, socket}
other -> bad_lifecycle_response!(other, hook)
end
end
defp reduce_socket([], acc, _function), do: {:cont, acc}
defp bad_lifecycle_response!(result, hook) do
raise ArgumentError, """
invalid return from hook #{inspect(hook.id)} for lifecycle event #{inspect(hook.stage)}.
Expected one of:
#{expected_return(hook)}
Got: #{inspect(result)}
"""
end
defp expected_return(%{stage: :handle_event}) do
"""
{:cont, %Socket{}}
{:halt, %Socket{}}
{:halt, map, %Socket{}}
"""
end
defp expected_return(_) do
"""
{:cont, %Socket{}}
{:halt, %Socket{}}
"""
end
defp raise_halt_without_redirect!(hook) do
raise ArgumentError,
"the hook #{inspect(hook.id)} for lifecycle event :mount attempted to halt without redirecting."
end
defp raise_continue_with_redirect!(hook) do
raise ArgumentError,
"the hook #{inspect(hook.id)} for lifecycle event :mount attempted to redirect without halting."
end
end