defmodule Fermo.Pagination do @enforce_keys [:items, :page, :total_items, :base, :suffix] defstruct items: nil, page: nil, per_page: 10, total_items: nil, base: nil, suffix: "?page=:page", first: nil @type t() :: %__MODULE__{ items: Array.t(), page: integer(), per_page: integer(), total_items: integer(), base: String.t(), suffix: String.t(), first: String.t() } @callback paginate(map(), String.t(), map(), map(), function()) :: map() def paginate(config, template, options \\ %{}, context \\ %{}, fun \\ nil) do base = options.base items = options.items per_page = options[:per_page] || 20 suffix = options[:suffix] || "pages/:page/index.html" first = options[:first] total_items = length(items) paginated = Stream.chunk_every(items, per_page, per_page, []) |> Stream.with_index |> Enum.map(fn ({chunk, i}) -> # index is 1 based index = i + 1 pagination = %__MODULE__{ items: chunk, total_items: total_items, page: index, per_page: per_page, base: base, suffix: suffix, first: first } with_pagination = Map.put(context, :pagination, pagination) prms = if fun do fun.(with_pagination, index) else with_pagination end Fermo.Config.page_from(template, page_path(pagination), prms) end) pages = Map.get(config, :pages, []) put_in(config, [:pages], pages ++ paginated) end def total_pages(%__MODULE__{} = pagination) do round((pagination.total_items - 1) / pagination.per_page + 1) end def paginatable?(%__MODULE__{} = pagination) do pagination.page > 1 || pagination.page < total_pages(pagination) end def prev_page(%__MODULE__{} = pagination) do to_page(pagination, pagination.page - 1) end def next_page(%__MODULE__{} = pagination) do to_page(pagination, pagination.page + 1) end def to_page(%__MODULE__{} = pagination, page) do page_path(%__MODULE__{pagination | page: page, items: []}) end def page_path(%__MODULE__{} = pagination) do cond do pagination.page < 1 -> nil pagination.page == 1 && pagination.first -> pagination.base <> pagination.first pagination.page > total_pages(pagination) -> nil true -> path = Regex.replace( ~r/:([\w_]+)/, pagination.suffix, fn _, name -> num = Map.get(pagination, String.to_atom(name)) Integer.to_charlist(num) |> List.to_string end ) pagination.base <> path end end end