View Source Flop Phoenix
Phoenix components for pagination, sortable tables and filter forms with Flop and Ecto.
installation
Installation
Add flop_phoenix
to your list of dependencies in the mix.exs
of your Phoenix
application.
def deps do
[
{:flop_phoenix, "~> 0.17.0"}
]
end
Follow the instructions in the Flop documentation to set up your business logic.
fetch-the-data
Fetch the data
Define a function that calls Flop.validate_and_run/3
to query the list of
pets.
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.
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
.
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
Sortable tables and pagination
In your template, add a sortable table and pagination links.
<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.
<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.
<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
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:
<.form :let={f} for={@meta}>
<.filter_fields :let={i} form={f} fields={[:email, :name]}>
<.input
id={i.id}
name={i.name}
label={i.label}
type={i.type}
value={i.value}
field={{i.form, i.field}}
{i.rest}
/>
</.filter_fields>
</.form>
The filter_fields
component renders all necessary hidden inputs, but it does
not render the inputs for the filter values on its own. Instead, it passes all
necessary details to the inner block. This allows you to render the filter
inputs with your custom input component.
You can pass additional options for each field. Refer to the
Flop.Phoenix.filter_fields/1
documentation for details.
custom-filter-form-component
Custom filter form component
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.
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={i} form={f} fields={@fields}>
<.input
id={i.id}
name={i.name}
label={i.label}
type={i.type}
value={i.value}
field={{i.form, i.field}}
hide_labels={true}
phx-debounce={@debounce}
{i.rest}
/>
</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:
<.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.
@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,
backend: assigns.meta.backend
)
{:noreply, push_patch(socket, to: path)}
end