diff --git a/lib/flop_phoenix.ex b/lib/flop_phoenix.ex
index d32f059..95a4770 100644
--- a/lib/flop_phoenix.ex
+++ b/lib/flop_phoenix.ex
@@ -428,115 +428,115 @@ defmodule Flop.Phoenix do
raise Flop.Phoenix.IncorrectPaginationTypeError, component: :pagination
end
- def pagination(%{meta: meta, opts: opts, path: path} = assigns) do
- assigns =
- assigns
- |> assign(:opts, Pagination.merge_opts(opts))
- |> assign(
- :page_link_helper,
- Pagination.build_page_link_helper(meta, path)
- )
- |> assign(:path, nil)
+ def pagination(%{opts: opts} = assigns) do
+ assigns = assign(assigns, :opts, Pagination.merge_opts(opts))
~H"""
-
+ <.pagination_for
+ :let={p}
+ meta={@meta}
+ page_links={@opts[:page_links]}
+ path={@path}
+ >
+
+
"""
end
- attr :meta, Flop.Meta, required: true
+ attr :current_page, :integer, required: true
+ attr :ellipsis_end?, :boolean, required: true
+ attr :ellipsis_start?, :boolean, required: true
attr :on_paginate, JS
- attr :page_link_helper, :any, required: true
- attr :target, :string, required: true
attr :opts, :list, required: true
+ attr :page_link_fun, :any, required: true
+ attr :page_range_end, :integer, required: true
+ attr :page_range_start, :integer, required: true
+ attr :target, :string, required: true
+ attr :total_pages, :integer, required: true
- defp page_links(%{meta: meta} = assigns) do
- max_pages =
- Pagination.max_pages(assigns.opts[:page_links], assigns.meta.total_pages)
-
- range =
- first..last//_ =
- Pagination.get_page_link_range(
- meta.current_page,
- max_pages,
- meta.total_pages
- )
-
- assigns = assign(assigns, first: first, last: last, range: range)
-
+ defp page_links(assigns) do
~H"""
- - 1} {@opts[:pagination_list_item_attrs]}>
+
- 1} {@opts[:pagination_list_item_attrs]}>
<.pagination_link
target={@target}
page={1}
- path={@page_link_helper.(1)}
+ path={@page_link_fun.(1)}
on_paginate={@on_paginate}
- {Pagination.attrs_for_page_link(1, @meta, @opts)}
+ {Pagination.attrs_for_page_link(1, @current_page, @opts)}
>
1
- - 2} {@opts[:pagination_list_item_attrs]}>
+
-
{@opts[:ellipsis_content]}
- -
+
-
<.pagination_link
target={@target}
page={page}
- path={@page_link_helper.(page)}
+ path={@page_link_fun.(page)}
on_paginate={@on_paginate}
- {Pagination.attrs_for_page_link(page, @meta, @opts)}
+ {Pagination.attrs_for_page_link(page, @current_page, @opts)}
>
{page}
- -
+
-
{@opts[:ellipsis_content]}
- -
+
-
<.pagination_link
target={@target}
- page={@meta.total_pages}
- path={@page_link_helper.(@meta.total_pages)}
+ page={@total_pages}
+ path={@page_link_fun.(@total_pages)}
on_paginate={@on_paginate}
- {Pagination.attrs_for_page_link(@meta.total_pages, @meta, @opts)}
+ {Pagination.attrs_for_page_link(@total_pages, @current_page, @opts)}
>
- {@meta.total_pages}
+ {@total_pages}
@@ -592,6 +592,154 @@ defmodule Flop.Phoenix do
"""
end
+ @doc """
+ 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
+
+ ```heex
+ <.pagination_for
+ :let={p}
+ meta={@meta}
+ page_links={{:ellipsis, 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,
+ page_link_fun: #Function<42.18682967/1 in :erl_eval.expr/6>,
+ page_range_start: 5,
+ pagination_type: :page,
+ previous_page: 5,
+ total_pages: 10
+ }
+ %>
+
+ ```
+ """
+ @doc section: :components
+ @doc since: "0.24.0"
+ @spec pagination_for(map) :: Phoenix.LiveView.Rendered.t()
+
+ attr :meta, Flop.Meta,
+ required: true,
+ doc: """
+ The meta information of the query as returned by the `Flop` query functions.
+ """
+
+ attr :path, :any,
+ default: nil,
+ doc: """
+ 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 `page_link_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.
+ """
+
+ attr :page_links, :any,
+ default: :all,
+ doc: """
+ Specifies how many page links should be rendered.
+
+ Default: `#{inspect(Pagination.default_opts()[:page_links])}`.
+
+ - `:all` - Renders all page links.
+ - `{:ellipsis, n}` - Renders `n` page links. Renders ellipsis elements if
+ there are more pages than displayed.
+ - `:hide` - Does not render any page links.
+
+ 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 `:hide`, both
+ of those values will be `nil`.
+ """
+
+ slot :inner_block, required: true
+
+ def pagination_for(
+ %{
+ meta: %Flop.Meta{errors: []} = meta,
+ page_links: page_links,
+ path: path
+ } = assigns
+ ) do
+ page_link_fun = Pagination.build_page_link_fun(meta, path)
+ pagination_type = pagination_type(meta.flop)
+
+ {page_range_start, page_range_end} =
+ Pagination.get_page_link_range(
+ page_links,
+ meta.current_page,
+ meta.total_pages
+ )
+
+ assigns =
+ assigns
+ |> assign(:current_page, meta.current_page)
+ |> assign(:ellipsis_end?, page_range_end < meta.total_pages - 1)
+ |> assign(:ellipsis_start?, page_range_start > 2)
+ |> assign(:meta, nil)
+ |> assign(:next_page, meta.next_page)
+ |> assign(:page_link_fun, page_link_fun)
+ |> assign(:page_range_end, page_range_end)
+ |> assign(:page_range_start, page_range_start)
+ |> assign(:pagination_type, pagination_type)
+ |> assign(:path, nil)
+ |> assign(:previous_page, meta.previous_page)
+ |> assign(:total_pages, meta.total_pages)
+
+ ~H"""
+ {render_slot(@inner_block, %{
+ current_page: @current_page,
+ ellipsis_end?: @ellipsis_end?,
+ ellipsis_start?: @ellipsis_start?,
+ next_page: @next_page,
+ page_range_end: @page_range_end,
+ page_link_fun: @page_link_fun,
+ page_range_start: @page_range_start,
+ pagination_type: @pagination_type,
+ previous_page: @previous_page,
+ total_pages: @total_pages
+ })}
+ """
+ end
+
+ def pagination_for(assigns) do
+ ~H""
+ end
+
+ defp pagination_type(%Flop{limit: limit, offset: offset})
+ when is_integer(limit) and is_integer(offset) do
+ :offset
+ end
+
+ defp pagination_type(%Flop{page: page, page_size: page_size})
+ when is_integer(page) and is_integer(page_size) do
+ :page
+ end
+
+ defp pagination_type(%Flop{first: first}) when is_binary(first) do
+ :first
+ end
+
+ defp pagination_type(%Flop{last: last}) when is_binary(last) do
+ :last
+ end
+
@doc """
Renders a cursor pagination element.
diff --git a/lib/flop_phoenix/pagination.ex b/lib/flop_phoenix/pagination.ex
index 186b687..e734286 100644
--- a/lib/flop_phoenix/pagination.ex
+++ b/lib/flop_phoenix/pagination.ex
@@ -44,36 +44,35 @@ defmodule Flop.Phoenix.Pagination do
|> Misc.deep_merge(opts)
end
- def max_pages(:all, total_pages), do: total_pages
- def max_pages(:hide, _), do: 0
- def max_pages({:ellipsis, max_pages}, _), do: max_pages
-
@spec get_page_link_range(
- non_neg_integer(),
+ :all | :hide | {:ellipsis, non_neg_integer()},
non_neg_integer(),
non_neg_integer()
- ) :: Range.t()
- def get_page_link_range(current_page, max_pages, total_pages) do
+ ) :: {non_neg_integer() | nil, non_neg_integer() | nil}
+ def get_page_link_range(:all, _, total_pages), do: {1, total_pages}
+ def get_page_link_range(:hide, _, _), do: {nil, nil}
+
+ def get_page_link_range({:ellipsis, max_pages}, current_page, total_pages) do
# number of additional pages to show before or after current page
additional = ceil(max_pages / 2)
cond do
max_pages >= total_pages ->
- 1..total_pages
+ {1, total_pages}
current_page + additional > total_pages ->
- (total_pages - max_pages + 1)..total_pages
+ {total_pages - max_pages + 1, total_pages}
true ->
first = max(current_page - additional + 1, 1)
last = min(first + max_pages - 1, total_pages)
- first..last
+ {first, last}
end
end
- def build_page_link_helper(_meta, nil), do: fn _ -> nil end
+ def build_page_link_fun(_meta, nil), do: fn _ -> nil end
- def build_page_link_helper(meta, path) do
+ def build_page_link_fun(meta, path) do
query_params = build_query_params(meta)
fn page ->
@@ -121,11 +120,11 @@ defmodule Flop.Phoenix.Pagination do
defp maybe_put_page(params, 1), do: Keyword.delete(params, :page)
defp maybe_put_page(params, page), do: Keyword.put(params, :page, page)
- def attrs_for_page_link(page, %{current_page: page}, opts) do
+ def attrs_for_page_link(page, page, opts) do
add_page_link_aria_label(opts[:current_link_attrs], page, opts)
end
- def attrs_for_page_link(page, _meta, opts) do
+ def attrs_for_page_link(page, _current_page, opts) do
add_page_link_aria_label(opts[:pagination_link_attrs], page, opts)
end
diff --git a/test/flop/pagination_test.exs b/test/flop/pagination_test.exs
index 36ab701..4bdc051 100644
--- a/test/flop/pagination_test.exs
+++ b/test/flop/pagination_test.exs
@@ -4,34 +4,42 @@ defmodule Flop.Phoenix.PaginationTest do
alias Flop.Phoenix.Pagination
describe "get_page_link_range/3" do
+ test "returns nil values for :hide option" do
+ assert Pagination.get_page_link_range(:hide, 1, 10) == {nil, nil}
+ end
+
+ test "returns full range for :all option" do
+ assert Pagination.get_page_link_range(:all, 4, 10) == {1, 10}
+ end
+
test "returns page range with odd max pages" do
- assert Pagination.get_page_link_range(1, 3, 10) == 1..3
- assert Pagination.get_page_link_range(2, 3, 10) == 1..3
- assert Pagination.get_page_link_range(3, 3, 10) == 2..4
- assert Pagination.get_page_link_range(4, 3, 10) == 3..5
- assert Pagination.get_page_link_range(5, 3, 10) == 4..6
- assert Pagination.get_page_link_range(6, 3, 10) == 5..7
- assert Pagination.get_page_link_range(7, 3, 10) == 6..8
- assert Pagination.get_page_link_range(8, 3, 10) == 7..9
- assert Pagination.get_page_link_range(9, 3, 10) == 8..10
- assert Pagination.get_page_link_range(10, 3, 10) == 8..10
+ assert Pagination.get_page_link_range({:ellipsis, 3}, 1, 10) == {1, 3}
+ assert Pagination.get_page_link_range({:ellipsis, 3}, 2, 10) == {1, 3}
+ assert Pagination.get_page_link_range({:ellipsis, 3}, 3, 10) == {2, 4}
+ assert Pagination.get_page_link_range({:ellipsis, 3}, 4, 10) == {3, 5}
+ assert Pagination.get_page_link_range({:ellipsis, 3}, 5, 10) == {4, 6}
+ assert Pagination.get_page_link_range({:ellipsis, 3}, 6, 10) == {5, 7}
+ assert Pagination.get_page_link_range({:ellipsis, 3}, 7, 10) == {6, 8}
+ assert Pagination.get_page_link_range({:ellipsis, 3}, 8, 10) == {7, 9}
+ assert Pagination.get_page_link_range({:ellipsis, 3}, 9, 10) == {8, 10}
+ assert Pagination.get_page_link_range({:ellipsis, 3}, 10, 10) == {8, 10}
end
test "returns page range with even max pages" do
- assert Pagination.get_page_link_range(1, 4, 10) == 1..4
- assert Pagination.get_page_link_range(2, 4, 10) == 1..4
- assert Pagination.get_page_link_range(3, 4, 10) == 2..5
- assert Pagination.get_page_link_range(4, 4, 10) == 3..6
- assert Pagination.get_page_link_range(5, 4, 10) == 4..7
- assert Pagination.get_page_link_range(6, 4, 10) == 5..8
- assert Pagination.get_page_link_range(7, 4, 10) == 6..9
- assert Pagination.get_page_link_range(8, 4, 10) == 7..10
- assert Pagination.get_page_link_range(9, 4, 10) == 7..10
- assert Pagination.get_page_link_range(10, 4, 10) == 7..10
+ assert Pagination.get_page_link_range({:ellipsis, 4}, 1, 10) == {1, 4}
+ assert Pagination.get_page_link_range({:ellipsis, 4}, 2, 10) == {1, 4}
+ assert Pagination.get_page_link_range({:ellipsis, 4}, 3, 10) == {2, 5}
+ assert Pagination.get_page_link_range({:ellipsis, 4}, 4, 10) == {3, 6}
+ assert Pagination.get_page_link_range({:ellipsis, 4}, 5, 10) == {4, 7}
+ assert Pagination.get_page_link_range({:ellipsis, 4}, 6, 10) == {5, 8}
+ assert Pagination.get_page_link_range({:ellipsis, 4}, 7, 10) == {6, 9}
+ assert Pagination.get_page_link_range({:ellipsis, 4}, 8, 10) == {7, 10}
+ assert Pagination.get_page_link_range({:ellipsis, 4}, 9, 10) == {7, 10}
+ assert Pagination.get_page_link_range({:ellipsis, 4}, 10, 10) == {7, 10}
end
test "does not return range beyond total pages" do
- assert Pagination.get_page_link_range(1, 3, 2) == 1..2
+ assert Pagination.get_page_link_range({:ellipsis, 3}, 1, 2) == {1, 2}
end
end
end
diff --git a/test/flop_phoenix_test.exs b/test/flop_phoenix_test.exs
index 9c082ae..d95585b 100644
--- a/test/flop_phoenix_test.exs
+++ b/test/flop_phoenix_test.exs
@@ -776,6 +776,7 @@ defmodule Flop.PhoenixTest do
build(
:meta_on_second_page,
flop: %Flop{
+ page: 1,
page_size: 20,
order_by: [:name],
order_directions: [:asc]