Shared pagination + search behavior for LiveViews using the HawkExDashboard.Table component with URL-backed page/search state.
Usage
defmodule MyLive do
use Phoenix.LiveView
use HawkExDashboard.PaginatedSearch, path: "/hawk_ex"
@impl true
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(:total_pages, 1)
|> assign(:total_count, 0)
|> assign(:loading, true)
|> assign(:error, nil)
|> stream(:my_rows, [])}
end
@impl true
def handle_params(params, _uri, socket) do
{:noreply, paginated_search_params(socket, params, &load_data/3)}
end
defp load_data(socket, page, search) do
start_async(socket, :load_recent, fn ->
MyContext.list(page: page, per_page: 20, search: search)
end)
end
@impl true
def handle_async(:load_recent, {:ok, page_result}, socket) do
handle_paginated_result(socket, page_result, fn socket, result ->
socket
|> assign(:total_pages, result.total_pages)
|> assign(:total_count, result.total_count)
|> stream(:my_rows, result.entries, reset: true)
end)
end
def handle_async(:load_recent, {:exit, reason}, socket) do
{:noreply, assign(socket, error: "...", loading: false)}
end
endThis injects:
handle_event("table_page", ...)handle_event("table_search", ...)handle_event("table_retry", ...)— requires the using module to defineload_data/3(called asload_data(socket, page, search))- Helper functions:
paginated_search_params/3,handle_paginated_result/4
Summary
Functions
Builds an Ecto-compatible order_by keyword from the socket's
current sort assigns. Returns [desc: :inserted_at] as default.
Call from the success branch of handle_async/3. Given the loaded
page_result (must have :page and :total_pages keys), either
redirects to the last valid page if the requested page no longer
exists (e.g. search narrowed results), or calls assign_fun.(socket, result)
to apply the real data to assigns.
Call from handle_params/3. Parses page/search from URL params,
assigns them, and calls the given load_fun.(socket, page, search)
to kick off data loading (typically an async load).
Functions
Builds an Ecto-compatible order_by keyword from the socket's
current sort assigns. Returns [desc: :inserted_at] as default.
Use inside load_data/3 to avoid repeating this pattern in every
LiveView:
defp load_data(socket, page, search) do
start_async(socket, :load_recent, fn ->
MyContext.list(
page: page,
per_page: 20,
search: search,
order_by: current_order_by(socket)
)
end)
end
Call from the success branch of handle_async/3. Given the loaded
page_result (must have :page and :total_pages keys), either
redirects to the last valid page if the requested page no longer
exists (e.g. search narrowed results), or calls assign_fun.(socket, result)
to apply the real data to assigns.
Call from handle_params/3. Parses page/search from URL params,
assigns them, and calls the given load_fun.(socket, page, search)
to kick off data loading (typically an async load).