Code
Links
We’ve previously seen in detail how Phoenix LiveView works. In this lesson we are going to see:
- Layouts, how they work with regular views and LiveViews.
- How to update the page title in LiveView.
.leex
LiveEEx template file
Layouts
We’ve seen that when a browser connects to a LiveView route, it initially makes a normal HTTP GET request to get a fully rendered HTML page from the server.
But our LiveView template is made just by few lines of code, so where does all this html come from? It comes from the layout! Like regular views, LiveViews are also rendered in a layout.
Regular views
When rendering regular views, for example the :index
action in ProductController
, Phoenix wraps the view using the root (lib/poeticoins_web/templates/layout/root.html.leex) layout
<!-- root.html.leex -->
<!DOCTYPE html>
<html lang="en">
<head>
...
<%= csrf_meta_tag() %>
<%= live_title_tag assigns[:page_title] || "Poeticoins", suffix: " · Phoenix Framework" %>
<link phx-track-static rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
<script defer phx-track-static type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
</head>
<body>
<header>
<section class="container">
<nav role="navigation"> ... </nav>
</section>
</header>
<%= @inner_content %>
</body>
</html>
and app (lib/poeticoins_web/templates/layout/app.html.leex) layout
<!-- app.html.eex -->
<main role="main" class="container">
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<%= @inner_content %>
</main>
The root.html.leex layout is used by both LiveViews and regular views. The .leex
extension stays for LiveEEx template and the syntax is the same as a regular EEx template.
app.html.eex is the default application layout, and it’s used only when rendering regular views. By default, the app.html.eex template has some extra code to render flash messages, but we can customize it as we want.
By calling <%= @inner_content %>
we inject the rendered content inside a layout. In case of a regular view, the rendered action html is then injected inside app.html.eex layout and the resulting html is injected in the root.html.leex layout.
By default the :root
layout is defined in router.ex, in the :browser
pipeline.
defmodule PoeticoinsWeb.Router do
use PoeticoinsWeb, :router
pipeline :browser do
...
plug :put_root_layout, {PoeticoinsWeb.LayoutView, :root}
end
...
end
It can be also passed to a live
route with the :layout
option.
live "/", CryptoDashboardLive, layout: {PoeticoinsWeb.LayoutView, :root}
LiveViews
While regular views use root and app layouts, LiveViews go with root.html.leex and live.html.leex. The live.html.leex layout wraps the LiveView and it’s rendered as part of the LiveView life-cycle. This means that the root layout is fixed, while the content in the live layout can be updated by LiveView.
By default, the live.html.leex template has some extra code to render live flash messages.
<!-- live.html.leex -->
<main role="main" class="container">
<p class="alert alert-info" role="alert"
phx-click="lv:clear-flash"
phx-value-key="info"><%= live_flash(@flash, :info) %></p>
<p class="alert alert-danger" role="alert"
phx-click="lv:clear-flash"
phx-value-key="error"><%= live_flash(@flash, :error) %></p>
<%= @inner_content %>
</main>
Let’s take the browser, connect to the live "/"
route and inspect the HTML generated by the HTTP request. We notice that live.html.leex is rendered inside the special LiveView tag.
Then, when the browser connects to a stateful LiveView process via websocket, in the phx_reply
message it receives the dynamic and static parts of the rendered live layout.
Update the page title
Since the root layout is fixed, its content can’t be changed by LiveView, with the exception of the <title>
.
In the root.html.leex
template we see that <title>
tag is set by calling the live_title_tag
helper.
<html lang="en">
<head>
...
<%= live_title_tag assigns[:page_title] || "Poeticoins", suffix: " · Phoenix Framework" %>
...
</head>
...
</html>
By assigning a new :page_title
to the socket
, for example setting the updated price of a product, we immediately see that the page title gets updated.
defmodule PoeticoinsWeb.CryptoDashboardLive do
def mount(_params, _session, socket) do
products = [Poeticoins.Product.new("coinbase", "BTC-USD")]
...
end
def handle_info({:new_trade, trade}, socket) do
socket =
socket
|> update(:trades, &Map.put(&1, trade.product, trade))
|> assign(:page_title, "#{trade.price}")
{:noreply, socket}
end
end
Since LiveView can’t change the root layout’s content, how can it be possible? Because LiveView uses a special t
key in the diff
message, which instructs the frontend LiveView JS to update the <title>
tag.
LiveEEx templates with ~L sigil and .leex file
To define a LiveEEx template, we’ve used the render/1
callback and the ~L
sigil, to return the template. When the template is big, it can be better to move it to a separate LiveEEx template file. It needs to be in the same directory of the LiveView and with the same name, in our case crypto_dashboard_live.html.leex
. After moving the template to this file (just the html, without the ~L
sigil), we can remove the render/1
callback in the LiveView.