What is pagination?

It's highly likely that you're already familiar with the concept of pagination, without really knowing it.

When you search for something on a search engine such as Google, a list of suggestions appears. Down this list, or sometimes even at the top of it, there is a whole string of numbers mentioned, starting from 1. Each of these numbers points to a given page of the results.

An example follows:

<123>
Pagination on a search engine.

For instance, 1 would point to page 1 of the list of suggestions; similarly, 2 would point to page 2 of the list of suggestions, and so on.

This is pagination.

Its purpose is to break down content into a list of pages each of which can be visited by pressing the corresponding element in the pagination block.

Pagination is also used extensively on mobile phone operating systems. For example, on Android, there is a sequence of circles near the bottom of the home page that represents the total number of thumbnails on the phone. At any given instant, a specific circle is filled to indicate that the corresponding slide is currently shown.

Thumbnail pagination on some mobile OS.

Anyways, moving on, in this chapter we shall implement the idea of pagination in our slider. By paginating our slider we'll do two things:

  1. Make the user aware of the fact that how many slides are there in a slider.
  2. Make navigation a lot more quicker. For instance, to navigate to the 10th slide, the user would only have to click the 10th pagination element - that's it!

With the explanation out of the way, it's time to move on to the coding part!

Giving pagination

As can be seen in the pagination illustrations above, we need to have a container that can hold the individual pagination buttons (shown as numbers, or circles above).

We'll call this container .slider_pagination.

Now where to give it? This is a very good question!

Sometimes the user might want to give the pagination in a specific position in the slider. For example, he/she might want to give it between the navigation buttons of the slider, or sometimes before the .slider_slides-cont element and so on.

In these cases, the user definitely needs to specify exactly where is the pagination container desired. And the most straightforward way to do this is to let the user place the pagination container inside the .slider element.

Shown below is an example:

<div class="slider">
    <div class="slider_slides-cont">
        <div class="slider_slide">Slide 1</div>
        <div class="slider_slide">Slide 2</div>
        <div class="slider_slide">Slide 3</div>
    </div>
    <button class="slider_nav">&larr; Back</button>
    <div class="slider_pagination"></div>
    <button class="slider_nav">Forward &rarr;</button>
</div>

Here the user has decided to give the pagination between the next and previous buttons, and likewise places the .slider_pagination element between the markup for the buttons.

The .slider_pagination element here would have display: inline-block set, in order to show it inline with the navigation buttons.

On the other hand, if the user isn't really concerned with where the slider's pagination is shown, then he/she doesn't need to specify its location.

In these cases, it's much more efficient to let the slider's script do the placement itself — place the pagination after the navigation buttons.

This can easily be accomplished using the HTML DOM appendChild() method on .slider.

Here's the complete process:

  1. First, it needs to be confirmed whether the user is concerned about where the pagination is shown.
  2. If the user is concerned with this, then he/she would have already placed the .slider_pagination element within the HTML of the slider, in the desired location.
  3. However, if the user is not concerned with this, then our script takes over. We create the pagination container using document.createElement() and then place it inside .slider using appendChild().

The following code demonstrates this:

var pagination = document.getElementsByClassname("slider_pagination")[0];

if (!pagination) {
    // the user isn't concerned with where the
    // pagination is displayed
    pagination = document.createElement("div");
    pagination.className = "slider_pagination";
    slider.appendChild(pagination);    
}

Line 1 fetches the .slider_pagination element, if it exists. The conditional statement in line 3 checks whether a pagination container exists already — if it does, there is no need to relocate it. However, if it doesn't we create it and place it after the navigation buttons.

Now that the pagination container is there, it's time to see how to create the individual pagination buttons in it.

Creating the buttons

Let's say we have 10 slides in our slider. How many pagination buttons would we need to create? That's right - 10!

As many pagination buttons ought to be created as many slides are there.

But how to put these pagination buttons inside .slider_pagination?

How to put the pagination buttons inside the .slider_pagination element?

Well, any way, we do need a loop with the number of iterations equal to the number of slides. In each iteration, we create a pagination button to be put into .slider_pagination.

And another question pops up! Coding is just full of questions.

How to create a pagination button in each iteration?

There are two ways to create a pagination button in each iteration of the for loop:

  1. Create a string containing the HTML markup of the button, to be later put into the innerHTML property of .slider_pagination
  2. Create a DOM node representing the button, to be later put inside the .slider_pagination element.

Keep performance in mind!

One thing worth mentioning over here is that in choice 2, we won't be appending the pagination button on each iteration to the DOM. This is because doing so causes frequent reflows (layout recalculations made by the browser), negatively affecting performance.

In choice 2, what we'll do is create a node on each iteration, append it to a document fragment, and then in the end finally put everything stored in this fragment to the DOM.

We'll go with the former choice - the one that relies on the innerHTML property - since it's shorter and easier to set up.

Shown below is its code:

var pagination = document.getElementsByClassname("slider_pagination")[0];

/* ... */

var paginationHTML = [];
for (var i = 0; i < slidesLength; i++) {
    paginationHTML.push('<button class="slider_pagination_btn"></button>');
}
pagination.innerHTML = paginationHTML.join("");

We've set paginationHTML to [] at the start so that each button's html can be lined up in this array and in the end joined up together at once.

Using a string value instead, would require concatenation in each iteration which is comparatively a more expensive operation than appending items into an array and joining them at once, after all the iterations complete.

Styling the buttons

We want the pagination buttons to look like real pagination buttons. It's now that we shall accomplish this requirement.

First of all, we'll make each button look like a circle. Then we'll give each one a :hover rule. Lastly, we'll give the currently selected circle — denoted via the class .slider_pagination_btn--sel — a filled background to indicate that it is currently active.

This is it!

On a separate HTML page we write the following markup along with the CSS shown, just so that we can test how our pagination buttons are looking:

<div class="slider_pagination">
    <button class="slider_pagination_btn"></button>
    <button class="slider_pagination_btn"></button>
    <button class="slider_pagination_btn"></button>
</div>
.slider_pagination_btn {
    display: inline-block;
    padding: 6px;
    margin: 5px;
    background-color: #ccc;
    border-radius: 10px;
    cursor: pointer;

    /* override default button styles */
    border: none;
    outline: none;
}
.slider_pagination_btn:hover {
    background-color: #aaa;
}
.slider_pagination_btn:focus {
    background-color: #777;
 }
Instead of giving padding: 6px you can also give both the declarations width: 12px and height: 12px. But if you do this, keep in mind that you'll need to set padding: 0 in order to remove the default user-agent padding on buttons.

Let's give one arbitrary button the class .slider_pagination_btn--sel and see how does the selected button look:

<div class="slider_pagination">
    <button class="slider_pagination_btn"></button>
    <button class="slider_pagination_btn slider_pagination_btn--sel"></button>
    <button class="slider_pagination_btn"></button>
</div>
.slider .slider_pagination_btn--sel {
    background-color: black
}

See how the darker background color gets emphasis placed on the currently selected button.

The reason of including .slider in the selector .slider .slider_pagination_btn--sel above, is to prevent the previous :hover and :focus selector from overriding the styles of .slider_pagination_btn--sel.

Live Example

Giving interactivity

When a given pagination button is clicked, it's desired to navigate the slider to that very slide. This means that there needs to be a click handler working on these buttons.

That whether this handler needs to be on each button or on the containing .slider_pagination element is to be sorted through discussion.

So there are two ways to make the slider's pagination buttons interactive:

  1. Give a click handler to each button
  2. Give a click handler to .slider_pagination and check whether the target of the click is a button.

What do you think would be more efficient? Remember, efficiency shall always be in mind.

Well, choice 2 is apparently more efficient. Why? It is because it requires only one click handler per a slider. In contrast to this, choice 1 requires as many click handlers as the number of slides.

Keeping the total number of handlers in an application as little as possible is always a good thing to do.

Moving on, in the click handler we retrieve the target of the event and see if it has a class "slider_pagination_btn" on it. If it has, we know that we have clicked a button and likewise bring its corresponding slide into view.

But how do we know which button corresponds to which slide. Trivially, the 1st button would correspond to the 1st slide, the 2nd one would correspond to the 2nd slide, the 3rd to the 3rd slide, and so on.

The thing is that how does the code know of this?

How does the code know which pagination button corresponds to which slide?

We can either:

  1. Put an attribute on each button that holds its index.
  2. Save each slide's reference on the corresponding button.

Once again, a battle of two choices to accomplish a particular task. Who wins?

Well, choice 1 this time. Saving the index on each button is way more flexible than saving the corresponding slide's reference. Why?

Later, in the effects chapter we would need to move .slider_slides-cont completely by a given value whe a given .slider_pagination button is clicked. Having the index on each button would aid us in quickly calculating the amount by which the container would need to be moved to get the corresponding slide into view.

So, going with choice 1, let's first change our previous code that creates the pagination buttons, to include a string in it that represents the data-index attribute which will hold the index of each slide:

var pagination = document.getElementsByClassname("slider_pagination")[0];

/* ... */

var paginationHTML = [];
for (var i = 0; i < slidesLength; i++) {
    paginationHTML.push('<button class="slider_pagination_btn" data-index="' + i + '"></button>');
}
pagination.innerHTML = paginationHTML.join("");

The highlighted portion shows the addition we've made.

After this, the second thing to be done is to give a click handler to the .slider_pagination element:

pagination.onclick = function(e) {
    var target = e.target;
    if (target.classList.contains("slider_pagination_btn")) {
        newIndex = Number(target.getAttribute("data-index"));
        navigateSlider();
    }
}

When .slider_pagination is clicked, it's first checked whether the target of the click is a button. If it is a button, we retrieve the data-index attribute of this button, assign it to the variable newIndex and finally navigate the slider.

A functional, paginated, slider awaits your view!

Live Example

Filling the selected button

If you notice in the live example above, when the slider navigates to a new slide, the corresponding pagination button is NOT filled/highlighted — which is all the essence of pagination.

We should know exactly at which slide we are, by looking at the pagination buttons!

Let's now get this dealt with too.

To do the highlighting part we just ought to alter our previous navigateSlider() function slightly and get the job done in the span of seconds.

Whenever the slider is navigated to a new slide (using the navigateSlider() function), the corresponding pagination button gets the class .slider_pagination_btn--sel added, whereas the previously filled button gets the same class removed.

To access the ith pagination button we refer to the ith child node of the .slider_pagination element in our script.

Consider the following code:

function navigateSlider() {
    slides[currentIndex].style.display = "none";
    slides[newIndex].style.display = "block";

    pagination.childNodes[currentIndex].classList.remove("slider_pagination_btn--sel");
    pagination.childNodes[newIndex].classList.add("slider_pagination_btn--sel");

    sliderNav[0].disabled = (counter === 0) ? true : false;
    sliderNav[1].disabled = (counter === len - 1) ? true : false;
    prevIndex = index;
 }

Now when we click on any button, it gets highlighted and the slider gets navigated to the corresponding slide. Perfect!

Paginated Slider

And we are all done with our pagination feature — it has the look to it and even functionality.