Phoenix LiveView Demonstration
Mix.install([
{:phoenix_playground, "~> 0.1.8"}
])
defmodule PlaygroundManager do
def start(opts) do
stop()
PhoenixPlayground.start(opts)
end
def stop do
case Supervisor.which_children(PhoenixPlayground.Application) do
[{PhoenixPlayground, _pid, _, _} | _] ->
Supervisor.terminate_child(PhoenixPlayground.Application, PhoenixPlayground)
Supervisor.delete_child(PhoenixPlayground.Application, PhoenixPlayground)
_ ->
:ok
end
end
end
Simple Counter
defmodule DemoLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, count: 0)}
end
def render(assigns) do
~H"""
{@count}
+
body { padding: 1em; }
"""
end
def handle_event("inc", _params, socket) do
{:noreply, assign(socket, count: socket.assigns.count + 1)}
end
end
PlaygroundManager.start(live: DemoLive)
Simple Timeline
defmodule TimelineLive do
use Phoenix.LiveView
@topic "timeline"
def mount(_params, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(PhoenixPlayground.PubSub, @topic)
end
socket =
socket
|> assign(temporary_assigns: [form: nil])
|> stream(:posts, [])
|> assign(:form, to_form(%{"content" => ""}))
{:ok, socket}
end
def handle_event("create_post", %{"content" => content}, socket) do
post = %{
id: System.unique_integer([:positive]),
content: content,
inserted_at: DateTime.utc_now()
}
Phoenix.PubSub.broadcast(PhoenixPlayground.PubSub, @topic, {:new_post, post})
{:noreply,
socket
|> assign(:form, to_form(%{"content" => ""}))}
end
def handle_event("delete_post", %{"dom_id" => dom_id}, socket) do
Phoenix.PubSub.broadcast(PhoenixPlayground.PubSub, @topic, {:delete_post, dom_id})
{:noreply, socket}
end
def handle_info({:new_post, post}, socket) do
{:noreply, stream_insert(socket, :posts, post, at: 0)}
end
def handle_info({:delete_post, dom_id}, socket) do
{:noreply, stream_delete_by_dom_id(socket, :posts, dom_id)}
end
def render(assigns) do
~H"""
Timeline
<.form for={@form} phx-submit="create_post" id="post-form">
<%= @form.params["content"] %>
Post
<%= post.content %>
{DateTime.truncate(post.inserted_at, :second)}
Delete
window.hooks.CmdEnterSubmit = {
mounted() {
this.el.addEventListener("keydown", (e) => {
if (e.metaKey && e.key === 'Enter') {
this.el.form.dispatchEvent(
new Event('submit', {bubbles: true, cancelable: true}));
}
})
}
}
* {
box-sizing: border-box;
}
.timeline {
max-width: 800px;
margin: 0 auto;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
h1 {
color: #2c3e50;
font-size: 2.5em;
margin-bottom: 1em;
text-align: center;
}
.post {
border: 1px solid #e1e8ed;
border-radius: 12px;
padding: 16px;
margin: 16px 0;
display: flex;
justify-content: space-between;
align-items: flex-start;
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
transition: all 0.2s ease;
}
.post:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.post-content {
flex: 1;
margin-right: 16px;
}
.post-content p {
color: #2c3e50;
font-size: 1.1em;
line-height: 1.5;
margin: 0 0 8px 0;
word-wrap: break-word;
overflow-wrap: break-word;
}
.post-content small {
color: #8795a1;
font-size: 0.9em;
display: block;
word-wrap: break-word;
overflow-wrap: break-word;
}
form {
margin: 20px 0;
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
width: 100%;
}
textarea {
width: 100%;
min-height: 3ch;
margin-bottom: 16px;
padding: 12px;
border: 2px solid #e1e8ed;
border-radius: 8px;
font-size: 1em;
resize: vertical;
transition: border-color 0.2s ease;
}
textarea:focus {
outline: none;
border-color: #4a9eff;
}
button {
padding: 10px 20px;
background: #4a9eff;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1em;
font-weight: 500;
transition: all 0.2s ease;
}
button:hover {
background: #357abd;
transform: translateY(-1px);
}
.delete-btn {
background: #ff4a4a;
margin-left: 10px;
padding: 8px 16px;
font-size: 0.9em;
opacity: 0.8;
}
.delete-btn:hover {
background: #bd3535;
opacity: 1;
}
body {
background: #f8fafc;
margin: 0;
padding: 20px;
}
"""
end
end
PlaygroundManager.start(live: TimelineLive)
Simple Hooks
defmodule DemoHooks do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, socket}
end
def render(assigns) do
~H"""
LiveView
window.hooks.MyHook = {
mounted() {
this.el.innerHTML += " rocks";
setInterval(() => this.el.innerHTML += "!", 1000);
}
}
body { padding: 1em; }
"""
end
end
PlaygroundManager.start(live: DemoHooks)