Table filters DSL

v0.0.1

The filters and filter_groups sections of a resource's table block declare the filter inputs above the table and the collapsible panels that hold them.

filter

Declares one filter input. The inline form works too: filter :search, :text, fields: [:title, :slug]. It sits at mishka_gervaz.table.filters.filter.

Call: filter name, type

Arguments:

ArgumentTypeDefaultDescription
nameatomnoneRequired. Filter identifier.
type:text | :select | :boolean | :number | :date | :date_range | :relation | module:textBuilt-in types: :text (the default), :select, :boolean, :number, :date, :date_range and :relation. A module that implements the FilterType behaviour also works.

Options:

OptionTypeDefaultDescription
sourceatomnoneSource field if different from name.
fieldslist(atom)noneSearch fields for :text type.
depends_onatomnoneParent filter for cascading.
visibleboolean | (any -> any)trueVisibility.
restrictedboolean | (any -> any)falseWhen true, only master users see the filter. A function is ignored.
optionslist(any) | (-> any)noneThe choices, as a list or a zero-arity function that returns one. An entry is {label, value}, [label: label, value: value], or a bare value that is its own label. A function runs once, when the table mounts.
defaultanynoneDefault value for the filter. Will be pre-selected on initial load.
presetslist(any)noneRange presets as [{label, map}]. The value is compiled into the config, but no built-in template or handler reads it.
display_fieldatom | (any -> any) | (any, any -> any)noneHow a relation option is labeled: a field name, a one-argument function that gets the record, or a two-argument function that gets the record and the state. A function of one argument needs search_field as well. With a function of two arguments, the search falls back to the field :name.
search_fieldatomnoneField to search in (combobox/async).
include_nilboolean | String.tfalseInclude nil option.
minintegernoneMin value for number type.
maxintegernoneMax value for number type.
min_charsinteger2Min chars before search.
virtualbooleanfalseMarks a filter with no database column of its own. The flag records intent only. What makes such a filter work is resource for the options and apply for the query.
resourcemodulenoneResource to load options from (for virtual).
load_actionatom:readThe Ash action that loads the options. For a master user, :read and an unset value pick :master_read when the resource has it, then :read_any, and then :read.
load(any, any -> any)noneScopes the list of options a relation filter offers: fn query, state -> query.
apply(any, any, any -> any)noneDecides how the chosen value narrows the table query: fn query, value, context -> query. The context is a map with path_params, current_user, master_user?, filter_values and archive_status.
mode:static | :load_more | :search | :search_multi:staticHow a relation filter loads its options. :static loads all of them at once. :search searches as the reader types, one choice at a time. :search_multi does the same for several choices. The schema also accepts :load_more, but the built-in relation filter has no input for it and raises when it draws the filter.
page_sizepos_integer20Number of options per page when using paginated modes.

filter ui

How the filter input is drawn. It sits at mishka_gervaz.table.filters.filter.ui.

OptionTypeDefaultDescription
labelString.t | (-> any)noneFilter label. String or fn -> gettext(...) end for i18n.
placeholderString.t | (-> any)nonePlaceholder text. String or fn -> gettext(...) end for i18n.
promptString.t"Select..."The empty choice of a select. Defaults to "Select...".
disabled_prompt(-> any) | String.tnonePrompt when disabled. Can be a string or a zero-arity function. If not set, will show 'Select {parent label} first'.
iconString.tnoneIcon identifier.
debounceinteger300Debounce in milliseconds.
extramap%{}Template-specific options.

filter preload

Relationships a relation filter loads so that display_field can read them. It sits at mishka_gervaz.table.filters.filter.preload.

OptionTypeDefaultDescription
alwayslist(any)noneRelationships to always load, regardless of user type. Example: always [:site, :category]
tenantlist(any)noneRelationships to load for tenant users. Example: tenant [:category]
masterlist(any)noneRelationships to load for master users. Renamed entries work: master [master_category: :category].

filter_groups.group

Puts filters that already exist into a collapsible panel. A filter may sit in at most one group. It sits at mishka_gervaz.table.filter_groups.group.

Call: group name

Arguments:

ArgumentTypeDefaultDescription
nameatomnoneRequired. Group identifier.

Options:

OptionTypeDefaultDescription
filterslist(atom)noneRequired. Filter names belonging to this group.
collapsedbooleantrueStart collapsed.
collapsiblebooleanfalseLets the reader collapse the group. A group that is not collapsible draws inline, with no panel.
visibleboolean | (any -> any)trueStatic or dynamic visibility.
restrictedboolean | (any -> any)falseWhen true, only master users see the group. A function is ignored.
positionanynoneGroup position (integer, :first, :last).

group ui

How the group is drawn. It sits at mishka_gervaz.table.filter_groups.group.ui.

OptionTypeDefaultDescription
labelString.t | (-> any)noneGroup label.
iconString.tnoneGroup icon.
descriptionString.tnoneGroup description. The value is compiled into the config, but no built-in template or handler reads it.
classString.tnoneGroup CSS classes. The value is compiled into the config, but no built-in template or handler reads it.
header_classString.tnoneGroup header CSS classes. The value is compiled into the config, but no built-in template or handler reads it.
columns1 | 2 | 3 | 4 | 5 | 6noneNumber of grid columns for this group's filters. The default is 3. Only a collapsible group draws a grid.
extramap%{}Additional options. The value is compiled into the config, but no built-in template or handler reads it.