Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
4.8 kB · 152 lines
Elixir
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153defmodule TrackerWeb.TableParams do @moduledoc """ Centralizes URL parameter parsing and pagination for LiveView lists. """
use TypedStruct
typedstruct do field :search, String.t(), default: "" field :search_key, atom(), default: :search field :page, pos_integer(), default: 1 field :offset, non_neg_integer(), default: 0 field :page_size, pos_integer(), default: 15 field :channel, String.t() | nil field :rev, String.t() | nil end
@doc """ Parse URL params into a TableParams struct.
## Options
* `:page_size` - number of items per page (default: 15) * `:search_key` - atom URL key the search input writes to (default: `:search`). Set to e.g. `:package_search` when an inner table shares a page with the global `?search=` so the two inputs don't fight over one URL slot. """ @spec from_params(map(), keyword()) :: t() def from_params(params, opts \\ []) do page_size = Keyword.get(opts, :page_size, 15) search_key = Keyword.get(opts, :search_key, :search)
search = Map.get(params, Atom.to_string(search_key), "") page = parse_page(params["page"])
%__MODULE__{ search: search, search_key: search_key, page: page, offset: (page - 1) * page_size, page_size: page_size, channel: params["channel"], rev: params["rev"] } end
@doc """ Convert TableParams to a query params map, omitting default values. Optionally merges extra params (empty string values are omitted). """ @spec to_query_params(t(), map()) :: map() def to_query_params(%__MODULE__{} = tp, extras \\ %{}) do params = %{} |> maybe_put(tp.search_key, tp.search, "") |> maybe_put(:page, tp.page, 1) |> maybe_put(:channel, tp.channel, nil) |> maybe_put(:rev, tp.rev, nil)
extras |> Enum.reject(fn {_k, v} -> v in ["", nil, false] end) |> Map.new() |> Map.merge(params) end
@doc """ Convert TableParams to a string-keyed map suitable for `<input type="hidden">` fallbacks on a search form. Excludes `:search` (the visible input), `:page` (a fresh search resets to the first page), and any default-valued params; merges extras using the same omit-empty rules as `to_query_params/2`. """ @spec to_hidden_inputs(t(), map()) :: %{String.t() => String.t()} def to_hidden_inputs(%__MODULE__{} = tp, extras \\ %{}) do tp |> to_query_params(extras) |> Map.drop([tp.search_key, :page]) |> Map.new(fn {k, v} -> {to_string(k), to_string(v)} end) end
@doc """ Build a URL path with query params, omitting defaults. """ @spec to_path(t(), String.t(), map()) :: String.t() def to_path(%__MODULE__{} = tp, base_path, extras \\ %{}) do case tp |> to_query_params(extras) |> URI.encode_query() do "" -> base_path qs -> "#{base_path}?#{qs}" end end
@doc """ Build the URL for a specific page, preserving the rest of the table params. Used by pagination links for the no-JS fallback. """ @spec page_path(t(), pos_integer(), String.t(), map()) :: String.t() def page_path(%__MODULE__{} = tp, page, base_path, extras \\ %{}) do to_path(%{tp | page: page}, base_path, extras) end
@doc """ Returns true if any table params have changed (triggering a data reload). Returns true if the first argument is nil (initial load). """ @spec changed?(t() | nil, t()) :: boolean() def changed?(nil, %__MODULE__{}), do: true def changed?(%__MODULE__{} = old, %__MODULE__{} = new), do: old != new
@doc """ Compute pagination assigns from an Ash page result.
Returns a map with: * `:has_prev_page?` - boolean * `:has_next_page?` - boolean * `:total_pages` - integer, or nil when the page was fetched without a count * `:current_page` - integer * `:stream_name` - the atom to use for stream * `:stream_results` - the results list """ @spec apply_pagination(t(), map(), atom()) :: map() def apply_pagination(%__MODULE__{} = tp, page_result, stream_name) do total_pages = case page_result.count do nil -> nil count when count > 0 -> ceil(count / tp.page_size) _zero -> 0 end
current_page = div(tp.offset, tp.page_size) + 1
%{ has_prev_page?: tp.offset > 0, has_next_page?: page_result.more?, total_pages: total_pages, current_page: current_page, stream_name: stream_name, stream_results: page_result.results } end
defp parse_page(nil), do: 1
defp parse_page(value) when is_binary(value) do case Integer.parse(value) do {n, _} -> max(n, 1) :error -> 1 end end
defp maybe_put(map, _key, value, value), do: map defp maybe_put(map, _key, nil, _default), do: map defp maybe_put(map, key, value, _default), do: Map.put(map, key, value)end