Setting tag plewww-5.2-4
[plewww.git] / plekit / tablesort / paginate.js
1 /*
2         paginate table object v2.0 by frequency-decoder.com
3
4         Released under a creative commons Attribution-ShareAlike 2.5 license (http://creativecommons.org/licenses/by-sa/2.5/)
5
6         Please credit frequency decoder in any derivative work - thanks
7
8         You are free:
9
10         * to copy, distribute, display, and perform the work
11         * to make derivative works
12         * to make commercial use of the work
13
14         Under the following conditions:
15
16                 by Attribution.
17                 --------------
18                 You must attribute the work in the manner specified by the author or licensor.
19
20                 sa
21                 --
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.
23
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.
26 */
27
28 var tablePaginater = (function() {
29         /*
30
31         Localise the button titles here...
32
33         %p is replaced with the appropriate page number
34         %t is replaced with the total number of pages
35         
36         */
37         var tableInfo = {},
38             uniqueID  = 0,
39             text      = ["First Page","Previous Page (Page %p)","Next Page (Page %p)","Last Page (Page %t)","Page %p of %t"];
40             
41         var addClass = function(e,c) {
42                 if(new RegExp("(^|\\s)" + c + "(\\s|$)").test(e.className)) return;
43                 e.className += ( e.className ? " " : "" ) + c;
44         }; 
45         
46         /*@cc_on
47         /*@if (@_win32)
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');
50         };
51         @else @*/
52         var removeClass = function(e,c) {                 
53                 e.className = !c ? "" : (e.className || "").replace(new RegExp("(^|\\s)" + c + "(\\s|$)"), " ").replace(/^\s\s*/, '').replace(/\s\s*$/, '');
54         };
55         /*@end
56         @*/  
57         
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] );
63                 } else {
64                         obj.addEventListener( type, fn, true );
65                 };
66         };
67         var removeEvent = function(obj, type, fn) {
68                 try {
69                         if( obj.detachEvent ) {
70                                 obj.detachEvent( "on"+type, obj[type+fn] );
71                                 obj[type+fn] = null;
72                         } else {
73                                 obj.removeEventListener( type, fn, true );
74                         };
75                 } catch(err) {};
76         };
77         var stopEvent = function(e) {
78                 e = e || window.event;
79                 if(e.stopPropagation) {
80                         e.stopPropagation();
81                         e.preventDefault();
82                 };
83                 
84                 /*@cc_on@*/
85                 /*@if(@_win32)
86                 e.cancelBubble = true;
87                 e.returnValue  = false;
88                 /*@end@*/
89                 return false;
90         }; 
91         
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;
95                 
96                 for(var t = 0, tbl; tbl = tables[t]; t++) {
97                         if(tbl.className.search(/paginate-([0-9]+)/) == -1) { continue; };
98
99                         if(!tbl.id) { tbl.id = "fdUniqueTableId_" + uniqueID++; };
100
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--; };
103                         
104                         hook = tbl.getElementsByTagName('tbody');
105                         hook = (hook.length) ? hook[0] : tbl;
106
107                         visibleRows = calculateVisibleRows(hook);
108                         
109                         if(maxPages > (visibleRows / Number(tbl.className.match(/paginate-([0-9]+)/)[1]))) {
110                                 maxPages = null;
111                         };
112                         
113                         numPages = Math.ceil(visibleRows / Number(tbl.className.match(/paginate-([0-9]+)/)[1]));
114                         
115                         if(numPages < 2 && !(tbl.id in tableInfo)) {
116                                 continue;
117                         };
118                         
119                         cp = (tbl.id in tableInfo) ? Math.min(tableInfo[tbl.id].currentPage, numPages) : 1;
120                         
121                         tableInfo[tbl.id] = {
122                                 rowsPerPage:Number(tbl.className.match(/paginate-([0-9]+)/)[1]),
123                                 currentPage:cp,
124                                 totalRows:hook.getElementsByTagName('tr').length,
125                                 hook:hook,
126                                 maxPages:maxPages,
127                                 numPages:numPages,
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)
130                         };
131                         
132                         showPage(tbl.id);
133                         hook = null;
134                 };
135         };
136         
137         var parseCallback = function(head, regExp, cname) {
138                 var cbs    = [],
139                     matchs = cname.match(regExp),
140                     parts, obj, func;
141                 
142                 if(!matchs) { return []; };
143                   
144                 for(var i = 0, mtch; mtch = matchs[i]; i++) {                         
145                         mtch = mtch.replace(head, "").replace(/-/g, ".");
146                          
147                         try {
148                                 if(mtch.indexOf(".") != -1) {
149                                         parts = mtch.split('.');
150                                         obj   = window;
151                                         for (var x = 0, part; part = obj[parts[x]]; x++) {
152                                                 if(part instanceof Function) {
153                                                         (function() {
154                                                                 var method = part;
155                                                                 func = function (data) { method.apply(obj, [data]) };
156                                                         })();
157                                                 } else {
158                                                         obj = part;
159                                                 };
160                                         };
161                                 } else {
162                                         func = window[mtch];
163                                 };
164                             
165                                 if(!(func instanceof Function)) continue;
166                                 cbs[cbs.length] = func;                              
167                         } catch(err) {};
168                 };
169                 
170                 return cbs;                      
171         };
172                 
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++) {
176                         func(opts || {});
177                 };
178         };
179         
180         var calculateVisibleRows = function(hook) {
181                 var trs = hook.rows,
182                     cnt = 0,
183                     reg = /(^|\s)invisibleRow(\s|$)/;
184                 
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++; };
188                 };
189                 
190                 return cnt;
191         };
192         
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");
197
198                 if(!pseudo) { 
199                         but.href = "#"; 
200                         but.title = details.title; 
201                 };
202                 
203                 but.className = details.className;
204
205                 ul.appendChild(li);
206                 li.appendChild(but);
207                 but.appendChild(span);
208                 span.appendChild(document.createTextNode(details.text));
209
210                 if(!pseudo) { 
211                         li.onclick = but.onclick = buttonClick; 
212                         if(details.id) { but.id = details.id; };
213                 };                 
214                 
215                 li = but = span = null;
216         };
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); };
222         };
223         var buildPagination = function(tblId) {
224                 if(!(tblId in tableInfo)) { return; };
225
226                 removePagination(tblId);
227
228                 var details = tableInfo[tblId];
229                 
230                 if(details.numPages < 2) return;
231                 
232                 function resolveText(txt, curr) {
233                         curr = curr || details.currentPage;
234                         return txt.replace("%p", curr).replace("%t", details.numPages);
235                 };
236
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));
243                         };
244                 } else {
245                         findex = 0;
246                         lindex = details.numPages;
247                 };
248                 
249
250                 var wrapT = document.createElement("div");
251                 wrapT.className = "fdtablePaginaterWrap";
252                 wrapT.id = tblId + "-fdtablePaginaterWrapTop";
253
254                 var wrapB = document.createElement("div");
255                 wrapB.className = "fdtablePaginaterWrap";
256                 wrapB.id = tblId + "-fdtablePaginaterWrapBottom";
257
258                 // Create list scaffold
259                 var ulT = document.createElement("ul");
260                 ulT.id  = tblId + "-tablePaginater";
261
262                 var ulB = document.createElement("ul");
263                 ulB.id  = tblId + "-tablePaginaterClone";
264                 ulT.className = ulB.className = "fdtablePaginater";
265
266                 // Add to the wrapper DIVs
267                 wrapT.appendChild(ulT);
268                 wrapB.appendChild(ulB);
269
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);
274                 };
275                 
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);
280                 };
281                 
282                 // NUMBERED
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);
286                 };
287
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);
292                 };
293                 
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);
298                 };
299                 
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);
303                 } else {
304                         document.getElementById(tblId).parentNode.insertBefore(wrapT, document.getElementById(tblId));
305                 };
306
307                 if(document.getElementById(tblId+"-paginationListWrapBottom")) {
308                         document.getElementById(tblId+"-paginationListWrapBottom").appendChild(wrapB);
309                 } else {
310                         document.getElementById(tblId).parentNode.insertBefore(wrapB, document.getElementById(tblId).nextSibling);
311                 };
312         };
313         
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; };
317                 
318                 var dataObj     = fdTableSort.tableCache[tableid],
319                     data        = dataObj.data,
320                     len1        = data.length,
321                     len2        = len1 ? data[0].length - 1 : 0,
322                     hook        = dataObj.hook,
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), 
329                     cnt         = 0,
330                     rs          = 0,
331                     reg         = /(^|\s)invisibleRow(\s|$)/,                
332                     tr, tds, cell, pos;
333                 
334                 for(var i = 0; i < len1; i++) {
335                         tr = data[i][len2];
336                         
337                         if(colStyle) {
338                                 tds = tr.cells;
339                                 for(thPos in colOrder) {
340                                         if(!colOrder[thPos]) removeClass(tds[thPos], colStyle);
341                                         else addClass(tds[thPos], colStyle);
342                                 };
343                         };
344                         
345                         if(tr.className.search(reg) != -1) { continue; };
346                         
347                         if(!identical) {
348                                 cnt++;
349
350                                 if(cnt > d1 && cnt <= d2) {
351                                         if(rowStyle) {
352                                                 if(rs++ & 1) addClass(tr, rowStyle);
353                                                 else removeClass(tr, rowStyle);
354                                         };
355                                         tr.style.display = "";
356                                 } else {
357                                         tr.style.display = "none";
358                                 };
359
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);
363                         };
364                 };
365
366                 tr = tds = hook = null;
367         };
368         
369         var showPage = function(tblId, pageNum) {
370                 if(!(tblId in tableInfo)) { return; };
371
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,
376                     cnt = 0,
377                     rc  = 0,
378                     len = trs.length,
379                     rs  = tableInfo[tblId].rowStyle,
380                     reg = /(^|\s)invisibleRow(\s|$)/,
381                     row = [];
382                 
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; };
385                         
386                         cnt++;
387                         
388                         if(cnt > d1 && cnt <= d2) {
389                                 if(rs) {
390                                         if(rc++ & 1) {
391                                                 addClass(trs[i], rs);
392                                         } else {
393                                                 removeClass(trs[i], rs);
394                                         }
395                                 };
396                                 trs[i].style.display = "";
397                                 row[row.length] = trs[i];
398                         } else {
399                                 trs[i].style.display = "none";
400                         };
401                 };
402
403                 buildPagination(tblId);
404                 callback(tblId, {"totalRows":len, "currentPage":(page+1), "rowsPerPage":tableInfo[tblId].rowsPerPage, "visibleRows":row});
405         };
406         
407         var buttonClick = function(e) {
408                 e = e || window.event;
409
410                 var a = this.tagName.toLowerCase() == "a" ? this : this.getElementsByTagName("a")[0];
411
412                 if(a.className.search("currentPage") != -1) return false;
413
414                 var ul = this;
415                 while(ul.tagName.toLowerCase() != "ul") ul = ul.parentNode;
416
417                 var tblId = ul.id.replace("-tablePaginaterClone","").replace("-tablePaginater", "");
418
419                 tableInfo[tblId].lastPage = tableInfo[tblId].currentPage;
420                 
421                 var showPrevNext = 0;
422                 
423                 if(a.className.search("previous-page") != -1) {
424                         tableInfo[tblId].currentPage = tableInfo[tblId].currentPage > 1 ? tableInfo[tblId].currentPage - 1 : tableInfo[tblId].numPages;
425                         showPrevNext = 1;
426                 } else if(a.className.search("next-page") != -1) {
427                         tableInfo[tblId].currentPage = tableInfo[tblId].currentPage < tableInfo[tblId].numPages ? tableInfo[tblId].currentPage + 1 : 1;
428                         showPrevNext = 2;
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;
433                 } else {
434                         tableInfo[tblId].currentPage = parseInt(a.className.match(/page-([0-9]+)/)[1]) || 1;
435                 };
436
437                 showPage(tblId);
438
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");
445                 } else {
446                         var elem = document.getElementById(ul.id.search("-tablePaginaterClone") != -1 ? tblId + "-currentPageC" : tblId + "-currentPage");
447                 };
448                 
449                 if(elem && elem.tagName.toLowerCase() == "a") { elem.focus(); };   
450                 
451                 return stopEvent(e);
452         };
453         
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++) {
463                                         li.onclick = null;
464                                         if(li.getElementsByTagName("a").length) { li.getElementsByTagName("a")[0].onclick = null; };
465                                 };
466                         };
467                 };
468         };
469         
470         addEvent(window, "load",   init);
471         addEvent(window, "unload", onUnLoad); 
472         
473         return {                  
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; }
478         };        
479 })();