Configuration
View SourceLiveTable provides flexible configuration options that can be set at three different levels, with each level taking precedence over the previous one.
Configuration Hierarchy
- LiveTable Defaults - Built-in sensible defaults
- Application-wide Configuration - Set in your
config.exs
- Per-table Configuration - Override specific tables via
table_options/0
Application-wide Configuration
Configure LiveTable globally in your config/config.exs
:
config :live_table,
repo: YourApp.Repo,
pubsub: YourApp.PubSub,
components: YourApp.CustomComponents, # Optional
defaults: %{
pagination: %{
enabled: true,
sizes: [10, 25, 50]
},
sorting: %{
enabled: true,
default_sort: [id: :asc]
},
exports: %{
enabled: true,
formats: [:csv, :pdf]
},
search: %{
enabled: true,
debounce: 300,
placeholder: "Search..."
},
mode: :table,
use_streams: true
}
Required Configuration
repo
- Your application's Ecto repositorypubsub
- Your application's PubSub module for real-time updates
Optional Configuration
components
- Custom component module (defaults to built-in components)defaults
- Override default table options
Per-table Configuration
Override settings for specific tables by implementing table_options/0
:
defmodule YourAppWeb.ProductLive.Index do
use LiveTable.LiveResource, schema: Product
def table_options do
%{
pagination: %{
enabled: true,
sizes: [5, 15, 30]
},
sorting: %{
default_sort: [name: :asc]
},
exports: %{
enabled: false # Disable exports for this table
},
mode: :table
}
end
end
Configuration Options
Pagination Options
Control how pagination behaves in your tables:
pagination: %{
enabled: true, # Enable/disable pagination
sizes: [10, 25, 50] # Available page size options
}
Options:
enabled
(boolean) - Enable or disable pagination entirelysizes
(list) - Available page size options
Examples:
# Disable pagination (show all records)
pagination: %{enabled: false}
# Custom page sizes
pagination: %{
enabled: true,
sizes: [5, 15, 30]
}
Sorting Options
Configure sorting behavior and defaults:
sorting: %{
enabled: true, # Enable/disable sorting
default_sort: [id: :asc] # Default sort order
}
Options:
enabled
(boolean) - Enable or disable sortingdefault_sort
(keyword list) - Default sort columns and directions
Examples:
# Default sort order
sorting: %{
default_sort: [name: :asc]
}
# Disable sorting entirely
sorting: %{
enabled: false
}
Export Options
Control export functionality:
exports: %{
enabled: true, # Enable/disable exports
formats: [:csv, :pdf] # Available export formats
}
Options:
enabled
(boolean) - Enable or disable exportsformats
(list) - Available formats (:csv
,:pdf
, or both)
Examples:
# CSV only
exports: %{
enabled: true,
formats: [:csv]
}
# Disable exports
exports: %{
enabled: false
}
Search Options
Configure the global search behavior:
search: %{
enabled: true, # Enable/disable search
debounce: 300, # Debounce time in milliseconds
placeholder: "Search..." # Search input placeholder text
}
Options:
enabled
(boolean) - Enable or disable global searchdebounce
(integer) - Delay before search executes (milliseconds)placeholder
(string) - Search input placeholder text
View Mode Options
Configure table display modes:
mode: :table # or :card
Table Mode:
mode: :table
Card Mode:
mode: :card,
card_component: &product_card/1
Streams vs Assigns
LiveTable supports two data handling modes controlled by use_streams
:
# Default: Use Phoenix LiveView streams (recommended)
use_streams: true # Default
# Alternative: Use traditional assigns
use_streams: false
When use_streams: true
(default):
- Uses Phoenix LiveView streams for efficient DOM updates
- Pass
streams={@streams}
to the live_table component - Better performance with large datasets and real-time updates
When use_streams: false
:
- Uses traditional assigns for data
- Pass
streams={@resources}
to the live_table component - Simpler for small datasets or when streams aren't needed
Template usage:
# With streams (default)
<.live_table
fields={fields()}
filters={filters()}
options={@options}
streams={@streams}
/>
# With assigns
<.live_table
fields={fields()}
filters={filters()}
options={@options}
streams={@resources}
/>
Custom Components
LiveTable allows complete UI customization through custom components. You can replace any section of the table interface with your own implementation while still benefiting from LiveTable's data handling, filtering, and state management.
# Override entire sections
def table_options do
%{
custom_header: {MyApp.CustomComponents, :header},
custom_content: {MyApp.CustomComponents, :content},
custom_footer: {MyApp.CustomComponents, :footer}
}
end
Available customizations:
custom_header
- Replace the entire header section (search, filters, controls)custom_content
- Replace the table/card content areacustom_footer
- Replace the footer section (pagination, exports)
Real-World Example: College Counselling Interface
This example shows a sophisticated custom header that integrates transformers with a beautiful UI:
# table_options with custom header
def table_options do
%{
mode: :card,
card_component: &CounsellingWeb.CollegeComponent.college_component/1,
custom_header: {CounsellingWeb.CollegeLive.CustomHeader, :custom_header}
}
end
# Custom header component
defmodule CounsellingWeb.CollegeLive.CustomHeader do
use Phoenix.Component
def custom_header(assigns) do
~H"""
<section class="relative mb-6 -mt-4 sm:mb-8 sm:-mt-8 lg:mb-12 lg:-mt-12">
<div class="bg-white dark:bg-gray-800 shadow-2xl border border-gray-100 dark:border-gray-700 rounded-xl sm:rounded-2xl">
<div class="p-4 sm:p-6 lg:p-8">
<!-- Hero section with transformer input -->
<div class="mb-6 p-4 bg-gradient-to-r from-violet-50 to-purple-50 dark:from-violet-900/20 dark:to-purple-900/20 border border-violet-200 dark:border-violet-800 rounded-xl sm:p-6">
<div class="flex items-center mb-3 sm:mb-4">
<div class="w-1 h-6 mr-3 bg-gradient-to-b from-violet-500 to-purple-600 rounded-full sm:h-8 sm:mr-4"></div>
<h3 class="text-lg font-bold text-violet-900 dark:text-violet-100 sm:text-xl">
Find Colleges for Your Rank
</h3>
</div>
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3 sm:gap-4">
<!-- Transformer input for rank filtering -->
<div>
<.form for={%{}} phx-debounce={get_in(@table_options, [:search, :debounce])} phx-change="sort">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Your JEE Rank <span class="text-xs text-gray-500">(Saved automatically)</span>
</label>
<input
type="number"
placeholder="e.g., 15000"
name="filters[rank][value]"
value={
Map.get(@options["filters"], :rank) &&
Map.get(@options["filters"], :rank).options.applied_data["value"]
}
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-violet-500"
/>
</.form>
</div>
<!-- Category selector -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Category
</label>
<select class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-violet-500">
<option>General</option>
<option>OBC-NCL</option>
<option>SC</option>
<option>ST</option>
<option>EWS</option>
</select>
</div>
</div>
</div>
<!-- Advanced filters with transformers -->
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4 mb-6">
<!-- Search -->
<div>
<.form for={%{}} phx-change="sort">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Search Colleges
</label>
<div class="relative">
<input
type="text"
name="search"
value={@options["filters"]["search"]}
placeholder="Search by name or location..."
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-violet-500"
/>
<svg class="absolute left-3 top-2.5 w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</div>
</.form>
</div>
<!-- NIRF Ranking transformer -->
<div>
<.form for={%{}} phx-change="sort">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
NIRF Ranking
</label>
<select name="filters[limit_results][nirf]" class="w-full px-3 py-2 border border-gray-300 rounded-lg">
<option selected={
Map.get(@options["filters"], :limit_results) &&
Map.get(@options["filters"], :limit_results).options.applied_data["nirf"] == "All Rankings"
}>All Rankings</option>
<option selected={
Map.get(@options["filters"], :limit_results) &&
Map.get(@options["filters"], :limit_results).options.applied_data["nirf"] == "Top 10"
}>Top 10</option>
<option selected={
Map.get(@options["filters"], :limit_results) &&
Map.get(@options["filters"], :limit_results).options.applied_data["nirf"] == "Top 25"
}>Top 25</option>
</select>
</.form>
</div>
<!-- Sort transformer -->
<div>
<.form for={%{}} phx-change="sort">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Sort By
</label>
<select name="filters[sort_mode][sort_by]" class="w-full px-3 py-2 border border-gray-300 rounded-lg">
<option>NIRF Ranking</option>
<option>Name (A-Z)</option>
<option>Name (Z-A)</option>
</select>
</.form>
</div>
</div>
<!-- Boolean filters for institution types -->
<div class="mb-4">
<.form for={%{}} phx-change="sort">
<span class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
Institution Type
</span>
<div class="flex flex-wrap gap-x-6">
<.input
:for={{id, %Boolean{field: :class, options: %{label: label}}} <- @filters}
type="checkbox"
name={"filters[#{id}]"}
label={label}
checked={Map.has_key?(@options["filters"], id)}
/>
</div>
</.form>
</div>
<!-- Clear filters -->
<div class="flex items-center justify-end pt-4 border-t border-gray-200">
<.link
:if={@options["filters"] != %{"search" => ""}}
phx-click="sort"
phx-value-clear_filters="true"
class="text-sm text-gray-500 hover:text-gray-700 transition-colors"
>
Clear All Filters
</.link>
</div>
</div>
</div>
</section>
"""
end
end
Complete UI Control
With custom components, you have complete control over:
- Layout and styling - Use any CSS framework or design system
- User interactions - Implement custom form controls and behaviors
- Data presentation - Create sophisticated filtering interfaces
- Integration - Combine LiveTable data with your own UI components
- Responsive design - Build mobile-optimized interfaces
Key Benefits:
- Keep LiveTable's powerful data handling and state management
- Replace only the UI parts you want to customize
- Maintain URL persistence and real-time updates
- Integrate seamlessly with your existing design system
The college counselling example shows how you can create a completely custom interface that still benefits from LiveTable's transformer system, URL persistence, and efficient data handling.
Environment-specific Configuration
Configure different settings per environment:
# config/dev.exs
config :live_table,
defaults: %{
pagination: %{sizes: [5, 10]}, # Smaller pages in development
exports: %{enabled: false} # Disable exports in dev
}
# config/prod.exs
config :live_table,
defaults: %{
pagination: %{sizes: [25, 50, 100]},
exports: %{enabled: true}
}
Oban Configuration
Configure background job processing for exports:
# config/config.exs
config :your_app, Oban,
repo: YourApp.Repo,
engine: Oban.Engines.Basic,
notifier: Oban.Notifiers.Postgres,
plugins: [
Oban.Plugins.Pruner,
{Oban.Plugins.Cron,
crontab: [
# Clean up old export files daily at 2 AM
{"0 2 * * *", YourApp.Workers.CleanupExports}
]}
],
queues: [
exports: 10, # Export processing queue
default: 5 # Other background jobs
]
Oban Web (Optional)
Monitor export jobs with Oban Web:
# lib/your_app_web/router.ex
import Oban.Web.Router
scope "/admin" do
pipe_through [:browser, :admin_required]
oban_dashboard "/oban"
end
Custom Components
Replace LiveTable's default components with your own:
# config/config.exs
config :live_table,
components: YourApp.CustomComponents
Create your custom components module:
# lib/your_app/custom_components.ex
defmodule YourApp.CustomComponents do
use Phoenix.Component
# Override the default input component
def input(assigns) do
~H"""
<div class="relative">
<input
type={@type}
name={@name}
checked={@checked}
class="peer sr-only"
/>
<label class="flex items-center cursor-pointer">
<div class="relative">
<div class="w-4 h-4 border-2 border-gray-300 rounded peer-checked:bg-blue-600 peer-checked:border-blue-600"></div>
<svg class="absolute inset-0 w-4 h-4 text-white opacity-0 peer-checked:opacity-100" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
</svg>
</div>
<span class="ml-2 text-sm text-gray-700"><%= @label %></span>
</label>
</div>
"""
end
# Override the default icon component
def icon(%{name: "sort-asc"} = assigns) do
~H"""
<svg class="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
"""
end
end
Complete Example
Here's a comprehensive configuration example:
# config/config.exs
config :live_table,
repo: YourApp.Repo,
pubsub: YourApp.PubSub,
defaults: %{
pagination: %{
enabled: true,
sizes: [10, 25, 50]
},
sorting: %{
enabled: true,
default_sort: [id: :asc]
},
exports: %{
enabled: true,
formats: [:csv, :pdf]
},
search: %{
enabled: true,
debounce: 300,
placeholder: "Search..."
},
mode: :table,
use_streams: true
}
# Per-table override
defmodule YourAppWeb.ProductLive.Index do
def table_options do
%{
pagination: %{sizes: [5, 15, 30]},
sorting: %{default_sort: [name: :asc]},
exports: %{formats: [:csv]},
mode: :card,
card_component: &product_card/1
}
end
end
Complete Configuration Reference
Based on the actual table configuration, here are all available options:
# All available configuration options
config :live_table,
defaults: %{
pagination: %{
enabled: true,
sizes: [10, 25, 50]
},
sorting: %{
enabled: true,
default_sort: [id: :asc]
},
exports: %{
enabled: true,
formats: [:csv, :pdf]
},
search: %{
enabled: true,
debounce: 300,
placeholder: "Search..."
},
mode: :table,
use_streams: true
}