Subscribe to access all episodes. View plans →
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.
Almog Koren
6 years agoHi, So I first implemented the search based on the search cast you did and now I added pagination from this cast but the search stopped working, how can you get both to work
Alekx
6 years agoHey Almog, thanks for watching! Do you have the code somewhere for review? Thanks.
ehayun
5 years agoHow do I add pagination and order by? I tried to add a query but it gave an error whene I tried to paginate
Thanks
Alekx
5 years agoHave you tried something like this, calling
Ecto.Query.order_by
first and then piping that result intoRepo.paginate
?I think this should work for the
Movie
example in the episoode:ehayun
5 years agoGreat. Thanks