# ExLiveTable
A comprehensive DataTable solution for Phoenix LiveView applications with built-in sorting, filtering, pagination, and responsive design.
## Installation
The package can be installed by adding `ex_live_table` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:ex_live_table, "~> 0.1.0"}
]
end
```
## Features
- Responsive design with desktop and mobile views
- Sorting by columns
- Quick search filter
- Pagination with Scrivener integration
- CSV/Excel/PDF export functionality
- Customizable styling
## Usage
```elixir
<ExLiveTable.main id="users" rows={@users} params={@params}>
<:col :let={user} label="ID"><%= user.id %></:col>
<:col :let={user} label="Username"><%= user.username %></:col>
<:action :let={user}>
<button phx-click="edit" phx-value-id={user.id}>Edit</button>
</:action>
</ExLiveTable.main>
```
## Documentation
The docs can be found at [https://hexdocs.pm/ex_live_table](https://hexdocs.pm/ex_live_table).
# LiveTable Usage Guide
`LiveTable` is a comprehensive data table solution for Phoenix LiveView applications with built-in sorting, filtering, pagination, and responsive design.
## Basic Example
Here's a simple example of using LiveTable in your LiveView application:
### Live View Module
```elixir
defmodule MyAppWeb.UserLive.Index do
use MyAppWeb, :live_view
alias MyApp.Accounts
@impl true
def mount(_params, _session, socket) do
{:ok, socket}
end
@impl true
def handle_params(params, _url, socket) do
params = params || %{}
page = Accounts.list_users(params)
{:noreply,
socket
|> assign(:params, params)
|> assign(:users, page)}
end
@impl true
def handle_event("iSearch", %{"isearch" => search_term}, socket) do
params = put_in(socket.assigns.params, ["filter", "isearch"], search_term)
{:noreply, push_patch(socket, to: ~p"/users?#{params}")}
end
@impl true
def handle_event("refresh_table", _, socket) do
{:noreply, push_patch(socket, to: ~p"/users?#{socket.assigns.params}")}
end
@impl true
def handle_event("export", %{"file_type" => file_type}, socket) do
# Export logic for CSV, Excel, or PDF
users = Accounts.list_all_users(socket.assigns.params)
case file_type do
"csv" -> generate_csv(users)
"xlsx" -> generate_xlsx(users)
"pdf" -> generate_pdf(users)
end
{:noreply, socket}
end
end
```
### LiveView Template
```heex
<h1 class="text-2xl font-bold mb-4">Users</h1>
<ExLiveTable.main_table id="users" rows={@users} params={@params}>
<:col :let={user} label="ID"><%= user.id %></:col>
<:col :let={user} label="Name"><%= user.name %></:col>
<:col :let={user} label="Email"><%= user.email %></:col>
<:col :let={user} label="Role"><%= user.role %></:col>
<:col :let={user} label="Created">
<%= Calendar.strftime(user.inserted_at, "%Y-%m-%d") %>
</:col>
<:action :let={user}>
<div class="flex items-center space-x-2">
<.link navigate={~p"/users/#{user}/edit"} class="text-blue-600 hover:text-blue-800">
Edit
</.link>
<button phx-click="delete" phx-value-id={user.id} class="text-red-600 hover:text-red-800"
data-confirm="Are you sure you want to delete this user?">
Delete
</button>
</div>
</:action>
</ExLiveTable.main_table>
```
### Context Module
```elixir
defmodule MyApp.Accounts do
import Ecto.Query
alias MyApp.Repo
alias MyApp.Accounts.User
def list_users(params) do
User
|> ExLiveTable.handle_search(params)
|> ExLiveTable.handle_sorting(params)
|> Repo.paginate(params)
end
def list_all_users(params) do
User
|> ExLiveTable.handle_search(params)
|> ExLiveTable.handle_sorting(params)
|> Repo.all()
end
end
```
## Advanced Options
### Customizing Row Click Behavior
```heex
<ExLiveTable.main_table
id="users"
rows={@users}
params={@params}
row_click={fn user -> JS.navigate(~p"/users/#{user}") end}>
<!-- columns -->
</ExLiveTable.main_table>
```
### Dynamic Row ID and Item Transformation
```heex
<ExLiveTable.main_table
id="users"
rows={@users}
params={@params}
row_id={fn user -> "user-#{user.id}" end}
row_item={fn user -> Map.put(user, :full_name, "#{user.first_name} #{user.last_name}") end}>
<:col :let={user} label="Full Name"><%= user.full_name %></:col>
<!-- more columns -->
</ExLiveTable.main_table>
```
### Loading State
```heex
<ExLiveTable.main_table
id="users"
rows={@users}
params={@params}
data_loader={@loading}>
<!-- columns -->
</ExLiveTable.main_table>
```
## Styling Options
The ExLiveTable comes with a default styling system based on Tailwind CSS. You can customize the appearance by:
1. Adding custom CSS classes to columns
2. Overriding the default styles in your app.css
Example of custom column classes:
```heex
<ExLiveTable.main_table id="users" rows={@users} params={@params}>
<:col :let={user} label="ID" class="font-mono"><%= user.id %></:col>
<:col :let={user} label="Status" label_class="text-center" class="text-center">
<span class={"badge #{status_color(user.status)}"}>
<%= user.status %>
</span>
</:col>
<!-- more columns -->
</ExLiveTable.main_table>
```
## Handling Pagination with Scrivener
Make sure to add Scrivener to your dependencies:
```elixir
defp deps do
[
{:scrivener_ecto, "~> 2.7"}
]
end
```
Configure Scrivener in your repo:
```elixir
defmodule MyApp.Repo do
use Ecto.Repo,
otp_app: :my_app,
adapter: Ecto.Adapters.Postgres
use Scrivener, page_size: 10
end
```
Use Scrivener in your context:
```elixir
def list_users(params) do
page = params["page"] || "1"
page_size = params["page_size"] || "10"
params = Map.merge(params, %{
"page" => page,
"page_size" => page_size
})
User
|> ExLiveTable.handle_search(params)
|> ExLiveTable.handle_sorting(params)
|> Repo.paginate(params)
end
```
## Export Functionality
To implement the export functionality, you'll need additional libraries depending on the export format:
- For CSV: `{:csv, "~> 2.4"}`
- For Excel: `{:xlsx_creator, "~> 0.4.2"}`
- For PDF: `{:pdf_generator, "~> 0.6.2"}`
Example implementation:
```elixir
def generate_csv(users) do
csv_content =
[["ID", "Name", "Email", "Role", "Created At"]] ++
Enum.map(users, fn user ->
[
user.id,
user.name,
user.email,
user.role,
Calendar.strftime(user.inserted_at, "%Y-%m-%d")
]
end)
|> CSV.encode()
|> Enum.to_list()
|> Enum.join()
# Return the CSV for download
%{
content: csv_content,
content_type: "text/csv",
filename: "users_export.csv"
}
end
```
## Custom Search Implementation
For more advanced search needs, you can override the default search behavior:
```elixir
def list_users(params) do
search_term = get_in(params, ["filter", "isearch"]) || ""
query = from u in User
query =
if search_term != "" do
search_term = "%#{search_term}%"
from u in query,
where:
ilike(u.name, ^search_term) or
ilike(u.email, ^search_term) or
ilike(u.role, ^search_term)
else
query
end
query
|> ExLiveTable.handle_sorting(params)
|> Repo.paginate(params)
end
```
## Performance Considerations
For large datasets:
1. Ensure proper database indexing on sorted and filtered columns
2. Consider using limit/offset instead of fetching all records
3. Use `row_item` for complex transformations to avoid doing them in the template
## Troubleshooting
Common issues:
1. **Pagination not working**: Ensure you're using Scrivener correctly and passing the `params` to the table component
2. **Sorting not affecting results**: Check that the field names match your database columns
3. **Search not working**: Verify that the `iSearch` event is properly handled and the filter parameters are correctly passed