So I saw this blog from Chris McCord, but I can’t find the repo for it… Anyone knows if the official one exists?
Here you go:
Thank you for sharing!!!
Around seven minutes in the ‘likes’, ‘reposts’, 'edits", and ‘delete’ are added to the component with Font Awesome icons. I have gone through this twice, from the beginning, including deleting the db, and I can’t figure out where I am missing it.
Does anyone know where he is adding Font Awesome to the project?
I am a noob so I am sure I am missing something simple.
Hi Bill.
To follow it through:
- We see the root .eex template loads an app.css in (lib/chirp_web/templates/layout/root.html.leex).
- That file gets built by the asset pipeline and therefore must come from the Webpack build, so we check the assets folder.
- If you open the package.json there, you’ll see the font awesome package is required from npm, so that’s how it gets installed.
- Open the assets/css/app.css file and you’ll see the font awesome files “included” at the top. Webpack picks up these imports and inlines the requested files into the built app.css. The same one we initially saw in Point 1.
Point 4 can be seen on GitHub here.
Thanks so much for the response! I have learned from it.
I made the mistake of running around github looking for examples before I came back to reread your last sentence. That made all of the confusion on my part go away. Thanks for the example!
I was doing these steps and It was needed for me added this on the file webpack.config.js
:
// Load fonts
{
test: /.(woff(2)?|ttf|eot|svg)(?v=\d+.\d+.\d+)?$/,
use: [{
loader: ‘file-loader’,
options: {
name: ‘[name].[ext]’,
outputPath: ‘…/fonts’
},
}],
},
]
},
and then:
cd assets && npm install && node node_modules/webpack/bin/webpack.js --mode development
any one know how to live render when post deleted from this example ?
looks like phx-update=“prepend” does not work with deleted post ,
or is there another solution ?
The mine works you need a double click on the garbage icon:
If you look the file live/post_live/index.ex
Has the "delete"
event
@impl true
def handle_event("delete", %{"id" => id}, socket) do
post = Timeline.get_post!(id)
{:ok, _} = Timeline.delete_post(post)
{:noreply, assign(socket, :posts, fetch_posts())}
end
I use https://github.com/dersnek/chirp.
Thanks!
If I use it as-is, I does not work with deleted post too.
It works with the following changes.
lib/chirp_web/live/post_live/index.ex
{:ok, assign(socket, :posts, fetch_posts()), temporary_assigns: [posts: []]}
↓
{:ok, assign(socket, :posts, fetch_posts())}
lib/chirp_web/live/post_live/index.html.leex
<div id="posts" phx-update="prepend">
↓
<div id="posts" phx-update="replace">
However, the original source code was showed in the video, so it may cause another problem.
Also, this change will not reflect the deletion in other browsers.
In addition to the changes above, the following changes make deletion in another browser.
lib/chirp/timeline.ex
def delete_post(%Post{} = post) do
{:ok, %Post{}} = Repo.delete(post)
broadcast({:ok, post}, :post_deleted)
end
lib/chirp_web/live/post_live/index.ex
def handle_event("delete", %{"id" => id}, socket) do
post = Timeline.get_post!(id)
{:ok, _} = Timeline.delete_post(post)
{:noreply, socket}
end
def handle_info({:post_deleted, post}, socket) do
{:noreply, update(socket, :posts, fn posts -> posts -- [post] end)}
end
Thx for the hint
My solution:
…/timeline.ex
def delete_post(%Post{} = post) do
Repo.delete(post)
|>broadcast(:post_deleted)
end
/post_live/index.ex
def mount(_params, _session, socket) do
if connected?(socket), do: Timeline.subscribe()
# {:ok, assign(socket, :posts, fetch_posts()), temporary_assigns: [posts: []]}
{:ok, assign(socket, :posts, fetch_posts())}
end
.
.
.
def handle_info({:post_updated, updated_post}, socket) do
{:noreply, update(socket, :posts, fn posts ->
for post <- posts do
case post.id == updated_post.id do
true -> updated_post
_ -> post
end
end
end)}
end
def handle_info({:post_deleted, deleted_post}, socket) do
{:noreply,
update(socket, :posts, fn posts ->
posts
|> Enum.reject(fn post ->
post.id == deleted_post.id
end)
end)}
end
/post_live/index.html.leex
<div id="posts" phx-update="replace">
<%= for post <- @posts do %>
<%= live_component @socket, ChripWeb.PostLive.PostComponent, id: post.id, post: post %>
<% end %>
</div>
hmm still not sure what combination to use (temporary_assigns + phx_update=“prepend or append”) or with “replace”
Great! It worked for me!
This is true but it’s complaining an error when I update and click the :like
or the :repost
.
And I notice more slowly for the like and repost events.
The error when updates, but after the error it works:
[debug] QUERY OK db=1.4ms queue=0.3ms idle=1089.3ms
UPDATE "posts" SET "body" = $1, "updated_at" = $2 WHERE "id" = $3 ["My post!!!!", ~N[2020-04-30 00:58:50], 30]
[error] GenServer #PID<0.618.0> terminating
** (RuntimeError) found duplicate ID 30 for component ChirpWeb.PostLive.PostComponent when rendering template
(phoenix_live_view 0.12.1) lib/phoenix_live_view/diff.ex:401: anonymous fn/5 in Phoenix.LiveView.Diff.render_pending_components/5
(elixir 1.10.0) lib/enum.ex:2111: Enum."-reduce/3-lists^foldl/2-0-"/3
(stdlib 3.10) maps.erl:232: :maps.fold_1/3
(phoenix_live_view 0.12.1) lib/phoenix_live_view/diff.ex:392: Phoenix.LiveView.Diff.render_pending_components/5
(phoenix_live_view 0.12.1) lib/phoenix_live_view/diff.ex:95: Phoenix.LiveView.Diff.render/3
(phoenix_live_view 0.12.1) lib/phoenix_live_view/channel.ex:504: Phoenix.LiveView.Channel.render_diff/2
(phoenix_live_view 0.12.1) lib/phoenix_live_view/channel.ex:361: Phoenix.LiveView.Channel.handle_changed/4
(stdlib 3.10) gen_server.erl:637: :gen_server.try_dispatch/4
(stdlib 3.10) gen_server.erl:711: :gen_server.handle_msg/6
(stdlib 3.10) proc_lib.erl:259: :proc_lib.wake_up/3
Last message: {:post_updated, %Chirp.Timeline.Post{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">, body: "My post!!!!", id: 30, inserted_at: ~N[2020-04-30 00:58:38], likes_count: 0, reposts_count: 0, updated_at: ~N[2020-04-30 00:58:50], username: "romenigld"}}
State: %{components: {%{{ChirpWeb.PostLive.PostComponent, 30} => {2, %{flash: %{}, id: 30, myself: 2, post: %Chirp.Timeline.Post{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">, body: "My post!", id: 30, inserted_at: ~N[2020-04-30 00:58:38], likes_count: 0, reposts_count: 0, updated_at: ~N[2020-04-30 00:58:38], username: "romenigld"}}, %{}, {128518509547916470656476619130439709679, %{}}}}, %{2 => {ChirpWeb.PostLive.PostComponent, 30}}, 3}, join_ref: "22", serializer: Phoenix.Socket.V2.JSONSerializer, socket: #Phoenix.LiveView.Socket<assigns: %{flash: %{}, live_action: :index, live_module: ChirpWeb.PostLive.Index, page_title: "Listing Posts", post: nil, posts: [%Chirp.Timeline.Post{...}]}, changed: %{}, endpoint: ChirpWeb.Endpoint, id: "phx-FgpyvggJDgijrwCC", parent_pid: nil, root_pid: #PID<0.618.0>, router: ChirpWeb.Router, view: ChirpWeb.PostLive.Index, ...>, topic: "lv:phx-FgpyvggJDgijrwCC", transport_pid: #PID<0.549.0>, uri: %URI{authority: nil, fragment: nil, host: "localhost", path: nil, port: 4000, query: nil, scheme: "http", userinfo: nil}}
[info] GET /posts
[debug] Processing with Phoenix.LiveView.Plug.index/2
Parameters: %{}
Pipelines: [:browser]
[info] Sent 200 in 417µs
[debug] QUERY OK source="posts" db=0.5ms queue=0.1ms idle=1140.3ms
SELECT p0."id", p0."body", p0."likes_count", p0."reposts_count", p0."username", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 ORDER BY p0."id" DESC []
[debug] QUERY OK source="posts" db=0.4ms idle=1014.1ms
SELECT p0."id", p0."body", p0."likes_count", p0."reposts_count", p0."username", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 ORDER BY p0."id" DESC []
error for the like:
[debug] QUERY OK source="posts" db=1.7ms idle=1860.9ms
UPDATE "posts" AS p0 SET "likes_count" = p0."likes_count" + $1 WHERE (p0."id" = $2) RETURNING p0."id", p0."body", p0."likes_count", p0."reposts_count", p0."username", p0."inserted_at", p0."updated_at" [1, 30]
[error] GenServer #PID<0.639.0> terminating
** (RuntimeError) found duplicate ID 30 for component ChirpWeb.PostLive.PostComponent when rendering template
(phoenix_live_view 0.12.1) lib/phoenix_live_view/diff.ex:401: anonymous fn/5 in Phoenix.LiveView.Diff.render_pending_components/5
(elixir 1.10.0) lib/enum.ex:2111: Enum."-reduce/3-lists^foldl/2-0-"/3
(stdlib 3.10) maps.erl:232: :maps.fold_1/3
(phoenix_live_view 0.12.1) lib/phoenix_live_view/diff.ex:392: Phoenix.LiveView.Diff.render_pending_components/5
(phoenix_live_view 0.12.1) lib/phoenix_live_view/diff.ex:95: Phoenix.LiveView.Diff.render/3
(phoenix_live_view 0.12.1) lib/phoenix_live_view/channel.ex:504: Phoenix.LiveView.Channel.render_diff/2
(phoenix_live_view 0.12.1) lib/phoenix_live_view/channel.ex:361: Phoenix.LiveView.Channel.handle_changed/4
(stdlib 3.10) gen_server.erl:637: :gen_server.try_dispatch/4
(stdlib 3.10) gen_server.erl:711: :gen_server.handle_msg/6
(stdlib 3.10) proc_lib.erl:259: :proc_lib.wake_up/3
Last message: {:post_updated, %Chirp.Timeline.Post{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">, body: "My post!!!!", id: 30, inserted_at: ~N[2020-04-30 00:58:38], likes_count: 1, reposts_count: 0, updated_at: ~N[2020-04-30 00:58:50], username: "romenigld"}}
State: %{components: {%{{ChirpWeb.PostLive.PostComponent, 30} => {0, %{flash: %{}, id: 30, myself: 0, post: %Chirp.Timeline.Post{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">, body: "My post!!!!", id: 30, inserted_at: ~N[2020-04-30 00:58:38], likes_count: 0, reposts_count: 0, updated_at: ~N[2020-04-30 00:58:50], username: "romenigld"}}, %{}, {128518509547916470656476619130439709679, %{}}}}, %{0 => {ChirpWeb.PostLive.PostComponent, 30}}, 1}, join_ref: "28", serializer: Phoenix.Socket.V2.JSONSerializer, socket: #Phoenix.LiveView.Socket<assigns: %{flash: %{}, live_action: :index, live_module: ChirpWeb.PostLive.Index, page_title: "Listing Posts", post: nil, posts: [%Chirp.Timeline.Post{...}]}, changed: %{}, endpoint: ChirpWeb.Endpoint, id: "phx-FgpyvggJDgijrwCC", parent_pid: nil, root_pid: #PID<0.639.0>, router: ChirpWeb.Router, view: ChirpWeb.PostLive.Index, ...>, topic: "lv:phx-FgpyvggJDgijrwCC", transport_pid: #PID<0.549.0>, uri: %URI{authority: nil, fragment: nil, host: "localhost", path: nil, port: 4000, query: nil, scheme: "http", userinfo: nil}}
[error] GenServer #PID<0.638.0> terminating
** (RuntimeError) found duplicate ID 30 for component ChirpWeb.PostLive.PostComponent when rendering template
(phoenix_live_view 0.12.1) lib/phoenix_live_view/diff.ex:401: anonymous fn/5 in Phoenix.LiveView.Diff.render_pending_components/5
(elixir 1.10.0) lib/enum.ex:2111: Enum."-reduce/3-lists^foldl/2-0-"/3
(stdlib 3.10) maps.erl:232: :maps.fold_1/3
(phoenix_live_view 0.12.1) lib/phoenix_live_view/diff.ex:392: Phoenix.LiveView.Diff.render_pending_components/5
(phoenix_live_view 0.12.1) lib/phoenix_live_view/diff.ex:95: Phoenix.LiveView.Diff.render/3
(phoenix_live_view 0.12.1) lib/phoenix_live_view/channel.ex:504: Phoenix.LiveView.Channel.render_diff/2
(phoenix_live_view 0.12.1) lib/phoenix_live_view/channel.ex:361: Phoenix.LiveView.Channel.handle_changed/4
(stdlib 3.10) gen_server.erl:637: :gen_server.try_dispatch/4
(stdlib 3.10) gen_server.erl:711: :gen_server.handle_msg/6
(stdlib 3.10) proc_lib.erl:259: :proc_lib.wake_up/3
Last message: {:post_updated, %Chirp.Timeline.Post{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">, body: "My post!!!!", id: 30, inserted_at: ~N[2020-04-30 00:58:38], likes_count: 1, reposts_count: 0, updated_at: ~N[2020-04-30 00:58:50], username: "romenigld"}}
State: %{components: {%{{ChirpWeb.PostLive.PostComponent, 30} => {0, %{flash: %{}, id: 30, myself: 0, post: %Chirp.Timeline.Post{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">, body: "My post!!!!", id: 30, inserted_at: ~N[2020-04-30 00:58:38], likes_count: 0, reposts_count: 0, updated_at: ~N[2020-04-30 00:58:50], username: "romenigld"}}, %{}, {128518509547916470656476619130439709679, %{}}}}, %{0 => {ChirpWeb.PostLive.PostComponent, 30}}, 1}, join_ref: "132", serializer: Phoenix.Socket.V2.JSONSerializer, socket: #Phoenix.LiveView.Socket<assigns: %{flash: %{"info" => "Post updated successfully"}, live_action: :index, live_module: ChirpWeb.PostLive.Index, page_title: "Listing Posts", post: nil, posts: [%Chirp.Timeline.Post{...}]}, changed: %{}, endpoint: ChirpWeb.Endpoint, id: "phx-FgpzD-VLX7ir3ABE", parent_pid: nil, root_pid: #PID<0.638.0>, router: ChirpWeb.Router, view: ChirpWeb.PostLive.Index, ...>, topic: "lv:phx-FgpzD-VLX7ir3ABE", transport_pid: #PID<0.542.0>, uri: %URI{authority: nil, fragment: nil, host: "localhost", path: nil, port: 4000, query: nil, scheme: "http", userinfo: nil}}
[debug] QUERY OK source="posts" db=0.8ms idle=1873.8ms
SELECT p0."id", p0."body", p0."likes_count", p0."reposts_count", p0."username", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 ORDER BY p0."id" DESC []
[debug] QUERY OK source="posts" db=1.4ms queue=0.2ms idle=1876.4ms
SELECT p0."id", p0."body", p0."likes_count", p0."reposts_count", p0."username", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 ORDER BY p0."id" DESC []
It complains for this but do the like. why??
looks like
{:ok, assign(socket, :posts, fetch_posts())}
is saving the lists to memory, u can try this solution:
def handle_info({:post_updated, updated_post}, socket) do
{:noreply, update(socket, :posts, fn posts ->
for post <- posts do
case post.id == updated_post.id do
true -> updated_post
_ -> post
end
end
end)}
end
def handle_info({:post_deleted, deleted_post}, socket) do
{:noreply,
update(socket, :posts, fn posts ->
posts
|> Enum.reject(fn post ->
post.id == deleted_post.id
end)
end)}
end
Anyone know why presented in the video css classes worked out?
For example column-10
and column-90
I didn’t found definition of that classes in that repo or any of libraries source code on phoenix and ** phoenix_live_view
Is phoenix use some css framework by default?
Hello and welcome,
By default Phoenix uses Milligram, and You might find it in assets/css/phoenix.css.
Thanks, now it make sense
I forked the dersnek repo and backed out the code that Chris types in for anyone that wants to ‘code along’ without having to figure out how to add the icons to the project.
solution for delete
Thank you! I was wondering on why the :posts
were empty during :handle_info
calls and I think this explains why.