Actions of your own, on a grid: on the rows selected (:action), on a
row (:row_action), and items of its menus, in a column's header menu
(:header_action) or in the menu of a cell (:cell_action).
<.data_grid id="orders" source={Order} repo={Repo} selectable cell_menu scope={@current_scope}>
<:col field={:customer} />
<:col field={:total} type={:decimal} />
<:action label="Archive" on_action={&OrdersGrid.archive/1} />
<:action :if={@admin?} label="Delete" on_action={&OrdersGrid.delete/1}
confirm="Delete {count} orders?" danger />
<:row_action label="Open" navigate={&~p"/orders/#{&1}"} />
<:row_action label="Send again" on_action={&OrdersGrid.resend/1}
disabled={&(&1.status == "draft")} />
<:header_action label="Explain" on_action={&explain/1} />
<:cell_action label="Open the customer" event="open_customer" columns={["customer"]} />
<:cell_action label="Copy a link" js={JS.dispatch("app:copy-link")} />
<:header_action :let={action} label="Column stats" types={[:integer, :decimal]}>
<.stats column={action.column} />
</:header_action>
</.data_grid>Where they are
:action- a button of the bar above the grid while rows are selected ("4 selected · Archive · Delete · Clear"); it needsselectable;:row_action- an item of the menu at the end of each row (its button, a right click, or the context-menu key or Shift+F10 on the row), and of the menu of its cells;:header_action- an item of a column's header menu;:cell_action- an item of the menu of a cell (a right click, or the context-menu key or Shift+F10 on the active cell).
What an action does
on_action- a function of the action's context, called in the LiveView's process (ason_selectis), which gives its result (below);event- a name: the LiveView gets{Masu.Action, name, context}in itshandle_info/2, to change its own assigns (open a modal, navigate);navigateandpatch(:row_action) - a function of the row that gives a path: the item is a link, which a middle click opens in a tab and a right click copies;js(:header_action,:cell_action) - aPhoenix.LiveView.JScommand, run in the browser on the grid's element; the element gets amasu:actionevent first, whosedetailis the context as the browser has it;- content (
:header_action,:cell_action) - a panel of your own, which the server renders with the context (:let) under the header or the cell, until the person closes it (Escape, or a click away).
The context
A map:
grid- the id of the grid;scope- the grid'sscope(@current_scope);ref- the action's reference, for:asyncandMasu.reply/2;- for the rows selected:
selected, asMasu.Live.selected/1gives it ({:ids, ids}, or{:query, query}when the person selected every row of an Ecto source's query: an action on a million rows is oneRepo.update_all/3), andcount; - for a row:
id,row, the row as the source reads it now, andeditors, the people (of the grid's Issho room) whose editor is open on a cell of the row; - for a header or a cell:
column, the key of the column; - for a cell:
id,row,value, the cell's value; read on the server, not taken from the browser; - for a cell in a selected range:
range,%{ids: ids, columns: keys}, the rows (up to 10,000) and the columns of the range; the ids are the browser's, which an action checks as it checks any params.
In the browser (js), the context has the column's key, the row's id,
and the value as the browser shows it.
The result
on_action gives one of:
:ok(or any other value) - done;{:ok, message}- done, and the grid saysmessage(to a screen reader too);{:error, message}- it failed: the grid says so;:async- it goes on in another process (an Oban job, a task), which says how it went withMasu.reply(context.ref, result), and how far it is withMasu.progress/3. The grid shows it running meanwhile.
After an action on the rows selected, or on a row, that is done (or
replied :ok), the grid reads its rows again, and the selection is
cleared (keep_selection keeps it). A failed action keeps it.
An action of a LiveView's own (to change its assigns, run it with
start_async/3) sends itself a message, and replies when it is done:
<:action label="Archive" on_action={&send(self(), {:archive, &1})} ... />
def handle_info({:archive, context}, socket) do
socket =
start_async(socket, :archive, fn ->
Orders.archive(context.scope, context.selected)
Masu.reply(context.ref, {:ok, "#{context.count} orders archived"})
end)
{:noreply, socket}
end…with on_action giving :async: &(send(self(), {:archive, &1}) && :async).
Which actions a grid offers
:if leaves an action out altogether (:if={@admin?}). columns
(keys) and types limit the columns of a header or cell item, and
show, a function of the Masu.Column, too. A row action's show is
a function of the row: the row has the item or not; its disabled, a
function of the row (or a boolean), shows it unavailable.
confirm asks first: "Delete {count} orders?" ({count} is the rows
selected); danger shows the action as one that destroys.
The server runs only the actions that the grid offers, for the column
and for the row: one left out by :if, hidden or disabled for the row
(read again from the source), or with no selection, is refused.
Summary
Types
@type kind() :: :header | :cell | :selection | :row
What an action's on_action gives.
@type t() :: %Masu.Action{ columns: [String.t()] | nil, confirm: String.t() | nil, danger: boolean(), disabled: (term() -> boolean()) | boolean(), event: String.t() | nil, index: non_neg_integer(), js: String.t() | nil, keep_selection: boolean(), kind: kind(), label: String.t(), navigate: (term() -> String.t()) | nil, on_action: (map() -> term()) | nil, patch: (term() -> String.t()) | nil, show: (term() -> boolean()) | nil, slot: map() | nil, types: [Masu.Column.type()] | nil }