lib/pagination/pagination_helper.ex

defmodule Pagination.PaginationHelper do
  @moduledoc """
  contains helper functions to create
  - order_by buttons
  - page links
  - filters
  """
  use Phoenix.Component
  alias Pagination.PaginatorState

  # attr :label, :string, required: true
  @doc """
  provides a link to order following a given field
  <.order_tag label="id" order_by={:id} paginator={@things} />
  """
  def order_tag(
        %{label: label, order_by: order_by, paginator: %PaginatorState{} = paginator} = assigns
      ) do
    # IO.inspect(_w?: {__MODULE__, :order_tag}, paginator_order: paginator.order_by)

    arrow = order_arrow_indicator(paginator, order_by)

    ~H"""
    <a href="#" phx-click="paginate", phx-value-order_by={@order_by}><%= label %></a><%= arrow %>
    """
  end

  defp order_arrow_indicator(%PaginatorState{order_by: {direction, field}}, tag_order_by_field)
       when field == tag_order_by_field do
    case direction do
      :asc -> " (asc)"
      _ -> " (desc)"
    end
  end

  defp order_arrow_indicator(_, _), do: ""

  @doc """
  provides links for pages 1...N and a select box for number of items per page
  """
  def page_tag(%{paginator: %PaginatorState{} = pg} = assigns) do
    # set defaults
    assigns = Map.merge(%{delta: 1}, assigns)

    ~H"""
    <div class="paginator pager" style="">
      <%= page_indicators(@paginator.page, @paginator.page_max, @delta) %>
      <form id="paginator" phx-change="paginate" style="display: inline">
        <select name="per_page_nb">
        <%= for pnb <- @paginator.per_page_items do %>
          <%= per_page_tag_option(pnb, pg.per_page_nb) %>
        <% end %>
        </select>
      </form>
    </div>

    """
  end

  defp per_page_tag_option(per_page_nb, per_page_nb_current, options \\ %{}) do
    default = if per_page_nb == per_page_nb_current, do: " selected", else: ""
    label = if per_page_nb == 0, do: Map.get(options, :all_label, "All"), else: per_page_nb

    """
    <option value=#{per_page_nb}#{default}>#{label}</option>
    """
    |> Phoenix.HTML.raw()
  end

  # displays a block of page numbers unless there's only one page
  def page_indicators(_page, 1, _), do: ""

  def page_indicators(page, page_nb, delta) do
    # get the boundaries of the window showing the current page
    w_start = max(1, page - delta)
    w_end = min(page_nb, page + delta)

    1..page_nb
    # 1, 2, 3, 4, 5, 6, 7, 8, 9 (e.g. page=4, delta=1)
    |> Enum.map(fn e ->
      if (e > 1 and e < w_start) or (e > w_end and e < page_nb), do: :space, else: e
    end)
    # 1, :space, :space, 3, 4, 5, :space, :space, :space, 9
    |> Enum.dedup()
    # 1, :space, 3, 4, 5, :space, 9
    |> Enum.reduce("", fn p, acc -> acc <> page_indicator(p, page, page_nb) end)
    |> Phoenix.HTML.raw()
  end

  defp page_indicator(page_nb, page_nb_current, page_max) do
    css_class =
      "paginator pager-page" <>
        case page_nb do
          1 -> " first"
          ^page_max -> " last"
          _ -> ""
        end

    case page_nb do
      :space ->
        """
        <span class="#{css_class} spacer">&hellip;</span>
        """

      ^page_nb_current ->
        """
          <span class="#{css_class} current">#{page_nb}</span>
        """

      _ ->
        """
        <a href="#" phx-click="paginate" phx-value-page=#{page_nb} class="#{css_class}">#{page_nb}</a>
        """
    end
  end

  # === FILTER
  def search_filter_tag(assigns) do
    btn_label = Map.get(assigns, :label, "Go!")

    ~H"""
    <form id={"paginator-search"} phx-submit="paginate" class="paginator search-form">
      <%= for {f, v} <- @paginator.filters do %>
        <input type="search" name={"filters[#{f}]"} placeholder={f} value={"#{v}"}>
      <% end %>
      <button type="submit"><%= btn_label %></button>
    </form>
    """
  end
end