PhoenixKitWeb.TableColumns (phoenix_kit v2.40.1)

Copy Markdown View Source

Which columns a table shows, and in what order, for each user — stored as the "columns" field of the user's PhoenixKit.Users.ViewPrefs for the table's key, edited through core's live column_settings_modal/1.

A table describes itself with a spec:

%{
  key: "catalogue.detail_items",           # the ViewPrefs key
  columns: [%{id: "sku", label: fn -> gettext("SKU") end}, …],
  defaults: ["sku", "price"],              # optional; all columns when absent
  site_default: &MyApp.site_columns/0,     # optional; -> [id] | nil
  min: 0                                   # optional; fewest a user may keep
}

columns is exactly what the modal can show or hide. A column that is always on (a Name, an Actions cell) is not in it: the table draws it around the resolved list.

The rules

  • A user who has not chosen sees the site's default when there is one (site_default), else defaults. Reset takes the user's choice back out, so they follow that default again — it does not save a copy of it.
  • An empty list is a choice: every optional column hidden — unless the spec keeps a :min: a choice or site default with fewer columns than that is not used, and the next default in line shows instead.
  • Ids no longer in columns (a custom field deleted, an extension switched off) are skipped when reading and never rewritten by a read; when none of a non-empty choice is left, the default shows until the user changes something.
  • A change saves the list the user now sees — ids already skipped are not carried along.

In a LiveView

def handle_event(event, params, socket)
    when event in ~w(add_column remove_column reorder_columns reset_columns) do
  {:noreply, TableColumns.handle_event(event, params, socket, spec(), :columns)}
end

handle_event/5 updates the assign and saves for the signed-in user (PhoenixKitWeb.Actor); with nobody signed in the change lasts for the page. A LiveComponent has only the assigns its parent passed, so it needs phoenix_kit_current_scope (or phoenix_kit_current_user) handed in, or its choices are never saved.

A modal with several tables (sections) sends a "section" param with add, remove and reorder — pick the spec by it and pass that. Reset carries no section: it resets every table the modal shows, so handle it apart.

Summary

Functions

current with id added at the end — when it is offered and not shown yet.

What a user who has not chosen sees: the site's default, read by the same rules as a user's choice (an empty one is a choice), else the spec's defaults (all columns when there are none).

The events handle_event/5 answers.

Applies one of the modal's events (events/0) to the list in assign name, saves it for the signed-in user, and answers the socket. Anything else — an unknown event, a malformed param — leaves it alone.

The columns user sees for the table spec describes.

current without id — unless that would leave fewer than the spec's :min.

current in the order ordered gives. Ids ordered names that are not shown are ignored; shown ones it leaves out keep their place at the end, so a partial or crafted payload can reorder but never drop or add.

The columns a stored choice shows (see the rules in the moduledoc): nil for no choice, else the stored list.

Types

column()

@type column() :: %{id: String.t(), label: String.t() | (-> String.t())}

spec()

@type spec() :: %{
  :key => String.t(),
  :columns => [column()],
  optional(:defaults) => [String.t()],
  optional(:site_default) => (-> [String.t()] | nil),
  optional(:min) => non_neg_integer()
}

Functions

add(current, id, spec)

@spec add([String.t()], term(), spec()) :: [String.t()]

current with id added at the end — when it is offered and not shown yet.

default(spec)

@spec default(spec()) :: [String.t()]

What a user who has not chosen sees: the site's default, read by the same rules as a user's choice (an empty one is a choice), else the spec's defaults (all columns when there are none).

events()

@spec events() :: [String.t()]

The events handle_event/5 answers.

handle_event(event, params, socket, spec, name)

@spec handle_event(String.t(), map(), Phoenix.LiveView.Socket.t(), spec(), atom()) ::
  Phoenix.LiveView.Socket.t()

Applies one of the modal's events (events/0) to the list in assign name, saves it for the signed-in user, and answers the socket. Anything else — an unknown event, a malformed param — leaves it alone.

load(user, spec)

The columns user sees for the table spec describes.

remove(current, id, spec)

@spec remove([String.t()], term(), spec()) :: [String.t()]

current without id — unless that would leave fewer than the spec's :min.

reorder(current, ordered, spec)

@spec reorder([String.t()], term(), spec()) :: [String.t()]

current in the order ordered gives. Ids ordered names that are not shown are ignored; shown ones it leaves out keep their place at the end, so a partial or crafted payload can reorder but never drop or add.

resolve(stored, spec)

@spec resolve(term(), spec()) :: [String.t()]

The columns a stored choice shows (see the rules in the moduledoc): nil for no choice, else the stored list.