Feature
Table Pagination with LiveView
A table with Phoenix LiveView pagination
NOTE I do a manual pagination from a list of 100 products. They are in memory and not read from the database. I could have used a pagination package for Ecto.
all_products = Products.list_products()
paginated_products = all_products |> Enum.chunk_every(@page_size)
products_for_page = paginated_products |> Enum.at(page_number - 1)
total_entries = all_products |> Enum.count()
total_pages = paginated_products |> Enum.count()
lib/phoenix_features_web/live/components/table_simple.ex
defmodule PhoenixFeaturesWeb.Components.TableSimple do
use PhoenixFeaturesWeb, :live_component
alias PhoenixFeatures.Products
def update(assigns, socket) do
page =
case assigns[:params] do
%{"page" => page} -> String.to_integer(page)
_ -> 1
end
{:ok,
socket
|> assign(assigns)
|> assign(get_products(page))
}
end
@page_size 10
def path(socket, page) do
Routes.demos_show_path(socket, :show, "table_simple", page: page)
end
def current_page?(page_number, idx) do
page_number == idx
end
def first_page?(page_number) do
page_number <= 1
end
def last_page?(page_number) do
page_number >= @page_size
end
defp get_products(page_number) do
all_products = Products.list_products()
paginated_products = all_products |> Enum.chunk_every(@page_size)
products_for_page = paginated_products |> Enum.at(page_number - 1)
total_entries = all_products |> Enum.count()
total_pages = paginated_products |> Enum.count()
[
products: products_for_page,
page_number: page_number,
page_size: @page_size,
total_entries: total_entries,
total_pages: total_pages
]
end
end
lib/phoenix_features_web/live/components/table_simple.html.leex
<div id="<%= @id %>">
<div class="flex items-baseline justify-between">
<h3 class="ml-4 text-lg font-semibold text-gray-600">
Products
</h3>
<nav class="flex border-t border-gray-200">
<%= if first_page?(@page_number) do %>
<%= live_redirect "Prev", to: path(@socket, @page_number), class: "mr-1 pointer-events-none bg-gray-100 inline-block text-sm bg-white p-2 rounded shadow text-gray-600 border border-gray-200" %>
<% else %>
<%= live_redirect "Prev", to: path(@socket, @page_number - 1), class: "mr-1 inline-block text-sm bg-white p-2 rounded shadow text-gray-800 border border-gray-200" %>
<% end %>
<%= for idx <- Enum.to_list(1..@total_pages) do %>
<%= if current_page?(@page_number, idx) do %>
<%= live_redirect idx, to: path(@socket, idx), class: "mr-1 pointer-events-none bg-gray-100 inline-block text-sm bg-white p-2 rounded shadow text-gray-600 border border-gray-200" %>
<% else %>
<%= live_redirect idx, to: path(@socket, idx), class: "mr-1 inline-block text-sm bg-white p-2 rounded shadow text-gray-800 border border-gray-200" %>
<% end %>
<% end %>
<%= if last_page?(@page_number) do %>
<%= live_redirect "Next", to: path(@socket, @page_number), class: "pointer-events-none bg-gray-100 inline-block text-sm bg-white p-2 rounded shadow text-gray-600 border border-gray-200" %>
<% else %>
<%= live_redirect "Next", to: path(@socket, @page_number + 1), class: "inline-block text-sm bg-white p-2 rounded shadow text-gray-800 border border-gray-200" %>
<% end %>
</nav>
</div>
<table class="w-full mt-4 bg-white border border-gray-200 rounded-lg shadow-lg">
<thead class="bg-gray-100 rounded-t-lg border-b border-gray-200 text-left">
<tr>
<th class="text-xs p-3 text-gray-600">Name</th>
<th class="text-xs p-3 text-gray-600">Description</th>
<th class="text-xs p-3 text-gray-600">Price</th>
</tr>
</thead>
<tbody>
<%= for product <- @products do %>
<tr id="product-<%= product.id %>" class="border-b border-gray-100">
<td class="px-2 py-3 text-sm font-semibold text-gray-800"><%= product.name %></td>
<td class="px-2 py-3 text-sm text-gray-500"><%= product.description %></td>
<td class="px-2 py-3 text-sm font-semibold text-gray-800"><%= product.price %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
test/phoenix_features_web/live/demo_live_test.exs
defmodule PhoenixFeaturesWeb.Components.CalendarSimpleTest do
use PhoenixFeaturesWeb.ConnCase
import Phoenix.LiveViewTest
describe "TableSimple" do
test "shows the component", %{conn: conn} do
{:ok, view, _html} = live(conn, Routes.demos_show_path(conn, :show, "table_simple"))
assert view |> element("#table_simple") |> has_element?()
end
test "click the next and prev buttons paginates to the table", %{conn: conn} do
{:ok, view, _html} = live(conn, Routes.demos_show_path(conn, :show, "table_simple"))
first_product = view |> element("table tbody tr:first-child td:first-child") |> render()
assert first_product =~ "<td class="
{:ok, view, _html} =
view
|> element("a", "Next")
|> render_click()
|> follow_redirect(conn, Routes.demos_show_path(conn, :show, "table_simple", page: 2))
refute view |> element("table tbody tr:first-child td:first-child") |> render() == first_product
{:ok, view, _html} =
view
|> element("a", "Prev")
|> render_click()
|> follow_redirect(conn, Routes.demos_show_path(conn, :show, "table_simple", page: 1))
assert view |> element("table tbody tr:first-child td:first-child") |> render() == first_product
end
end
end