Flop.Phoenix (Flop Phoenix v0.25.3)
View SourcePhoenix components for pagination, sortable tables and filter forms with Flop.
Introduction
Please refer to the Readme for an introduction.
Customization
To customize the components, it is recommended to define wrapper components in
your CoreComponents module that pass attributes that are constant for your
application and add additional markup as necessary.
For example, to customize the pagination component, define your own
pagination component:
defmodule MyAppWeb.CoreComponents do
use Phoenix.Component
attr :meta, Flop.Meta, required: true
attr :path, :any, default: nil
attr :on_paginate, JS, default: nil
attr :target, :string, default: nil
attr :aria_label, :string,
default: "Pagination",
doc: """
Aria label for the `<nav>` element. The value should be localized. In
languages with latin characters, the first letter should be capitalized.
If multiple pagination components are rendered on the same page, each one
should have a distinct aria label.
"""
def pagination(assigns) do
~H"""
<Flop.Phoenix.pagination
class="pagination"
meta={@meta}
path={@path}
on_paginate={@on_paginate}
target={@target}
aria-label={@aria_label}
page_link_aria_label_fun={&"#{&1}ページ目へ"}
>
<:previous attrs={[class: "previous"]}>
<i class="fas fa-chevron-left"/>
</:previous>
<:next attrs={[class: "next"]}>
<i class="fas fa-chevron-right"/>
</:next>
<:ellipsis>
<span class="ellipsis">‥</span>
</:ellipsis>
</Flop.Phoenix.pagination>
"""
end
endRefer to the documentation of Flop.Phoenix.pagination/1 for available
attributes and slots on the pagination component and to table_option/0 for
a list of available options and defaults for the table component.
Using links
If the path attribute is set on the pagination and table component,
pagination and sorting is handled via query parameters. You will need to
handle those parameters in the Phoenix.LiveView.handle_params/3 callback
of your LiveView module.
def handle_params(params, _, socket) do
{pets, meta} = Pets.list_pets(params)
{:noreply, assign(socket, pets: pets, meta: meta)}
endUsing 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. This can be used to scroll to the top after a
pagination or sorting event, for example.
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 with the target attribute, which will be used
as phx-target.
<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.
# for page-based pagination
def handle_event("paginate-pets", %{"page" => page}, socket) do
flop = Flop.set_page(socket.assigns.meta.flop, page)
{pets, meta} = Pets.list_pets(flop)
{:noreply, assign(socket, pets: pets, meta: meta)}
end
# for cursor-based pagination
def handle_event("paginate-pets", %{"to" => direction}, socket) do
flop =
case direction do
:previous -> Flop.to_previous_cursor(socket.assigns.meta)
:next -> Flop.to_next_cursor(socket.assigns.meta)
end
{pets, meta} = Pets.list_pets(flop)
{:noreply, assign(socket, pets: pets, meta: meta)}
end
def handle_event("sort-pets", %{"order" => order}, socket) do
flop = Flop.push_order(socket.assigns.meta.flop, order)
{pets, meta} = Pets.list_pets(flop)
{:noreply, assign(socket, pets: pets, meta: meta)}
end
Summary
Components
Renders all inputs for a filter form including the hidden inputs.
Renders hidden inputs for the given form.
Renders pagination links for the given Flop.Meta struct.
This component is a pagination builder.
Generates a table with sortable columns.
Types
Defines how many page links to render.
Defines the available options for Flop.Phoenix.table/1.
Functions
Builds a path that includes query parameters for the given Flop struct
using the referenced Phoenix path helper function.
Returns an aria label for a link to the given page number.
Returns the range of page links to be rendered.
Converts a Flop struct into a keyword list that can be used as a query with Phoenix verified routes or route helper functions.
Components
@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>
"""
endThis 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
fieldis aPhoenix.HTML.FormFieldstruct. - The
typeis the input type as a string (e.g."text","number"). The type is derived from the type of the field being filtered on, but it can be overridden in the field options. restcontains 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
dynamicis set tofalse, only fields in this list are rendered. Ifdynamicis set totrue, only fields for filters present in the givenFlop.Metastruct are rendered, and the fields are rendered even if they are not passed in thefieldslist. In the latter case,fieldsis 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.Metastruct passed to the form. You can use this for rendering filter forms that allow the user to add and remove filters dynamically. Thefieldsassign 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.FormFieldstruct.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()
Renders pagination links for the given Flop.Meta struct.
This component can render both page-based or cursor-based pagination links. Which one is used depends on the pagination type that was used to make the Flop query.
Examples
With a verified route:
<Flop.Phoenix.pagination
meta={@meta}
path={~p"/pets"}
/>With an event:
<Flop.Phoenix.pagination
meta={@meta}
on_paginate={JS.push("paginate")}
/>With a route helper:
<Flop.Phoenix.pagination
meta={@meta}
path={{Routes, :pet_path, [@socket, :index]}}
/>With all attributes and slots:
<Flop.Phoenix.pagination
meta={@meta}
path={~p"/pets"}
on_paginate={JS.dispatch("my_app:scroll_to", to: "#pet-table")}
target={@myself}
class="pagination"
page_link_aria_label_fun={&"#{&1}ページ目へ"}
page_links={5}
page_list_attrs={[class: "pagination-list"]}
page_list_item_attrs={[class: "pagination-item"]}
page_link_attrs={[class: "pagination-link"]}
current_page_link_attrs={[class: "pagination-link is-current"]}
disabled_link_attrs={[class: "is-disabled"]}
reverse
>
<:previous attrs={[class: "pagination-previous"]}>
Previous
</:previous>
<:next attrs={[class: "pagination-next"]}>
Next
</:next>
<:ellipsis>
<span class="pagination-ellipsis" aria-hidden="true">…</span>
</:ellipsis>
</Flop.Phoenix.pagination>Attributes
meta(Flop.Meta) (required) - The meta information of the query as returned by theFlopquery functions.path(:any) - If set, the current view is patched with updated query parameters when a pagination link is clicked. In case theon_paginateattribute 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/3for details.Defaults to
nil.on_paginate(Phoenix.LiveView.JS) - APhoenix.LiveView.JScommand that is triggered when a pagination link is clicked.If used without the
pathattribute, you should include apushoperation to handle the event with thehandle_eventcallback.<.pagination meta={@meta} on_paginate={ JS.dispatch("my_app:scroll_to", to: "#pet-table") |> JS.push("paginate") } />If used with the
pathattribute, the URL is patched and the given JS command is executed.<.pagination meta={@meta} path={~p"/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.target(:string) - Sets thephx-targetattribute for the pagination links. Defaults tonil.page_link_aria_label_fun({:fun, 1}) - Function that returns an aria label for the page link or button to the given page number.The returned label should be localized and start with a capital letter.
Defaults to
&Flop.Phoenix.page_link_aria_label/1.page_links(:any) - Defines how many page links to render.:all- Renders all page links.:none- Does not render any page links.- Integer - Renders up to the specified number of page links in addition to the first and last page.
This attribute is only used for page-based pagination.
Defaults to
5.reverse(:boolean) - By default, thenextlink moves forward with the:afterparameter set to the end cursor, and thepreviouslink moves backward with the:beforeparameter set to the start cursor. Ifreverseis set totrue, the destinations of the links are switched.This attribute is only for cursor-based pagination.
Defaults to
false.page_list_attrs(:list) - Attributes to be added to the<ul>that contains the page links. Defaults to[].page_list_item_attrs(:list) - Attributes to be added to the<li>elements that contain the page links. Defaults to[].page_link_attrs(:list) - Attributes to be added to the page links or buttons.These attributes are not applied to previous links, next links, or the current page link.
Defaults to
[].current_page_link_attrs(:list) - Attributes to be added to the current page link or button.Note that the
aria-currentattribute is automatically set.It is recommended to define CSS styles using the
[aria-current="page"]selector instead of using a class.Defaults to
[].disabled_link_attrs(:list) - Attributes to be added to disabled previous/next links or buttons.If a
classis set, it is merged with the class set on the previous/next slot.Note that the
disabledattribute is automatically set for buttons and thearia-disabled="true"attribute is automatically set for links.It is recommended to define CSS styles using the
[disabled], [aria-disabled="true"]selector instead of using a class.Defaults to
[].Global attributes are accepted. The attributes are added to the outer
<nav>element.The
aria-labeldefaults to"Pagination". If your application is localized, the label should be translated to the user locale. In languages with latin characters, the first letter should be capitalized. If multiple pagination components are rendered on the same page, each one should have a distinct aria label.
Slots
previous- The content of the pagination link or button to the previous page.If the slot is not used, the text "Previous" is rendered.
Accepts attributes:
attrs(:list) - Any additional attributes to add to the link or button.Defaults to
[aria: [label: "Go to previous page"]].
next- The content of the pagination link or button to the next page.If the slot is not used, the text "Next" is rendered.
Accepts attributes:
attrs(:list) - Any additional attributes to add to the link or button.Defaults to
[aria: [label: "Go to next page"]].
ellipsis- The content of the<li>element that usually shows an ellipsis and is rendered toward the beginning and/or end of the page links if there are more pages than the configured limit.If the slot is not used, a default element is used:
<span aria-hidden="true">…</span>
@spec pagination_for(map()) :: Phoenix.LiveView.Rendered.t()
This component is a pagination builder.
It does not render anything by itself. Instead, it prepares all the necessary information needed to render a pagination component and passes it to the inner block.
For an example implementation, see pagination/1.
Example
<.pagination_for
:let={p}
meta={@meta}
page_links={4}
path={~p"/birds"}
>
<%!-- put together your component here --%>
</.pagination_for>The variable passed to the inner block is a Flop.Phoenix.Pagination struct.
Attributes
meta(Flop.Meta) (required) - The meta information of the query as returned by theFlopquery functions.path(:any) - If set, a function that takes a page number and returns a link with pagination, filter, and sort parameters based on the given path is passed aspath_funto the inner block.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/3for details.Defaults to
nil.page_links(:any) - Defines how many page links to render.:all- Renders all page links.:none- Does not render any page links.- Integer - Renders up to the specified number of page links in addition to the first and last page.
A
page_range_startandpage_range_endattribute are passed to the inner block based on this option. If this attribute is set to:none, both of those values will benil.Defaults to
5.reverse(:boolean) - By default, thenextlink moves forward with the:afterparameter set to the end cursor, and thepreviouslink moves backward with the:beforeparameter set to the start cursor. Ifreverseis set totrue, the destinations of the links are switched.Defaults to
false.
Slots
inner_block(required)
@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.Metastruct.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.Metastruct 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_sortattribute 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/3for details.Defaults to
nil.on_sort(Phoenix.LiveView.JS) - APhoenix.LiveView.JScommand that is triggered when a header link for sorting is clicked.If used without the
pathattribute, you should include apushoperation to handle the event with thehandle_eventcallback.<.table items={@items} meta={@meta} on_sort={ JS.dispatch("my_app:scroll_to", to: "#pet-table") |> JS.push("sort") } />If used with the
pathattribute, the URL is patched and the given JS command is executed.<.table meta={@meta} path={~p"/pets"} on_sort={JS.dispatch("my_app:scroll_to", to: "#pet-table")} />Defaults to
nil.target(:string) - Sets thephx-targetattribute 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 theCustomizationsection 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(:any) - Sets thephx-clickfunction attribute for each rowtd. Expects to be a function that receives a row item as an argument. This does not add thephx-clickattribute to theactionslot.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 thefieldattribute is omitted or set tonilorfalse, 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}col_style(:string) - If set, a<colgroup>element is rendered and the value of thecol_styleassign is set asstyleattribute for the<col>element of the respective column. You can set thewidth,background,border, andvisibilityof a column this way.col_class(:string) - If set, a<colgroup>element is rendered and the value of thecol_classassign is set asclassattribute for the<col>element of the respective column. You can set thewidth,background,border, andvisibilityof 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_attrsthat 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_attrsthat 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_attrsthat are set at the table level.
action- The slot for showing user actions in the last table column. These columns do not receive therow_clickattribute.<:action :let={user}> <.link navigate={~p"/users/#{user}"}>Show</.link> </:action>Accepts attributes:
label(:string) - The content for the header column.col_style(:string) - If set, a<colgroup>element is rendered and the value of thecol_styleassign is set asstyleattribute for the<col>element of the respective column. You can set thewidth,background,border, andvisibilityof a column this way.col_class(:string) - If set, a<colgroup>element is rendered and the value of thecol_classassign is set asclassattribute for the<col>element of the respective column. You can set thewidth,background,border, andvisibilityof 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 atfootelement.<Flop.Phoenix.table> <:foot> <tr><td>Total: <span class="total">{@total}</span></td></tr> </:foot> </Flop.Phoenix.table>
Types
@type page_link_option() :: :all | :none | pos_integer()
Defines how many page links to render.
:all- Renders all page links.:none- Does not render any page links.- Integer - Renders up to the specified number of page links in addition to the first and last page.
@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:[].
Functions
@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 or Time fields,
you may need to implement the Phoenix.Param protocol for these structs.
See the documentation for to_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"
Returns an aria label for a link to the given page number.
This is the default function used by pagination/1.
Example
iex> page_link_aria_label(5)
"Go to page 5"
@spec page_link_range(page_link_option(), pos_integer(), pos_integer()) :: {pos_integer() | nil, pos_integer() | nil}
Returns the range of page links to be rendered.
Usage
iex> page_link_range(:all, 4, 20)
{1, 20}
iex> page_link_range(:none, 4, 20)
{nil, nil}
iex> page_link_range(5, 4, 20)
{2, 6}
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
:foroption to pick up the default values from a schema module derivingFlop.Schema. - Pass the
:backendoption 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)
endIt 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"