The feature is really interesting, because it provides SPA-like functionality without needing to write any JavaScript, and the state is managed on the server, so you can save it and/or broadcast it to other users without much effort.
Here’s a basic description about how it works, based on the tutorial series above.
You tell the router what LiveView to load at a specific path:
scope "/", GalleryWeb do
pipe_through :browser
get "/", PageController, :index
# This route loads the Gallery LiveView
live "/gallery", GalleryLive
end
The LiveView module that handles it defines a mount
and a render
function.
The mount
function does initial setup. In the tutorial’s example, it creates two key-value pairs for the state:
-
key:
current_id
, value: the ID of the first image
-
key:
slideshow
, value: stopped
(the current state of the auto-advancing slideshow)
def mount(_session, socket) do
# Adding key-value pairs to the websocket connection
socket = socket
|> assign(:current_id, Gallery.first_id())
|> assign(:slideshow, :stopped)
# returning a tuple with the message `:ok` and the new `socket`
{:ok, socket}
end
The render
function contains an initial template to send. The HTML gets rendered server-side (good for SEO), but then the JS establishes a websocket connection and further HTML changes are sent over the websocket.
def render(assigns) do
# this returns the template
~L"""
<center>
<%= for id <- Gallery.image_ids() do %>
<img src="<%= Gallery.thumb_url(id) %>"
class="<%= thumb_css_class(id, @current_id) %>">
<% end %>
</center>
<label>Image ID: <%= @current_id %></label>
<center>
<button phx-click="prev">Prev</button>
<button phx-click="next">Next</button>
<%= if @slideshow == :stopped do %>
<button phx-click="play_slideshow">Play</button>
<% else %>
<button phx-click="stop_slideshow">Stop</button>
<% end %>
</center>
<img src="<%= Gallery.large_url(@current_id) %>">
"""
end
This snippet loops over the image IDs:
<%= for id <- Gallery.image_ids() do %>
The templates indicate which actions should be run for events, in this case phx-click
runs the "prev"
(previous image) action:
<button phx-click="prev">Prev</button>
Then you can handle those events with handle_event
and the name of the action. This example runs “prev” to tell the server that it should show the previous image:
def handle_event("prev", _event, socket) do
# returns the new state in the socket
{:noreply, assign_prev_id(socket)}
end
# Returns the socket with a new `current_id`
def assign_prev_id(socket) do
assign(socket, :current_id, Gallery.prev_image_id(socket.assigns.current_id))
end
When events are fired, or the server updates, the changes are sent back and forth over the websocket with lightweight JSON messages (all managed by Phoenix) like this:
[
"4",
"7",
"lv:phx-abcdef-",
"event",
{
"type": "click",
"event": "next",
"value": {
"altKey": false,
"shiftKey": false,
"ctrlKey": false,
"metaKey": false,
"x": 477,
"y": 281,
"pageX": 477,
"pageY": 399,
"screenX": 479,
"screenY": 413,
"value": ""
}
}
]
There’s a bit more to it, but that’s the basic idea.