2 paginate table object v2.0 by frequency-decoder.com
4 Released under a creative commons Attribution-ShareAlike 2.5 license (http://creativecommons.org/licenses/by-sa/2.5/)
6 Please credit frequency decoder in any derivative work - thanks
10 * to copy, distribute, display, and perform the work
11 * to make derivative works
12 * to make commercial use of the work
14 Under the following conditions:
18 You must attribute the work in the manner specified by the author or licensor.
22 Share Alike. If you alter, transform, or build upon this work, you may distribute the resulting work only under a license identical to this one.
24 * For any reuse or distribution, you must make clear to others the license terms of this work.
25 * Any of these conditions can be waived if you get permission from the copyright holder.
28 var tablePaginater = (function() {
31 Localise the button titles here...
33 %p is replaced with the appropriate page number
34 %t is replaced with the total number of pages
39 text = ["First Page","Previous Page (Page %p)","Next Page (Page %p)","Last Page (Page %t)","Page %p of %t"];
41 var addClass = function(e,c) {
42 if(new RegExp("(^|\\s)" + c + "(\\s|$)").test(e.className)) return;
43 e.className += ( e.className ? " " : "" ) + c;
48 var removeClass = function(e,c) {
49 e.className = !c ? "" : e.className.replace(new RegExp("(^|\\s)" + c + "(\\s|$)"), " ").replace(/^\s*((?:[\S\s]*\S)?)\s*$/, '$1');
52 var removeClass = function(e,c) {
53 e.className = !c ? "" : (e.className || "").replace(new RegExp("(^|\\s)" + c + "(\\s|$)"), " ").replace(/^\s\s*/, '').replace(/\s\s*$/, '');
58 var addEvent = function(obj, type, fn) {
59 if( obj.attachEvent ) {
60 obj["e"+type+fn] = fn;
61 obj[type+fn] = function(){obj["e"+type+fn]( window.event );};
62 obj.attachEvent( "on"+type, obj[type+fn] );
64 obj.addEventListener( type, fn, true );
67 var removeEvent = function(obj, type, fn) {
69 if( obj.detachEvent ) {
70 obj.detachEvent( "on"+type, obj[type+fn] );
73 obj.removeEventListener( type, fn, true );
77 var stopEvent = function(e) {
78 e = e || window.event;
79 if(e.stopPropagation) {
86 e.cancelBubble = true;
87 e.returnValue = false;
92 var init = function(tableId) {
93 var tables = tableId && typeof(tableId) == "string" ? [document.getElementById(tableId)] : document.getElementsByTagName('table'),
94 hook, maxPages, visibleRows, numPages, cp, cb, rowList;
96 for(var t = 0, tbl; tbl = tables[t]; t++) {
97 if(tbl.className.search(/paginate-([0-9]+)/) == -1) { continue; };
99 if(!tbl.id) { tbl.id = "fdUniqueTableId_" + uniqueID++; };
101 maxPages = tbl.className.search(/max-pages-([0-9]+)/) == -1 ? null : Number(tbl.className.match(/max-pages-([0-9]+)/)[1]);
102 if(maxPages % 2 == 0 && maxPages > 1) { maxPages--; };
104 hook = tbl.getElementsByTagName('tbody');
105 hook = (hook.length) ? hook[0] : tbl;
107 visibleRows = calculateVisibleRows(hook);
109 if(maxPages > (visibleRows / Number(tbl.className.match(/paginate-([0-9]+)/)[1]))) {
113 numPages = Math.ceil(visibleRows / Number(tbl.className.match(/paginate-([0-9]+)/)[1]));
115 if(numPages < 2 && !(tbl.id in tableInfo)) {
119 cp = (tbl.id in tableInfo) ? Math.min(tableInfo[tbl.id].currentPage, numPages) : 1;
121 tableInfo[tbl.id] = {
122 rowsPerPage:Number(tbl.className.match(/paginate-([0-9]+)/)[1]),
124 totalRows:hook.getElementsByTagName('tr').length,
128 rowStyle:tbl.className.search(/rowstyle-([\S]+)/) != -1 ? tbl.className.match(/rowstyle-([\S]+)/)[1] : false,
129 callbacks:parseCallback(/^paginationcallback-/i, /paginationcallback-([\S-]+)/ig, tbl.className)
137 var parseCallback = function(head, regExp, cname) {
139 matchs = cname.match(regExp),
142 if(!matchs) { return []; };
144 for(var i = 0, mtch; mtch = matchs[i]; i++) {
145 mtch = mtch.replace(head, "").replace(/-/g, ".");
148 if(mtch.indexOf(".") != -1) {
149 parts = mtch.split('.');
151 for (var x = 0, part; part = obj[parts[x]]; x++) {
152 if(part instanceof Function) {
155 func = function (data) { method.apply(obj, [data]) };
165 if(!(func instanceof Function)) continue;
166 cbs[cbs.length] = func;
173 var callback = function(tblId, opts) {
174 if(!(tblId in tableInfo) || !(tableInfo[tblId]["callbacks"].length)) return;
175 for(var i = 0, func; func = tableInfo[tblId]["callbacks"][i]; i++) {
180 var calculateVisibleRows = function(hook) {
183 reg = /(^|\s)invisibleRow(\s|$)/;
185 for(var i = 0, tr; tr = trs[i]; i++) {
186 if(tr.parentNode != hook || tr.getElementsByTagName("th").length || (tr.parentNode && tr.parentNode.tagName.toLowerCase().search(/thead|tfoot/) != -1)) continue;
187 if(tr.className.search(reg) == -1) { cnt++; };
193 var createButton = function(details, ul, pseudo) {
194 var li = document.createElement("li"),
195 but = document.createElement(pseudo ? "div" : "a"),
196 span = document.createElement("span");
200 but.title = details.title;
203 but.className = details.className;
207 but.appendChild(span);
208 span.appendChild(document.createTextNode(details.text));
211 li.onclick = but.onclick = buttonClick;
212 if(details.id) { but.id = details.id; };
215 li = but = span = null;
217 var removePagination = function(tableId) {
218 var wrapT = document.getElementById(tableId + "-fdtablePaginaterWrapTop"),
219 wrapB = document.getElementById(tableId + "-fdtablePaginaterWrapBottom");
220 if(wrapT) { wrapT.parentNode.removeChild(wrapT); };
221 if(wrapB) { wrapB.parentNode.removeChild(wrapB); };
223 var buildPagination = function(tblId) {
224 if(!(tblId in tableInfo)) { return; };
226 removePagination(tblId);
228 var details = tableInfo[tblId];
230 if(details.numPages < 2) return;
232 function resolveText(txt, curr) {
233 curr = curr || details.currentPage;
234 return txt.replace("%p", curr).replace("%t", details.numPages);
237 if(details.maxPages) {
238 findex = Math.max(0, Math.floor(Number(details.currentPage - 1) - (Number(details.maxPages - 1) / 2)));
239 lindex = findex + Number(details.maxPages);
240 if(lindex > details.numPages) {
241 lindex = details.numPages;
242 findex = Math.max(0, details.numPages - Number(details.maxPages));
246 lindex = details.numPages;
250 var wrapT = document.createElement("div");
251 wrapT.className = "fdtablePaginaterWrap";
252 wrapT.id = tblId + "-fdtablePaginaterWrapTop";
254 var wrapB = document.createElement("div");
255 wrapB.className = "fdtablePaginaterWrap";
256 wrapB.id = tblId + "-fdtablePaginaterWrapBottom";
258 // Create list scaffold
259 var ulT = document.createElement("ul");
260 ulT.id = tblId + "-tablePaginater";
262 var ulB = document.createElement("ul");
263 ulB.id = tblId + "-tablePaginaterClone";
264 ulT.className = ulB.className = "fdtablePaginater";
266 // Add to the wrapper DIVs
267 wrapT.appendChild(ulT);
268 wrapB.appendChild(ulB);
270 // FIRST (only created if maxPages set)
271 if(details.maxPages) {
272 createButton({title:text[0], className:"first-page", text:"\u00ab"}, ulT, !findex);
273 createButton({title:text[0], className:"first-page", text:"\u00ab"}, ulB, !findex);
276 // PREVIOUS (only created if there are more than two pages)
277 if(details.numPages > 2) {
278 createButton({title:resolveText(text[1], details.currentPage-1), className:"previous-page", text:"\u2039", id:tblId+"-previousPage"}, ulT, details.currentPage == 1);
279 createButton({title:resolveText(text[1], details.currentPage-1), className:"previous-page", text:"\u2039", id:tblId+"-previousPageC"}, ulB, details.currentPage == 1);
283 for(var i = findex; i < lindex; i++) {
284 createButton({title:resolveText(text[4], i+1), className:i != (details.currentPage-1) ? "page-"+(i+1) : "currentPage page-"+(i+1), text:(i+1), id:i == (details.currentPage-1) ? tblId + "-currentPage" : ""}, ulT);
285 createButton({title:resolveText(text[4], i+1), className:i != (details.currentPage-1) ? "page-"+(i+1) : "currentPage page-"+(i+1), text:(i+1), id:i == (details.currentPage-1) ? tblId + "-currentPageC" : ""}, ulB);
288 // NEXT (only created if there are more than two pages)
289 if(details.numPages > 2) {
290 createButton({title:resolveText(text[2], details.currentPage + 1), className:"next-page", text:"\u203a", id:tblId+"-nextPage"}, ulT, details.currentPage == details.numPages);
291 createButton({title:resolveText(text[2], details.currentPage + 1), className:"next-page", text:"\u203a", id:tblId+"-nextPageC"}, ulB, details.currentPage == details.numPages);
294 // LAST (only created if maxPages set)
295 if(details.maxPages) {
296 createButton({title:resolveText(text[3], details.numPages), className:"last-page", text:"\u00bb"}, ulT, lindex == details.numPages);
297 createButton({title:resolveText(text[3], details.numPages), className:"last-page", text:"\u00bb"}, ulB, lindex == details.numPages);
300 // DOM inject wrapper DIVs (FireFox 2.x Bug: this has to be done here if you use display:table)
301 if(document.getElementById(tblId+"-paginationListWrapTop")) {
302 document.getElementById(tblId+"-paginationListWrapTop").appendChild(wrapT);
304 document.getElementById(tblId).parentNode.insertBefore(wrapT, document.getElementById(tblId));
307 if(document.getElementById(tblId+"-paginationListWrapBottom")) {
308 document.getElementById(tblId+"-paginationListWrapBottom").appendChild(wrapB);
310 document.getElementById(tblId).parentNode.insertBefore(wrapB, document.getElementById(tblId).nextSibling);
314 // The tableSort script uses this function to redraw.
315 var tableSortRedraw = function(tableid, identical) {
316 if(!tableid || !(tableid in fdTableSort.tableCache) || !(tableid in tableInfo)) { return; };
318 var dataObj = fdTableSort.tableCache[tableid],
321 len2 = len1 ? data[0].length - 1 : 0,
323 colStyle = dataObj.colStyle,
324 rowStyle = dataObj.rowStyle,
325 colOrder = dataObj.colOrder,
326 page = tableInfo[tableid].currentPage - 1,
327 d1 = tableInfo[tableid].rowsPerPage * page,
328 d2 = Math.min(tableInfo[tableid].totalRows, d1 + tableInfo[tableid].rowsPerPage),
331 reg = /(^|\s)invisibleRow(\s|$)/,
334 for(var i = 0; i < len1; i++) {
339 for(thPos in colOrder) {
340 if(!colOrder[thPos]) removeClass(tds[thPos], colStyle);
341 else addClass(tds[thPos], colStyle);
345 if(tr.className.search(reg) != -1) { continue; };
350 if(cnt > d1 && cnt <= d2) {
352 if(rs++ & 1) addClass(tr, rowStyle);
353 else removeClass(tr, rowStyle);
355 tr.style.display = "";
357 tr.style.display = "none";
360 // Netscape 8.1.2 requires the removeChild call or it freaks out, so add the line if you want to support this browser
361 // hook.removeChild(tr);
362 hook.appendChild(tr);
366 tr = tds = hook = null;
369 var showPage = function(tblId, pageNum) {
370 if(!(tblId in tableInfo)) { return; };
372 var page = Math.max(0, !pageNum ? tableInfo[tblId].currentPage - 1 : pageNum - 1),
373 d1 = tableInfo[tblId].rowsPerPage * page,
374 d2 = Math.min(tableInfo[tblId].totalRows, d1 + tableInfo[tblId].rowsPerPage),
375 trs = tableInfo[tblId].hook.rows,
379 rs = tableInfo[tblId].rowStyle,
380 reg = /(^|\s)invisibleRow(\s|$)/,
383 for(var i = 0; i < len; i++) {
384 if(trs[i].className.search(reg) != -1 || trs[i].getElementsByTagName("th").length || (trs[i].parentNode && trs[i].parentNode.tagName.toLowerCase().search(/thead|tfoot/) != -1)) { continue; };
388 if(cnt > d1 && cnt <= d2) {
391 addClass(trs[i], rs);
393 removeClass(trs[i], rs);
396 trs[i].style.display = "";
397 row[row.length] = trs[i];
399 trs[i].style.display = "none";
403 buildPagination(tblId);
404 callback(tblId, {"totalRows":len, "currentPage":(page+1), "rowsPerPage":tableInfo[tblId].rowsPerPage, "visibleRows":row});
407 var buttonClick = function(e) {
408 e = e || window.event;
410 var a = this.tagName.toLowerCase() == "a" ? this : this.getElementsByTagName("a")[0];
412 if(a.className.search("currentPage") != -1) return false;
415 while(ul.tagName.toLowerCase() != "ul") ul = ul.parentNode;
417 var tblId = ul.id.replace("-tablePaginaterClone","").replace("-tablePaginater", "");
419 tableInfo[tblId].lastPage = tableInfo[tblId].currentPage;
421 var showPrevNext = 0;
423 if(a.className.search("previous-page") != -1) {
424 tableInfo[tblId].currentPage = tableInfo[tblId].currentPage > 1 ? tableInfo[tblId].currentPage - 1 : tableInfo[tblId].numPages;
426 } else if(a.className.search("next-page") != -1) {
427 tableInfo[tblId].currentPage = tableInfo[tblId].currentPage < tableInfo[tblId].numPages ? tableInfo[tblId].currentPage + 1 : 1;
429 } else if(a.className.search("first-page") != -1) {
430 tableInfo[tblId].currentPage = 1;
431 } else if(a.className.search("last-page") != -1) {
432 tableInfo[tblId].currentPage = tableInfo[tblId].numPages;
434 tableInfo[tblId].currentPage = parseInt(a.className.match(/page-([0-9]+)/)[1]) || 1;
439 // Focus on the appropriate button (previous, next or the current page)
440 // I'm hoping screen readers are savvy enough to indicate the focus event to the user
441 if(showPrevNext == 1) {
442 var elem = document.getElementById(ul.id.search("-tablePaginaterClone") != -1 ? tblId + "-previousPageC" : tblId + "-previousPage");
443 } else if(showPrevNext == 2) {
444 var elem = document.getElementById(ul.id.search("-tablePaginaterClone") != -1 ? tblId + "-nextPageC" : tblId + "-nextPage");
446 var elem = document.getElementById(ul.id.search("-tablePaginaterClone") != -1 ? tblId + "-currentPageC" : tblId + "-currentPage");
449 if(elem && elem.tagName.toLowerCase() == "a") { elem.focus(); };
454 var onUnLoad = function(e) {
455 var tbl, lis, pagination, uls;
456 for(tblId in tableInfo) {
457 uls = [tblId + "-tablePaginater", tblId + "-tablePaginaterClone"];
458 for(var z = 0; z < 2; z++) {
459 pagination = document.getElementById(uls[z]);
460 if(!pagination) { continue; };
461 lis = pagination.getElementsByTagName("li");
462 for(var i = 0, li; li = lis[i]; i++) {
464 if(li.getElementsByTagName("a").length) { li.getElementsByTagName("a")[0].onclick = null; };
470 addEvent(window, "load", init);
471 addEvent(window, "unload", onUnLoad);
474 init: function(tableId) { init(tableId); },
475 redraw: function(tableid, identical) { tableSortRedraw(tableid, identical); },
476 tableIsPaginated: function(tableId) { return (tableId in tableInfo); },
477 changeTranslations: function(translations) { text = translations; }