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

Phoenix 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

  def pagination(assigns) do
    ~H"""
    <Flop.Phoenix.pagination
      meta={@meta}
      path={@path}
      on_paginate={@on_paginate}
      target={@target}
      opts={[
        ellipsis_attrs: [class: "ellipsis"],
        ellipsis_content: "‥",
        next_link_attrs: [class: "next"],
        next_link_content: next_icon(),
        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
end

The given opts attributes are deep-merged into the default options.

Refer to pagination_option/0 and table_option/0 for a list of available options and defaults.

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 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 setting the target attribute. 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.

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 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.pagination/1.

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 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

filter_fields(assigns)

(since 0.12.0)
@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 a Phoenix.HTML.FormField struct.
  • The type is 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.
  • 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 (see Flop.Schema).

    If dynamic is set to false, only fields in this list are rendered. If dynamic is set to true, only fields for filters present in the given Flop.Meta struct are rendered, and the fields are rendered even if they are not passed in the fields 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) - If true, fields are only rendered for filters that are present in the Flop.Meta struct passed to the form. You can use this for rendering filter forms that allow the user to add and remove filters dynamically. The fields 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.
    <.filter_fields :let={i} form={@form} fields={[:email, :name]}>
      <.input
        field={i.field}
        label={i.label}
        type={i.type}
        {i.rest}
      />
    </.filter_fields>
    The options passed to the inner block are:
    • field - A Phoenix.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.

hidden_inputs_for_filter(assigns)

(since 0.16.0)

Renders hidden inputs for the given form.

You can use this for convenience if you have a complex form layout that cannot be accomplished with Flop.Phoenix.filter_fields/1. Put it as a direct child of the form component to render the hidden inputs for pagination and order parameters. Then use PhoenixHTMLHelpers.Form.inputs_for/3 to render a single filter field, and place this component within the anonymous function to render the hidden inputs for the filter field and operator.

Since the filters are represented as an array in the params, make sure to add the offset option so that the Flop.Meta can be properly mapped back to your input fields. For every call to inputs_for always add the length of all previous calls to inputs_for as offset. Note that the example below uses the old PhoenixHTMLHelpers.Form.inputs_for/4 function. Phoenix.Component.inputs_for/1 currently overrides the index set by Flop.Phoenix.FormData, which leads to duplicate DOM IDs.

<.form :let={f} for={@meta}>
  <.hidden_inputs_for_filter form={@form} />

  <div class="field-group">
    <div class="field">
      <%= PhoenixHTMLHelpers.Form.inputs_for f, :filters, [fields: [:name]], fn ff -> %>
        <.hidden_inputs_for_filter form={ff} />
        <.input label="Name" type="text" field={{ff, :value}} />
      <% end %>
    </div>
    <div class="field">
      <%= PhoenixHTMLHelpers.Form.inputs_for f, :filters, [fields: [{:email, op: :ilike}], offset: 1] fn ff -> %>
        <.hidden_inputs_for_filter form={ff} />
        <.input label="E-mail" type="email" field={{ff, :value}} />
      <% end %>
    </div>
  </div>
</.form>

Attributes

pagination(assigns)

@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 a route helper:

<Flop.Phoenix.pagination
  meta={@meta}
  path={{Routes, :pet_path, [@socket, :index]}}
/>

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.

Attributes

  • meta (Flop.Meta) (required) - The meta information of the query as returned by the Flop query functions.

  • path (:any) - If set, the current view is patched with updated query parameters when a pagination link is clicked. In case the on_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) - A Phoenix.LiveView.JS command that is triggered when a pagination link is clicked.

    If used without the path attribute, you should include a push operation to handle the event with the handle_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={~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 the phx-target attribute for the pagination links. 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.

    This attribute is only used for page-based pagination.

    Defaults to 5.

  • reverse (:boolean) - By default, the next link moves forward with the :after parameter set to the end cursor, and the previous link moves backward with the :before parameter set to the start cursor. If reverse is set to true, the destinations of the links are switched.

    This attribute is only for for cursor-based pagination.

    Defaults to false.

  • opts (:list) - Options to customize the pagination. See Flop.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 the Customization section of the module documentation.

    Defaults to [].

pagination_for(assigns)

(since 0.24.0)
@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"}
>
  <%!--
  The variable passed to the inner block via `:let` looks similar to:

  %{
    current_page: 6,
    ellipsis_start?: true,
    ellipsis_end?: true,
    next_page: 7,
    page_range_end: 8,
    path_fun: #Function<42.18682967/1 in :erl_eval.expr/6>,
    page_range_start: 5,
    pagination_type: :page,
    previous_page: 5,
    total_pages: 10
  }
  %>
</.pagination_for>

Attributes

  • meta (Flop.Meta) (required) - The meta information of the query as returned by the Flop query 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 as path_fun to 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/3 for 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_start and page_range_end attribute are passed to the inner block based on this option. If this attribute is set to :none, both of those values will be nil.

    Defaults to 5.

  • reverse (:boolean) - By default, the next link moves forward with the :after parameter set to the end cursor, and the previous link moves backward with the :before parameter set to the start cursor. If reverse is set to true, the destinations of the links are switched.

    Defaults to false.

Slots

  • inner_block (required)

table(assigns)

(since 0.6.0)
@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 the Flop.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) - The Flop.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 the on_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) - A Phoenix.LiveView.JS command that is triggered when a header link for sorting is clicked.

    If used without the path attribute, you should include a push operation to handle the event with the handle_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={~p"/pets"}
      on_sort={JS.dispatch("my_app:scroll_to", to: "#pet-table")}
    />

    Defaults to nil.

  • target (:string) - Sets the phx-target attribute for the header links. Defaults to nil.

  • caption (:string) - Content for the <caption> element. Defaults to nil.

  • opts (:list) - Keyword list with additional options (see Flop.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 the Customization section of the module documentation.

    Defaults to [].

  • row_id (:any) - Overrides the default function that retrieves the row ID from a stream item. Defaults to nil.

  • row_click (:any) - Sets the phx-click function attribute for each row td. Expects to be a function that receives a row item as an argument. This does not add the phx-click attribute to the action 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 the field attribute is omitted or set to nil or false, 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 the col_style assign is set as style attribute for the <col> element of the respective column. You can set the width, background, border, and visibility of a column this way.

    • col_class (:string) - If set, a <colgroup> element is rendered and the value of the col_class assign is set as class attribute for the <col> element of the respective column. You can set the width, background, border, and visibility 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 conflicting thead_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 conflicting th_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 conflicting tbody_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 the row_click attribute.

    <: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 the col_style assign is set as style attribute for the <col> element of the respective column. You can set the width, background, border, and visibility of a column this way.

    • col_class (:string) - If set, a <colgroup> element is rendered and the value of the col_class assign is set as class attribute for the <col> element of the respective column. You can set the width, background, border, and visibility 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 a foot. The inner block will be rendered inside a tfoot element.

    <Flop.Phoenix.table>
      <:foot>
        <tr><td>Total: <span class="total">{@total}</span></td></tr>
      </:foot>
    </Flop.Phoenix.table>

Types

pagination_option()

@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()}
  | {: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, "&hellip;"}.
  • :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".
  • :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"]].

table_option()

@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> if true. 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

build_path(path, meta_or_flop_or_params, opts \\ [])

(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 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"

to_query(flop, opts \\ [])

(since 0.6.0)

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 deriving Flop.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"