1. 01. Incoming
  2. 02. Hifi
  3. 03. Lofi
  4. 04. The Lab
  5. 05. Tags

frequency decoder

Client-Side Table Pagination Script

Posted Friday October 19, 2007

frequency decoder

An unobtrusive client-side table pagination script

Why the need for a client-side solution?

There are times when a server-side/Ajax based table pagination solution isn’t possible (for example, if using static HTML pages or if you just don’t have access to the server-side scripting language). This script attempts to fill the need for a pagination solution in these “worst case scenarios”.

At a glance…

  • Unobtrusive and nameSpace friendly
  • Supports the pagination of multiple tables on the same page
  • No JavaScript knowledge required, all parameters are passed within the table’s className
  • Two pagination lists created for each table, the first positioned above the table and the second positioned below
  • If required, the pagination lists can be positioned within a wrapper element of your choice
  • A JavaScript callback function (or Object.method) can be declared, called immediately after a new page is shown by the script
  • It’s keyboard accessible

What this is not…

This is not an excuse to push a 5000 line table to the client! As noted earlier, the script was developed for “worst case scenarios” and if at all possible, you should use an Ajax or simple server-side pagination solution instead.

The pagination algorithm

The pagination system attempts to adhere to the “rules of good pagination” detailed within KuraFire’s pagination 101 article.

In brief…

  • First and Last links created where applicable
  • Next and Previous links created where applicable
  • The current page is intelligently centered
  • The current page is easily identifiable
  • Large clickable areas used (although this is more a styling issue)
  • No underlines used (again, more of a styling issue)
  • First and Last links positioned on the outside of the list

Configuration parameters

There are three configuration parameters that can defined within the table’s className; the first being “paginate-N”, where N represents an integer value that tells the script how many table rows make up a “page” – this is a compulsory class as without it, no pagination list is created.

The second (optional) parameter is “max-pages-N”, where N represents an integer value that tells the script the maximum number of numbered pagination buttons to show on screen.

The third and final parameter, used should you wish to Zebra-Stripe the table, is “rowstyle-X”; where X represents a className to give to each alternate row within each page. For example, the className “rowstyle-alt” will mean each alternate table row is given the className “alt”.

Not declaring the “max-pages-N” parameter

In this case, all of the numbered buttons will be shown on screen at once and so the first/last buttons are not created as logically, they are not required.

Positioning the lists

By default, the lists are inserted directly before and directly after the table within the DOM. Should you wish to have the lists positioned elsewhere, create a wrapper DIV for the list you wish to position and give the DIV an id of the form “X-paginationListWrapTop” or “X-paginationListWrapBottom”, where X represents the ID of the associated table.

The callback function

Should the function “paginationCallback” exist, it is called after a new page is displayed by the script; with the ID of the table passed in as an argument.

Stipulating Object.methods or bespoke JavaScript functions as a callback

You may wish to use an Object.method or a bespoke JavaScript function as a callback, to do this, just add the following class to the table’s className:

The className paginationcallback-XXX stipulates a function that will be called after the pagination gets redrawn, where XXX should be replaced with the name of the function in question.

Object.methods can be stipulated by converting the “dots” (i.e. the full-stops) to minus signs (-); for example, the className “paginationcallback-myObject-myMethod” will tell the script to call the “myMethod” method of the JavaScript Object “myObject” e.g. myObject.myMethod();

Filtering the table data

Should you be using a function to filter the table data i.e. to remove table rows that do not fall within the filter rules; just make sure your filter script adds the className “invisibleRow” to each hidden TR node and then call the tablePaginater.init method, passing in the ID of the table just filtered.

The script will then recalculate and redraw the pagination list for the table in question.

An example of this can be viewed within the pagination demo.

HTML structure

The script creates HTML of the following format:

  
 <div className=“fdtablePaginaterWrap”>
   <ul id=“theAssociatedTableId-tablePaginater[Clone]”>
     <li><a … ><span>«</span></a></li>
     <li><a … ><span>‹</span></a></li>
     <li><a … ><span>1</span></a></li>
     <li><a … ><span>2</span></a></li>

     ...     

     <li><a … ><span>N</span></a></li>
     <li><a … ><span>&rsaquo</span></a></li>
     <li><div … ><span>»</span></div></li>
   </ul>
 </div>
  
  

Note: I understand that some of you may question the use of the extra SPAN element created as a child of each link but it’s there in order to give you one more hook in which to style the list.

List styling

The lists have been styled using display:table and display:table-cell for the demo, with alternative styles passed to Internet Explorer using conditional comments. This means I was able to center the pagination “buttons” without resorting to the use of negative margins on the wrapper DIV (which can cause scrollbar and overflow problems under certain conditions).

I’ve tested the demo styles in Internet Explorer 6 & 7, FireFox 2, Opera 9 and Safari 3 (Windows).

Keyboard accessibility

As the list is recreated each time a new page is called, the “focus” on the current button is lost (as the element is removed from the DOM and a new element created). The script attempts to programmatically refocus on the appropriate button as soon as the new list is created (and I’m hoping that screen-readers etc are clever enough to indicate the new focus event to the user).

I would appreciate some real-world screen-reader testing though so, If anyone has access to a screen-reader and can confirm that the refocus event is indicated to the user, I would be much obliged.

The License

The script is released under a creative commons Attribution-ShareAlike 2.5 license. Should you use the script within a commercial context please think about clicking the payPal donate button – it’s certainly not an obligation but it would most certainly put a smile on my face.

Demo, downloads and updates

Tested in Internet Explorer 6, Opera 9.01, Safari 3.0.3 (Windows) and Firefox 2.

View the pagination demo (which also demos the tablesort script) or download the 11k uncompressed JavaScript source code (you can compress the script down to a 6k by using Dean Edwards Packer if the size is an issue).

27/12/2007 (v1.6):

  • Embedded table support
  • Demo changed to use callback function

19/12/2007 (v1.5):

  • Refactored the script to take bespoke filter functions into account i.e. the “invisibleRow” className
  • Better integration with the tablesort script

15/11/2007 (v1.4):

  • Fixed a Safari2 bug

23/10/2007 (v1.3):

  • Rewrote the pagination algorithm
  • Added code to refocus on the appropriate button
  • Made the show/hide portion of the code only show/hide the necessary table rows (without traversing through the entire table)

19/10/2007 (v1.2):

  • Added the Callback functionality
  • Added the Next/Previous buttons to each pagination list
  • Added support for multiple tables on the same page
  • Added the ability to position each of the pagination lists within a predefined wrapper element

Tags: javascript, pagination, table, unobtrusive

Previous Comments ~

Don Albrecht · http://www.ajaxbestiary.com
#1 · Friday October 19, 2007

This is wonderful, Thank you.

This really works well with your table sort script and is a great concept for use with other tables as well.

frequency decoder
#2 · Tuesday October 23, 2007

@ Don: Hi Don, glad you like the script. I’ve rewritten the pagination algorithm since your comment was posted so you might like to take another look at the demo.

Regards,
Brian.

Kit Grose
#3 · Thursday November 15, 2007

Brian,

I’m working on implementing some of your table scripts into a commercial project which required full A-grade browser support so I fixed the issue that was causing your table sort script to fail on Safari 2.

The parse error Safari was complaining about is likely affecting compatibility with all your scripts. I tried to find your email address on the site (and Google) like the closed comments form indicates, but couldn’t, so I’ll leave it here (it’s likely somewhat on-topic here anyway).

The basic problem is with your Object Notation.

Where you had:
addEvent: function addEvent(obj, type, fn, tmp) { ...
}

Safari (IMO, rightly) expects:
addEvent: function(obj, type, fn, tmp) { ...
}

The change had to be implemented for every function declared, but the script worked without any other change. There’s also a validation issue on the table itself (tfoot belongs below thead, not tbody; something I would never have expected, personally), which is an easy fix.

Feel free to email me if you want any further explanation with this/Safari checking of other scripts. I’m a big fan of the scripts; they’re saving me a tonne of effort, so I figured I’d repay the favour with Safari 2 compatibility :)

frequency decoder
#4 · Thursday November 15, 2007

@ Kit Grose: Kit, you are a star! I can’t believe that Safari2 won’t accept named functions (it’s way better for debugging if the functions are named). I’ll have to do a lot of editing this weekend to make the scripts Safari2 compatible! As per usual, there’s a pint of Guinness in the virtual keg waiting for you…

Regards & many thanks,
Brian

Kit Grose
#5 · Thursday November 15, 2007

No sweat. Glad I could help.

Means I feel slightly less guilty now if my boss can’t get a donation to you for the tablesort functions!

tribun
#6 · Monday November 19, 2007

Hi,
thanks for this great stuff. My table ist sometimes reloaded from an tajax-request. If so, the pagination drop off and the hole table is displayed again.
If i type in the firebug-console “tablePaginater.init()” it is ok again. But the call of this init-funktion in my ajax-javascript don’t work. any ideas?
thanks in advance

frequency decoder
#7 · Monday November 19, 2007

@ Kit: Hi Kit, don’t feel guilty my man, you nailed the error and thats just fine with me…

@ Tribun: Hi Tribun, if your using Ajax then you shouldn’t require a client-side pagination script. What errors are you getting if you call the init method from the Ajax code?

Aurélien Baudet
#8 · Tuesday December 18, 2007

I want to add a filter on the table. When a filter reduces the number of results, the pagination isn’t updated because rows are hidden and that’s ok. Is it possible to add a class on rows that won’t be “visible” for pagination instead of removing them ? (I want a sorter and a filter on this table). Thank you for these great scripts!!!

seb
#9 · Tuesday December 18, 2007

Hi Brian,
just wanted to let you know that i just uploaded your extremlly nice script as a TYPO3 extension in the TYPO3 Extension Repository:
http://typo3.org/extensions/repository/view/bm_tablesort/0.1.0/
write me a mail if you want me to do any changes…
thanks for the script,
seb

Pat McGarvey
#10 · Tuesday December 18, 2007

Brian, I haven’t visited you since our last contact about making the table sort 508 accessible. WOW, have you taken off on your table sort! Great job.

frequency decoder
#11 · Wednesday December 19, 2007

@ Aurélien: Hi Aurélien, I’m currently working on this (in fact, the code’s finished but I’m still testing it). The script assumes that hidden/filtered rows have been given the className “invisibleRow” and takes them into account when redrawing the pagination list…

@ Seb: Hi Seb and thanks for the inclusion in the typo projet!

@ Pat: Hi Pat, good to see ya back and I’m glad you like the script…

Regards,
Brian.

Aurelien
#12 · Wednesday December 26, 2007

I found a little problem with the sorter and pagination. If the table has a table in a cell (without sorting and pagination), the sorter and the pagination don’t work well.
I thought about a class for every row in order to flag rows that are inclded in pagination calculation and sorting.
Have you encountered this bug?
Can you fix it or I have to do it by myself ?
Thank you

frequency decoder
#13 · Thursday December 27, 2007

@ Aurelien: Hi Aurelien, I specifically haven’t tested for embedded tables as the tableSort was written with data tables in mind i.e. a flat table containing simple data. It takes too many clock cycles to test if a row is part of another table (even if testing if a certain className is present) and so I’ve not included it.

Update: I’ve changed the scripts to use the available rows/cells array which means it should be possible to use an embedded table.

Regards,
Brian

Antonio · http://digilander.libero.it/odnalro
#14 · Friday January 11, 2008

Hi Brian, happy to write back to you after a while (I am the one who fixed a bug in your stickies script, and then adapted it to make browser stickies in Opera, hope you can remember me).

Looking to your Client-Side Table Pagination Script, I had an idea: it would be very cool (and really useful) making a piece of code integrating a classic server-side/Ajax technique with your client-side script: I mean, in some parameter it should be possible to define “how many pages to store client-side” (say 5 pages), and show them with your script; but, when the user clicks on a page which is out of the range of the 5 pages loaded (or click-click-clicks on arrows until the page to show is out of the range of the 5) a “please wait while loading data…” message appears (with the classic loading “wheel”) and goes away when the needed 5 pages have been loaded (via Ajax).
I hope I’ve been clear enough: it would be nice if someone succeeds in making this sort of thing (if it does not already exist: cannot be sure, but I think not).

Semiyi
#15 · Monday February 11, 2008

Hi,
newbie to css I don’t understand whot you mean with create a wrapper div for positionning de pagination list (ex. bottom)

Helpful example are welcome
Thanks

frequency decoder
#16 · Monday February 11, 2008

@ Antonio: hi Antonio, the guys over at particleTree have code for paginating tables.

@ Semiyi: Hi Semiyi, if I have a table whose id is “fd” then I can position both lists by creating two divs, one with an id of “fd-paginationListWrapTop” and another with an id of “fd-paginationListWrapBottom” (these divs can be positioned anywhere on the page) and the pagination lists will be created within the divs in question, not just above and below the associated table.

Nuno
#17 · Tuesday March 18, 2008

Brilliant script. Can’t thank you enough.

Rajan Vora
#18 · Tuesday April 29, 2008

Hi,
These scripts are amazing. appreciate all the work you are doing here. i got the tablesort.js to work without a problem.
now i trying to incoporate the paginate.js. the sceipt creates the number for pagination, but they show up vertical instead of horizontal on my table.

frequency decoder
#19 · Friday May 2, 2008

@ Nuno: Hi Nuno, glad you like the script.

@ Rajan: Hi Rajan, it must be a problem with the CSS. Have you remembered to add the IE specific stuff using conditional comments?

eugene
#20 · Saturday May 3, 2008

Hi, I have a setup where a user can move a mouse over a marker on a map and the the related table entry is highlighted. The problem is that when you combine your tablesort + pagination scripts, you don’t know what page your entry will be on, as sorting will shuffle table rows around. To work around this issue I wrote the following function that will find and display the right page of a table row with a specified ID:

findAndShowPage: function(tblId, findId) { if(!(tblId in tablePaginater.tableInfo)) { return; }; var trs = tablePaginater.tableInfo[tblId].hook.rows; var cnt = 0; var reg = /(^|\s)invisibleRow(\s|$)/; for(var i = 0, tr; tr = trs[i]; i++) { if(tr.getElementsByTagName(“th”).length || (tr.parentNode && tr.parentNode.tagName.toLowerCase().search(/thead|tfoot/) != -1)) continue; if(tr.className.search(reg) == -1) { cnt++; }; if (tr.id == findId) { tablePaginater.tableInfo[tblId].currentPage = Math.ceil(cnt / tablePaginater.tableInfo[tblId].rowsPerPage); return tablePaginater.showPage(tblId); } }; }

p.s. sorry about the formatting, not sure how to display the code properly w/o html

yaseen khan
#21 · Wednesday May 14, 2008

This may sound little dumb… but what needs to go into tableInfo as the parameter, because I have the requirement where I have more than table and I need to provide pagination for a specific table and which function should be called from client side(javascript) to initiate the pagination….pls let me know thanks , as other wise this is an excellent script

frequency decoder
#22 · Thursday May 15, 2008

@ Yaseen: Hi Yaseen, you shouldn’t need to call anything as the script will initiate itself “onload”. If your dynamically rendering the table, just call:

tablePaginater.init(“yourTableId”)

To initiate the pagination for a specific table.

Regards,
Brian

Toby
#23 · Saturday May 17, 2008

I’m using the pagination script with ajax. With each update I use:
fdTableSort.init(‘table_name’);
tablePaginater.init(‘table_name’);

I’m finding that if I set pagination to say 20, and there are more than 20 records then pagination works.

If I update and the new recordset has > 1 record then all is good, the pagination buttons are correct for < 20 and more than 20 i.e. they disappear and re appear correctly.

The problem comes after an update with reveals a recordset of 0 rows. The next search which has a recordset of > 20 displays the buttons. But the table is blank. If I click on one of the pagination buttons, then the table appears once more.

If a table has > 1 and < 20 after a 0 recordset nothing is shown, and there is no way of getting the table to appear.

The thead is always visible, the bug only effects the tbody.

Any ideas?

If however I then update, but this time the table has no records then the pagination fails.

Frances
#24 · Wednesday May 28, 2008

Thank you so much for this, it makes my life so much easier. Got to implement it along with your sorting script without much problem.

saraswati
#25 · Tuesday June 10, 2008

Does this script work with table TD , my assumption is it works only with TH elements, wish I can make it work with TD!! Can you let me know if I need to make any tweaks to suit this requirement…..

Jaap
#26 · Thursday August 14, 2008

Thank you muchly for this! It works great.

However, is it also possible to “remember” a page and sorting method when you (for example) click on a button an the page reloads? I’m not sure how to do this.

philichio
#27 · Friday August 15, 2008

Thanks for your script!
Now i use you pagenatetale in my project ,but recently i found after queryed some datarows it could not show “totle rows”,i want to know if you script has the function to show how many rows.
Thank you!
a friend from china

Sonu
#28 · Wednesday September 17, 2008

how do you bring across the ‘displaying page X of X number of pages’?...like the ones near your comments pagination links.

A penny for your thoughts…

Remember: Off-topic or dumb-ass comments will, of course, be deleted. Spammers shall have the scary flying-monkeys from “The Wizard of Oz” dispatched to their abode. Please remember to RTM before asking questions pertaining to any of the Lab experiments.

Reporting a bug? The error message and browser and operating system details would be much appreciated. Thanks.

Popular Frequencies

  • Unobtrusive Table Sort Script (revi…
    Saturday September 16, 2006
  • Unobtrusive Date-Picker Widget Upda…
    Monday October 02, 2006
  • Unobtrusive Table Sort Script
    Friday November 18, 2005
  • Unobtrusive Date-Picker Widgit
    Friday October 14, 2005
  • Unobtrusive Table Actions Script
    Thursday November 15, 2007

Google Ads

All articles