Reusable data-table LiveComponent. Owns its own filter / sort / page state,
drives data via a parent-supplied :fetcher function, and emits query-change
notifications to the parent LV so the parent can update the URL via
live_patch. Real-time updates from the parent are triggered with
Phoenix.LiveView.send_update(__MODULE__, id: "...", refresh: true).
Why a LiveComponent
Three list views (Workflows, Schedules, Inputs) need the same filter/sort/ pagination machinery. Extracting it as a function component would force every parent to hold the state itself, defeating the point. As a stateful component it owns the state once and the parents stay declarative.
Mount-time assigns from the parent
| assign | required | description |
|---|---|---|
:id | yes | Unique component id (also used for stream key) |
:fetcher | yes | (query :: map -> {rows :: list, total :: integer}) |
:columns | yes | List of column specs (see below) |
:filters | no | List of filter definitions (see below); default [] |
:query | yes | Current query — usually parsed from URL by the parent |
:row_id | no | (row -> id_string) for stream identity (default: & &1.id) |
:row_navigate | no | (row -> url_string) — clicking a row live-navigates here |
:per_page | no | default 20 |
:empty_title | no | empty-state title (default "No results") |
:empty_description | no | empty-state subtitle (optional) |
:empty_icon | no | empty-state icon (optional) |
:search_placeholder | no | search input placeholder (default "Search…") |
Column spec
%{
key: :status, # atom — accessible via Map.get(row, key) | row[to_string(key)]
label: "Status",
sortable?: false,
class: nil, # optional td class
render: &my_render/1 # (row -> rendered HEEx) — required
}Filter spec
%{
key: :status, # atom or string — keyed into query map
type: :select | :search,
label: "Status",
options: ["", "running", "failed"], # for :select, [""] is "all"
placeholder: nil
}Query map shape
%{
page: integer, # 1-based
search: string | nil, # text search
sort_by: atom | nil,
sort_dir: :asc | :desc,
# plus any filter keys, e.g. status: "running"
}Communication
- LC → parent: when query changes, the LC sends a regular Erlang message
{:data_table, component_id, :query_changed, new_query}to the parent LV pid (captured at mount). The parent decides whether tolive_patchor update assigns directly. - Parent → LC: new
:queryviasend_update(__MODULE__, id: id, query: q)re-fetches automatically.:refresh -> truere-fetches the current query.