Hi all,
I am very new to Elixir and Phoenix, so forgive me if this is an obvious question…
I am trying to create a grid layout displaying a stream of posts from a SQL database. In order to get the display right, I need to split these posts up into different row <div>
s. It’s easy to get a simple list of posts working, but when I split the stream up into multiple lists like this I can’t get it to update correctly. It loads correctly on mount, but generating a new post wipes out all the others and displays only one. What is the correct way to go about something like this?
For reference, here is a picture of the display I’m going for: https://i.imgur.com/JUGPuRj.png
And here is my code:
defmodule DinoWeb.PostLive.Index do
use DinoWeb, :live_view
alias Dino.Posts
alias Dino.Posts.Post
@impl true
def mount(_params, _session, socket) do
{:ok, stream(socket, :posts, Posts.list_posts())}
end
@impl true
def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
defp apply_action(socket, :edit, %{"id" => id}) do
socket
|> assign(:page_title, "Edit Post")
|> assign(:post, Posts.get_post!(id))
end
defp apply_action(socket, :new, _params) do
socket
|> assign(:page_title, "New Post")
|> assign(:post, %Post{})
end
defp apply_action(socket, :index, _params) do
socket
|> assign(:page_title, "Listing Posts")
|> assign(:post, nil)
end
@impl true
def handle_info({DinoWeb.PostLive.FormComponent, {:saved, post}}, socket) do
{:noreply, stream_insert(socket, :posts, post)}
end
@impl true
def handle_event("delete", %{"id" => id}, socket) do
post = Posts.get_post!(id)
{:ok, _} = Posts.delete_post(post)
{:noreply, stream_delete(socket, :posts, post)}
end
@impl true
def render(assigns) do
~H"""
<.header>
Listing Posts
<:actions>
<.link patch={~p"/posts/new"}>
<.button>New Post</.button>
</.link>
</:actions>
</.header>
<.image_grid id="posts" phx-update="stream" posts={@streams.posts} />
<.modal :if={@live_action in [:new, :edit]} id="post-modal" show on_cancel={JS.patch(~p"/posts")}>
<.live_component
module={DinoWeb.PostLive.FormComponent}
id={@post.id || :new}
title={@page_title}
action={@live_action}
post={@post}
patch={~p"/posts"}
/>
</.modal>
"""
end
def image_grid(assigns) do
assigns = assign(assigns, :cols, split_list(assigns.posts, 4))
~H"""
<div class="grid grid-cols-2 md:grid-cols-4 gap-1">
<.image_col :for={col <- @cols} posts={col} />
</div>
"""
end
def image_col(assigns) do
~H"""
<div>
<.image_card :for={{post_id, post} <- @posts} id={post_id} image_link={post.image_link} />
</div>
"""
end
def image_card(assigns) do
~H"""
<div class="my-1 mx-auto">
<a href="#">
<img
class="object-cover w-full overflow-hidden rounded-md"
style="max-height: 512px;"
src={"#{@image_link}"}
/>
</a>
</div>
"""
end
defp split_list(list, n) do
chunk_size = div(Enum.count(list) + n - 1, n)
case(chunk_size) do
0 -> []
_ -> Enum.chunk_every(list, chunk_size)
end
end
end