refrakt/paginate

Pagination helpers for list views and JSON APIs.

Pages are numbered from 1 and fetched with LIMIT and OFFSET. That works with any ORDER BY, lets visitors jump to any page and gives a total count; for very large tables or infinite scrolling, paginate by key (WHERE id < $last_id) instead.

Usage in a repo

pub fn list(db: pog.Connection, params: paginate.Params) -> paginate.Page(Post) {
  let posts =
    pog.query("SELECT ... ORDER BY id DESC LIMIT $1 OFFSET $2")
    |> pog.parameter(pog.int(params.per_page))
    |> pog.parameter(pog.int(paginate.offset(params)))
    ...
  let total = // SELECT COUNT(*) FROM posts
  paginate.page(posts, params, total)
}

Usage in a handler

const per_page = 20

pub fn index(req: Request, ctx: Context) -> Response {
  let params = paginate.Params(page: paginate.page_number(req), per_page:)

  post_repo.list(ctx.db, params)
  |> post_views.index_view
  |> root_layout.wrap("Posts")
  |> wisp.html_response(200)
}

Usage in a view

case paginate.next_page(page) {
  Ok(next) -> a([href(paginate.page_path("/posts", next))], [text("Next")])
  Error(_) -> text("")
}

Types

A paginated result.

pub type Page(item) {
  Page(
    items: List(item),
    page: Int,
    per_page: Int,
    total: Int,
    total_pages: Int,
    has_previous: Bool,
    has_next: Bool,
  )
}

Constructors

  • Page(
      items: List(item),
      page: Int,
      per_page: Int,
      total: Int,
      total_pages: Int,
      has_previous: Bool,
      has_next: Bool,
    )

Pagination parameters.

pub type Params {
  Params(page: Int, per_page: Int)
}

Constructors

  • Params(page: Int, per_page: Int)

Values

pub fn default_params() -> Params

Default pagination: page 1, 20 items per page.

pub fn from_request(
  req: request.Request(wisp.Connection),
) -> Params

Extract pagination params from the query string (?page=2&per_page=10), with 20 items per page by default.

pub fn from_request_with(
  req: request.Request(wisp.Connection),
  per_page default: Int,
) -> Params

Extract pagination params from the query string (?page=2&per_page=10), with per_page items per page when the query has no valid per_page. A per_page from the query is kept between 1 and max_per_page.

pub const max_per_page: Int

The largest per_page that from_request and from_request_with accept from a query string.

pub fn next_page(page: Page(item)) -> Result(Int, Nil)

The number of the page after this one, if there is one.

pub fn offset(params: Params) -> Int

Calculate the SQL OFFSET from pagination params.

pub fn page(
  items: List(item),
  params: Params,
  total: Int,
) -> Page(item)

Build a Page from items, params, and total count.

pub fn page_number(req: request.Request(wisp.Connection)) -> Int

The page number from the page query parameter. A missing, invalid, zero or negative page number is page 1.

pub fn page_path(base_path: String, page_num: Int) -> String

The path of a page: page_path("/posts", 2) is "/posts?page=2".

pub fn page_url(
  base_path: String,
  page_num: Int,
  per_page: Int,
) -> String

Generate a pagination query string for a given page number.

pub fn previous_page(page: Page(item)) -> Result(Int, Nil)

The number of the page before this one. Past the last page, it is the last page, so a visitor who followed a stale link can get back.

✨ Search Document