Do not follow this link

~arx10/elixir-pageantry

pagination library for Elixir
version 0.6.0
filter arrays, special-casing inet arrays
upgrade to erlang 27.1.2 and elixir 1.17.3

clone

read-only
https://git.sr.ht/~arx10/elixir-pageantry
read/write
git@git.sr.ht:~arx10/elixir-pageantry

You can also use your local clone with git send-email.

#Pageantry

Pagination library for Elixir.

#Basic Usage

If you have an Ecto schema like the following:

defmodule Example.Product do
  use Ecto.Schema

  schema "products" do
    field :product_name
    field :quantity_per_unit
    field :unit_price, :decimal
    field :units_in_stock, :integer
    field :units_on_order, :integer
    field :reorder_level, :integer
    field :discontinued, :boolean
    timestamps()
  end
end

You can query a sliced and sorted page of the products table with the following Page.query/4 call:

defmodule Example.Context do
  alias Example.Product
  alias Example.Repo
  alias Pageantry.{Field, Page}

  def page_products(page \\ Page.new()) do
    Page.query(page, Repo, Product, products_validation())
  end

  defp products_validation() do
    fields = [
      id: %Field{field: :id, all: false},
      name: %Field{field: :product_name, filter: :like},
      quantity: %Field{field: :quanity_per_unit, sort: false, filter: false},
      price: %Field{field: :unit_price},
      in_stock: %Field{field: :units_in_stock},
      on_order: %Field{field: :units_on_order},
      reorder: %Field{field: :reorder_level},
      created: %Field{field: :inserted_at},
      updated: %Field{field: :updated_at}
    ]

    %Validation{schema: Product, fields: fields, base_sort: [asc: :id]}
  end
end

Which you might call from a controller with the pagination input parsed from the request params:

defmodule ExampleWeb.ProductController do
  use ExampleWeb, :controller
  alias Example.Context
  alias Pageantry.Page

  def index(conn, params) do
    page =
      Page.new()
      |> Page.parse(params)
      |> Context.page_products()

    render(conn, :index, page: page)
  end
end

And render with an HTML table like the following:

<table>
  <thead>
    <tr>
      <th><a href="?sort=id">ID</a></th>
      <th><a href="?sort=name">Product Name</a></th>
      <th>Quantity per Unit</th>
      <th><a href="?sort=price">Unit Price</a></th>
    </tr>
  </thead>
  <tbody>
<%= for item <- @page.output.items do %>
    <tr>
      <td><%= item.id %></td>
      <td><%= item.product_name %></td>
      <td><%= item.quantity_per_unit %></td>
      <td><%= item.unit_price %></td>
    </tr>
<% end %>
  </tbody>
</table>

<div class="pagination">
  <a href={"?off=#{@page.input.off - @page.input.max}"}>Prev</a>
  <a href={"?off=#{@page.input.off + @page.input.max}"}>Next</a>
  <span>
    <%= @page.input.off + 1 %> to <%= @page.input.off + @page.input.max %>
    of <%= @page.output.total %>
  </span>
  <form>
    <input name="q">
    <button type="submit">Filter</button>
  </form>
</div>

#Join Usage

If a Product can belong to a Category, like the following:

defmodule Example.Category do
  use Ecto.Schema
  alias Example.Product

  schema "categories" do
    field :category_name
    field :description
    field :picture, :binary
    has_many :products, Product
  end
end

You can add a query_builder/0 function to the Product schema to define a custom query builder module for it:

defmodule Example.Product do
  use Ecto.Schema
  alias Example.Category

  schema "products" do
    field :product_name
    field :quantity_per_unit
    field :unit_price, :decimal
    field :units_in_stock, :integer
    field :units_on_order, :integer
    field :reorder_level, :integer
    field :discontinued, :boolean
    belongs_to :category, Category
    timestamps()
  end

  def query_builder, do: Example.Product.ProductQueryBuilder
end

And use the query builder to define 1) the alias to use for the products table; and 2) the alias and join expression to use to for the categories table:

defmodule Example.Product.ProductQueryBuilder do
  import Ecto.Query
  alias Example.{Category, Product}

  def from, do: from(x in Product, as: :products)

  def join(query, Category, qualifier) do
    join(query, qualifier, [{:products, x}], y in assoc(x, :category), as: :categories)
  end
end

Then if you define additional validation fields for a Product using its Category relation:

defmodule Example.Context do
  import Ecto.Query
  alias Example.Product
  alias Example.Repo
  alias Pageantry.{Field, FieldRelation, Page}

  def page_products(page \\ Page.new()) do
    query = from(p in Product, as: :products, preload: :category)
    Page.query(page, Repo, query, products_validation())
  end

  defp products_validation() do
    fields = [
      id: %Field{field: :id, all: false},
      name: %Field{field: :product_name, filter: :like},
      quantity: %Field{field: :quanity_per_unit, sort: false, filter: false},
      price: %Field{field: :unit_price},
      in_stock: %Field{field: :units_in_stock},
      on_order: %Field{field: :units_on_order},
      reorder: %Field{field: :reorder_level},
      created: %Field{field: :inserted_at},
      updated: %Field{field: :updated_at},
      category: %Field{
        field: :category_name,
        relation: %FieldRelation{association: :category}
      },
      category_description: %Field{
        field: :description,
        relation: %FieldRelation{association: :category},
        filter: :like
      },
    ]

    %Validation{schema: Product, fields: fields, base_sort: [asc: :id]}
  end
end

You can sort and filter on the joined Category fields:

<form>
  <input name="field" value="category_description" type="hidden">
  <label>
    Category description:
    <input name="q">
  </label>
  <button type="submit">Filter</button>
</form>

<table>
  <thead>
    <tr>
      <th><a href="?sort=id">ID</a></th>
      <th><a href="?sort=name">Product Name</a></th>
      <th>Quantity per Unit</th>
      <th><a href="?sort=price">Unit Price</a></th>
      <th><a href="?sort=category">Category Name</a></th>
      <th>Category Description</th>
    </tr>
  </thead>
  <tbody>
<%= for item <- @page.output.items do %>
    <tr>
      <td><%= item.id %></td>
      <td><%= item.product_name %></td>
      <td><%= item.quantity_per_unit %></td>
      <td><%= item.unit_price %></td>
      <td><%= item.category.category_name %></td>
      <td><%= item.category.description %></td>
    </tr>
<% end %>
  </tbody>
</table>

#Development

  1. make run.db: start db (for tests)
  2. make rebuild.elixir: build dev docker image
  3. make shell, then mix deps.get (then exit): init elixir dev env
  4. make check: run linting and tests

#Resources

#License

The MIT License

Do not follow this link