Masu.Source behaviour (Masu v0.1.0)

Copy Markdown View Source

Where a grid's rows come from.

A grid never holds all its rows. It asks its source for the rows of its query (prepare/3), how many there are (count/1), and the rows the browser shows (rows/3): a block at a time, as the person scrolls.

Masu has two sources:

  • Masu.Source.List, for rows the LiveView already has (rows={@users});
  • Masu.Source.Ecto, for an Ecto query or schema (source={User} repo={Repo}): it sorts, filters, searches and counts in the database, and reads only the rows on the screen.

A source of your own is a module with these callbacks, given as source={{MySource, opts}}; the grid calls init/2 with the opts.

Summary

Types

The state of a source; a source keeps what it needs between calls.

t()

Callbacks

Takes changes into the source's rows (see Masu.Live): {:upsert, row} and {:delete, row_or_id}, oldest first, with the grid's function that gives a row's id. The grid calls prepare/3 after it.

The source's columns, for a grid with no :col slots: a query tool's, whose columns are known when its query runs. Each is a key (a string), or a keyword list or map of a :col's attributes (key, label, type, field, width...). A column without a field reads the row at its place: its rows are lists of values in the columns' order. A column without a type is :auto: the grid learns it from the values (see Masu.Column).

How many rows the query has: a number, {:at_least, n} when the source knows only that there are at least n (a search engine's "10,000+"), or :unknown. A grid whose source does not know scrolls over the rows it read, and a block that comes back short says where they end (see the Export, selection and large tables guide).

The rows with the given ids, among the rows the source may show (its query without the person's filters): the rows a person edits. A row that is not there is not given, so that no one edits a row the grid would not show them.

The operators of Masu.Filter the source filters a column by. Without it, every operator of the column's type. A source that is not a database Masu translates itself (an API, a search engine, another query tool) gives those it runs: the filter menu, the chips, the menu of a cell, a view's params (a link, a saved view) and Masu.Test offer only them, and the server refuses the others.

Starts the source from what the grid was given.

The place (from 0) of the row with an id in the rows of the query, in their order, or nil when the query does not have it: where another person's cell is in this person's order (Masu.Presence).

For a tree (Masu.Tree): the rows from offset, as rows/3 gives them, each with where it is in the tree (Masu.Tree.node_info/0), or nil when the source is not a tree. The rows of a tree are the roots and the children of the rows that are open (Masu.Query's expanded), in order.

Applies a query: the rows that count/1 counts and rows/3 reads are from now on those of the query, in its order. The query is checked against the columns (Masu.Query).

The rows from offset (from 0), at most limit of them; or {rows, count}, when the source learned the count of the query with them (a total_count of the same request), which the grid takes; or {:error, reason}, which a grid with load={:async} shows (see Masu.Source.Error).

Every row of a query but those of except (ids, as the browser has them): the person selected every row (Masu.Live.selected/1). Gives their ids, or, for a database, a query of them.

For a tree: the ids of the siblings of a row (it among them) that have children: the rows that open with the row's level (*).

Whether the source can sort and filter by a column. A database can sort only by a field it stores, not by a function of the row; the grid makes such a column one that does not sort or filter.

The totals of the rows of the query (Masu.Group): the value of each column with an aggregate, by its key.

The values of a column among the rows of a query, with how many rows have each, the most frequent first: what a column's filter menu offers. search narrows them to the values that contain it, without case. nil counts as a value: the rows with no value.

Functions

The source for what a grid was given: a list, an Ecto query or schema (with the :repo option), or {module, opts}.

Types

state()

@type state() :: term()

The state of a source; a source keeps what it needs between calls.

t()

@type t() :: %Masu.Source{module: module(), state: state()}

Callbacks

change(state, list, function)

(optional)
@callback change(state(), [Masu.Live.change()], (term() -> term())) :: state()

Takes changes into the source's rows (see Masu.Live): {:upsert, row} and {:delete, row_or_id}, oldest first, with the grid's function that gives a row's id. The grid calls prepare/3 after it.

A source that reads its rows anew for each query (as a database does) does not need it: without it, the grid asks the query again.

columns(state)

(optional)
@callback columns(state()) :: [String.t() | keyword() | map()]

The source's columns, for a grid with no :col slots: a query tool's, whose columns are known when its query runs. Each is a key (a string), or a keyword list or map of a :col's attributes (key, label, type, field, width...). A column without a field reads the row at its place: its rows are lists of values in the columns' order. A column without a type is :auto: the grid learns it from the values (see Masu.Column).

count(state)

@callback count(state()) :: non_neg_integer() | {:at_least, non_neg_integer()} | :unknown

How many rows the query has: a number, {:at_least, n} when the source knows only that there are at least n (a search engine's "10,000+"), or :unknown. A grid whose source does not know scrolls over the rows it read, and a block that comes back short says where they end (see the Export, selection and large tables guide).

fetch(state, ids, function)

(optional)
@callback fetch(state(), ids :: [term()], (term() -> term())) :: [term()]

The rows with the given ids, among the rows the source may show (its query without the person's filters): the rows a person edits. A row that is not there is not given, so that no one edits a row the grid would not show them.

filters(state, t)

(optional)
@callback filters(state(), Masu.Column.t()) :: [Masu.Filter.op()]

The operators of Masu.Filter the source filters a column by. Without it, every operator of the column's type. A source that is not a database Masu translates itself (an API, a search engine, another query tool) gives those it runs: the filter menu, the chips, the menu of a cell, a view's params (a link, a saved view) and Masu.Test offer only them, and the server refuses the others.

Masu runs :between as :gte and :lte for a source that has those and not it. [] is a column the source does not filter by. See the Sources guide for what each operator means.

init(input, opts)

@callback init(input :: term(), opts :: keyword()) :: state()

Starts the source from what the grid was given.

locate(state, id, function)

(optional)
@callback locate(state(), id :: term(), (term() -> term())) :: non_neg_integer() | nil

The place (from 0) of the row with an id in the rows of the query, in their order, or nil when the query does not have it: where another person's cell is in this person's order (Masu.Presence).

nodes(state, offset, limit)

(optional)
@callback nodes(state(), offset :: non_neg_integer(), limit :: pos_integer()) ::
  [{term(), Masu.Tree.node_info()}] | nil

For a tree (Masu.Tree): the rows from offset, as rows/3 gives them, each with where it is in the tree (Masu.Tree.node_info/0), or nil when the source is not a tree. The rows of a tree are the roots and the children of the rows that are open (Masu.Query's expanded), in order.

prepare(state, t, list)

@callback prepare(state(), Masu.Query.t(), [Masu.Column.t()]) :: state()

Applies a query: the rows that count/1 counts and rows/3 reads are from now on those of the query, in its order. The query is checked against the columns (Masu.Query).

rows(state, offset, limit)

@callback rows(state(), offset :: non_neg_integer(), limit :: pos_integer()) ::
  [term()] | {[term()], non_neg_integer()} | {:error, term()}

The rows from offset (from 0), at most limit of them; or {rows, count}, when the source learned the count of the query with them (a total_count of the same request), which the grid takes; or {:error, reason}, which a grid with load={:async} shows (see Masu.Source.Error).

select_all(state, t, list, except, function)

(optional)
@callback select_all(
  state(),
  Masu.Query.t(),
  [Masu.Column.t()],
  except :: [term()],
  (term() -> term())
) ::
  {:ids, [term()]} | {:query, term()}

Every row of a query but those of except (ids, as the browser has them): the person selected every row (Masu.Live.selected/1). Gives their ids, or, for a database, a query of them.

siblings(state, id)

(optional)
@callback siblings(state(), id :: term()) :: [term()]

For a tree: the ids of the siblings of a row (it among them) that have children: the rows that open with the row's level (*).

supports?(state, t)

(optional)
@callback supports?(state(), Masu.Column.t()) :: boolean()

Whether the source can sort and filter by a column. A database can sort only by a field it stores, not by a function of the row; the grid makes such a column one that does not sort or filter.

totals(state)

(optional)
@callback totals(state()) :: %{required(String.t()) => term()}

The totals of the rows of the query (Masu.Group): the value of each column with an aggregate, by its key.

values(state, t, t, list, keyword)

(optional)
@callback values(state(), Masu.Column.t(), Masu.Query.t(), [Masu.Column.t()], keyword()) ::
  [
    {term(), non_neg_integer()}
  ]

The values of a column among the rows of a query, with how many rows have each, the most frequent first: what a column's filter menu offers. search narrows them to the values that contain it, without case. nil counts as a value: the rows with no value.

A source without it offers no values: the menu then filters by what the person types.

Functions

new(rows, grid_opts)

@spec new(term(), keyword()) :: t()

The source for what a grid was given: a list, an Ecto query or schema (with the :repo option), or {module, opts}.