Skip to content

Commit bc6580d

Browse files
Add demo_live_pubsub example (#23)
1 parent 59d2b67 commit bc6580d

File tree

2 files changed

+204
-0
lines changed

2 files changed

+204
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ See more examples below:
6767
* [`examples/demo_router.exs`]
6868
* [`examples/demo_hooks.exs`]
6969
* [`examples/demo_endpoint.exs`]
70+
* [`examples/demo_live_pubsub.exs`]
7071

7172
## License
7273

@@ -90,3 +91,4 @@ limitations under the License.
9091
[`examples/demo_router.exs`]: examples/demo_router.exs
9192
[`examples/demo_hooks.exs`]: examples/demo_hooks.exs
9293
[`examples/demo_endpoint.exs`]: examples/demo_endpoint.exs
94+
[`examples/demo_live_pubsub.exs`]: examples/demo_live_pubsub.exs

examples/demo_live_pubsub.exs

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
Mix.install([
2+
{:phoenix_playground, "~> 0.1.6"}
3+
])
4+
5+
defmodule TimelineLive do
6+
use Phoenix.LiveView
7+
8+
@topic "timeline"
9+
10+
def mount(_params, _session, socket) do
11+
if connected?(socket) do
12+
Phoenix.PubSub.subscribe(PhoenixPlayground.PubSub, @topic)
13+
end
14+
15+
socket =
16+
socket
17+
|> assign(temporary_assigns: [form: nil])
18+
|> stream(:posts, [])
19+
|> assign(:form, to_form(%{"content" => ""}))
20+
21+
{:ok, socket}
22+
end
23+
24+
def handle_event("validate", %{"content" => content}, socket) do
25+
{:noreply, assign(socket, :form, to_form(%{"content" => content}))}
26+
end
27+
28+
def handle_event("create_post", %{"content" => content}, socket) do
29+
post = %{
30+
id: System.unique_integer([:positive]),
31+
content: content,
32+
inserted_at: DateTime.utc_now()
33+
}
34+
35+
Phoenix.PubSub.broadcast(PhoenixPlayground.PubSub, @topic, {:new_post, post})
36+
37+
{:noreply,
38+
socket
39+
|> assign(:form, to_form(%{"content" => ""}))}
40+
end
41+
42+
def handle_event("delete_post", %{"dom_id" => dom_id}, socket) do
43+
Phoenix.PubSub.broadcast(PhoenixPlayground.PubSub, @topic, {:delete_post, dom_id})
44+
45+
{:noreply, socket}
46+
end
47+
48+
def handle_info({:new_post, post}, socket) do
49+
{:noreply, stream_insert(socket, :posts, post, at: 0)}
50+
end
51+
52+
def handle_info({:delete_post, dom_id}, socket) do
53+
{:noreply, stream_delete_by_dom_id(socket, :posts, dom_id)}
54+
end
55+
56+
def render(assigns) do
57+
~H"""
58+
<div class="timeline">
59+
<h1>Timeline</h1>
60+
61+
<.form for={@form} phx-submit="create_post" phx-change="validate" id="post-form">
62+
<textarea name="content" placeholder="What's on your mind?"><%= @form.params["content"] %></textarea>
63+
<button type="submit">Post</button>
64+
</.form>
65+
66+
<div class="posts" phx-update="stream" id="posts">
67+
<div :for={{dom_id, post} <- @streams.posts} id={dom_id} class="post">
68+
<div class="post-content">
69+
<p><%= post.content %></p>
70+
<small><%= post.inserted_at %></small>
71+
</div>
72+
<button phx-click="delete_post" phx-value-dom_id={dom_id} class="delete-btn">Delete</button>
73+
</div>
74+
</div>
75+
</div>
76+
77+
<style>
78+
* {
79+
box-sizing: border-box;
80+
}
81+
82+
.timeline {
83+
max-width: 800px;
84+
margin: 0 auto;
85+
padding: 20px;
86+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
87+
}
88+
89+
h1 {
90+
color: #2c3e50;
91+
font-size: 2.5em;
92+
margin-bottom: 1em;
93+
text-align: center;
94+
}
95+
96+
.post {
97+
border: 1px solid #e1e8ed;
98+
border-radius: 12px;
99+
padding: 16px;
100+
margin: 16px 0;
101+
display: flex;
102+
justify-content: space-between;
103+
align-items: flex-start;
104+
background: white;
105+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
106+
transition: all 0.2s ease;
107+
}
108+
109+
.post:hover {
110+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
111+
transform: translateY(-2px);
112+
}
113+
114+
.post-content {
115+
flex: 1;
116+
margin-right: 16px;
117+
}
118+
119+
.post-content p {
120+
color: #2c3e50;
121+
font-size: 1.1em;
122+
line-height: 1.5;
123+
margin: 0 0 8px 0;
124+
word-wrap: break-word;
125+
overflow-wrap: break-word;
126+
}
127+
128+
.post-content small {
129+
color: #8795a1;
130+
font-size: 0.9em;
131+
display: block;
132+
word-wrap: break-word;
133+
overflow-wrap: break-word;
134+
}
135+
136+
form {
137+
margin: 20px 0;
138+
background: white;
139+
padding: 20px;
140+
border-radius: 12px;
141+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
142+
width: 100%;
143+
}
144+
145+
textarea {
146+
width: 100%;
147+
min-height: 3ch;
148+
margin-bottom: 16px;
149+
padding: 12px;
150+
border: 2px solid #e1e8ed;
151+
border-radius: 8px;
152+
font-size: 1em;
153+
resize: vertical;
154+
transition: border-color 0.2s ease;
155+
}
156+
157+
textarea:focus {
158+
outline: none;
159+
border-color: #4a9eff;
160+
}
161+
162+
button {
163+
padding: 10px 20px;
164+
background: #4a9eff;
165+
color: white;
166+
border: none;
167+
border-radius: 8px;
168+
cursor: pointer;
169+
font-size: 1em;
170+
font-weight: 500;
171+
transition: all 0.2s ease;
172+
}
173+
174+
button:hover {
175+
background: #357abd;
176+
transform: translateY(-1px);
177+
}
178+
179+
.delete-btn {
180+
background: #ff4a4a;
181+
margin-left: 10px;
182+
padding: 8px 16px;
183+
font-size: 0.9em;
184+
opacity: 0.8;
185+
}
186+
187+
.delete-btn:hover {
188+
background: #bd3535;
189+
opacity: 1;
190+
}
191+
192+
body {
193+
background: #f8fafc;
194+
margin: 0;
195+
padding: 20px;
196+
}
197+
</style>
198+
"""
199+
end
200+
end
201+
202+
PhoenixPlayground.start(live: TimelineLive)

0 commit comments

Comments
 (0)