Flop.Phoenix (Flop Phoenix v0.6.0) 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&page_size=10">Next</a>
<ul class="pagination-list">
<li><span class="pagination-ellipsis">…</span></li>
<li>
<a aria-current="page"
aria-label="Goto page 1"
class="pagination-link is-current"
href="/pets?page=1&page_size=10">1</a>
</li>
<li>
<a aria-label="Goto page 2"
class="pagination-link"
href="/pets?page=2&page_size=10">2</a>
</li>
<li>
<a aria-label="Goto page 3"
class="pagination-link"
href="/pets?page=3&page_size=2">3</a>
</li>
<li><span class="pagination-ellipsis">…</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
Page link options
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. Thex
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>
)
Pagination link aria label
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}"
.
Previous/next links
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
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
Specs
pagination(Flop.Meta.t(), function(), [any()], keyword()) :: Phoenix.LiveView.Rendered.t()
Generates a pagination element.
meta
: The meta information of the query as returned by theFlop
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.
Specs
table(map()) :: Phoenix.LiveView.Rendered.t()
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
: TheFlop.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 callRoutes.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 theitems
list and theopts
and returns the column values for that item's row.
Available options
:for
- The schema module derivingFlop.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>
iftrue
. Defaults tofalse
.:container_class
- The CSS class for the table container. Defaults to"table-container"
.
See the module documentation for examples.
Link to this section Miscellaneous
Specs
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"
Specs
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"