PetalComponents.DataTable.State (petal_components v4.12.0)

Copy Markdown View Source

The data table's entire backend contract in one struct.

Flop-shaped on purpose, Flop-free on purpose: anything that can produce this struct can drive <.data_table>, and anything that can consume it can execute the query - the in-memory Engine.List for plain lists, or (Petal Pro) a Flop adapter for Ecto.

%State{
  order_by: [{:email, :asc}],
  filters: [%{field: :email, op: :contains, value: "d"}],
  page: 1,
  page_size: 10,
  total: 74            # nil = cursor/unknown mode
}

from_params/2 and to_params/1 round-trip the struct through URL params, so URL-as-state (shareable sorts/filters, working back button) is a one-liner in handle_params rather than a hand-rolled encoding.

Filter operators

Text: :contains, :eq, :starts_with - number: :eq, :neq, :gt, :lt, :between - select: :in - date: :before, :on, :after. Engines may support a subset; unknown ops are an engine concern, not a state concern.

Security

from_params/2 never creates atoms from user input: :fields is a required whitelist and anything outside it is dropped, ops outside the known set are dropped, and page/page_size are clamped (:max_page_size, default 100).

Summary

Functions

Removes every filter, resetting to page 1.

Builds a State from URL/event params.

Replaces the filter for field (or removes it when value is nil/empty), resetting to page 1.

Encodes the state as a flat params map suitable for push_patch query strings. Defaults (page 1, empty sorts/filters, the default page size) are omitted so URLs stay clean; total never round-trips - it is a result, not a request.

Returns the state with field as the primary sort: cycles asc -> desc -> removed on repeated calls (the header-click grammar), and always resets to page 1 - a reordered page 7 is meaningless.

Total pages when total is known, else nil (cursor/unknown mode).

Types

filter()

@type filter() :: %{field: atom(), op: atom(), value: term()}

order()

@type order() :: {atom(), :asc | :desc}

t()

@type t() :: %PetalComponents.DataTable.State{
  filters: [filter()],
  order_by: [order()],
  page: pos_integer(),
  page_size: pos_integer(),
  total: non_neg_integer() | nil
}

Functions

clear_filters(state)

Removes every filter, resetting to page 1.

from_params(params, opts)

Builds a State from URL/event params.

Options:

  • :fields (required) - the whitelist of sortable/filterable fields, as atoms. Params referencing any other field are silently dropped.
  • :page_size - default page size when the params carry none (10).
  • :max_page_size - clamp ceiling for user-supplied sizes (100).

Accepted param shapes (all optional, all strings - what to_params/1 emits and what hand-written URLs naturally produce):

  • "order_by" - "email" or "email:desc" or "email:desc,name"
  • "filters" - a list (or Phoenix-style indexed map) of %{"field" => f, "op" => op, "value" => v}
  • "page", "page_size" - integers as strings

put_filter(state, field, op, value)

Replaces the filter for field (or removes it when value is nil/empty), resetting to page 1.

to_params(state, opts \\ [])

Encodes the state as a flat params map suitable for push_patch query strings. Defaults (page 1, empty sorts/filters, the default page size) are omitted so URLs stay clean; total never round-trips - it is a result, not a request.

Pass the same :page_size default given to from_params/2 so the two stay symmetric (an omitted size decodes back to that default).

toggle_sort(state, field)

Returns the state with field as the primary sort: cycles asc -> desc -> removed on repeated calls (the header-click grammar), and always resets to page 1 - a reordered page 7 is meaningless.

total_pages(state)

Total pages when total is known, else nil (cursor/unknown mode).