Flop.Phoenix (Flop Phoenix v0.6.1) View Source

View helper functions for Phoenix and Flop.

Pagination

Flop.meta/3 returns a Flop.Meta struct, which holds information such as the total item count, the total page count, the current page etc. This is all you need to render pagination links. Flop.run/3, Flop.validate_and_run/3 and Flop.validate_and_run!/3 all return the query results alongside the meta information.

If you set up your context as described in the Flop documentation, you will have a list function similar to the following:

@spec list_pets(Flop.t() | map) ::
        {:ok, {[Pet.t()], Flop.Meta.t}} | {:error, Changeset.t()}
def list_pets(flop \\ %{}) do
  Flop.validate_and_run(Pet, flop, for: Pet)
end

Controller

You can call this function from your controller to get both the data and the meta data and pass both to your template.

defmodule MyAppWeb.PetController do
  use MyAppWeb, :controller

  alias Flop
  alias MyApp.Pets
  alias MyApp.Pets.Pet

  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

View

To make the Flop.Phoenix functions available in all templates, locate the view_helpers/0 macro in my_app_web.ex and add another import statement:

defp view_helpers do
  quote do
    # ...

    import Flop.Phoenix

    # ...
  end
end

Template

In your index template, you can now add pagination links like this:

<h1>Listing Pets</h1>

<table>
# ...
</table>

<%= pagination(@meta, &Routes.pet_path/3, [@conn, :index]) %>

The second argument of Flop.Phoenix.pagination/4 is the route helper function, and the third argument is a list of arguments for that route helper. If you want to add path parameters, you can do that like this:

<%= pagination(@meta, &Routes.owner_pet_path/4, [@conn, :index, @owner]) %>

Customization

Flop.Phoenix sets some default classes and aria attributes.

<nav aria-label="pagination" class="pagination is-centered" role="navigation">
  <span class="pagination-previous" disabled="disabled">Previous</span>
  <a class="pagination-next" href="/pets?page=2&amp;page_size=10">Next</a>
  <ul class="pagination-list">
    <li><span class="pagination-ellipsis">&hellip;</span></li>
    <li>
      <a aria-current="page"
         aria-label="Goto page 1"
         class="pagination-link is-current"
         href="/pets?page=1&amp;page_size=10">1</a>
    </li>
    <li>
      <a aria-label="Goto page 2"
         class="pagination-link"
         href="/pets?page=2&amp;page_size=10">2</a>
    </li>
    <li>
      <a aria-label="Goto page 3"
         class="pagination-link"
         href="/pets?page=3&amp;page_size=2">3</a>
    </li>
    <li><span class="pagination-ellipsis">&hellip;</span></li>
  </ul>
</nav>

You can customize the css classes and add additional HTML attributes. It is recommended to set up the customization once in a view helper module, so that your templates aren't cluttered with options.

Create a new file called views/flop_helpers.ex:

defmodule MyAppWeb.FlopHelpers do
  use Phoenix.HTML

  def pagination(meta, path_helper, path_helper_args) do
    Flop.Phoenix.pagination(
      meta,
      path_helper,
      path_helper_args,
      pagination_opts()
    )
  end

  def pagination_opts do
    [
      # ...
    ]
  end
end

Change the import in my_app_web.ex:

defp view_helpers do
  quote do
    # ...

    import MyAppWeb.FlopHelpers

    # ...
  end
end

By default, page links for all pages are show. You can limit the number of page links or disable them altogether by passing the :page_links option.

  • :all: Show all page links (default).
  • :hide: Don't show any page links. Only the previous/next links will be shown.
  • {:ellipsis, x}: Limits the number of page links. The first and last page are always displayed. The x refers to the number of additional page links to show.

Attributes and CSS classes

You can overwrite the default attributes of the <nav> tag and the pagination links by passing these options:

  • :wrapper_attrs: attributes for the <nav> tag
  • :previous_link_attrs: attributes for the previous link (<a> if active, <span> if disabled)
  • :next_link_attrs: attributes for the next link (<a> if active, <span> if disabled)
  • :pagination_list_attrs: attributes for the page link list (<ul>)
  • :pagination_link_attrs: attributes for the page links (<a>)
  • :ellipsis_attrs: attributes for the ellipsis element (<span>)
  • :ellipsis_content: content for the ellipsis element (<span>)

For the page links, there is the :pagination_link_aria_label option to set the aria label. Since the page number is usually part of the aria label, you need to pass a function that takes the page number as an integer and returns the label as a string. The default is &"Goto page #{&1}".

By default, the previous and next links contain the texts Previous and Next. To change this, you can pass the :previous_link_content and :next_link_content options.

Customization example

def pagination(meta, path_helper, path_helper_args) do
  opts = [
    ellipsis_attrs: [class: "ellipsis"],
    ellipsis_content: "‥",
    next_link_attrs: [class: "next"],
    next_link_content: next_icon(),
    page_links: {:ellipsis, 7},
    pagination_link_aria_label: &"#{&1}ページ目へ",
    pagination_link_attrs: [class: "page-link"],
    pagination_list_attrs: [class: "page-links"],
    previous_link_attrs: [class: "prev"],
    previous_link_content: previous_icon(),
    wrapper_attrs: [class: "paginator"]
  ]

  Flop.Phoenix.pagination(meta, path_helper, path_helper_args, opts)
end

defp next_icon do
  tag :i, class: "fas fa-chevron-right"
end

defp previous_icon do
  tag :i, class: "fas fa-chevron-left"
end

Sortable table

To add a sortable table for pets on the same page, define a function in MyAppWeb.PetView that returns the table headers:

def table_headers do
  ["ID", {"Name", :name}, {"Age", :age}, ""]
end

This defines four header columns: One for the ID, which is not sortable, and columns for the name and the age, which are both sortable, and a fourth column without a header value. The last column will hold the links to the detail pages. The name and age column headers will be linked, so that they the order on the :name and :age field, respectively.

Next, we define a function that returns the column values for a single pet:

def table_row(%Pet{id: id, name: name, age: age}, opts) do
  conn = Keyword.fetch!(opts, :conn)
  [id, name, age, link("show", to: Routes.pet_path(conn, :show, id))]
end

The controller already assigns the pets and the meta struct to the conn. All that's left to do is to add the table to the index template:

<%= table(%{
      items: @pets,
      meta: @meta,
      path_helper: &Routes.pet_path/3,
      path_helper_args: [@conn, :index],
      headers: table_headers(),
      row_func: &table_row/2,
      opts: [conn: @conn]
  })
%>

LiveView

The functions in this module can be used in both .eex and .leex templates.

Links are generated with Phoenix.LiveView.Helpers.live_patch/2. This will lead to <a> tags with data-phx-link and data-phx-link-state attributes, which will be ignored in .eex templates. When used in LiveView templates, you will need to handle the new params in the handle_params/3 callback of your LiveView module.

Link to this section Summary

Generators

Generates a table with sortable columns.

Miscellaneous

Takes a Phoenix path helper function and a list of path helper arguments and builds a path that includes query parameters for the given Flop struct.

Converts a Flop struct into a keyword list that can be used as a query with Phoenix route helper functions.

Link to this section Generators

Link to this function

pagination(meta, path_helper, path_helper_args, opts \\ [])

View Source

Specs

Generates a pagination element.

  • meta: The meta information of the query as returned by the Flop query functions.
  • path_helper: The path helper function that builds a path to the current page, e.g. &Routes.pet_path/3.
  • path_helper_args: The arguments to be passed to the route helper function, e.g. [@conn, :index]. The page number and page size will be added as query parameters.
  • opts: Options to customize the pagination. See section Customization.

See the module documentation for examples.

Link to this function

table(assigns)

View Source (since 0.6.0)

Specs

Generates a table with sortable columns.

The argument is a map with the following keys:

  • headers: A list of header columns. Can be a list of strings (or markup), or a list of {value, field_name} tuples.
  • items: The list of items to be displayed in rows. This is the result list returned by the query.
  • meta: The Flop.Meta struct returned by the query function.
  • path_helper: The Phoenix path or url helper that leads to the current page.
  • path_helper_args: The argument list for the path helper. For example, if you would call Routes.pet_path(@conn, :index) to generate the path for the current page, this would be [@conn, :index].
  • opts: Keyword list with additional options (see below). This list will also be passed as the second argument to the row function.
  • row_func: A function that takes one item of the items list and the opts and returns the column values for that item's row.

Available options

  • :for - The schema module deriving Flop.Schema. If set, header links are only added for fields that are defined as sortable.
  • :table_class - The CSS class for the <table> element. No default.
  • :symbol_class - The CSS class for the <span> element that wraps the order direction indicator in the header columns. Defaults to "order-direction".
  • :symbol_asc - The symbol that is used to indicate that the column is sorted in ascending order. Defaults to "▴".
  • :symbol_desc - The symbol that is used to indicate that the column is sorted in ascending order. Defaults to "▾".
  • :container - Wraps the table in a <div> if true. Defaults to false.
  • :container_class - The CSS class for the table container. Defaults to "table-container".

See the module documentation for examples.

Link to this section Miscellaneous

Link to this function

build_path(path_helper, args, flop)

View Source (since 0.6.0)

Specs

build_path(function(), [any()], Flop.Meta.t() | Flop.t() | keyword()) ::
  String.t()

Takes a Phoenix path helper function and a list of path helper arguments and builds a path that includes query parameters for the given Flop struct.

Examples

iex> pet_path = fn _conn, :index, query ->
...>   "/pets?" <> Plug.Conn.Query.encode(query)
...> end
iex> flop = %Flop{page: 2, page_size: 10}
iex> build_path(pet_path, [%Plug.Conn{}, :index], flop)
"/pets?page_size=10&page=2"

We're defining fake path helpers for the scope of the doctests. In a real Phoenix application, you would pass something like &Routes.pet_path/3 as the first argument.

You can also pass a Flop.Meta struct or a keyword list as the third argument.

iex> pet_path = fn _conn, :index, query ->
...>   "/pets?" <> Plug.Conn.Query.encode(query)
...> end
iex> flop = %Flop{page: 2, page_size: 10}
iex> meta = %Flop.Meta{flop: flop}
iex> build_path(pet_path, [%Plug.Conn{}, :index], meta)
"/pets?page_size=10&page=2"
iex> query_params = to_query(flop)
iex> build_path(pet_path, [%Plug.Conn{}, :index], query_params)
"/pets?page_size=10&page=2"

If the path helper takes additional path parameters, just add them to the second argument.

iex> user_pet_path = fn _conn, :index, id, query ->
...>   "/users/#{id}/pets?" <> Plug.Conn.Query.encode(query)
...> end
iex> flop = %Flop{page: 2, page_size: 10}
iex> build_path(user_pet_path, [%Plug.Conn{}, :index, 123], flop)
"/users/123/pets?page_size=10&page=2"

If the last path helper argument is a query parameter list, the Flop parameters are merged into it.

iex> pet_url = fn _conn, :index, query ->
...>   "https://pets.flop/pets?" <> Plug.Conn.Query.encode(query)
...> end
iex> flop = %Flop{order_by: :name, order_directions: [:desc]}
iex> build_path(pet_url, [%Plug.Conn{}, :index, [user_id: 123]], flop)
"https://pets.flop/pets?user_id=123&order_directions[]=desc&order_by=name"
iex> build_path(
...>   pet_url,
...>   [%Plug.Conn{}, :index, [category: "small", user_id: 123]],
...>   flop
...> )
"https://pets.flop/pets?category=small&user_id=123&order_directions[]=desc&order_by=name"
Link to this function

to_query(flop)

View Source (since 0.6.0)

Specs

to_query(Flop.t()) :: keyword()

Converts a Flop struct into a keyword list that can be used as a query with Phoenix route helper functions.

Examples

iex> to_query(%Flop{})
[]

iex> f = %Flop{order_by: [:name, :age], order_directions: [:desc, :asc]}
iex> to_query(f)
[order_directions: [:desc, :asc], order_by: [:name, :age]]
iex> f |> to_query |> Plug.Conn.Query.encode()
"order_directions[]=desc&order_directions[]=asc&order_by[]=name&order_by[]=age"

iex> f = %Flop{page: 5, page_size: 20}
iex> to_query(f)
[page_size: 20, page: 5]

iex> f = %Flop{first: 20, after: "g3QAAAABZAAEbmFtZW0AAAAFQXBwbGU="}
iex> to_query(f)
[first: 20, after: "g3QAAAABZAAEbmFtZW0AAAAFQXBwbGU="]

iex> f = %Flop{
...>   filters: [
...>     %Flop.Filter{field: :name, op: :=~, value: "Mag"},
...>     %Flop.Filter{field: :age, op: :>, value: 25}
...>   ]
...> }
iex> to_query(f)
[
  filters: %{
    0 => %{field: :name, op: :=~, value: "Mag"},
    1 => %{field: :age, op: :>, value: 25}
  }
]
iex> f |> to_query() |> Plug.Conn.Query.encode()
"filters[0][field]=name&filters[0][op]=%3D~&filters[0][value]=Mag&filters[1][field]=age&filters[1][op]=%3E&filters[1][value]=25"