Subscribe to access all episodes. View plans →

#4: Elixir Pagination with Scrivener

Published February 6, 2017

Elixir 1.3.4

Phoenix 1.2.1

Scrivener.Ecto 1.1.3

Scrivener.HTML 2.8.0

Episode source code on GitHub


Here we have a site that displays a list of movies.

One thing we may want to do with this list of movies it to paginate them, and one Elixir library to help you pagination is Scrivener .

There are a few different Scrivener libraries to help you handle different kinds of pagination.

Since our project is a Phoenix app that uses Ecto , we’ll use the Scrivener.Ecto library.

The first thing we want to do is include the scrivener_ecto dependency. And then add it to our applications.

mix.exs

defmodule Teacher.Mixfile do
  …
  def application do
    [mod: {Teacher, []},
     applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext,
                    :phoenix_ecto, :postgrex, :scrivener_ecto]]
  end
  …
  defp deps do
    …
    {:scrivener_ecto, "~> 1.0"}
    …
  end
end

Now we can fetch our dependencies with mix.

$ mix deps.get

We’ll want to use Scrivener in our Repo module.

then we can set the page_size’ we want to use. It’s set to 10 by default.

Let’s set ours to 4.

lib/teacher/repo.ex

defmodule Teacher.Repo do
  use Ecto.Repo, otp_app: :teacher
  use Scrivener, page_size: 4
end

This adds a paginate/2 function to our Repo that we can use when we query the database.

We’ll go to our MovieController and the ‘index’ action is where we’re fetching our movies from the database.

Let’s update this to use include the ‘paginate’ function in order to return a set of our movies.

web/controllers/movie_controller.ex

defmodule Teacher.MovieController do
  …
  def index(conn, params) do
    page = Movie
           |> Repo.paginate(params)
    render(conn, "index.html", movies: page.entries, page: page)
  end
  …
end

Paginate returns a Scrivener.Page struct. If we take a look at the Scrivener.Ecto documentation we see that this struct provides us with a few different fields we can use to help handle pagination.

We’ll update our render function to return our movies with page.entries and we’ll also make the page variable, which is our Scrivener.Page struct, accessible to the template.

Let’s restart our server and if we refresh our page, we can see only 4 results were returned.

$ mix phoenix.server

Since we’re using the ‘paginate’ function and passing in the params, Scrivener will use the values of the ‘page’ and ‘page_size’ keys to perform the pagination.

We can test this by setting the ‘page’ parameter in our URL. And changing it gives us different results.

Now let’s add pagination links to our site.

Going to our movie index page, we’ll add a link to the ‘previous page’ and in it include the ‘page’ parameter. For the value we’ll use the ‘page_number’ field provided by the Scrivener.Page struct and subtract one from it.

And then a link for the ‘next page’ that also has a page parameter again with the value of the ‘page_number’, but this time we’ll add one to it.

web/templates/movie/index.html.eex

…
<%= link "Prev Page", to: movie_path(@conn, :index, page: @page.page_number - 1) %>
<%= link "Next Page", to: movie_path(@conn, :index, page: @page.page_number + 1) %>
…

And if we refresh our page we can see our pagination links are there.

And clicking next page increases the page number and loads our next set of movies.

One improvement we could make to this is to only display the ‘previous page’ and ‘next page’ links when we actually have a previous or next page of results to display.

Let’s go back to our index template.

And we’ll update our ‘previous page’ link to display if the page number is greater than one.

Then let’s also wrap our ‘next page’ link in an if statement to display if the page number is less than the total number of pages.

web/templates/movie/index.html.eex

…
<%= if @page.page_number > 1 do %>
  <%= link "Prev Page", to: movie_path(@conn, :index, page: @page.page_number - 1) %>
<% end %>
<%= if @page.page_number < @page.total_pages do %>
  <%= link "Next Page", to: movie_path(@conn, :index, page: @page.page_number + 1) %>
<% end %>
…

Refreshing our page we can see the ‘previous page’ link is hidden because we’re on our first page and if we click ‘next page’ it’s displayed.

Great our pagination is working. But it doesn’t looks too pretty right now.

There’s another Scrivener library - Scrivener.HTML - which provides view helpers for Scrivener.

Let’s add it to our project. We’ll open our mix file and add scrivener_html to our dependencies and then to our applications.

mix.exs

  …
  def application do
    [mod: {Teacher, []},
     applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext,
                    :phoenix_ecto, :postgrex, :scrivener_ecto, :scrivener_html]]
  end
  …
  defp deps do
     {:scrivener_ecto, "~> 1.0"},
     {:scrivener_html, "~> 1.1"}
  end
  …

Then we’ll stop our server and fetch our dependencies.

$ mix deps.get

Since we’re using Phoenix.HTML we’ll need to configure the routes helper module so we can use Scrivener.HTML.

We’ll paste this into our config file.

config/config.exs

…  
config :scrivener_html,
  routes_helper: Teacher.Router.Helpers
…

Now we can go to the view that we want to render our pagination links on, in this case our MovieView, and we’ll import Scrivener.HTML.

web/views/movie_view.ex

defmodule Teacher.MovieView do
  use Teacher.Web, :view
  import Scrivener.HTML
end

With that done we can go back to our movie index template remove our old pagination links.

Replacing it with pagination_links @page, where @page is our Scrivener.Page struct.

web/templates/movie/index.html.eex

…
<%= pagination_links @page %>
…

Let’s restart our server and if we go back to our site, we see our pagination is styled and works.

$ mix phoenix.server

By default Scrivener.HTML uses the Bootstrap pagination styles.

If we go back to our template we can specify the css framework we want to use with the view_style option.

For example if we were using foundation, we would update the view_style option with :foundation.

There are a lot of other options we can update to customize our HTML.

For example we could update our ‘previous’ and ‘next’ links by including ‘next’ and ‘previous’ with what we want to display.

web/templates/movie/index.html.eex

…
<%= pagination_links @page, next: "Next", previous: "Prev" %>
…

And if we go back to our site we can see the changes on our page.

For a full list of supported css frameworks and other pagination options check out the Scrivener.HTML README.

© 2024 HEXMONSTER LLC