` if `true`.
Default: `#{inspect(Table.default_opts()[:container])}`.
- `:container_attrs` - The attributes for the table container.
Default: `#{inspect(Table.default_opts()[:container_attrs])}`.
- `:event`: If set, `Flop.Phoenix` will render links with a `phx-click`
attribute. Default: `#{inspect(Table.default_opts()[:event])}`.
- `:for` - The schema module deriving `Flop.Schema`. If set, header links are
only added for fields that are defined as sortable.
Default: `#{inspect(Table.default_opts()[:for])}`.
- `:no_results_content` - Any content that should be rendered if there are no
results. Default: `#{inspect(Table.default_opts()[:no_results_content])}`.
- `:table_attrs` - The attributes for the `
` element.
Default: `#{inspect(Table.default_opts()[:table_attrs])}`.
- `:th_wrapper_attrs` - The attributes for the `` element that wraps the
header link and the order direction symbol.
Default: `#{inspect(Table.default_opts()[:th_wrapper_attrs])}`.
- `:symbol_asc` - The symbol that is used to indicate that the column is
sorted in ascending order.
Default: `#{inspect(Table.default_opts()[:symbol_asc])}`.
- `:symbol_attrs` - The attributes for the `` element that wraps the
order direction indicator in the header columns.
Default: `#{inspect(Table.default_opts()[:symbol_attrs])}`.
- `:symbol_desc` - The symbol that is used to indicate that the column is
sorted in ascending order.
Default: `#{inspect(Table.default_opts()[:symbol_desc])}`.
- `:target`: Sets the `phx-target` attribute for the header links.
Default: `#{inspect(Table.default_opts()[:target])}`.
- `:tbody_td_attrs`: Attributes to added to each `| ` tag within the
` | `. Default: `#{inspect(Table.default_opts()[:tbody_td_attrs])}`.
- `:tbody_tr_attrs`: Attributes to added to each `` tag within the
`
`. Default: `#{inspect(Table.default_opts()[:tbody_tr_attrs])}`.
- `:thead_th_attrs`: Attributes to added to each `` tag within the
``. Default: `#{inspect(Table.default_opts()[:thead_th_attrs])}`.
- `:thead_tr_attrs`: Attributes to added to each `` tag within the
``. Default: `#{inspect(Table.default_opts()[:thead_tr_attrs])}`.
"""
@type table_option ::
{:container, boolean}
| {:container_attrs, keyword}
| {:event, binary | atom}
| {:for, module}
| {:no_results_content, Phoenix.HTML.safe() | binary}
| {:symbol_asc, Phoenix.HTML.safe() | binary}
| {:symbol_attrs, keyword}
| {:symbol_desc, Phoenix.HTML.safe() | binary}
| {:table_attrs, keyword}
| {:target, binary | atom}
| {:tbody_td_attrs, keyword}
| {:tbody_tr_attrs, keyword}
| {:th_wrapper_attrs, keyword}
| {:thead_th_attrs, keyword}
| {:thead_tr_attrs, keyword}
@doc """
Generates a pagination element.
- `meta`: The meta information of the query as returned by the `Flop` query
functions.
- `path_helper`: The path helper function that builds a path to the current
page, e.g. `&Routes.pet_path/3`.
- `path_helper_args`: The arguments to be passed to the route helper
function, e.g. `[@conn, :index]`. The page number and page size will be
added as query parameters.
- `opts`: Options to customize the pagination. See
`t:Flop.Phoenix.pagination_option/0`. Note that the options passed to the
function are deep merged into the default options.
## 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. The `x` 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.
See the module documentation and [Readme](README.md) for examples.
"""
@doc section: :generators
@spec pagination(map) :: Phoenix.LiveView.Rendered.t()
def pagination(assigns) do
assigns = assign(assigns, :opts, Pagination.init_opts(assigns.opts))
~H"""
<%= if @meta.total_pages > 1 do %>
<% end %>
"""
end
@doc """
Generates a table with sortable columns.
The argument is a map with the following keys:
- `headers`: A list of header columns. Can be a list of strings (or markup),
or a list of `{value, field_name}` tuples.
- `items`: The list of items to be displayed in rows. This is the result list
returned by the query.
- `meta`: The `Flop.Meta` struct returned by the query function.
- `path_helper`: The Phoenix path or url helper that leads to the current
page.
- `path_helper_args`: The argument list for the path helper. For example, if
you would call `Routes.pet_path(@conn, :index)` to generate the path for the
current page, this would be `[@conn, :index]`.
- `opts`: Keyword list with additional options (see
`t:Flop.Phoenix.table_option/0`). This list will also be passed as the
second argument to the row function. Note that the options passed to the
function are deep merged into the default options.
- `row_func`: A function that takes one item of the `items` list and the
`opts` and returns the column values for that item's row.
## Table headers
Table headers need to be passed as a list. It is recommended to define a
function in the `View`, `LiveView` or `LiveComponent` module that returns the
table headers:
def table_headers do
["ID", {"Name", :name}, {"Age", :age}, ""]
end
This defines four header columns: One for the ID, which is not sortable, and
columns for the name and the age, which are both sortable, and a fourth
column without a header value. The last column will hold the links to the
detail pages. The name and age column headers will be linked, so that they the
order on the `:name` and `:age` field, respectively.
## Table rows
You need to define a function that takes a single item from the list and the
opts passed to the component. The function needs to return a list with one
item for each column.
def table_row(%Pet{id: id, name: name, age: age}, opts) do
socket = Keyword.fetch!(opts, :socket)
[id, name, age, link("show", to: Routes.pet_path(socket, :show, id))]
end
See the module documentation and [Readme](README.md) for examples.
"""
@doc since: "0.6.0"
@doc section: :generators
@spec table(map) :: Phoenix.LiveView.Rendered.t()
def table(assigns) do
assigns = assign(assigns, :opts, Table.init_opts(assigns.opts))
~H"""
<%= if @items == [] do %>
<%= @opts[:no_results_content] %>
<% else %>
<%= if @opts[:container] do %>
<% else %>
<% end %>
<% end %>
"""
end
@doc """
Converts a Flop struct into a keyword list that can be used as a query with
Phoenix route helper functions.
Default limits and default order parameters set via the application
environment are omitted. You can pass the `:for` option to pick up the
default options from a schema module deriving `Flop.Schema`. You can also
pass `default_limit` and `default_order` as options directly. The function
uses `Flop.get_option/2` internally to retrieve the default options.
## Examples
iex> to_query(%Flop{})
[]
iex> f = %Flop{order_by: [:name, :age], order_directions: [:desc, :asc]}
iex> to_query(f)
[order_directions: [:desc, :asc], order_by: [:name, :age]]
iex> f |> to_query |> Plug.Conn.Query.encode()
"order_directions[]=desc&order_directions[]=asc&order_by[]=name&order_by[]=age"
iex> f = %Flop{page: 5, page_size: 20}
iex> to_query(f)
[page_size: 20, page: 5]
iex> f = %Flop{first: 20, after: "g3QAAAABZAAEbmFtZW0AAAAFQXBwbGU="}
iex> to_query(f)
[first: 20, after: "g3QAAAABZAAEbmFtZW0AAAAFQXBwbGU="]
iex> f = %Flop{
...> filters: [
...> %Flop.Filter{field: :name, op: :=~, value: "Mag"},
...> %Flop.Filter{field: :age, op: :>, value: 25}
...> ]
...> }
iex> to_query(f)
[
filters: %{
0 => %{field: :name, op: :=~, value: "Mag"},
1 => %{field: :age, op: :>, value: 25}
}
]
iex> f |> to_query() |> Plug.Conn.Query.encode()
"filters[0][field]=name&filters[0][op]=%3D~&filters[0][value]=Mag&filters[1][field]=age&filters[1][op]=%3E&filters[1][value]=25"
iex> f = %Flop{page: 5, page_size: 20}
iex> to_query(f, default_limit: 20)
[page: 5]
"""
@doc since: "0.6.0"
@doc section: :miscellaneous
@spec to_query(Flop.t()) :: keyword
def to_query(%Flop{filters: filters} = flop, opts \\ []) do
filter_map =
filters
|> Stream.with_index()
|> Enum.into(%{}, fn {filter, index} ->
{index, Map.from_struct(filter)}
end)
keys = [
:after,
:before,
:first,
:last,
:offset,
:page
]
default_limit = Flop.get_option(:default_limit, opts)
default_order = Flop.get_option(:default_order, opts)
keys
|> Enum.reduce([], &maybe_add_param(&2, &1, Map.get(flop, &1)))
|> maybe_add_param(:page_size, flop.page_size, default_limit)
|> maybe_add_param(:limit, flop.limit, default_limit)
|> maybe_add_order_params(flop, default_order)
|> maybe_add_param(:filters, filter_map)
end
defp maybe_add_param(params, key, value, default \\ nil)
defp maybe_add_param(params, _, nil, _), do: params
defp maybe_add_param(params, _, [], _), do: params
defp maybe_add_param(params, _, map, _) when map == %{}, do: params
defp maybe_add_param(params, :page, 1, _), do: params
defp maybe_add_param(params, :offset, 0, _), do: params
defp maybe_add_param(params, _, val, val), do: params
defp maybe_add_param(params, key, val, _), do: Keyword.put(params, key, val)
defp maybe_add_order_params(
params,
%Flop{order_by: order_by, order_directions: order_directions},
%{order_by: order_by, order_directions: order_directions}
),
do: params
defp maybe_add_order_params(
params,
%Flop{order_by: order_by, order_directions: order_directions},
_
) do
params
|> maybe_add_param(:order_by, order_by)
|> maybe_add_param(:order_directions, order_directions)
end
@doc """
Takes a Phoenix path helper function and a list of path helper arguments and
builds a path that includes query parameters for the given `Flop` struct.
Default values for `limit`, `page_size`, `order_by` and `order_directions` are
omit from the query parameters. To pick up the default parameters from a
schema module deriving `Flop.Schema`, you need to pass the `:for` option.
## Examples
iex> pet_path = fn _conn, :index, query ->
...> "/pets?" <> Plug.Conn.Query.encode(query)
...> end
iex> flop = %Flop{page: 2, page_size: 10}
iex> build_path(pet_path, [%Plug.Conn{}, :index], flop)
"/pets?page_size=10&page=2"
We're defining fake path helpers for the scope of the doctests. In a real
Phoenix application, you would pass something like `&Routes.pet_path/3` as the
first argument.
You can also pass a `Flop.Meta` struct or a keyword list as the third
argument.
iex> pet_path = fn _conn, :index, query ->
...> "/pets?" <> Plug.Conn.Query.encode(query)
...> end
iex> flop = %Flop{page: 2, page_size: 10}
iex> meta = %Flop.Meta{flop: flop}
iex> build_path(pet_path, [%Plug.Conn{}, :index], meta)
"/pets?page_size=10&page=2"
iex> query_params = to_query(flop)
iex> build_path(pet_path, [%Plug.Conn{}, :index], query_params)
"/pets?page_size=10&page=2"
If the path helper takes additional path parameters, just add them to the
second argument.
iex> user_pet_path = fn _conn, :index, id, query ->
...> "/users/\#{id}/pets?" <> Plug.Conn.Query.encode(query)
...> end
iex> flop = %Flop{page: 2, page_size: 10}
iex> build_path(user_pet_path, [%Plug.Conn{}, :index, 123], flop)
"/users/123/pets?page_size=10&page=2"
If the last path helper argument is a query parameter list, the Flop
parameters are merged into it.
iex> pet_url = fn _conn, :index, query ->
...> "https://pets.flop/pets?" <> Plug.Conn.Query.encode(query)
...> end
iex> flop = %Flop{order_by: :name, order_directions: [:desc]}
iex> build_path(pet_url, [%Plug.Conn{}, :index, [user_id: 123]], flop)
"https://pets.flop/pets?user_id=123&order_directions[]=desc&order_by=name"
iex> build_path(
...> pet_url,
...> [%Plug.Conn{}, :index, [category: "small", user_id: 123]],
...> flop
...> )
"https://pets.flop/pets?category=small&user_id=123&order_directions[]=desc&order_by=name"
"""
@doc since: "0.6.0"
@doc section: :miscellaneous
@spec build_path(function, [any], Meta.t() | Flop.t() | keyword, keyword) ::
String.t()
def build_path(path_helper, args, meta_or_flop_or_params, opts \\ [])
def build_path(path_helper, args, %Meta{flop: flop}, opts),
do: build_path(path_helper, args, flop, opts)
def build_path(path_helper, args, %Flop{} = flop, opts) do
build_path(path_helper, args, Flop.Phoenix.to_query(flop, opts))
end
def build_path(path_helper, args, flop_params, _opts)
when is_function(path_helper) and
is_list(args) and
is_list(flop_params) do
final_args =
case Enum.reverse(args) do
[last_arg | rest] when is_list(last_arg) ->
query_arg = Keyword.merge(last_arg, flop_params)
Enum.reverse([query_arg | rest])
_ ->
args ++ [flop_params]
end
apply(path_helper, final_args)
end
end
|