https://github.com/cpursley/livefilter
A flexible and composable filtering library for Phoenix LiveView applications
https://github.com/cpursley/livefilter
admin-dashboard elixir liveview
Last synced: 2 months ago
JSON representation
A flexible and composable filtering library for Phoenix LiveView applications
- Host: GitHub
- URL: https://github.com/cpursley/livefilter
- Owner: cpursley
- License: mit
- Created: 2025-07-12T23:33:08.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2025-07-20T20:28:41.000Z (3 months ago)
- Last Synced: 2025-07-20T21:22:09.933Z (3 months ago)
- Topics: admin-dashboard, elixir, liveview
- Language: Elixir
- Homepage: https://livefilter.fly.dev/todos
- Size: 96.7 KB
- Stars: 10
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# LiveFilter
A flexible and composable filtering library for Phoenix LiveView applications, inspired by Linear, Notion and Airtable. Provides filtering, sorting, and pagination with clean URL state management and a modern UI built on [SaladUI](https://salad-storybook.fly.dev/) and inspired by [shadcn/ui data tables](https://tablecn.com/).
[](https://hex.pm/packages/livefilter)
[](https://hexdocs.pm/livefilter)**Demo**: [https://livefilter.fly.dev](https://livefilter.fly.dev/) - source: [https://github.com/cpursley/livefilter-demo](https://github.com/cpursley/livefilter-demo)
## Features
- Filter operators for all data types (string, numeric, boolean, date, enum, array)
- URL state management with shareable filter links
- Autogeneration of database-efficient queries
- Sorting and column management
- Components built on shadcn-inspired [SaladUI](https://salad-storybook.fly.dev/)## Installation
Add to your `mix.exs`:
```elixir
def deps do
[
{:livefilter, "~> 0.1.7"}
]
end
```Install JavaScript assets:
```bash
mix live_filter.install.assets
```Add to your `app.js`:
```javascript
import LiveFilter from "./hooks/live_filter/live_filter"let liveSocket = new LiveSocket("/live", Socket, {
hooks: { LiveFilter }
})
```## Basic Usage
### 1. Minimal Setup
```elixir
defmodule MyAppWeb.ProductLive do
use MyAppWeb, :live_view
alias LiveFilter.{Filter, FilterGroup, QueryBuilder}def mount(_params, _session, socket) do
{:ok, assign(socket, :products, load_products())}
enddefp load_products(filter_group \\ %FilterGroup{}) do
from(p in Product)
|> QueryBuilder.build_query(filter_group)
|> Repo.all()
end
end
```### 2. URL-Persisted Filters
```elixir
defmodule MyAppWeb.ProductLive do
use MyAppWeb, :live_view
use LiveFilter.Mountable # Adds filter helpersdef mount(_params, _session, socket) do
socket = mount_filters(socket)
{:ok, assign(socket, :products, [])}
enddef handle_params(params, _url, socket) do
socket = handle_filter_params(socket, params)
{:noreply, assign(socket, :products, load_products(socket.assigns.filter_group))}
enddefp load_products(filter_group) do
from(p in Product)
|> QueryBuilder.build_query(filter_group)
|> Repo.all()
end
end
```## Advanced Example
Here's a complete implementation with UI components (similar to our [demo app](https://livefilter.fly.dev/)):
```elixir
defmodule MyAppWeb.TodoLive do
use MyAppWeb, :live_view
use LiveFilter.Mountabledef mount(_params, _session, socket) do
socket =
socket
|> mount_filters(registry: field_registry())
|> assign(:todos, [])
{:ok, socket}
enddef handle_params(params, _url, socket) do
socket = handle_filter_params(socket, params)
{:noreply, assign(socket, :todos, load_todos(socket.assigns.filter_group))}
enddef render(assigns) do
~H"""
<.live_component
module={LiveFilter.Components.SearchSelect}
id="status-filter"
field={:status}
value={get_filter_value(@filter_group, :status)}
options={[
{"pending", "Pending"},
{"in_progress", "In Progress"},
{"completed", "Completed"}
]}
/>
<.live_component
module={LiveFilter.Components.FilterBuilder}
id="filter-builder"
filter_group={@filter_group}
field_options={field_options()}
/>
<.live_component
module={LiveFilter.Components.SortableHeader}
id="sort-title"
field={:title}
label="Title"
current_sort={@current_sort}
/>
Status
Due Date
<%= todo.title %>
<%= todo.status %>
<%= todo.due_date %>
"""
enddef handle_event("search", %{"q" => query}, socket) do
filter = %Filter{field: :title, operator: :contains, value: query, type: :string}
filter_group = %FilterGroup{filters: [filter]}
socket = apply_filters_and_reload(socket, filter_group)
{:noreply, assign(socket, :search, query)}
enddefp field_registry do
LiveFilter.FieldRegistry.new()
|> LiveFilter.FieldRegistry.put(:title, :string, "Title")
|> LiveFilter.FieldRegistry.put(:status, :enum, "Status",
options: [{"pending", "Pending"}, {"in_progress", "In Progress"}, {"completed", "Completed"}])
|> LiveFilter.FieldRegistry.put(:due_date, :date, "Due Date")
|> LiveFilter.FieldRegistry.put(:tags, :array, "Tags")
enddefp load_todos(filter_group) do
from(t in Todo)
|> QueryBuilder.build_query(filter_group)
|> QueryBuilder.apply_sort(socket.assigns.current_sort)
|> Repo.all()
end
end
```## Documentation
- [API Documentation](https://hexdocs.pm/livefilter)
- [Demo Application](https://github.com/cpursley/livefilter-demo)## License
MIT