Skip to content

Components for pagination, sortable tables and filter forms using Phoenix, Ecto and Flop

License

Notifications You must be signed in to change notification settings

helderbarboza/flop_phoenix

 
 

Repository files navigation

Flop Phoenix

CI Hex Coverage Status

Phoenix components for pagination, sortable tables and filter forms with Flop and Ecto.

Installation

Add flop_phoenix to your list of dependencies in the mix.exs of your Phoenix application.

def deps do
  [
    {:flop_phoenix, "~> 0.19.0"}
  ]
end

Follow the instructions in the Flop documentation 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.

defmodule MyApp.Pets do
  alias MyApp.Pet

  def list_pets(params) do
    Flop.validate_and_run(Pet, params, for: Pet)
  end
end

LiveView

Fetch the data and assign it along with the meta data to the socket.

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: ~p"/pets")}
    end
  end
end

If you don't want the Flop.Phoenix components to reflect the pagination, sorting and filtering parameters in the URL, you can fetch and assign the data in the c:Phoenix.LiveView.handle_event/3 callback instead. In that case, you need to pass the event name as an attribute to the components.

Controller

For dead views, pass the data and the Flop meta struct to your template in your controller.

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, meta: meta, pets: pets)
    end
  end
end

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={~p"/pets"}>
  <: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={~p"/pets"} />

The path attribute points to the current path. Flop.Phoenix will add the pagination, filtering and sorting parameters to that path. You can use verified routes, route helpers, or custom path builder functions. The different formats are explained in the documentation of Flop.Phoenix.build_path/3.

The field attribute of the :col slot is optional. If set and the field is configured as sortable in the schema, the column header will be clickable, allowing the user to sort by that column. If the field is not marked as sortable or if the attribute is omitted or set to nil or false, the column header will not be clickable.

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.

Alternatively, you can pass an event name instead of a path. Refer to the component documentation for details.

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:

attr :meta, Flop.Meta, required: true
attr :id, :string, default: nil
attr :on_change, :string, default: "update-filter"
attr :on_reset, :string, default: "reset-filter"
attr :target, :string, default: nil

def filter_form(%{meta: meta} = assigns) do
  assigns = assign(assigns, form: Phoenix.Component.to_form(meta), meta: nil)

  ~H"""
  <.form
    for={@form}
    id={@id}
    phx-target={@target}
    phx-change={@on_change}
    phx-submit={@on_change}
  >
    <.filter_fields :let={i} form={@form} fields={[:email, :name]}>
      <.input
        field={i.field}
        label={i.label}
        type={i.type}
        phx-debounce={120}
        {i.rest}
      />
    </.filter_fields>

    <a href="#" class="button" phx-target={@target} phx-click={@on_reset}>
      reset
    </a>
  </.form>
  """
end

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.

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", params, socket) do
  {:noreply, push_patch(socket, to: ~p"/pets?#{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(~p"/pets", flop, backend: assigns.meta.backend)
  {:noreply, push_patch(socket, to: path)}
end

About

Components for pagination, sortable tables and filter forms using Phoenix, Ecto and Flop

Resources

License

Code of conduct

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Elixir 100.0%