Masu.Column (Masu v0.7.0)

Copy Markdown View Source

A column of a grid: which value of a row it shows, how, and what a person may do with it.

A grid makes its columns from its :col slots (see Masu.Components.data_grid/1):

<:col field={:name} />
<:col field={:total} type={:decimal} label="Total" width={140} />
<:col :let={order} field={:status} type={:enum}>
  <.badge status={order.status} />
</:col>

Types

The type says how the browser formats a value, in the grid's locale, how a filter reads the value it is given, and where the cell aligns:

  • :text (the default) and :enum, as they are;
  • :integer, :number and :decimal, as numbers, aligned to the end of the cell. A decimal goes to the browser as a string, so no digit is lost on the way;
  • :date, :datetime and :time;
  • :boolean, as a check mark.

A column with an inner block is rendered by it, on the server, for each row that the browser shows; the type still says how it sorts and filters.

filters, a list of Masu.Filter operators, limits a column's filters to those: the operators the app runs, in a controlled grid (on_query) whose app's queries have fewer than Masu (as a source's Masu.Source.filters/2 does). Masu adds :between for a column that has :gte and :lte.

type={:auto} is a column whose type the grid learns from its values, in the first block of rows of its source: integers make an :integer column, Decimals a :decimal one, Dates a :date one, and so on; values of several types, or of none of these, make it :text. The grid keeps the type until its source changes. A column whose rows are a query tool's results, of no types, takes it.

Rows as lists

field={2} reads the value at that place of a row that is a list (from 0): a query tool's rows, which come as lists of values in the order of its columns. Its key is the place as a string unless given.

field={[:customer, :name]} reads a field of a related row: a path of the row's associations (or keys of maps in it) to a field. Its key is the path joined with dots ("customer.name") and its label the path in words ("Customer name") unless given. An Ecto source sorts, filters, searches and groups by it in the database, with a join; a list follows the path in each row. The column is read-only.

A path through several related rows (a has_many or many_to_many: [:tags, :name]) is a column of many values: a list of the values of the rows that are loaded. It does not sort or group; it filters by "has any of" (:in), "has all of" (:all_of), "has none of" (:none_of), "has none" (:is_nil) and "has some" (:not_nil). The source says whether a path is of many values (an Ecto source from its associations, a list from its rows); many={true} says so for a source of your own.

Editing

In a grid that edits, values lists the values of an :enum column, which its editor offers (a schema's Ecto.Enum field has them unless given); decimals shows a :decimal or :number column with that many digits after the point, whatever the value has.

editor gives the column an editor of your own, in place of the one of its type; see editor/0 and the Editors of your own guide.

Layout

width is the column's width in pixels, of its type unless given, and at least min_width (48 unless given), the narrowest a person resizes it to.

grow makes the column take the room the columns leave in a grid wider than them, from its width up: a title or a description, rather than empty room after the last column. grow={2} takes twice the share of a grow column. A grid narrower than its columns scrolls sideways, and a column that grows is its width there. A person who resizes the column gives it their width, and it grows no more (in a view, too); see Masu.Layout.

hidden starts the column hidden (a person shows it from the columns menu), and pinned={:start} or pinned={:end} keeps it in view while the others scroll sideways. See Masu.Layout.

Totals

aggregate={:sum} totals the column in the row of each group and in a row of totals at the bottom of the grid; see Masu.Group.

Summary

Types

How a column totals its values, in a group's row and the totals row: :sum, :avg, :min, :max, :count (of the values that are not nil), :count_distinct, or, for a list, a function of the rows.

An editor of your own (editor on a :col)

t()

Functions

Whether a person may edit the column's cells: its grid edits (it has a changeset or an update), the column names a field (not a value function), and it does not say editable={false}. A column whose editable is a function of the row edits the cells of the rows for which it gives true (see editable?/2).

Whether a person may edit the column's cell of a row: the column edits (editable?/1), and its editable function, if it has one, says so for the row.

Finds a column by its key.

The value of the column for a row.

The type of a column of these values: the type that every value which is not nil has (an integer and a float make :number), else :text.

Whether the column's value is a list: a path through several related rows.

Makes a column from the attributes of a :col slot (or a keyword list or map of the same names).

Whether the column reads a field of a related row (field={[:customer, :name]}).

Whether the column's values can be sorted and filtered by a database: it reads a field, or a path to a field of a related row, not a function of the row.

A column of type={:auto} made again with a type (infer/1); any other column as it is.

The types a column can have.

Types

aggregate()

@type aggregate() ::
  :sum | :avg | :min | :max | :count | :count_distinct | ([term()] -> term())

How a column totals its values, in a group's row and the totals row: :sum, :avg, :min, :max, :count (of the values that are not nil), :count_distinct, or, for a list, a function of the rows.

editor()

@type editor() ::
  {:form, (map() -> Phoenix.LiveView.Rendered.t())} | {:js, String.t(), map()}

An editor of your own (editor on a :col):

  • a function component, of a form field: the grid renders it on the server, in a popover under the cell, and saves what its form posts (a component of Koyomi or Kotoba, a search of the server, any HEEx);
  • the name of an editor of the browser's, given to configure in the app's JavaScript ({editors: {color: ...}}), or {name, options} with options of it, which go to it as JSON.

t()

@type t() :: %Masu.Column{
  aggregate: aggregate() | nil,
  align: :start | :center | :end,
  auto: map() | nil,
  creatable: boolean(),
  decimals: non_neg_integer() | nil,
  editable: boolean() | nil,
  editable_if: (term() -> boolean()) | nil,
  editor: editor() | nil,
  field: atom() | String.t() | non_neg_integer() | [atom()] | nil,
  filter_ops: [Masu.Filter.op()] | nil,
  filterable: boolean(),
  grow: non_neg_integer(),
  hidden: boolean(),
  key: String.t(),
  label: String.t(),
  many: boolean() | nil,
  min_width: pos_integer(),
  pinned: :start | :end | nil,
  raw: boolean(),
  searchable: boolean(),
  slot: map() | nil,
  sortable: boolean(),
  type: type(),
  value: (term() -> term()) | nil,
  values: [term()] | nil,
  width: pos_integer()
}

type()

@type type() ::
  :text
  | :enum
  | :integer
  | :number
  | :decimal
  | :date
  | :datetime
  | :time
  | :boolean

Functions

editable?(column)

@spec editable?(t()) :: boolean()

Whether a person may edit the column's cells: its grid edits (it has a changeset or an update), the column names a field (not a value function), and it does not say editable={false}. A column whose editable is a function of the row edits the cells of the rows for which it gives true (see editable?/2).

editable?(column, row)

@spec editable?(t(), term()) :: boolean()

Whether a person may edit the column's cell of a row: the column edits (editable?/1), and its editable function, if it has one, says so for the row.

find(columns, key)

@spec find([t()], String.t()) :: t() | nil

Finds a column by its key.

get(column, row)

@spec get(t(), term()) :: term()

The value of the column for a row.

infer(values)

@spec infer([term()]) :: type()

The type of a column of these values: the type that every value which is not nil has (an integer and a float make :number), else :text.

many?(column)

@spec many?(t()) :: boolean()

Whether the column's value is a list: a path through several related rows.

new(attrs)

@spec new(map() | keyword()) :: t()

Makes a column from the attributes of a :col slot (or a keyword list or map of the same names).

field names the value of a row (a key of a map or a field of a struct), or is a path to a field of a related row ([:customer, :name]); value, a function of the row, computes it instead. key names the column on the wire and in Masu.Test; it is the field as a string unless given. label is the field in words unless given.

Raises ArgumentError for a column with neither field nor value, or with an unknown type.

path?(column)

@spec path?(t()) :: boolean()

Whether the column reads a field of a related row (field={[:customer, :name]}).

stored?(column)

@spec stored?(t()) :: boolean()

Whether the column's values can be sorted and filtered by a database: it reads a field, or a path to a field of a related row, not a function of the row.

typed(column, type)

@spec typed(t(), type()) :: t()

A column of type={:auto} made again with a type (infer/1); any other column as it is.

types()

@spec types() :: [type()]

The types a column can have.