Cinder.Table (Cinder v0.7.2)

View Source

Simplified Cinder table component with intelligent defaults.

This is the new, simplified API for Cinder tables that leverages automatic type inference and smart defaults while providing a clean, Phoenix LiveView-like interface.

Basic Usage

With Resource Parameter (Simple)

<Cinder.Table.table resource={MyApp.User} actor={@current_user}>
  <:col :let={user} field="name" filter sort>{user.name}</:col>
  <:col :let={user} field="email" filter>{user.email}</:col>
  <:col :let={user} field="created_at" sort>{user.created_at}</:col>
</Cinder.Table.table>

With Query Parameter (Advanced)

<!-- Using resource as query -->
<Cinder.Table.table query={MyApp.User} actor={@current_user}>
  <:col :let={user} field="name" filter sort>{user.name}</:col>
  <:col :let={user} field="email" filter>{user.email}</:col>
  <:col :let={user} field="created_at" sort>{user.created_at}</:col>
</Cinder.Table.table>

<!-- Pre-configured query with custom read action -->
<Cinder.Table.table query={Ash.Query.for_read(MyApp.User, :active_users)} actor={@current_user}>
  <:col :let={user} field="name" filter sort>{user.name}</:col>
  <:col :let={user} field="email" filter>{user.email}</:col>
  <:col :let={user} field="created_at" sort>{user.created_at}</:col>
</Cinder.Table.table>

<!-- Query with base filters -->
<Cinder.Table.table query={MyApp.User |> Ash.Query.filter(department: "Engineering")} actor={@current_user}>
  <:col :let={user} field="name" filter sort>{user.name}</:col>
  <:col :let={user} field="email" filter>{user.email}</:col>
  <:col :let={user} field="department.name" filter>{user.department.name}</:col>
  <:col :let={user} field="profile__country" filter>{user.profile.country}</:col>
</Cinder.Table.table>

Field Types

Relationship Fields

Use dot notation to access related resource fields:

<:col :let={user} field="department.name" filter sort>{user.department.name}</:col>
<:col :let={user} field="manager.email" filter>{user.manager.email}</:col>
<:col :let={user} field="office.building.address" filter>{user.office.building.address}</:col>

Embedded Resource Fields

Use double underscore notation for embedded resource fields:

<:col :let={user} field="profile__bio" filter>{user.profile.bio}</:col>
<:col :let={user} field="settings__country" filter>{user.settings.country}</:col>
<:col :let={user} field="metadata__preferences__theme" filter>{user.metadata.preferences.theme}</:col>

Embedded enum fields are automatically detected and rendered as select filters:

<!-- If profile.country is an Ash.Type.Enum, this becomes a select filter -->
<:col :let={user} field="profile__country" filter>{user.profile.country}</:col>

Search Configuration

Search is automatically enabled when columns have the search attribute:

<Cinder.Table.table resource={MyApp.Album} actor={@current_user}>
  <:col :let={album} field="title" filter search>{album.title}</:col>
  <:col :let={album} field="artist.name" filter search>{album.artist.name}</:col>
  <:col :let={album} field="genre" filter>{album.genre}</:col>
</Cinder.Table.table>

Customize search label and placeholder:

<Cinder.Table.table
  resource={MyApp.Album}
  actor={@current_user}
  search={[label: "Find Albums", placeholder: "Search by title or artist..."]}
>
  <:col :let={album} field="title" search>{album.title}</:col>
  <:col :let={album} field="artist.name" search>{album.artist.name}</:col>
</Cinder.Table.table>

Explicitly disable search even with searchable columns:

<Cinder.Table.table resource={MyApp.Album} search={false}>
  <:col :let={album} field="title" search>{album.title}</:col>
</Cinder.Table.table>

Advanced Configuration

<Cinder.Table.table
  resource={MyApp.Album}
  actor={@current_user}
  url_state={@url_state}
  page_size={50}
  theme="modern"
>
  <:col :let={album} field="title" filter sort class="w-1/2">
    {album.title}
  </:col>
  <:col :let={album} field="artist.name" filter sort>
    {album.artist.name}
  </:col>
  <:col :let={album} field="genre" filter={:select}>
    {album.genre}
  </:col>
</Cinder.Table.table>

Configurable Page Sizes

Allow users to select their preferred page size:

<Cinder.Table.table
  resource={MyApp.User}
  actor={@current_user}
  page_size={[default: 25, options: [10, 25, 50, 100]]}
>
  <:col :let={user} field="name" filter sort>{user.name}</:col>
  <:col :let={user} field="email" filter>{user.email}</:col>
  <:col :let={user} field="department.name" filter sort>{user.department.name}</:col>
</Cinder.Table.table>

The page size selector appears automatically when multiple options are provided. For fixed page sizes, use: page_size={25}

Complex Query Examples

<!-- Admin interface with authorization and tenant -->
<Cinder.Table.table
  query={MyApp.User
    |> Ash.Query.for_read(:admin_read, %{}, actor: @actor, authorize?: @authorizing)
    |> Ash.Query.set_tenant(@tenant)
    |> Ash.Query.filter(active: true)}
  actor={@actor}>
  <:col :let={user} field="name" filter sort>{user.name}</:col>
  <:col :let={user} field="email" filter>{user.email}</:col>
  <:col :let={user} field="last_login" sort>{user.last_login}</:col>
  <:col :let={user} field="role" filter={:select}>{user.role}</:col>
</Cinder.Table.table>

Multi-Tenant Examples

<!-- Simple tenant support -->
<Cinder.Table.table
  resource={MyApp.User}
  actor={@current_user}
  tenant={@tenant}>
  <:col :let={user} field="name" filter sort>{user.name}</:col>
  <:col :let={user} field="email" filter>{user.email}</:col>
</Cinder.Table.table>

<!-- Using Ash scope (only actor and tenant are extracted) -->
<Cinder.Table.table
  resource={MyApp.User}
  scope={%{actor: @current_user, tenant: @tenant}}>
  <:col :let={user} field="name" filter sort>{user.name}</:col>
  <:col :let={user} field="email" filter>{user.email}</:col>
</Cinder.Table.table>

<!-- Custom scope struct -->
<Cinder.Table.table
  resource={MyApp.User}
  scope={@my_scope}>
  <:col :let={user} field="name" filter sort>{user.name}</:col>
  <:col :let={user} field="email" filter>{user.email}</:col>
</Cinder.Table.table>

<!-- Mixed usage (explicit overrides scope) -->
<Cinder.Table.table
  resource={MyApp.User}
  scope={@scope}
  actor={@different_actor}>
  <:col :let={user} field="name" filter sort>{user.name}</:col>
  <:col :let={user} field="email" filter>{user.email}</:col>
</Cinder.Table.table>

Features

  • Automatic type inference from Ash resources
  • Intelligent filtering with automatic filter type detection
  • URL state management with browser back/forward support
  • Relationship support using dot notation (e.g., artist.name)
  • Flexible theming with built-in presets

Summary

Functions

process_columns(col_slots, resource)

process_search_config(search_config, columns)

table(assigns)

Renders a data table with intelligent defaults.

Attributes

Resource/Query (Choose One)

  • resource - Ash resource module to query (use either resource or query, not both)
  • query - Ash query to execute (use either resource or query, not both)

Required

  • actor - Actor for authorization (can be nil)

Authorization & Tenancy

  • tenant - Tenant for multi-tenant resources (default: nil)
  • scope - Ash scope containing actor and tenant (default: nil)

Optional Configuration

  • id - Component ID (defaults to "cinder-table")
  • page_size - Number of items per page (default: 25)
  • theme - Theme preset or custom theme map (default: "default")
  • url_state - URL state object from UrlSync.handle_params, or false to disable URL synchronization
  • query_opts - Additional query options for Ash (default: [])
  • on_state_change - Callback for state changes
  • show_filters - Show filter controls (default: auto-detect from columns)
  • show_pagination - Show pagination controls (default: true)
  • loading_message - Custom loading message
  • filters_label - Custom label for filtering (default: "🔍 Filters")
  • empty_message - Custom empty state message
  • class - Additional CSS classes

When to Use Resource vs Query

Use resource for:

  • Simple tables with default read actions
  • Getting started quickly
  • Standard use cases without custom requirements

Use query for:

  • Custom read actions (e.g., :active_users, :admin_only)
  • Pre-filtering data with base filters
  • Custom authorization settings
  • Tenant-specific queries
  • Admin interfaces with complex requirements
  • Integration with existing Ash query pipelines

Column Slot

The :col slot supports these attributes:

  • field (required) - Field name or relationship path (e.g., "user.name")
  • filter - Enable filtering (boolean or filter type atom)
  • sort - Enable sorting (boolean)
  • class - CSS classes for this column
  • label - Column header label (auto-generated from field name if not provided)

Filter types: :text, :select, :multi_select, :multi_checkboxes, :boolean, :checkbox, :date_range, :number_range

Filter Type Selection:

  • :multi_select - Modern tag-based interface with dropdown (default for array types)
    • Supports match_mode: :any (default) for OR logic or match_mode: :all for AND logic
  • :multi_checkboxes - Traditional checkbox interface for multiple selection
    • Supports match_mode: :any (default) for OR logic or match_mode: :all for AND logic
  • :boolean - Radio buttons for true/false selection (default for boolean fields)
  • :checkbox - Single checkbox for "show only X" filtering
    • For boolean fields: defaults to filtering for true when checked
    • For non-boolean fields: requires explicit value option

Filter Slot

The :filter slot allows filtering on fields that are not displayed in the table:

<:filter field="created_at" type="date_range" label="Creation Date" />
<:filter field="department" type="select" options={["Sales", "Marketing"]} />

The :filter slot supports these attributes:

Universal attributes (all filter types):

  • field (required) - Field name or relationship path (e.g., "user.name")
  • type - Filter type (e.g., :select, :text, :date_range) - auto-detected if not provided
  • label - Filter label (auto-generated from field name if not provided)
  • fn - Custom filter function

Filter type specific attributes:

Text filters (:text):

  • operator - Operator (:contains, :starts_with, :ends_with, :equals)
  • case_sensitive - Whether filter should be case sensitive
  • placeholder - Placeholder text for input

Select filters (:select):

  • options - Options list (e.g., [{"Label", "value"}])
  • prompt - Prompt text ("Choose..." style text)

Multi-select filters (:multi_select, :multi_checkboxes):

  • options - Options list (e.g., [{"Label", "value"}])
  • match_mode - Match mode (:any for OR logic, :all for AND logic)
  • prompt - Prompt text (:multi_select only)

Boolean filters (:boolean):

  • labels - Custom labels (map with :true, :false keys)

Checkbox filters (:checkbox):

  • value - Filter value when checked
  • label - Display text (required for checkbox)

Date range filters (:date_range):

  • format - Format (:date or :datetime)
  • include_time - Whether to include time selection

Number range filters (:number_range):

  • step - Step value for inputs
  • min - Minimum allowed value
  • max - Maximum allowed value

Filter-only slots use the same filter types and options as column filters, but are purely for filtering without displaying the field in the table.

Column Labels

Column labels are automatically generated from field names using intelligent humanization:

  • name → "Name"
  • email_address → "Email Address"
  • user.name → "User Name"
  • created_at → "Created At"

You can override the auto-generated label by providing a label attribute.

Row Click Functionality

Tables can be made interactive by providing a row_click function that will be executed when a row is clicked:

<Cinder.Table.table
  resource={MyApp.Item}
  actor={@current_user}
  row_click={fn item -> JS.navigate(~p"/items/#{item.id}") end}
>
  <:col :let={item} field="name" filter sort>{item.name}</:col>
  <:col :let={item} field="description">{item.description}</:col>
</Cinder.Table.table>

The row_click function receives the row item as its argument and should return a Phoenix.LiveView.JS command or similar action. When provided, rows will be styled to indicate they are clickable with hover effects and cursor changes.

Attributes

  • resource (:atom) - The Ash resource to query (use either resource or query, not both). Defaults to nil.
  • query (:any) - The Ash query to execute (use either resource or query, not both). Defaults to nil.
  • actor (:any) - Actor for authorization. Defaults to nil.
  • tenant (:any) - Tenant for multi-tenant resources. Defaults to nil.
  • scope (:any) - Ash scope containing actor and tenant. Defaults to nil.
  • id (:string) - Unique identifier for the table. Defaults to "cinder-table".
  • page_size (:any) - Number of items per page or [default: 25, options: [10, 25, 50]]. Defaults to 25.
  • theme (:any) - Theme name or theme map. Defaults to "default".
  • url_state (:any) - URL state object from UrlSync.handle_params, or false to disable. Defaults to false.
  • query_opts (:list) - Additional query options (load, select, etc.). Defaults to [].
  • on_state_change (:any) - Custom state change handler. Defaults to nil.
  • show_pagination (:boolean) - Whether to show pagination controls. Defaults to true.
  • show_filters (:boolean) - Whether to show filter controls (auto-detected if nil). Defaults to nil.
  • loading_message (:string) - Message to show while loading. Defaults to "Loading...".
  • filters_label (:string) - Label for the filters component. Defaults to "🔍 Filters".
  • search (:any) - Search configuration. Auto-enables when searchable columns exist. Use [label: "Custom", placeholder: "Custom..."] to customize, [fn: my_search_fn] for custom search function, or false to disable. Defaults to nil.
  • empty_message (:string) - Message to show when no results. Defaults to "No results found".
  • class (:string) - Additional CSS classes. Defaults to "".
  • row_click (:any) - Function to call when a row is clicked. Receives the row item as argument. Defaults to nil.

Slots

  • col (required) - Accepts attributes:
    • field (:string) - Field name (supports dot notation for relationships or __ for embedded attributes). Required when filter or sort is enabled.
    • filter (:any) - Enable filtering (true, false, filter type atom, or unified config [type: :select, options: [...], fn: &custom_filter/2]).
    • filter_options (:list) - Custom filter options (e.g., [options: [{"Label", "value"}]]) - DEPRECATED: Use filter={[type: :select, options: [...]]} instead.
    • sort (:any) - Enable sorting (true, false, or unified config [cycle: [nil, :asc, :desc]]).
    • search (:boolean) - Enable global search on this column (makes column searchable in global search input).
    • label (:string) - Custom column label (auto-generated if not provided).
    • class (:string) - CSS classes for this column.
  • filter - Accepts attributes:
    • field (:string) (required) - Field name (supports dot notation for relationships or __ for embedded attributes).
    • type (:any) - Filter type as atom or string (e.g., :select, "select", :text, "text", etc.) - auto-detected if not provided.
    • options (:list) - Filter options for select/multi-select filters.
    • value (:any) - Filter value for checkbox filters.
    • operator (:atom) - Text filter operator (:contains, :starts_with, :ends_with, :equals).
    • case_sensitive (:boolean) - Whether text filter should be case sensitive.
    • placeholder (:string) - Placeholder text for input filters.
    • labels (:map) - Custom labels for boolean filter (map with :true, :false keys).
    • prompt (:string) - Prompt text for select filters ('Choose...' style text).
    • match_mode (:atom) - Multi-select match mode (:any for OR logic, :all for AND logic).
    • format (:atom) - Date range format (:date or :datetime).
    • include_time (:boolean) - Whether date range should include time selection.
    • step (:any) - Step value for number range filters.
    • min (:any) - Minimum value for number range filters.
    • max (:any) - Maximum value for number range filters.
    • fn (:any) - Custom filter function.
    • label (:string) - Custom filter label (auto-generated if not provided).