# `Masu.Action`
[🔗](https://github.com/hattoriai/masu/blob/v0.7.0/lib/masu/action.ex#L1)

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 needs
    `selectable`;
  * `: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 (as `on_select` is), which gives its result
    (below);
  * `event` - a name: the LiveView gets `{Masu.Action, name, context}`
    in its `handle_info/2`, to change its own assigns (open a modal,
    navigate);
  * `navigate` and `patch` (`: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`) - a `Phoenix.LiveView.JS`
    command, run in the browser on the grid's element; the element
    gets a `masu:action` event first, whose `detail` is 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's `scope` (`@current_scope`);
  * `ref` - the action's reference, for `:async` and `Masu.reply/2`;
  * for the rows selected: `selected`, as `Masu.Live.selected/1` gives
    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 one `Repo.update_all/3`), and `count`;
  * for a row: `id`, `row`, the row as the source reads it now, and
    `editors`, 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 says `message` (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 with `Masu.reply(context.ref, result)`, and
    how far it is with `Masu.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.

# `kind`

```elixir
@type kind() :: :header | :cell | :selection | :row
```

# `result`

```elixir
@type result() :: :ok | {:ok, String.t()} | {:error, String.t()} | :async
```

What an action's `on_action` gives.

# `t`

```elixir
@type t() :: %Masu.Action{
  columns: [String.t()] | nil,
  confirm: String.t() | nil,
  danger: boolean(),
  disabled: (term() -&gt; boolean()) | boolean(),
  event: String.t() | nil,
  index: non_neg_integer(),
  js: String.t() | nil,
  keep_selection: boolean(),
  kind: kind(),
  label: String.t(),
  navigate: (term() -&gt; String.t()) | nil,
  on_action: (map() -&gt; term()) | nil,
  patch: (term() -&gt; String.t()) | nil,
  show: (term() -&gt; boolean()) | nil,
  slot: map() | nil,
  types: [Masu.Column.type()] | nil
}
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
