A simple pagination component with Ember
Ember's ethos of 'components everywhere' makes it really quick and easy to make reuseable widgets. This takes the tedium out of implementing some of the more basic features of web sites and applications. An example of this is pagination. This post shows you how to create a very basic pagination component that allows you to page through a list of items.
Pagination is a very basic building block in many web applications. It’s also a great illustration of how you can leverage Ember’s components to make reusable, flexible and DRY pieces of functionality.
To demonstrate, in this post we’ll make a simple {{list-pagination}}
component that can paginate a simple <ul>
of the teams that played in this years Euro 2016 tournament:
Setup the un-paginated list of teams
To get started, we’ll create the list of teams. To make life easy, we’ll hard-code them in our application route:
import Ember from "ember";
let teams = [
{
id: 1,
name: "Albania",
},
{
id: 2,
name: "Austria",
},
// ... you can get the rest here: https://gist.github.com/timmyomahony/e26ab2cf0ee1df839cfbea5877c4fa62
{
id: 24,
name: "Wales",
},
];
export default Ember.Route.extend({
model() {
return teams;
},
});
and display them in our application template:
<h1>Euro 2016 teams:</h1>
<ul>
{{#each model as |team|}}
<li>{{team.name}}</li>
{{/each}}
</ul>
so that we now have a basic un-paginated <ul>
of all the teams:
Create a pagination component
Now we’ll create a component that will paginate the above list and provide controls to move between the pages (as seen in the GIF above).
ember g component list-pagination
To get an idea of how this component will work, let’s first show how it will eventually work in our application template:
<h1>Euro 2016 teams:</h1>
{{#list-pagination paginateBy=5 items=model as |teams|}}
<ul>
{{#each teams as |team|}}
<li>{{team.name}}</li>
{{/each}}
</ul>
{{/list-pagination}}
We are using the component in its block form to entirely wrap the existing list.. This allows us to reuse the pagination component with any sort of list. The styling and implementation of the internal list can be left to the user to declare in the component’s block template (the content between {{#list-pagination ...}}
and {{/list-pagination}}
).
The pagination component and template
Lets’ first looks at list-pagination.js
:
import Ember from "ember";
export default Ember.Component.extend({
tagName: "section",
// The page we are currently on
page: 1,
// The number of items to show per page
paginateBy: 10,
// Returns the list of items for the current page only
paginatedItems: Ember.computed("items", "page", function () {
var i = (parseInt(this.get("page")) - 1) * parseInt(this.get("paginateBy"));
var j = i + parseInt(this.get("paginateBy"));
return this.get("items").slice(i, j);
}),
// The total number of pages that our items span
numberOfPages: Ember.computed("page", function () {
var n = this.get("items.length");
var c = parseInt(this.get("paginateBy"));
var r = Math.floor(n / c);
if (n % c > 0) {
r += 1;
}
return r;
}),
// An array containing the number of each page: [1, 2, 3, 4, 5, ...]
pageNumbers: Ember.computed("numberOfPages", function () {
var n = Array(this.get("numberOfPages"));
for (var i = 0; i < n.length; i++) {
n[i] = i + 1;
}
return n;
}),
// Whether or not to show the "next" button
showNext: Ember.computed("page", function () {
return this.get("page") < this.get("numberOfPages");
}),
// Whether or not to show the "previous" button
showPrevious: Ember.computed("page", function () {
return this.get("page") > 1;
}),
// The text to display on the "next" button
nextText: "Next page",
// The text to display on the "previous" button
previousText: "Previous page",
actions: {
// Show the next page of items
nextClicked() {
if (this.get("page") + 1 <= this.get("numberOfPages")) {
this.set("page", this.get("page") + 1);
}
},
// Show the previous page of items
previousClicked() {
if (this.get("page") > 0) {
this.set("page", this.get("page") - 1);
}
},
// Go to the clicked page of items
pageClicked(pageNumber) {
this.set("page", pageNumber);
},
},
});
The code is commented, but the important part to note is the computed property paginatedItems
. Instead of returning all of the items passed into our component, it figures out what page we are on and returns only those items that are within the current page.
Now let’s see the component’s template:
{{yield paginatedItems}}
<ul>
{{#if showPrevious}}
<li><button {{action "previousClicked"}}>{{previousText}}</button></li>
{{else}}
<li style="text-decoration: line-through;">{{previousText}}</li>
{{/if}}
{{#each pageNumbers as |pageNumber|}}
<li><button {{action "pageClicked" pageNumber}}>Page
{{pageNumber}}</button></li>
{{/each}}
{{#if showNext}}
<li><button {{action "nextClicked"}}>{{nextText}}</button></li>
{{else}}
<li style="text-decoration: line-through;">{{nextText}}</li>
{{/if}}
</ul>
The Trick
The trick here is our {{yield ...}}
helper.
Firstly, remember that the yield
helper is replaced with the component’s block template (again, the content between {{#list-pagination ...}}
and {{/list-pagination}}
in our application template). In other words, we are rendering our <ul>
of teams in place of {{yield ...}}
.
Now note that we have passed paginatedItems
as a parameter to the {{yield}}
helper. These are called block parameters. Doing this makes the parameter available to us back in our component’s block template if we use the as |myVariable|
syntax: {{#list-pagination items=model as |teams|}}
. In other words, the teams
variable in our block template is actually equal to paginatedItems
. This means that when we list the teams with {{#each teams as |team|}}
, only those teams from the current page are being displayed.
Below the yielded template we place the controls for the rest of the component. This is the easy part. We simply show/hide the appropriate buttons using computed properties and provide actions to change the page when a button is clicked. These actions will cause the paginatedItems
to change, in turn changing the listed teams.
Hopefully it should be clear how this works but to see it in action you can check out this Ember Twiddle as well as browse the entire code on Github Gist
See the code
You can see this in action on Ember Twiddle, or look at the code on Github