Packages
mob
0.6.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.26
0.6.25
0.6.24
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.2
0.6.1
0.6.0
0.5.18
0.5.17
0.5.16
0.5.15
0.5.14
0.5.11
0.5.10
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.0
BEAM-on-device mobile framework for Elixir
Current section
Files
Jump to
Current section
Files
lib/mob/event/target.ex
defmodule Mob.Event.Target do
@moduledoc """
Resolves a `target:` spec to a concrete delivery pid.
See `guides/event_model.md` for the full event model.
## Forms
| Form | Resolves to | When checked |
|------|-------------|--------------|
| `:parent` | nearest stateful ancestor | render time |
| `:screen` | the containing screen | render time |
| `{:component, id}` | named ancestor component | render time |
| atom | registered process | event time (best-effort) |
| pid | that pid | n/a |
| `{:via, mod, key}` | whatever `mod` resolves it to | event time |
Resolution returns either `{:ok, pid}` or `{:error, reason}`. Callers decide
how to handle errors — typically log + drop (for in-tree, this means the
target was unmounted; for external, it was never registered).
"""
alias Mob.Event.Address
@typedoc """
The user-facing form passed in `target:` props.
"""
@type spec ::
:parent
| :screen
| {:component, Address.id()}
| atom()
| pid()
| {:via, module(), term()}
@typedoc """
The render-time scope used to resolve in-tree targets.
- `screen_pid` — the screen GenServer's pid
- `component_chain` — list of `{id, pid}` from outermost to innermost
stateful ancestor of the widget being registered
"""
@type render_scope :: %{
required(:screen_pid) => pid(),
required(:component_chain) => [{Address.id(), pid()}]
}
@doc """
Resolve a target spec to a pid using the render-time scope.
In-tree forms (`:parent`, `:screen`, `{:component, id}`) are resolved
against `scope`. External forms (atom/pid/via) are resolved against the
process registry at the moment of resolution.
Returns `{:ok, pid}` or `{:error, reason}`.
## Examples
iex> Mob.Event.Target.resolve(:parent, %{screen_pid: self(), component_chain: []})
{:ok, self()}
iex> Mob.Event.Target.resolve(:screen, %{screen_pid: self(), component_chain: []})
{:ok, self()}
iex> Mob.Event.Target.resolve(self(), %{screen_pid: self(), component_chain: []})
{:ok, self()}
"""
@spec resolve(spec(), render_scope()) :: {:ok, pid()} | {:error, atom()}
def resolve(:parent, %{component_chain: chain, screen_pid: screen_pid}) do
case List.last(chain) do
nil -> {:ok, screen_pid}
{_id, pid} -> {:ok, pid}
end
end
def resolve(:screen, %{screen_pid: screen_pid}), do: {:ok, screen_pid}
def resolve({:component, id}, %{component_chain: chain}) do
case Enum.find(chain, fn {cid, _pid} -> cid == id end) do
{_, pid} -> {:ok, pid}
nil -> {:error, {:component_not_in_ancestors, id}}
end
end
def resolve(pid, _scope) when is_pid(pid) do
if Process.alive?(pid), do: {:ok, pid}, else: {:error, :dead_pid}
end
def resolve(name, _scope) when is_atom(name) do
case Process.whereis(name) do
nil -> {:error, {:not_registered, name}}
pid -> {:ok, pid}
end
end
def resolve({:via, mod, key} = via, _scope) when is_atom(mod) do
# GenServer.whereis/1 narrows to `pid | nil` for `{:via, _, _}`
# inputs (the registry callbacks normalize their result before
# returning). The historical `{_name, _node}` arm came from
# treating `GenServer.whereis/1`'s full @spec — that variant
# only fires for `{name, node}` inputs, which this clause never
# passes.
case GenServer.whereis(via) do
nil -> {:error, {:via_not_resolvable, mod, key}}
pid when is_pid(pid) -> {:ok, pid}
end
end
def resolve(other, _scope), do: {:error, {:invalid_target, other}}
@doc """
Classify a target spec as `:in_tree` or `:external`.
In-tree targets get framework guarantees (staleness check, lifecycle
cleanup); external targets are best-effort delivery.
"""
@spec classify(spec()) :: :in_tree | :external
def classify(:parent), do: :in_tree
def classify(:screen), do: :in_tree
def classify({:component, _}), do: :in_tree
def classify(_), do: :external
end