Sweet Pages: A jQuery Pagination Solution

Demo Download

Paginating content is a standard choice when dealing with large chunks of data. The implementation usually involves passing the page number to the back-end, where the appropriate data is fetched from the database and returned in some form. A cumbersome process, but it is a necessary evil. Or is it?

When dealing with small data sets, wouldn't it be better to have the content readily available, but still neatly organized and easy to access?

Today we are making a jQuery plugin that will enable you to convert a regular unordered list of items into a SEO friendly set of easily navigatable pages. It can be used for comment threads, slideshows, or any kind of structured content.

The Idea

When called, the jQuery plugin splits the LI elements contained in the unordered list into a configurable number of groups. These groups (or pages) are floated to the left and hidden from view, as they overflow the UL which is given overflow:hidden. A number of control links are generated, which slide the appropriate page of LIs into view.

You can also take a look at the illustration below.

i2.png

Step 1 - XHTML

The first step of the tutorial is to set up the XHTML markup. The plugin only needs an unordered list, UL, with some li elements inside it. Here is the code from demo.html, which you can find in the download archive:

demo.html

<div id="main">
    <ul id="holder">
        <li>Lorem ipsum dolor sit amet...</li>
        <li>Lorem ipsum dolor sit amet...</li>
        <li>Lorem ipsum dolor sit amet...</li>
        <li>Lorem ipsum dolor sit amet...</li>
    </ul>
</div>

The main div acts as a container for the paginated UL, and is styled with a nice light-gray background. The unordered list holds the list elements (hence the id).

In most practical situations, the markup above would probably be generated by a back-end script, freeing you from having to do it manually. You could have all sorts of content inside those LIs, as the height and size is dynamically calculated by jQuery (just a reminder - if you plan on using images, specify the width and the height).

i3.jpg

Step 2 - CSS

After creating the XHTML markup, we can move on to styling it. If is a good idea to style your pages as if there were no navigation, as the plug-in is JavaScript dependent. This means that it is possible that some users will not be able to see nor use the pagination.

styles.css - Part 1

#main{
    /* The main container div */
    position:relative;
    margin:50px auto;
    width:410px;
    background:url('img/main_bg.jpg') repeat-x #aeadad;
    border:1px solid #CCCCCC;
    padding:70px 25px 60px;

    /* CSS3 rounded cornenrs */

    -moz-border-radius:12px;
    -webkit-border-radius:12px;
    border-radius:12px;
}

#holder{
    /* The unordered list that is to be split into pages */

    width:400px;
    overflow:hidden;
    position:relative;
    background:url('img/dark_bg.jpg') repeat #4e5355;
    padding-bottom:10px;

    /*  CSS3 inner shadow (the webkit one is commeted, because Google Chrome
        does not like rounded corners combined with inset shadows): */

    -moz-box-shadow:0 0 10px #222 inset;
    /*-webkit-box-shadow:0 0 10px #222 inset;*/
    box-shadow:0 0 10px #222 inset;
}

.swControls{
    position:absolute;
    margin-top:10px;
}

First we style the main div and the unordered list (the latter is assigned the id of holder).

Notice how we use the CSS3 box shadow property with the inset attribute, to mimic an inner shadow. As with most CSS3 rules, we still have to provide vendor-specific prefixes for Mozilla (Firefox) and Webkit browsers (Safri and Chrome).

You can see that the webkit version of the property is commented out. This is because there is a bug in the rendering of box shadows in Chrome, when combined with the border-radius property (the shadows are rendered as if the div is square, ignoring the rounded corners and thus ruining the effect).

styles.css - Part 2

a.swShowPage{

    /* The links that initiate the page slide */

    background-color:#444444;
    float:left;
    height:15px;
    margin:4px 3px;
    text-indent:-9999px;
    width:15px;
    /*border:1px solid #ccc;*/

    /* CSS3 rounded corners */

    -moz-border-radius:7px;
    -webkit-border-radius:7px;
    border-radius:7px;
}

a.swShowPage:hover,
a.swShowPage.active{
    background-color:#2993dd;

    /*  CSS3 inner shadow */

    -moz-box-shadow:0 0 7px #1e435d inset;
    /*-webkit-box-shadow:0 0 7px #1e435d inset;*/
    box-shadow:0 0 7px #1e435d inset;
}

#holder li{
    background-color:#F4F4F4;
    list-style:none outside none;
    margin:10px 10px 0;
    padding:20px;
    float:left;

    /* Regular CSS3 box shadows (not inset): */

    -moz-box-shadow:0 0 6px #111111;
    -webkit-box-shadow:0 0 6px #111111;
    box-shadow:0 0 6px #111111;
}

#holder,
#holder li{
    /* Applying rouded corners to both the holder and the holder lis */
    -moz-border-radius:8px;
    -webkit-border-radius:8px;
    border-radius:8px;
}

.clear{
    /* This class clears the floated elements */
    clear:both;
}

In the second part of the code, we style the page control links and the li elements. As you can see on line 46, we are applying rounded corners to both the unordered list and the li elements in one declaration, which saves us a from duplicating code.

Lastly is the clear class, which is used to clear the floats of the elements, also known as the clearfix technique.

i1.jpg

Step 3 - jQuery

Moving to the last part of the tutorial, we need to include the latest version of the jQuery library in the page. Performance-wise, it is best to include all external JavaScript files just before the closing body tag, as scripts block he rendering of the page.

script.js - Part 1

(function($){

// Creating the sweetPages jQuery plugin:
$.fn.sweetPages = function(opts){

    // If no options were passed, create an empty opts object
    if(!opts) opts = {};

    var resultsPerPage = opts.perPage || 3;

    // The plugin works best for unordered lists,
    // although OLs would do just as well:
    var ul = this;
    var li = ul.find('li');

    li.each(function(){
        // Calculating the height of each li element,
        // and storing it with the data method:

        var el = $(this);
        el.data('height',el.outerHeight(true));
    });

    // Calculating the total number of pages:
    var pagesNumber = Math.ceil(li.length/resultsPerPage);

    // If the pages are less than two, do nothing:
    if(pagesNumber<2) return this;

    // Creating the controls div:
    var swControls = $('<div class="swControls">');

    for(var i=0;i<pagesNumber;i++)
    {
        // Slice a portion of the li elements, and wrap it in a swPage div:
        li.slice(i*resultsPerPage,(i+1)*resultsPerPage).wrapAll('<div class="swPage" />');

        // Adding a link to the swControls div:
        swControls.append('<a href="" class="swShowPage">'+(i+1)+'</a>');
    }

    ul.append(swControls);

Creating a jQuery plug-in is not as hard as you might think. We just need to create a new function as a property of jQuery.fn (or $.fn, as given here). The this of the function points to the original jQuery object that it was called on.

Moving from there, we check for the existence of the opts object and set resultsPerPage accordingly. This is the number of li elements that are going to be grouped as a page.

After this, we calculate the total number of pages with the Math.ceil() function. It rounds the result to the nearest greater integer, which gives the correct number of pages.

Now that we have the number of pages obtained, we can enter a for loop in which we split the li elements into portions and wrap them in a swPage div, forming a page. Keep in mind that calling the jQuery slice() method on line 36 creates a new set of elements and leaves the original set intact (thus in every iteration of the for loop we start with the original set of li elements).

script.js - Part 2

  var maxHeight = 0;
    var totalWidth = 0;

    var swPage = ul.find('.swPage');
    swPage.each(function(){

        // Looping through all the newly created pages:

        var elem = $(this);

        var tmpHeight = 0;
        elem.find('li').each(function(){tmpHeight+=$(this).data('height');});

        if(tmpHeight>maxHeight)
            maxHeight = tmpHeight;

        totalWidth+=elem.outerWidth();

        elem.css('float','left').width(ul.width());
    });

    swPage.wrapAll('<div class="swSlider" />');

    // Setting the height of the ul to the height of the tallest page:
    ul.height(maxHeight);

    var swSlider = ul.find('.swSlider');
    swSlider.append('<div class="clear" />').width(totalWidth);

    var hyperLinks = ul.find('a.swShowPage');

    hyperLinks.click(function(e){

        // If one of the control links is clicked, slide the swSlider div
        // (which contains all the pages) and mark it as active:

        $(this).addClass('active').siblings().removeClass('active');

        swSlider.stop().animate({'margin-left': -(parseInt($(this).text())-1)*ul.width()},'slow');
        e.preventDefault();
    });

    // Mark the first link as active the first time the code runs:
    hyperLinks.eq(0).addClass('active');

    // Center the control div:
    swControls.css({
        'left':'50%',
        'margin-left':-swControls.width()/2
    });

    return this;

}})(jQuery);

In the second part of the script, we loop through the newly created pages, set their sizes and float them to the left. In the process we also find the tallest page, and set the height of the ul accordingly.

We also wrap the pages inside a swSlider div, which is wide enough to display them side by side. After this we listen for the click event on the control links, and slide the swSlider div with the animate method. This creates the slide effect that, observed in the demo.

script.js - Part 3

$(document).ready(function(){
    /* The following code is executed once the DOM is loaded */

    // Calling the jQuery plugin and splitting the
    // #holder UL into pages of 3 LIs each:

    $('#holder').sweetPages({perPage:3});

    // The default behaviour of the plugin is to insert the
    // page links in the ul, but we need them in the main container:

    var controls = $('.swControls').detach();
    controls.appendTo('#main');

});

In the last part of the code, we just a call to the plugin and passing the perPage setting . Also notice the use of the detach method, introduced in jQuery 1.4. It removes elements from the DOM, but leaves all the event listeners intact. It enables us to move the controls outside of the UL they were originally inserted in, keeping the click functionality in place.

With this our sweet pagination solution with jQuery and CSS3 is complete!

Conclusion

With this plugin you can power any kinds of comment threads, slideshows, product pages or other kinds of data. The advantage is that if JavaScript is disabled you still end up with a semantic and SEO friendly code. However if you plan to display huge chunks of data, it is still best to implement a back-end solution, as with the plug-in all the content is transferred to the visitor's browser.

What do you think? How would you use this code?

Bootstrap Studio

The revolutionary web design tool for creating responsive websites and apps.

Learn more

Related Articles

Davidmoreen

Pagination is such a great feature, I just do not see it working out with javascript. Unless you are ajaxifying the process, I think server side is the only way... IMO

It works just fine... the concept is the same but now you are paginating li items :P it is exactly the same and for a PHP novice like me this plugin sure is great.

William Rouse

Interesting, but not obvious as to how to hookup to a database which is about the only time I paginate. You would have to build the list item and then call the plugin.

Very nice, Martin. I like it. Thanks for sharing.

Bobbytuck

What'd be a nice companion piece is doing this same thing but with tabular data. Paginating a long table, in other words.

Can we add numbers to the "Control links"?

Excellent tutorial.

Martin Angelov

@Bobbytuck

For tables a bit of a different approach would be needed, but it is possible. It may find its way as a tutorial in the future.

@Bogdan

The links are actually generated with page numbers. I am using a negative text-indent to hide them. Just remove it and probably fix the styling a bit, and you will have the page numbers.

christian

it's a disaster in explorer..tsk, i hate ie.

Martin Angelov

@ christian

It works fairly well in IE7+. A little boxy though, but is functional. Which version are you testing it in?

Дмитрий

Даже очень интересно!

HAPIA Mesir

that is great idea....

Jordan Walker

Excellent implimentation!

Amr hussien

Nice Implementation , might help in some cases.

Thanks for share !!

very nice working thanks for tutorial

thanks a lot for developer jQuery plug-in

Martin, is it possible to use this jQuery technique on a 'full screen' page (or say a 960px wide page)? So that if a visitor clicks the 'about' page for instance, the current page slides graciously to the left or right presenting the 'about' page.

Do you have tips for me how to achieve this? Thank you.

Martin Angelov

Yes, it should be fairly easy. You just need to group your site's pages into a single UL and set the width of the list to 960px (around line 20 of styles.css – Part 1).

You can optionally only set the content of the pages as LI elements, and leave the site header and navigation outside the UL, so it remains static.

The only part that would require a bit of coding, is the transition between the slides. You need to translate the clicks on your site's navigation bar to the control links.

Maybe something like this:

$('#goToAboutPage').click(function(e){  // This is your navigation link

    // Emulate a click on the fourth control link - 
    // the slide your about page is located on (counted from zero):

    $('a.swShowPage').eq(3).click();

    e.preventDefault();
});

Write how it goes, it would be great to see it in action : ).

Drew Wells

Great plugin, I made some modifications and moved some of the styling into the plugin to ensure proper display with minimal css required. Mind if I work on this some more and throw it into the jQuery plugins? I'll be giving you credit of course.

Drew Wells

@Elektric There have been a couple of people writing jQuery plugins to write their slide presentations. These show pagination as full pages. Here is one of them: http://www.jlleblanc.com/jquerypresentation/

Deluxe Blog Tips

Nice tutorial. I realize that it works similarly as some slide script. Thanks for sharing.

I really like this. I was just wondering about using it for dynamic data. Could I use php to get the information from a sql database and have it echo out in list tags? Would that work?

Great tutorial, tanhks.

Thanks! I'm testing this out as the non-mobile version of my mobile ipod touch / iphone blog. View it on your pc at http://iblogtest.co.cc and go to it on your iphone to see the full version. Thanks again for this post!

Alex Flueras

Great article and really nice translation effect. Thanks for sharing.

Sebastian

hi, i found the plugin really great! thanks for sharing!

the only thing is, i think i might have found a bug or something... (maybe im doing something wrong :D)

heres the thing, i used this plugin for paginate a set of videos embebed from youtube, and in doesnt hide the overflowing elements (in firefox) in explorer works fine..

can u tell me whats happening?

heres the link so u can see it...

http://www.taxicala.comuv.com/sweetpages/

thanks again!

Martin Angelov

@ Sebastian

I tested it in all major browsers (latest versions) and your implementation works as it should.

Flash is indeed a bit tricky to incorporate into a page but usually setting wmode to transparent fixes the issue.

I was wondering... how would you modify this so that it will automatically progress through the pages? I think I saw this somewhere but I can't find it anymore.

Thanks!

It is possible to add "Next" and "Previous" buttons? or

When many pages..let`s say 100 pages..it is possible to show like this (assuming we are at page 37)?

1 2 3 .... 36 37 38 .... 98 99 100

Thanks again for this great tutorial...

Bogdan

Hi. This plugin is exactly what I am looking for. Only problem is, the data that should be displayed is created dynamically based on the number of data objects in a json file. So I am creating the div tags depending on the number of objects. Could you please let me know if this plugin will work with dynamically created tags. Thanking you.

Martin Angelov

@ Bogdan, If you are dealing with this number of pages it would be better to implement a back-end PHP solution. A PHP pagination function I've found really useful is Digg-like Pagination.

Other from this it is possible to rewrite the algorithm from the digg-style function to the jQuery plug-in.

@ Tasneem, Yes it is possible to use dynamic lists. Just create the UL structure as you'd normally do with jQuery, after which run the plug-in. The list should then be properly paginated.

I'm trying to use the plugin along side a couple other plugins and having problems. The pagination is used to paginate gallery links, and those links are suppose to replace the gallery content below on the fly, then those images are suppose to open up in shadowbox when clicked. All this is done on one page.

When the pagination is enabled, the gallery content doesn't load on click in IE7, FF3.5.9, and Opera. It does work correctly in FF 3.6 and IE8 though. If I disable the pagination then the gallery content is reloaded correctly in all browsers.

So I'm just hoping somebody might have an idea of where this conflict between the two scripts is coming from.

Here the pagination is enabled and the clicks don't work.
Here the pagination is disabled and the clicks do work.

This is where I got the other script from to reload the gallery content.

Martin Angelov

@ Dave, after a quick look at your links, I noticed that in the first example, where the pagination is enabled, you are including the jQuery library twice in your page. Try removing the second inclusion (right before the pagination js file) and write here how it goes.

I'm not 100% but I think that did it. If that was the fix, that was extremely easy. Dunno why I didn't notice the two scripts loading essentially the same library before.

Now I to try and figure out why the shadowbox isn't working correctly... but that's between the dynamic loader and shadowbox.

Thanks Martin.

billy corgan

@ William Rouse "Interesting, but not obvious as to how to hookup to a database which is about the only time I paginate. You would have to build the list item and then call the plugin."

You shouldn't be using this then, as it's extremely obvious how you would include database content into this.. I suggest you go and learn how to connect to a DB & then output data 1st..

Charlotte Coleman

Thanks for posting this Martin - I've used it on a wordpress page where I'm pulling in posts but bypassing the blog structure (so the WP next page functionality doesn't work).

It's the only jquery solution I've found which iterates through li's and doesn't need the manual wrapping of each 'page' in a div. So anyway - thanks! It looks great too. Hurrah for unordered lists!

Charlotte

Hi,

I'm trying to use this one, and I have to use jQuery 1.3.2.

I changed:

var controls = $('.swControls').detach(); controls.appendTo('#main');

To:

$('.swControls').appendTo('#main');

Everything is "rendered" properly but when I click nothing happens?!
Could it be because deatach or its because jQuery 1.3.2?

Thanks

Yap its because of the jQuery version. It is working without detach() but only with jQuery 1.4.2.

Could You tell me what to change in JS please :)
Thanks

Me again... i found it... sorry for spaming, its because:

'margin-left'

and for jQuery 1.3.2 it should be without quotation and:

marginLeft

Hope someone will need it... sometime :)

Its to late to test it now, where I am, but i hope that more than one on the same page is possible?

Thank You one more time :)

So smooth :)

Is it possible to load other content with Ajax ? How to "recreate" controls?

Best

MBarcala

Very nice tutorial, can any one tell me how I can make this to pass the content automatically, something that pass every 4 or 5 minutes? any sample code for this please??

did you find the answer?

Nice Tutorial. It does not mention about how we can paginate at the db level e.g. how to avoid full data being sent to the client at once.

Katharine

Is there a way to integrate this on Wordpress? thanks a lot for the site, it's really useful!

Techie Talks

Cool thanks for sharing. So smooth and looks very good. Nice job man!

how would you sugest adding the navigation buttons to the top of the ul and the bottom at the same time.

and while someone asked earlier, but I didn't see an answer, what about adding next and previous for something like:

<>

I love this pagination tutorial, other than adding the nav at the top and next/previous this has been very lightweight and perfect for several of the pages I am building. Also, very easy to implement.

very very nice solution. in my opinion, one of the best paginations i've ever seen.

im trying to use this pagination solution together with mootols (i know....) and im getting all the time $("#holder").sweetPages is not a function or similar errors. I added

var $h = jQuery.noConflict();

at the beginning of the js file and also replaced all $ with $h, but still the error remains

any sugestions ??

thank you in advance

never mind, i found the solution (took me a few hours). anyway here it is

var $s = jQuery.noConflict();

$s(document).ready(function(){
/ The following code is executed once the DOM is loaded /

// Calling the jQuery plugin and splitting the
// #holder UL into pages of 1LIs each:

$s('#holder').sweetPages({perPage:1});

// The default behaviour of the plugin is to insert the 
// page links in the ul, but we need them in the main container:

var controls = $s('.swControls').detach();
controls.appendTo('#main');

});

only this way it worked fine together with mootools and prototype :)

again, thank you for this great Pagination Solution, i just love it :)

PS: the above piece of code goes into script.js and replaces the lines 97 to 111

sorry about that

I'm having a problem. The li goes under the ul holder at the bottom when setting it to display one li per page, help?

Hi there,

First off, great work!

I am curious if the #holder and #mail ids can be changed to classes.

The reason is because I'm trying to implement more than one of these paginated blocks on one page.

When I modify the js/ids to classes, all of the paginated items get grouped into one block, when it would be ideal for them to be separated.

Any input would be most appreciated.

Great content and instruction. I decided to take this example and bring it into my Wordpress environment. I'm a bit of a novice to jQuery and relatively new to Wordpress so I'm in a "high risk group" for making dumb mistakes but I've been banging my head for a while now and hoping the group can help.

Take a look at my demo page: http://ken.net/xxyyzz

It displays the thumbnails appropriately and the overflow goes into a hidden page to the right but the navigational elements never come up. :^(

Don't know if this has any bearing on my problem but one thing I had to do to get this working was to change away from the '$' reference in lines 97 to 111 in the JS file as Jan suggested earlier. This was driving me crazy but changing that made it so it started to run the script and clearly has done the work of breaking the elements into discrete pages. No if only I could get the navigational elements to show up.

Any help would be greatly appreciated.

Oh also, RAD MKT, it may be of interest to you that I did precisely what you suggest -- changing # anchors and changing them to classes -- and I think that part of things works. It's only when it gets to lines 70-95 where things seem to not go to plan (and I'm left with no navigational elements).

Ok, I'm a persistent git and eventually I figured it all out. The main thing was a spelling mistake. Damn! Then after that there were CSS issues that I'm still tweaking to get this aligned correctly. Any CSS wiz's that feel like giving me a hint on why all my images are shifted to the right would find me very grateful.

alhoseany

Guys, about adding the controls on top and bottom at the same time you need to change this:

replace this part in the script.js:

var controls = $('.swControls').detach();

with this

$('.swControls').clone(true).addClass("topc").prependTo('#main');
    var controls = $('.swControls:eq(1)').detach();

and this part in the script.js:

$(this).addClass('active').siblings().removeClass('active');

with this

if($(this).parent().hasClass('topc'))
{
    var bclick = $(this).index();
    $(this).addClass('active').siblings().removeClass('active');
    $('.swControls:not(.topc) a').eq(bclick).addClass('active').siblings().removeClass('active');
}
else
{
    var bclick = $(this).index();
    $(this).addClass('active').siblings().removeClass('active');
    $('.swControls:not(.topc)')
    $('.topc a').eq(bclick).addClass('active').siblings().removeClass('active');
}

Finally this to styles.css

.topc {
    margin-top: -20px;
}
Codicode

Thanks for sharing. it's smooth and looks very good.
Nice job!

It is possible to add “Next” and “Previous” buttons ?

Thanks !

this is amazing :D

thanks for sharing, you so good !!

Hi,

this is great! I was just wondering if this is possible with divs rather than the ul and li model used here? If so, what would need to change in the script file in order to target the divs correctly?

Any help would be appreciated!!

Stuart Dobson

Nice! I managed to get it working with a collection in MVC in a matter of minutes, just using

<div id="main">
<ul id="holder">
@foreach (var item in Model)
{
<li>
<div class="display-label">Report Name</div>
<div class="display-field">@Html.DisplayFor(model => item.ReportName)</div>
</li>
}
</ul>
</div>

and setting perPage to 1.

Thank you!

Stuart Dobson

May I also suggest adding

outline: none;

to a.swShowPage.active

to avoid the Firefox 'Dotted link' bug.

Awesome!!! Thanks a ton.

Howdy! I'm wondering how one might modify the CSS and JS to display an UL 6-up at a time, where each LI contains a fixed size image (250x140) and a single line of text. Such as:

LI - LI - LI
LI - LI - LI

I tried messing around with the code but keep breaking it. Also would be nice to add forward /back arrows to each end.

Any help is appreciated!

Can I have to instances of this on one page. I am finding difficult to implement this.

Lucas Moraes

AutoLoad - automatic slider

$(document).ready(function(){
    /* The following code is executed once the DOM is loaded */

    // Calling the jQuery plugin and splitting the
    // #holder UL into pages of 3 LIs each:

    $('#holder').sweetPages({perPage:1});

    // The default behaviour of the plugin is to insert the 
    // page links in the ul, but we need them in the main container:

    var controls = $('.swControls').detach();
    controls.appendTo('#main');

    /* BEGIN New function - Solution */
    function xautoLoadSlider() {
        var curPage = parseInt($('.swShowPage.active').text(), 10);
        var nextPage = ((curPage + 1) > $('.swShowPage').length) ? 1 : curPage + 1;
        $('.swShowPage:contains("' + nextPage + '")').click();      
    }

    var xAutoLoad = setInterval(function() {
        xautoLoadSlider();
    }, 5000);   
    /* END New function - Solution */

});

Can you use this plugin with multiple #main and #holders or just one?