Part #5 of our series on building web console for Elixir & Phoenix applications with Phoenix LiveView.
In this series:
- Part 1: Project set up and simple code executor
- Part 2: Making imports and macros work
- Part 3: Managing multiple supervised sessions
- Part 4: Making it real-time
- Part 5: Capturing the outpu
Video version
Watch the video version below or or directly on YouTube for a complete step-by-step walkthrough.
# lib/backdoor/live/backdoor_live.ex
...
def mount(_params, _session, socket) do
Phoenix.PubSub.subscribe(find_pubsub_server(socket.endpoint), @topic_name)
...
end
...
defp find_pubsub_server(endpoint) do
case Application.get_env(:backdoor, :pubsub_server) do
nil ->
server = endpoint.config(:pubsub_server) || endpoint.__pubsub_server__()
Application.put_env(:backdoor, :pubsub_server, server)
server
server ->
server
end
end
The configuration option is then stored in the application environment, but it can be overwritten by the end user setting it in their config/config.ex
or similar.
The place where we emit the events is Backdoor.Session.CodeRunner
, and it also needs to find out the PubSub
instance, but it’s easier since we can reliably rely on application environment variable being present at this moment:
# lib/backdoor/session/log.ex
...
@topic_name "backdoor_events"
defp log(session_id, value) do
Phoenix.PubSub.broadcast(pubsub_server(), @topic_name, {:put_log, session_id, value})
Backdoor.Session.Log.put_log(via_tuple(Backdoor.Session.Log, session_id), value)
end
defp pubsub_server() do
Application.get_env(:backdoor, :pubsub_server)
end
This code makes our code runner emit events, whenever a new log entry is being added. We use similar technique in Backdoor.Session
itself, but won’t describe it as it is fairly repetitive, and just link for the reader to have a peek at if interested.
Making LiveView reacting to published events
We already subscribe to broadcasted messages in the mount/2
function of our LiveView
, but we need to handle the incoming messages. This is done by introducing appropriate handle_info/2
callbacks to catch published broadcasts.
For example, to handle log messages we need to alter our execute
command handler, and instead of handling return value in this function - we add a handle_info/2
callback to catch this event asynchronously:
@impl true
def handle_info({:put_log, session_id, log}, %{assigns: %{current_session_id: sid}} = socket)
when session_id == sid do
{:noreply,
socket
|> assign(logs: socket.assigns.logs ++ [log])}
end
def handle_info({:put_log, _session_id, _log}, socket) do
{:noreply, socket}
end
@impl true
def handle_event("execute", %{"command" => %{"text" => command}}, socket) do
Backdoor.Session.execute(socket.assigns.current_session_id, command)
{:noreply, socket |> assign(:input_value, "")}
end
There are a few more events that need to be handled in the LiveView
, but they largely follow the same pattern: whenever an event happens that all LiveViews
need to be reacting to, we publish such events on the PubSub
topic and make the LiveViews
capture it.
Notes
Watch the screencast directly on YouTube
You can find this and future videos on our YouTube channel.
The full code from this episode can be found on GitHub
Post by Hubert Łępicki
Hubert is partner at AmberBit. Rails, Elixir and functional programming are his areas of expertise.