17

I'm working on a jQuery pagination tool, and while I have the pagination working, I see a flaw:

Having a row of 14+ pagination links is fine on a desktop, but it is not OK for a mobile device. So I want to limit the number of visible pages to 5 at a time (not including the next/prev buttons) and have it update when it reaches the third visible page and update the visible pages in the pagination essentially looking like

| Prev | 1 | ... | 3 | 4 | 5 | 6 | Next |

I've written this CodePen of what I have so far. I'm aware there are plug-ins that do this for me but I want to avoid using plug-ins for this project.

HTML (example content being paginated)

<div class="container" id="jar">
    <div class="row content">
         <div class="col">
              <img src="http://via.placeholder.com/350x150">
         </div>
         <div class="col">
             <img src="http://via.placeholder.com/350x150">
         </div>
    </div>
</div>

HTML (pagination element)

 <nav>
   <ul class="pagination justify-content-center pagination-sm">
     <li id="previous-page" class="page-item"><a class="page-link" href="javascript:void(0)">Prev</a>
     </li>
   </ul>
 </nav>

JavaScript:

"use strict";
 //NOTE: the class"page-item page-link" are classes used in bootstrap 4 and will not be seen declared or used outside of the <li> as the bootstrap framework uses those classes to apply CSS

 //sets number of items and limits the number of items per page

var numberOfItems = $("#jar .content").length;
var limitPerPage = 2;

//as the items start at 0 to keep the items per page at the limitPerPage set in variable, need to subtract 1 as is shown below

$("#jar .content:gt(" + (limitPerPage - 1) + ")").hide();

//sets total pages rounded to the next whole number

var totalPages = Math.round(numberOfItems / limitPerPage);

//append the 1 page to the pagination

$(".pagination").append(
        "<li class='page-item current-page active'><a class='page-link'  href='javascript:void(0)'>" +
        1 +
        "</a></li>"
        );

//append the pages in sequential order after prev button (as seen in the html in codepen)

for (var i = 2; i <= totalPages; i++) {
    $(".pagination").append(
            "<li class='page-item current-page'><a class='page-link' href='javascript:void(0)'>" +
            i +
            "</a></li>"
            );
}

//appends the next button link as the final child element in the pagination

$(".pagination").append(
        "<li class='page-item' id='next-page'><a class='page-link' href='javascript:void(0)'>Next</a></li>"
        );

//When a page is selected, if it has "active" class return false, if no "active" class, go to page and add "active" class attribute and remove from any other element that has "active" on it.

$(".pagination li.current-page").on("click", function () {
    if ($(this).hasClass("active")) {
        return false;
    } else {
        var currentPage = $(this).index();
        $(".pagination li").removeClass("active");
        $(this).addClass("active");
        $("#jar .content").hide();

       //.hide will hide content that does not fit into that page (ie 0 and 1 on page one, 2 and 3 on page two and so on will show on appropriate page)  If it does not fall within the range for that page .hide, if it falls within the range .show content

        var grandTotal = limitPerPage * currentPage;

        for (var i = grandTotal - limitPerPage; i < grandTotal; i++) {
            $("#jar .content:eq(" + i + ")").show();
        }
    }
});

//when next is clicked if it is on the last page, return false otherwise move on to next page in pagination and remove "active" class from previous page and add "active" class to new page

$("#next-page").on("click", function () {
    var currentPage = $(".pagination li.active").index();
    if (currentPage === totalPages) {
        return false;
    } else {
        currentPage++;
        $(".pagination li").removeClass("active");
        $("#jar .content").hide();

        var grandTotal = limitPerPage * currentPage;

        for (var i = grandTotal - limitPerPage; i < grandTotal; i++) {
            $("#jar .content:eq(" + i + ")").show();
        }
        $(".pagination li.current-page:eq(" + (currentPage - 1) + ")").addClass(
                "active"
                );
    }
});

//when prev is clicked if it is on the 1 page, return false otherwise move on to previous page and remove "active" class from last page visited and add "active" class to new page

$("#previous-page").on("click", function () {
    var currentPage = $(".pagination li.active").index();
    if (currentPage === 1) {
        return false;
    } else {
        currentPage--;
        $(".pagination li").removeClass("active");
        $("#jar .content").hide();

        var grandTotal = limitPerPage * currentPage;

        for (var i = grandTotal - limitPerPage; i < grandTotal; i++) {
            $("#jar .content:eq(" + i + ")").show();
        }
        $(".pagination li.current-page:eq(" + (currentPage - 1) + ")").addClass(
                "active"
                );
    }
});
trincot
  • 317,000
  • 35
  • 244
  • 286
Kyle Drew
  • 370
  • 2
  • 3
  • 16
  • 1
    Sharing your research helps everyone. Tell us what you've tried and why it didn’t meet your needs. This demonstrates that you’ve taken the time to try to help yourself, it saves us from reiterating obvious answers, and most of all it helps you get a more specific and relevant answer! See also: [ask] – glennsl Sep 23 '17 at 19:23
  • I've looked through the js files of different plugins to see how they have done it and most seem to destroy the paginationnand redraw it when it reaches a certain page number threshold. I'm not sure how to duplicate this with the code I've written as the plugins I've looked at don't exactly explain what they are doing with each section of their code. I've looked at YouTube tutorials which is where I found one to help write this but have not found one that helps explain how to destroy and redraw the pagination. – Kyle Drew Sep 23 '17 at 19:39
  • You should consider providing a [mcve]. I have no desire to read through and try to understand the code you've posted since, honestly, it looks awful. – glennsl Sep 23 '17 at 19:45
  • formatting code – Rohan Khude Sep 23 '17 at 20:06
  • Added comments to jquery as well as added html example (copy and paste starting with the "row" class and keep in container with id="jar" to create multiple pages if testing). The pagination nav element does not need to be in the same container. If you want to see the code in action for reference use [this codepen](https://codepen.io/kyledrew/pen/bowxvp) – Kyle Drew Sep 23 '17 at 21:02

2 Answers2

46

I would suggest using a function that -- given a current page number, the total number of pages, and the maximum number of buttons you want to have -- will return an array of numbers, where 0 denotes a ... button, and other numbers denote clickable page buttons.

So for example, it could return:

[1, 2, 0, 5, 6, 7, 0, 10, 11]

...which would represent the following buttons:

1, 2, ..., 5, 6, 7, ..., 10, 11

That function will do the hard work of calculating where those ... should be placed, but once you have that, it is a piece of cake to integrate it with your page.

Here is an example that will limit the number of buttons (excluding prev/next, including ... buttons) to 7. You can reduce that parameter to 5 if you like:

// Returns an array of maxLength (or less) page numbers
// where a 0 in the returned array denotes a gap in the series.
// Parameters:
//   totalPages:     total number of pages
//   page:           current page
//   maxLength:      maximum size of returned array
function getPageList(totalPages, page, maxLength) {
    if (maxLength < 5) throw "maxLength must be at least 5";

    function range(start, end) {
        return Array.from(Array(end - start + 1), (_, i) => i + start); 
    }

    var sideWidth = maxLength < 9 ? 1 : 2;
    var leftWidth = (maxLength - sideWidth*2 - 3) >> 1;
    var rightWidth = (maxLength - sideWidth*2 - 2) >> 1;
    if (totalPages <= maxLength) {
        // no breaks in list
        return range(1, totalPages);
    }
    if (page <= maxLength - sideWidth - 1 - rightWidth) {
        // no break on left of page
        return range(1, maxLength - sideWidth - 1)
            .concat(0, range(totalPages - sideWidth + 1, totalPages));
    }
    if (page >= totalPages - sideWidth - 1 - rightWidth) {
        // no break on right of page
        return range(1, sideWidth)
            .concat(0, range(totalPages - sideWidth - 1 - rightWidth - leftWidth, totalPages));
    }
    // Breaks on both sides
    return range(1, sideWidth)
        .concat(0, range(page - leftWidth, page + rightWidth),
                0, range(totalPages - sideWidth + 1, totalPages));
}

// Below is an example use of the above function.
$(function () {
    // Number of items and limits the number of items per page
    var numberOfItems = $("#jar .content").length;
    var limitPerPage = 2;
    // Total pages rounded upwards
    var totalPages = Math.ceil(numberOfItems / limitPerPage);
    // Number of buttons at the top, not counting prev/next,
    // but including the dotted buttons.
    // Must be at least 5:
    var paginationSize = 7; 
    var currentPage;

    function showPage(whichPage) {
        if (whichPage < 1 || whichPage > totalPages) return false;
        currentPage = whichPage;
        $("#jar .content").hide()
            .slice((currentPage-1) * limitPerPage, 
                    currentPage * limitPerPage).show();
        // Replace the navigation items (not prev/next):            
        $(".pagination li").slice(1, -1).remove();
        getPageList(totalPages, currentPage, paginationSize).forEach( item => {
            $("<li>").addClass("page-item")
                     .addClass(item ? "current-page" : "disabled")
                     .toggleClass("active", item === currentPage).append(
                $("<a>").addClass("page-link").attr({
                    href: "javascript:void(0)"}).text(item || "...")
            ).insertBefore("#next-page");
        });
        // Disable prev/next when at first/last page:
        $("#previous-page").toggleClass("disabled", currentPage === 1);
        $("#next-page").toggleClass("disabled", currentPage === totalPages);
        return true;
    }

    // Include the prev/next buttons:
    $(".pagination").append(
        $("<li>").addClass("page-item").attr({ id: "previous-page" }).append(
            $("<a>").addClass("page-link").attr({
                href: "javascript:void(0)"}).text("Prev")
        ),
        $("<li>").addClass("page-item").attr({ id: "next-page" }).append(
            $("<a>").addClass("page-link").attr({
                href: "javascript:void(0)"}).text("Next")
        )
    );
    // Show the page links
    $("#jar").show();
    showPage(1);

    // Use event delegation, as these items are recreated later    
    $(document).on("click", ".pagination li.current-page:not(.active)", function () {
        return showPage(+$(this).text());
    });
    $("#next-page").on("click", function () {
        return showPage(currentPage+1);
    });

    $("#previous-page").on("click", function () {
        return showPage(currentPage-1);
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

<div class="pagination">
</div>

<div id="jar" style="display:none">
    <div class="content">1) Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>
    <div class="content">2) Maecenas vitae elit arcu.</div>
    <div class="content">3) Pellentesque sagittis risus ac ante ultricies, ac convallis urna elementum.</div>
    <div class="content">4) Vivamus sodales aliquam massa quis lobortis. </div>
    <div class="content">5) Phasellus id sem sollicitudin lacus condimentum malesuada vel tincidunt neque.</div>
    <div class="content">6) Donec magna leo, rhoncus quis nunc eu, malesuada consectetur orci.</div>
    <div class="content">7) Praesent sollicitudin, quam a ullamcorper pharetra, urna lacus mollis sem, quis semper augue massa ac est.</div>
    <div class="content">8) Etiam leo magna, fermentum quis quam non, aliquam tincidunt erat.</div>
    <div class="content">9) Morbi pellentesque nibh nec nibh posuere, vel tempor magna dignissim.</div>
    <div class="content">10) In maximus fermentum elementum. Vestibulum ac lectus pretium, suscipit ante nec, bibendum erat.</div>
    <div class="content">11) Phasellus sit amet orci at lectus fermentum congue. Etiam faucibus scelerisque purus.</div>
    <div class="content">12) Pellentesque laoreet ipsum ac laoreet consectetur. </div>
    <div class="content">13) Integer aliquet odio magna, lobortis mattis tortor suscipit sed.</div>
    <div class="content">14) Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </div>
    <div class="content">15) Mauris a tellus luctus turpis elementum imperdiet vitae malesuada mauris. </div>
    <div class="content">16) Donec id libero sagittis, laoreet lorem vel, tempus nunc. </div>
    <div class="content">17) Donec vitae neque sed ex tristique hendrerit.</div>
    <div class="content">18) Aliquam sollicitudin gravida varius.</div>
    <div class="content">19) Donec auctor, augue sed finibus fermentum, neque erat interdum libero, eget porta metus lectus quis odio.</div>
    <div class="content">20) Nunc quis ante enim. Etiam nisl orci, hendrerit ut pretium nec, tempor in metus.</div>
    <div class="content">21) Donec et semper arcu.</div>
    <div class="content">22) Donec lobortis interdum purus, eu semper nisl pulvinar ac.</div>
    <div class="content">23) Cras laoreet eu elit vel porta.</div>
    <div class="content">24) Quisque pharetra arcu eget diam posuere commodo.</div>
    <div class="content">25) Nulla ornare eleifend neque, eget tincidunt nunc ullamcorper id. Nulla facilisi.</div>
</div>
trincot
  • 317,000
  • 35
  • 244
  • 286
  • Thank you. Have spent several hours trying to find things that work with what I originally wrote and nothing would do anything besides break it. I appreciate it. Thank you for doing an amazing job on the notes so I can understand what each thing does and how it relates to each component. I'm still learning and have a long ways to go, but your notes help a lot in helping me learn. – Kyle Drew Sep 24 '17 at 00:33
  • If I wanted to make the page focus the top of the page when when a page is changed, would I use the .focus function And add it to the showpage function? While auto focus isn't a problem on desktop, mobile is another matter. – Kyle Drew Sep 24 '17 at 07:08
  • That is a different question. If you mean you want to scroll to the top, then use [jQuery scrollToTop](https://stackoverflow.com/questions/10745485/scroll-to-top-of-page). Try it first, and if you bump into an issue, ask a new question. – trincot Sep 24 '17 at 07:28
  • I got it, just took me a couple minutes to write and test. I added this to event delegation: `$(".pagination").on("click", function () { $('html,body').animate({scrollTop:0},0); })` – Kyle Drew Sep 26 '17 at 00:34
  • Very interesting solution. How to add the page number on the routing? For example if you click next to go to page 2, it would show as www.example.com/anyotherparameter/2 – Auguste Sep 21 '19 at 13:33
  • @Auguste, for that you can use `history.pushState` and/or `history.replaceState` (see [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API)). See also [How do I modify the URL without reloading the page?](https://stackoverflow.com/questions/824349/how-do-i-modify-the-url-without-reloading-the-page) – trincot Sep 21 '19 at 15:57
  • 1
    Awesome, thanks for the getPageList() function, exactly what I needed – Jan Zikmund Dec 31 '19 at 21:21
  • Hey, it seems that this may be broken. Both on the minimal reproducible example and on my site despite the maxLength being set to 7 it only shows 5. As I don't fully understand the code I can't really fix it. Can you help? – bobsfriend12 Jun 21 '21 at 21:54
  • @bobsfriend12, what do you mean with "the minimal reproducible example"? The snippet displays 7 buttons between the prev/next buttons. If it isn't the case for you, can you create a snippet, like on jsfiddle.net, that illustrates what you are saying? – trincot Jun 22 '21 at 04:46
  • Wait. you count the `0` as one. So the get what I want I need a size of 9, but with nine it shows `[1,2,0,7,8,9,0,14,15]`. I would like it to show `[1,0,6,7,8,9,10,0,15]` how would I achieve this? – bobsfriend12 Jun 22 '21 at 13:59
  • For your desired output, you can replace `var sideWidth = maxLength < 9 ? 1 : 2` with just `var sideWidth = 1`. – trincot Jun 22 '21 at 14:20
  • when I go to the pages near to the final ones the function is buggy and displays bunch of pages outside of the range – Radoslav Aug 24 '21 at 12:48
  • @Radoslav, could you demonstrate the issue in a snippet (like on jsfiddle.net)? I cannot reproduce what you are claiming here. Did you maybe confuse the paragraph numbers in the *content* with the page numbers at the top? – trincot Aug 24 '21 at 19:01
  • 1
    An excellent solution. "getPageList" is an awesome function. And it really helped a lot in making the pagination as beautiful and elegant as possible. – Kiran Dec 16 '21 at 17:39
  • 1
    works like a charm, thanks man – Jitan Gupta Feb 09 '22 at 17:43
0
                        <nav>
                        <ul class="pagination">
                            <!-- Lien vers la page précédente (désactivé si on se trouve sur la 1ère page) -->
                            <li class="page-item <?= ($currentPage == 1) ? "disabled" : "" ?>">
                                <a href="./winners.php?page=<?= 1 ?><?php echo $slink;?>" class="page-link">First</a>
                            </li>

                            <li class="page-item <?= ($currentPage == 1) ? "disabled" : "" ?>">
                                <a href="./winners.php?page=<?= $currentPage - 1 ?><?php echo $slink;?>" class="page-link"><i class="fad fa-chevron-double-left"></i></a>
                            </li>
                            <?php for($page = $currentPage-2; $page <= $currentPage+2; $page++): ?>
                                <!-- Lien vers chacune des pages (activé si on se trouve sur la page correspondante) -->
                                <?php if(($page > 0 )&&($page < $pages )) {?>
                                <li class="page-item <?= ($currentPage == $page) ? "active" : "" ?>">
                                    <a href="./winners.php?page=<?= $page ?><?php echo $slink;?>" class="page-link"><?= $page ?></a>
                                </li>
                                <?php }endfor ?>
                                <!-- Lien vers la page suivante (désactivé si on se trouve sur la dernière page) -->
                                <li class="page-item <?= ($currentPage == $pages) ? "disabled" : "" ?>">
                                <a href="./winners.php?page=<?= $currentPage + 1 ?><?php echo $slink;?>" class="page-link"><i class="fad fa-chevron-double-right"></i></a>
                            </li> 

                            <li class="page-item <?= ($currentPage == $pages) ? "disabled" : "" ?>">
                                <a href="./winners.php?page=<?= $pages ?><?php echo $slink;?>" class="page-link">Last</a>
                            </li>
                        </ul>
                        </nav>
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 15 '22 at 08:11