View Source Flop.Phoenix (Flop Phoenix v0.23.0)
Phoenix components for pagination, sortable tables and filter forms with Flop.
Introduction
Please refer to the Readme for an introduction.
Customization
The default classes, attributes, texts and symbols can be overridden by
passing the opts
assign. Since you probably will use the same opts
in all
your templates, you can globally configure an opts
provider function for
each component.
The functions have to return the options as a keyword list. The overrides are deep-merged into the default options.
defmodule MyAppWeb.CoreComponents do
use Phoenix.Component
def pagination_opts do
[
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}ページ目へ",
previous_link_attrs: [class: "prev"],
previous_link_content: previous_icon()
]
end
defp next_icon do
assigns = %{}
~H"""
<i class="fas fa-chevron-right"/>
"""
end
defp previous_icon do
assigns = %{}
~H"""
<i class="fas fa-chevron-left"/>
"""
end
def table_opts do
[
container: true,
container_attrs: [class: "table-container"],
no_results_content: no_results_content(),
table_attrs: [class: "table"]
]
end
defp no_results_content do
assigns = %{}
~H"""
<p>Nothing found.</p>
"""
end
end
Refer to pagination_option/0
and table_option/0
for a list of
available options and defaults.
Once you have defined these functions, you can reference them with a
module/function tuple in config/config.exs
.
config :flop_phoenix,
pagination: [opts: {MyApp.CoreComponents, :pagination_opts}],
table: [opts: {MyApp.CoreComponents, :table_opts}]
Hiding default parameters
Default values for page size and ordering are omitted from the query
parameters. If you pass the :for
assign, the Flop.Phoenix function will
pick up the default values from the schema module deriving Flop.Schema
.
Links
Links are generated with Phoenix.Components.link/1
. This will
lead to <a>
tags with data-phx-link
and data-phx-link-state
attributes,
which will be ignored outside of LiveViews and LiveComponents.
When used within a LiveView or LiveComponent, you will need to handle the new
params in the Phoenix.LiveView.handle_params/3
callback of your LiveView
module.
Using JS commands
You can pass a Phoenix.LiveView.JS
command as on_paginate
and on_sort
attributes.
If used with the path
attribute, the URL will be patched and the given
JS command will be executed.
If used without the path
attribute, you will need to include a push
command to trigger an event when a pagination or sort link is clicked.
You can set a different target by assigning a :target
. The value
will be used as the phx-target
attribute.
<Flop.Phoenix.table
items={@items}
meta={@meta}
on_sort={JS.push("sort-pets")}
target={@myself}
/>
<Flop.Phoenix.pagination
meta={@meta}
on_paginate={JS.push("paginate-pets")}
target={@myself}
/>
You will need to handle the event in the Phoenix.LiveView.handle_event/3
or Phoenix.LiveComponent.handle_event/3
callback of your
LiveView or LiveComponent module. The event name will be the one you set with
the :event
option.
def handle_event("paginate-pets", %{"page" => page}, socket) do
flop = Flop.set_page(socket.assigns.meta.flop, page)
with {:ok, {pets, meta}} <- Pets.list_pets(flop) do
{:noreply, assign(socket, pets: pets, meta: meta)}
end
end
def handle_event("sort-pets", %{"order" => order}, socket) do
flop = Flop.push_order(socket.assigns.meta.flop, order)
with {:ok, {pets, meta}} <- Pets.list_pets(flop) do
{:noreply, assign(socket, pets: pets, meta: meta)}
end
end
Summary
Components
Renders a cursor pagination element.
Renders all inputs for a filter form including the hidden inputs.
Renders hidden inputs for the given form.
Generates a pagination element.
Generates a table with sortable columns.
Miscellaneous
Builds a path that includes query parameters for the given Flop
struct
using the referenced Phoenix path helper function.
Converts a Flop struct into a keyword list that can be used as a query with Phoenix verified routes or route helper functions.
Types
Defines the available options for Flop.Phoenix.cursor_pagination/1
.
Defines the available options for Flop.Phoenix.pagination/1
.
Defines the available options for Flop.Phoenix.table/1
.
Components
@spec cursor_pagination(map()) :: Phoenix.LiveView.Rendered.t()
Renders a cursor pagination element.
Examples
<Flop.Phoenix.cursor_pagination
meta={@meta}
path={~p"/pets"}
/>
<Flop.Phoenix.cursor_pagination
meta={@meta}
path={{Routes, :pet_path, [@socket, :index]}}
/>
Handling parameters and JS commands
If you set the path
assign, a link with query parameters is rendered.
In a LiveView, you need to handle the parameters in the
Phoenix.LiveView.handle_params/3
callback.
def handle_params(params, _, socket) do
{pets, meta} = MyApp.list_pets(params)
{:noreply, assign(socket, meta: meta, pets: pets)}
end
If you use LiveView and set the on_paginate
attribute, you need to update
the Flop parameters in the handle_event/3
callback.
def handle_event("paginate-users", %{"to" => to}, socket) do
flop = Flop.set_cursor(socket.assigns.meta, to)
{pets, meta} = MyApp.list_pets(flop)
{:noreply, assign(socket, meta: meta, pets: pets)}
end
Getting the right parameters from Flop
This component requires the start and end cursors to be set in Flop.Meta
. If
you pass a Flop.Meta
struct with page or offset-based parameters, this will
result in an error. You can enforce cursor-based pagination in your query
function with the default_pagination_type
and pagination_types
options.
def list_pets(params) do
Flop.validate_and_run!(Pet, params,
for: Pet,
default_pagination_type: :first,
pagination_types: [:first, :last]
)
end
default_pagination_type
ensures that Flop defaults to the right pagination
type when it cannot determine the type from the parameters. pagination_types
ensures that parameters for other types are not accepted.
Order fields
The pagination cursor is based on the ORDER BY
fields of the query. It is
important that the combination of order fields is unique across the data set.
You can use:
- the field with the primary key
- a field with a unique index
- all fields of a composite primary key or unique index
If you want to order by fields that are not unique, you can add the primary
key as the last order field. For example, if you want to order by family name
and given name, you should set the order_by
parameter to
[:family_name, :given_name, :id]
.
Attributes
meta
(Flop.Meta
) (required) - The meta information of the query as returned by theFlop
query functions.path
(:any
) - If set, the current view is patched with updated query parameters when a pagination link is clicked. In case theon_paginate
attribute is set as well, the URL is patched and the given JS command is executed.The value must be either a URI string (Phoenix verified route), an MFA or FA tuple (Phoenix route helper), or a 1-ary path builder function. See
Flop.Phoenix.build_path/3
for details.Defaults to
nil
.on_paginate
(Phoenix.LiveView.JS
) - APhoenix.LiveView.JS
command that is triggered when a pagination link is clicked.If used without the
path
attribute, you should include apush
operation to handle the event with thehandle_event
callback.<.cursor_pagination meta={@meta} on_paginate={ JS.dispatch("my_app:scroll_to", to: "#pet-table") |> JS.push("paginate") } />
If used with the
path
attribute, the URL is patched and the given JS command is executed.<.cursor_pagination meta={@meta} path={~"/pets"} on_paginate={JS.dispatch("my_app:scroll_to", to: "#pet-table")} />
With the above attributes in place, you can add the following JavaScript to your application to scroll to the top of your table whenever a pagination link is clicked:
window.addEventListener("my_app:scroll_to", (e) => { e.target.scrollIntoView(); });
You can use CSS to scroll to the new position smoothly.
html { scroll-behavior: smooth; }
Defaults to
nil
.event
(:string
) - If set,Flop.Phoenix
will render links with aphx-click
attribute. Alternatively, set:path
, if you want the parameters to appear in the URL. Deprecated. Useon_paginate
instead.Defaults to
nil
.target
(:string
) - Sets thephx-target
attribute for the pagination links. Defaults tonil
.reverse
(:boolean
) - By default, thenext
link moves forward with the:after
parameter set to the end cursor, and theprevious
link moves backward with the:before
parameter set to the start cursor. Ifreverse
is set totrue
, the destinations of the links are switched.Defaults to
false
.opts
(:list
) - Options to customize the pagination. SeeFlop.Phoenix.cursor_pagination_option/0
. Note that the options passed to the function are deep merged into the default options. Since these options will likely be the same for all the cursor pagination links in a project, it is recommended to define them once in a function or set them in a wrapper function as described in theCustomization
section of the module documentation.Defaults to
[]
.
@spec filter_fields(map()) :: Phoenix.LiveView.Rendered.t()
Renders all inputs for a filter form including the hidden inputs.
Example
def filter_form(%{meta: meta} = assigns) do
assigns = assign(assigns, :form, Phoenix.Component.to_form(meta))
~H"""
<.form for={@form}>
<.filter_fields :let={i} form={@form} fields={[:email, :name]}>
<.input
field={i.field}
label={i.label}
type={i.type}
{i.rest}
/>
</.filter_fields>
</.form>
"""
end
This assumes that you have defined an input
component that renders a form
input including the label.
These options are passed to the inner block via :let
:
- The
field
is aPhoenix.HTML.FormField.t
struct. - The
type
is the input type as a string, not the name of thePhoenix.HTML.Form
input function (e.g."text"
, not:text_input
). The type is derived from the type of the field being filtered on, but it can be overridden in the field options. rest
contains any additional field options passed.
Field configuration
The fields can be passed as atoms or keywords with additional options.
fields={[:name, :email]}
Or
fields={[
name: [label: gettext("Name")],
email: [
label: gettext("Email"),
op: :ilike_and,
type: "email"
],
age: [
label: gettext("Age"),
type: "select",
prompt: "",
options: [
{gettext("young"), :young},
{gettext("old"), :old)}
]
]
]}
Available options:
label
- Defaults to the humanized field name.op
- Defaults to:==
.type
- Defaults to an input type depending on the Ecto type of the filter field.
Any additional options will be passed to the input component (e.g. HTML classes or a list of options).
Attributes
form
(Phoenix.HTML.Form
) (required)fields
(:list
) - The list of fields and field options. Note that inputs will not be rendered for fields that are not marked as filterable in the schema (seeFlop.Schema
).If
dynamic
is set tofalse
, only fields in this list are rendered. Ifdynamic
is set totrue
, only fields for filters present in the givenFlop.Meta
struct are rendered, and the fields are rendered even if they are not passed in thefields
list. In the latter case,fields
is optional, but you can still pass label and input configuration this way.Note that in a dynamic form, it is not possible to configure a single field multiple times.
Defaults to
[]
.dynamic
(:boolean
) - Iftrue
, fields are only rendered for filters that are present in theFlop.Meta
struct passed to the form. You can use this for rendering filter forms that allow the user to add and remove filters dynamically. Thefields
assign is only used for looking up the options in that case.Defaults to
false
.
Slots
inner_block
- The necessary options for rendering a label and an input are passed to the inner block, which allows you to render the fields with your existing components.
The options passed to the inner block are:<.filter_fields :let={i} form={@form} fields={[:email, :name]}> <.input field={i.field} label={i.label} type={i.type} {i.rest} /> </.filter_fields>
field
- APhoenix.HTML.FormField
struct.type
- The input type as a string.label
- The label text as a string.rest
- Any additional options passed in the field options.
@spec pagination(map()) :: Phoenix.LiveView.Rendered.t()
Generates a pagination element.
Examples
<Flop.Phoenix.pagination
meta={@meta}
path={~p"/pets"}
/>
<Flop.Phoenix.pagination
meta={@meta}
path={{Routes, :pet_path, [@socket, :index]}}
/>
Page link options
By default, page links for all pages are shown. 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.
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.
Attributes
meta
(Flop.Meta
) (required) - The meta information of the query as returned by theFlop
query functions.path
(:any
) - If set, the current view is patched with updated query parameters when a pagination link is clicked. In case theon_paginate
attribute is set as well, the URL is patched and the given command is executed.The value must be either a URI string (Phoenix verified route), an MFA or FA tuple (Phoenix route helper), or a 1-ary path builder function. See
Flop.Phoenix.build_path/3
for details.Defaults to
nil
.on_paginate
(Phoenix.LiveView.JS
) - APhoenix.LiveView.JS
command that is triggered when a pagination link is clicked.If used without the
path
attribute, you should include apush
operation to handle the event with thehandle_event
callback.<.pagination meta={@meta} on_paginate={ JS.dispatch("my_app:scroll_to", to: "#pet-table") |> JS.push("paginate") } />
If used with the
path
attribute, the URL is patched and the given JS command is executed.<.pagination meta={@meta} path={~"/pets"} on_paginate={JS.dispatch("my_app:scroll_to", to: "#pet-table")} />
With the above attributes in place, you can add the following JavaScript to your application to scroll to the top of your table whenever a pagination link is clicked:
window.addEventListener("my_app:scroll_to", (e) => { e.target.scrollIntoView(); });
You can use CSS to scroll to the new position smoothly.
html { scroll-behavior: smooth; }
Defaults to
nil
.event
(:string
) - If set,Flop.Phoenix
will render links with aphx-click
attribute. Alternatively, set:path
, if you want the parameters to appear in the URL. Deprecated in favor ofon_paginate
.Defaults to
nil
.target
(:string
) - Sets thephx-target
attribute for the pagination links. Defaults tonil
.opts
(:list
) - Options to customize the pagination. SeeFlop.Phoenix.pagination_option/0
. Note that the options passed to the function are deep merged into the default options. Since these options will likely be the same for all the tables in a project, it is recommended to define them once in a function or set them in a wrapper function as described in theCustomization
section of the module documentation.Defaults to
[]
.
@spec table(map()) :: Phoenix.LiveView.Rendered.t()
Generates a table with sortable columns.
Example
<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.Schema
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
.
Attributes
id
(:string
) - ID used on the table. If not set, an ID is chosen based on the schema module derived from theFlop.Meta
struct.The ID is necessary in case the table is fed with a LiveView stream.
items
(:list
) (required) - The list of items to be displayed in rows. This is the result list returned by the query.meta
(Flop.Meta
) (required) - TheFlop.Meta
struct returned by the query function.path
(:any
) - If set, the current view is patched with updated query parameters when a header link for sorting is clicked. In case theon_sort
attribute is set as well, the URL is patched and the given JS command is executed.The value must be either a URI string (Phoenix verified route), an MFA or FA tuple (Phoenix route helper), or a 1-ary path builder function. See
Flop.Phoenix.build_path/3
for details.Defaults to
nil
.on_sort
(Phoenix.LiveView.JS
) - APhoenix.LiveView.JS
command that is triggered when a header link for sorting is clicked.If used without the
path
attribute, you should include apush
operation to handle the event with thehandle_event
callback.<.table items={@items} meta={@meta} on_sort={ JS.dispatch("my_app:scroll_to", to: "#pet-table") |> JS.push("sort") } />
If used with the
path
attribute, the URL is patched and the given JS command is executed.<.table meta={@meta} path={~"/pets"} on_sort={JS.dispatch("my_app:scroll_to", to: "#pet-table")} />
Defaults to
nil
.event
(:string
) - If set,Flop.Phoenix
will render links with aphx-click
attribute. Alternatively, set:path
, if you want the parameters to appear in the URL. Deprecated in favor ofon_sort
.Defaults to
nil
.target
(:string
) - Sets thephx-target
attribute for the header links. Defaults tonil
.caption
(:string
) - Content for the<caption>
element. Defaults tonil
.opts
(:list
) - Keyword list with additional options (seeFlop.Phoenix.table_option/0
). Note that the options passed to the function are deep merged into the default options. Since these options will likely be the same for all the tables in a project, it is recommended to define them once in a function or set them in a wrapper function as described in theCustomization
section of the module documentation.Defaults to
[]
.row_id
(:any
) - Overrides the default function that retrieves the row ID from a stream item. Defaults tonil
.row_click
(Phoenix.LiveView.JS
) - Sets thephx-click
function attribute for each rowtd
. Expects to be a function that receives a row item as an argument. This does not add thephx-click
attribute to theaction
slot.Example:
row_click={&JS.navigate(~p"/users/#{&1}")}
Defaults to
nil
.row_item
(:any
) - This function is called on the row item before it is passed to the :col and :action slots.Defaults to
&Function.identity/1
.
Slots
col
(required) - For each column to render, add one<:col>
element.<:col :let={pet} label="Name" field={:name} col_style="width: 20%;"> <%= pet.name %> </:col>
Any additional assigns will be added as attributes to the
<td>
elements.Accepts attributes:
label
(:any
) - The content for the header column.field
(:atom
) - The field name for sorting. 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 thefield
attribute is omitted or set tonil
orfalse
, the column header will not be clickable.directions
(:any
) - An optional 2-element tuple used for custom ascending and descending sort behavior for the column, i.e.{:asc_nulls_last, :desc_nulls_first}
show
(:boolean
) - Boolean value to conditionally show the column. Defaults totrue
Deprecated. Use:if
instead.hide
(:boolean
) - Boolean value to conditionally hide the column. Defaults tofalse
. Deprecated. Use:if
instead.col_style
(:string
) - If set, a<colgroup>
element is rendered and the value of thecol_style
assign is set asstyle
attribute for the<col>
element of the respective column. You can set thewidth
,background
,border
, andvisibility
of a column this way.col_class
(:string
) - If set, a<colgroup>
element is rendered and the value of thecol_class
assign is set asclass
attribute for the<col>
element of the respective column. You can set thewidth
,background
,border
, andvisibility
of a column this way.thead_th_attrs
(:list
) - Additional attributes to pass to the<th>
element as a static keyword list. Note that these attributes will override any conflictingthead_th_attrs
that are set at the table level.th_wrapper_attrs
(:list
) - Additional attributes for the<span>
element that wraps the header link and the order direction symbol. Note that these attributes will override any conflictingth_wrapper_attrs
that are set at the table level.tbody_td_attrs
(:any
) - Additional attributes to pass to the<td>
element. May be provided as a static keyword list, or as a 1-arity function to dynamically generate the list using row data. Note that these attributes will override any conflictingtbody_td_attrs
that are set at the table level.
action
- The slot for showing user actions in the last table column. These columns do not receive therow_click
attribute.<:action :let={user}> <.link navigate={~p"/users/#{user}"}>Show</.link> </:action>
Accepts attributes:
label
(:string
) - The content for the header column.show
(:boolean
) - Boolean value to conditionally show the column. Defaults totrue
.hide
(:boolean
) - Boolean value to conditionally hide the column. Defaults tofalse
.col_style
(:string
) - If set, a<colgroup>
element is rendered and the value of thecol_style
assign is set asstyle
attribute for the<col>
element of the respective column. You can set thewidth
,background
,border
, andvisibility
of a column this way.col_class
(:string
) - If set, a<colgroup>
element is rendered and the value of thecol_class
assign is set asclass
attribute for the<col>
element of the respective column. You can set thewidth
,background
,border
, andvisibility
of a column this way.thead_th_attrs
(:list
) - Any additional attributes to pass to the<th>
as a keyword list.tbody_td_attrs
(:any
) - Any additional attributes to pass to the<td>
. Can be a keyword list or a function that takes the current row item as an argument and returns a keyword list.
foot
- You can optionally add afoot
. The inner block will be rendered inside atfoot
element.<Flop.Phoenix.table> <:foot> <tr><td>Total: <span class="total"><%= @total %></span></td></tr> </:foot> </Flop.Phoenix.table>
Miscellaneous
build_path(path, meta_or_flop_or_params, opts \\ [])
View Source (since 0.6.0)@spec build_path( String.t() | {module(), atom(), [any()]} | {function(), [any()]} | (keyword() -> String.t()), Flop.Meta.t() | Flop.t() | keyword(), keyword() ) :: String.t()
Builds a path that includes query parameters for the given Flop
struct
using the referenced Phoenix path helper function.
The first argument can be either one of:
- an MFA tuple (module, function name as atom, arguments)
- a 2-tuple (function, arguments)
- a URL string, usually produced with a verified route (e.g.
~p"/some/path"
) - a function that takes the Flop parameters as a keyword list as an argument
Default values for limit
, page_size
, order_by
and order_directions
are
omitted from the query parameters. To pick up the default parameters from a
schema module deriving Flop.Schema
, you need to pass the :for
option. To
pick up the default parameters from the backend module, you need to pass the
:backend
option. If you pass a Flop.Meta
struct as the second argument,
these options are retrieved from the struct automatically.
Date and Time Filters
When using filters on
Date
,DateTime
,NaiveDateTime
orTime
fields, you may need to implement thePhoenix.Param
protocol for these structs. See the documentation forto_query/2
.
Examples
With a verified route
The examples below use plain URL strings without the p-sigil, so that the doc tests work, but in your application, you can use verified routes or anything else that produces a URL.
iex> flop = %Flop{page: 2, page_size: 10}
iex> path = build_path("/pets", flop)
iex> %URI{path: parsed_path, query: parsed_query} = URI.parse(path)
iex> {parsed_path, URI.decode_query(parsed_query)}
{"/pets", %{"page" => "2", "page_size" => "10"}}
The Flop query parameters will be merged into existing query parameters.
iex> flop = %Flop{page: 2, page_size: 10}
iex> path = build_path("/pets?species=dogs", flop)
iex> %URI{path: parsed_path, query: parsed_query} = URI.parse(path)
iex> {parsed_path, URI.decode_query(parsed_query)}
{"/pets", %{"page" => "2", "page_size" => "10", "species" => "dogs"}}
With an MFA tuple
iex> flop = %Flop{page: 2, page_size: 10}
iex> build_path(
...> {Flop.PhoenixTest, :route_helper, [%Plug.Conn{}, :pets]},
...> flop
...> )
"/pets?page_size=10&page=2"
With a function/arguments tuple
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, args}
or {&Routes.pet_path/3, args}
as the
first argument.
Passing a Flop.Meta
struct or a keyword list
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"
Additional path parameters
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"
Additional query parameters
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"
Set page as path parameter
Finally, you can also pass a function that takes the Flop parameters as a keyword list as an argument. Default values will not be included in the parameters passed to the function. You can use this if you need to set some of the parameters as path parameters instead of query parameters.
iex> flop = %Flop{page: 2, page_size: 10}
iex> build_path(
...> fn params ->
...> {page, params} = Keyword.pop(params, :page)
...> query = Plug.Conn.Query.encode(params)
...> if page, do: "/pets/page/#{page}?#{query}", else: "/pets?#{query}"
...> end,
...> flop
...> )
"/pets/page/2?page_size=10"
Note that in this example, the anonymous function just returns a string. With Phoenix 1.7, you will be able to use verified routes.
build_path(
fn params ->
{page, query} = Keyword.pop(params, :page)
if page, do: ~p"/pets/page/#{page}?#{query}", else: ~p"/pets?#{query}"
end,
flop
)
Note that the keyword list passed to the path builder function is built using
Plug.Conn.Query.encode/2
, which means filters are formatted as map with
integer keys.
Set filter value as path parameter
If you need to set a filter value as a path parameter, you can use
Flop.Filter.pop/3
.
iex> flop = %Flop{
...> page: 5,
...> order_by: [:published_at],
...> filters: [
...> %Flop.Filter{field: :category, op: :==, value: "announcements"}
...> ]
...> }
iex> build_path(
...> fn params ->
...> {page, params} = Keyword.pop(params, :page)
...> filters = Keyword.get(params, :filters, [])
...> {category, filters} = Flop.Filter.pop(filters, :category)
...> params = Keyword.put(params, :filters, filters)
...> query = Plug.Conn.Query.encode(params)
...>
...> case {page, category} do
...> {nil, nil} -> "/articles?#{query}"
...> {page, nil} -> "/articles/page/#{page}?#{query}"
...> {nil, %{value: category}} -> "/articles/category/#{category}?#{query}"
...> {page, %{value: category}} -> "/articles/category/#{category}/page/#{page}?#{query}"
...> end
...> end,
...> flop
...> )
"/articles/category/announcements/page/5?order_by[]=published_at"
Converts a Flop struct into a keyword list that can be used as a query with Phoenix verified routes or route helper functions.
Default parameters
Default parameters for the limit and order parameters are omitted. The
defaults are determined by calling Flop.get_option/3
.
- Pass the
:for
option to pick up the default values from a schema module derivingFlop.Schema
. - Pass the
:backend
option to pick up the default values from your backend configuration. - If neither the schema module nor the backend module have default options set, the function will fall back to the application environment.
Encoding queries
To encode the returned query as a string, you will need to use
Plug.Conn.Query.encode/1
. URI.encode_query/1
does not support bracket
notation for arrays and maps.
Date and time filters
If you use the result of this function directly with
Phoenix.VerifiedRoutes.sigil_p/2
for verified routes or in a route helper
function, all cast filter values need to be able to be converted to a string
using the Phoenix.Param
protocol.
This protocol is implemented by default for integers, binaries, atoms, and structs. For structs, Phoenix's default behavior is to fetch the id field.
If you have filters with Date
, DateTime
, NaiveDateTime
,
Time
values, or any other custom structs (e.g. structs that represent
composite types like a range column), you will need to implement the protocol
for these specific structs in your application.
defimpl Phoenix.Param, for: Date do
def to_param(%Date{} = d), do: to_string(d)
end
defimpl Phoenix.Param, for: DateTime do
def to_param(%DateTime{} = dt), do: to_string(dt)
end
defimpl Phoenix.Param, for: NaiveDateTime do
def to_param(%NaiveDateTime{} = dt), do: to_string(dt)
end
defimpl Phoenix.Param, for: Time do
def to_param(%Time{} = t), do: to_string(t)
end
It is important that the chosen string representation can be cast back into the Ecto type.
Examples
iex> to_query(%Flop{})
[]
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> to_query(f)
[filters: %{0 => %{value: "Mag", op: :=~, field: :name}, 1 => %{value: 25, op: :>, field: :age}}]
iex> f = %Flop{page: 5, page_size: 20}
iex> to_query(f, default_limit: 20)
[page: 5]
Encoding the query as a string:
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"
Types
@type cursor_pagination_option() :: {:disabled_class, String.t()} | {:next_link_attrs, keyword()} | {:next_link_content, Phoenix.HTML.safe() | binary()} | {:previous_link_attrs, keyword()} | {:previous_link_content, Phoenix.HTML.safe() | binary()} | {:wrapper_attrs, keyword()}
Defines the available options for Flop.Phoenix.cursor_pagination/1
.
:disabled_class
- The class which is added to disabled links. Default:"disabled"
.:next_link_attrs
- The attributes for the link to the next page. Default:[aria: [label: "Go to next page"], class: "pagination-next"]
.:next_link_content
- The content for the link to the next page. Default:"Next"
.:previous_link_attrs
- The attributes for the link to the previous page. Default:[aria: [label: "Go to previous page"], class: "pagination-previous"]
.:previous_link_content
- The content for the link to the previous page. Default:"Previous"
.:wrapper_attrs
- The attributes for the<nav>
element that wraps the pagination links. Default:[class: "pagination", role: "navigation", aria: [label: "pagination"]]
.
@type pagination_option() :: {:current_link_attrs, keyword()} | {:disabled_class, String.t()} | {:ellipsis_attrs, keyword()} | {:ellipsis_content, Phoenix.HTML.safe() | binary()} | {:next_link_attrs, keyword()} | {:next_link_content, Phoenix.HTML.safe() | binary()} | {:page_links, :all | :hide | {:ellipsis, pos_integer()}} | {:pagination_link_aria_label, (pos_integer() -> binary())} | {:pagination_link_attrs, keyword()} | {:pagination_list_attrs, keyword()} | {:pagination_list_item_attrs, keyword()} | {:previous_link_attrs, keyword()} | {:previous_link_content, Phoenix.HTML.safe() | binary()} | {:wrapper_attrs, keyword()}
Defines the available options for Flop.Phoenix.pagination/1
.
:current_link_attrs
- The attributes for the link to the current page. Default:[class: "pagination-link is-current", aria: [current: "page"]]
.:disabled_class
- The class which is added to disabled links. Default:"disabled"
.:ellipsis_attrs
- The attributes for the<span>
that wraps the ellipsis. Default:[class: "pagination-ellipsis"]
.:ellipsis_content
- The content for the ellipsis element. Default:{:safe, "…"}
.:next_link_attrs
- The attributes for the link to the next page. Default:[aria: [label: "Go to next page"], class: "pagination-next"]
.:next_link_content
- The content for the link to the next page. Default:"Next"
.:page_links
- Specifies how many page links should be rendered. Default::all
.:all
- Renders all page links.{:ellipsis, n}
- Rendersn
page links. Renders ellipsis elements if there are more pages than displayed.:hide
- Does not render any page links.
:pagination_link_aria_label
- 1-arity function that takes a page number and returns an aria label for the corresponding page link. Default:&"Go to page #{&1}"
.:pagination_link_attrs
- The attributes for the pagination links. Default:[class: "pagination-link"]
.:pagination_list_attrs
- The attributes for the pagination list. Default:[class: "pagination-list"]
.:pagination_list_item_attrs
- The attributes for the pagination list items. Default:[]
.:previous_link_attrs
- The attributes for the link to the previous page. Default:[aria: [label: "Go to previous page"], class: "pagination-previous"]
.:previous_link_content
- The content for the link to the previous page. Default:"Previous"
.:wrapper_attrs
- The attributes for the<nav>
element that wraps the pagination links. Default:[class: "pagination", role: "navigation", aria: [label: "pagination"]]
.
@type table_option() :: {:container, boolean()} | {:container_attrs, keyword()} | {:no_results_content, Phoenix.HTML.safe() | binary()} | {:symbol_asc, Phoenix.HTML.safe() | binary()} | {:symbol_attrs, keyword()} | {:symbol_desc, Phoenix.HTML.safe() | binary()} | {:symbol_unsorted, Phoenix.HTML.safe() | binary()} | {:table_attrs, keyword()} | {:tbody_attrs, keyword()} | {:thead_attrs, keyword()} | {:tbody_td_attrs, keyword()} | {:tbody_tr_attrs, keyword() | (any() -> keyword())} | {:th_wrapper_attrs, keyword()} | {:thead_th_attrs, keyword()} | {:thead_tr_attrs, keyword()}
Defines the available options for Flop.Phoenix.table/1
.
:container
- Wraps the table in a<div>
iftrue
. Default:false
.:container_attrs
- The attributes for the table container. Default:[class: "table-container"]
.:no_results_content
- Any content that should be rendered if there are no results. Default:<p>No results.</p>
.:table_attrs
- The attributes for the<table>
element. Default:[]
.:th_wrapper_attrs
- The attributes for the<span>
element that wraps the header link and the order direction symbol. Default:[]
.:symbol_asc
- The symbol that is used to indicate that the column is sorted in ascending order. Default:"▴"
.:symbol_attrs
- The attributes for the<span>
element that wraps the order direction indicator in the header columns. Default:[class: "order-direction"]
.:symbol_desc
- The symbol that is used to indicate that the column is sorted in ascending order. Default:"▾"
.:symbol_unsorted
- The symbol that is used to indicate that the column is not sorted. Default:nil
.:tbody_attrs
: Attributes to be added to the<tbody>
tag within the<table>
. Default:[]
.:tbody_td_attrs
: Attributes to be added to each<td>
tag within the<tbody>
. Default:[]
.:thead_attrs
: Attributes to be added to the<thead>
tag within the<table>
. Default:[]
.:tbody_tr_attrs
: Attributes to be added to each<tr>
tag within the<tbody>
. A function with arity of 1 may be passed to dynamically generate the attrs based on row data. Default:[]
.:thead_th_attrs
: Attributes to be added to each<th>
tag within the<thead>
. Default:[]
.:thead_tr_attrs
: Attributes to be added to each<tr>
tag within the<thead>
. Default:[]
.