# Flop Phoenix
 [](https://hex.pm/packages/flop_phoenix) [](https://coveralls.io/github/woylie/flop_phoenix)
Phoenix components for pagination, sortable tables and filter forms with
[Flop](https://hex.pm/packages/flop) and [Ecto](https://hex.pm/packages/ecto).
## Installation
Add `flop_phoenix` to your list of dependencies in the `mix.exs` of your Phoenix
application.
```elixir
def deps do
[
{:flop_phoenix, "~> 0.15.1"}
]
end
```
Follow the instructions in the
[Flop documentation](https://hex.pm/packages/flop) to set up your business
logic.
## Fetch the data
Define a function that calls `Flop.validate_and_run/3` to query the list of
pets.
```elixir
defmodule MyApp.Pets do
alias MyApp.Pet
def list_pets(params) do
Flop.validate_and_run(Pet, params, for: Pet)
end
end
```
In your controller, pass the data and the Flop meta struct to your template.
```elixir
defmodule MyAppWeb.PetController do
use MyAppWeb, :controller
alias MyApp.Pets
action_fallback MyAppWeb.FallbackController
def index(conn, params) do
with {:ok, {pets, meta}} <- Pets.list_pets(params) do
render(conn, "index.html", meta: meta, pets: pets)
end
end
end
```
You can fetch the data similarly in the `handle_params/3` function of a
`LiveView` or the `update/2` function of a `LiveComponent`.
```elixir
defmodule MyAppWeb.PetLive.Index do
use MyAppWeb, :live_view
alias MyApp.Pets
@impl Phoenix.LiveView
def handle_params(params, _, socket) do
case Pets.list_pets(params) do
{:ok, {pets, meta}} ->
{:noreply, assign(socket, %{pets: pets, meta: meta})}
_ ->
{:noreply, push_navigate(socket, to: Routes.pet_index_path(socket, :index))}
end
end
end
```
## Sortable tables and pagination
In your template, add a sortable table and pagination links.
```elixir
<h1>Pets</h1>
<Flop.Phoenix.table
items={@pets}
meta={@meta}
path={{Routes, :pet_path, [@socket, :index]}}
>
<:col :let={pet} label="Name" field={:name}><%= pet.name %></:col>
<:col :let={pet} label="Age" field={:age}><%= pet.age %></:col>
</Flop.Phoenix.table>
<Flop.Phoenix.pagination
meta={@meta}
path={{Routes, :pet_path, [@socket, :index]}}
/>
```
`path` should reference the path helper function that builds a path to
the current page. Add any additional path and query parameters to the argument
list.
```elixir
<Flop.Phoenix.pagination
meta={@meta}
path={{Routes, :pet_path, [@conn, :index, @owner, [hide_menu: true]]}}
/>
```
Alternatively, you can pass a URI string, which allows you to use the
verified routes introduced in Phoenix 1.7.
```elixir
<Flop.Phoenix.pagination
meta={@meta}
path={~p"/pets"}
/>
```
You can also use a custom path builder function, in case you need to set some
parameters in the path instead of the query. For more examples, have a look at
the documentation of `Flop.Phoenix.build_path/3`. The `path` assign can use any
format that is accepted by `Flop.Phoenix.build_path/3`.
If you pass the `for` option when making the query with Flop, Flop Phoenix can
determine which table columns are sortable. It also hides the `order` and
`page_size` parameters if they match the default values defined with
`Flop.Schema`.
See `Flop.Phoenix.cursor_pagination/1` for instructions to set up cursor-based
pagination.
## Filter forms
This library implements `Phoenix.HTML.FormData` for the `Flop.Meta` struct,
which means you can pass the struct to the Phoenix form functions. The
easiest way to render a filter form is to use the `Flop.Phoenix.filter_fields/1`
component:
```elixir
<.form :let={f} for={@meta}>
<Flop.Phoenix.filter_fields :let={entry} form={f} fields={[:name, :email]}>
<%= entry.label %>
<%= entry.input %>
</Flop.Phoenix.filter_fields>
</.form>
```
Refer to the `Flop.Phoenix` module documentation for more examples.
### Custom filter form component
Your filter form probably requires a bit of custom markup. It is recommended to
define a custom `filter_form` component that wraps
`Flop.Phoenix.filter_fields/1`, so that you can apply the same markup
throughout your live views.
```elixir
attr :meta, Flop.Meta, required: true
attr :fields, :list, required: true
attr :id, :string, default: nil
attr :change_event, :string, default: "update-filter"
attr :reset_event, :string, default: "reset-filter"
attr :target, :string, default: nil
attr :debounce, :integer, default: 100
def filter_form(assigns) do
~H"""
<div class="filter-form">
<.form
:let={f}
for={@meta}
as={:filter}
id={@id}
phx-target={@target}
phx-change={@change_event}
>
<div class="filter-form-inputs">
<Flop.Phoenix.filter_fields
:let={%{input: input, label: label}}
form={f}
fields={@fields}
input_opts={[phx_debounce: @debounce]}
>
<div class="field">
<span class="visually-hidden"><%= label %></span>
<%= input %>
</div>
</Flop.Phoenix.filter_fields>
</div>
<div class="filter-form-reset">
<a href="#" class="button" phx_target={@target} phx_click={@reset_event}>
reset
</a>
</div>
</.form>
</div>
"""
end
```
Now you can render a filter form like this:
```elixir
<.filter_form
fields={[:name, :email]}
meta={@meta}
id="user-filter-form"
/>
```
You will need to handle the `update-filter` and `reset-filter` events with the
`handle_event/3` callback function of your LiveView.
```elixir
@impl true
def handle_event("update-filter", %{"filter" => params}, socket) do
{:noreply,
push_patch(socket, to: Routes.pet_index_path(socket, :index, params))}
end
@impl true
def handle_event("reset-filter", _, %{assigns: assigns} = socket) do
flop = assigns.meta.flop |> Flop.set_page(1) |> Flop.reset_filters()
path =
Flop.Phoenix.build_path(
{Routes, :pet_index_path, [socket, :index]},
flop
)
{:noreply, push_patch(socket, to: path)}
end
```