togglable sections everywhere
[plewww.git] / planetlab / tablesort / paginate.js
1 /*\r
2         paginate table object v2.0 by frequency-decoder.com\r
3 \r
4         Released under a creative commons Attribution-ShareAlike 2.5 license (http://creativecommons.org/licenses/by-sa/2.5/)\r
5 \r
6         Please credit frequency decoder in any derivative work - thanks\r
7 \r
8         You are free:\r
9 \r
10         * to copy, distribute, display, and perform the work\r
11         * to make derivative works\r
12         * to make commercial use of the work\r
13 \r
14         Under the following conditions:\r
15 \r
16                 by Attribution.\r
17                 --------------\r
18                 You must attribute the work in the manner specified by the author or licensor.\r
19 \r
20                 sa\r
21                 --\r
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.\r
23 \r
24         * For any reuse or distribution, you must make clear to others the license terms of this work.\r
25         * Any of these conditions can be waived if you get permission from the copyright holder.\r
26 */\r
27 \r
28 var tablePaginater = (function() {\r
29         /*\r
30 \r
31         Localise the button titles here...\r
32 \r
33         %p is replaced with the appropriate page number\r
34         %t is replaced with the total number of pages\r
35         \r
36         */\r
37         var tableInfo = {},\r
38             uniqueID  = 0,\r
39             text      = ["First Page","Previous Page (Page %p)","Next Page (Page %p)","Last Page (Page %t)","Page %p of %t"];\r
40             \r
41         var addClass = function(e,c) {\r
42                 if(new RegExp("(^|\\s)" + c + "(\\s|$)").test(e.className)) return;\r
43                 e.className += ( e.className ? " " : "" ) + c;\r
44         }; \r
45         \r
46         /*@cc_on\r
47         /*@if (@_win32)\r
48         var removeClass = function(e,c) {\r
49                 e.className = !c ? "" : e.className.replace(new RegExp("(^|\\s)" + c + "(\\s|$)"), " ").replace(/^\s*((?:[\S\s]*\S)?)\s*$/, '$1');\r
50         };\r
51         @else @*/\r
52         var removeClass = function(e,c) {                 \r
53                 e.className = !c ? "" : (e.className || "").replace(new RegExp("(^|\\s)" + c + "(\\s|$)"), " ").replace(/^\s\s*/, '').replace(/\s\s*$/, '');\r
54         };\r
55         /*@end\r
56         @*/  \r
57         \r
58         var addEvent = function(obj, type, fn) {\r
59                 if( obj.attachEvent ) {\r
60                         obj["e"+type+fn] = fn;\r
61                         obj[type+fn] = function(){obj["e"+type+fn]( window.event );};\r
62                         obj.attachEvent( "on"+type, obj[type+fn] );\r
63                 } else {\r
64                         obj.addEventListener( type, fn, true );\r
65                 };\r
66         };\r
67         var removeEvent = function(obj, type, fn) {\r
68                 try {\r
69                         if( obj.detachEvent ) {\r
70                                 obj.detachEvent( "on"+type, obj[type+fn] );\r
71                                 obj[type+fn] = null;\r
72                         } else {\r
73                                 obj.removeEventListener( type, fn, true );\r
74                         };\r
75                 } catch(err) {};\r
76         };\r
77         var stopEvent = function(e) {\r
78                 e = e || window.event;\r
79                 if(e.stopPropagation) {\r
80                         e.stopPropagation();\r
81                         e.preventDefault();\r
82                 };\r
83                 \r
84                 /*@cc_on@*/\r
85                 /*@if(@_win32)\r
86                 e.cancelBubble = true;\r
87                 e.returnValue  = false;\r
88                 /*@end@*/\r
89                 return false;\r
90         }; \r
91         \r
92         var init = function(tableId) {\r
93                 var tables = tableId && typeof(tableId) == "string" ? [document.getElementById(tableId)] : document.getElementsByTagName('table'),\r
94                     hook, maxPages, visibleRows, numPages, cp, cb, rowList;\r
95                 \r
96                 for(var t = 0, tbl; tbl = tables[t]; t++) {\r
97                         if(tbl.className.search(/paginate-([0-9]+)/) == -1) { continue; };\r
98 \r
99                         if(!tbl.id) { tbl.id = "fdUniqueTableId_" + uniqueID++; };\r
100 \r
101                         maxPages = tbl.className.search(/max-pages-([0-9]+)/) == -1 ? null : Number(tbl.className.match(/max-pages-([0-9]+)/)[1]);\r
102                         if(maxPages % 2 == 0 && maxPages > 1) { maxPages--; };\r
103                         \r
104                         hook = tbl.getElementsByTagName('tbody');\r
105                         hook = (hook.length) ? hook[0] : tbl;\r
106 \r
107                         visibleRows = calculateVisibleRows(hook);\r
108                         \r
109                         if(maxPages > (visibleRows / Number(tbl.className.match(/paginate-([0-9]+)/)[1]))) {\r
110                                 maxPages = null;\r
111                         };\r
112                         \r
113                         numPages = Math.ceil(visibleRows / Number(tbl.className.match(/paginate-([0-9]+)/)[1]));\r
114                         \r
115                         if(numPages < 2 && !(tbl.id in tableInfo)) {\r
116                                 continue;\r
117                         };\r
118                         \r
119                         cp = (tbl.id in tableInfo) ? Math.min(tableInfo[tbl.id].currentPage, numPages) : 1;\r
120                         \r
121                         tableInfo[tbl.id] = {\r
122                                 rowsPerPage:Number(tbl.className.match(/paginate-([0-9]+)/)[1]),\r
123                                 currentPage:cp,\r
124                                 totalRows:hook.getElementsByTagName('tr').length,\r
125                                 hook:hook,\r
126                                 maxPages:maxPages,\r
127                                 numPages:numPages,\r
128                                 rowStyle:tbl.className.search(/rowstyle-([\S]+)/) != -1 ? tbl.className.match(/rowstyle-([\S]+)/)[1] : false,\r
129                                 callbacks:parseCallback(/^paginationcallback-/i, /paginationcallback-([\S-]+)/ig, tbl.className)\r
130                         };\r
131                         \r
132                         showPage(tbl.id);\r
133                         hook = null;\r
134                 };\r
135         };\r
136         \r
137         var parseCallback = function(head, regExp, cname) {\r
138                 var cbs    = [],\r
139                     matchs = cname.match(regExp),\r
140                     parts, obj, func;\r
141                 \r
142                 if(!matchs) { return []; };\r
143                   \r
144                 for(var i = 0, mtch; mtch = matchs[i]; i++) {                         \r
145                         mtch = mtch.replace(head, "").replace(/-/g, ".");\r
146                          \r
147                         try {\r
148                                 if(mtch.indexOf(".") != -1) {\r
149                                         parts = mtch.split('.');\r
150                                         obj   = window;\r
151                                         for (var x = 0, part; part = obj[parts[x]]; x++) {\r
152                                                 if(part instanceof Function) {\r
153                                                         (function() {\r
154                                                                 var method = part;\r
155                                                                 func = function (data) { method.apply(obj, [data]) };\r
156                                                         })();\r
157                                                 } else {\r
158                                                         obj = part;\r
159                                                 };\r
160                                         };\r
161                                 } else {\r
162                                         func = window[mtch];\r
163                                 };\r
164                             \r
165                                 if(!(func instanceof Function)) continue;\r
166                                 cbs[cbs.length] = func;                              \r
167                         } catch(err) {};\r
168                 };\r
169                 \r
170                 return cbs;                      \r
171         };\r
172                 \r
173         var callback = function(tblId, opts) {                \r
174                 if(!(tblId in tableInfo) || !(tableInfo[tblId]["callbacks"].length)) return;                \r
175                 for(var i = 0, func; func = tableInfo[tblId]["callbacks"][i]; i++) {\r
176                         func(opts || {});\r
177                 };\r
178         };\r
179         \r
180         var calculateVisibleRows = function(hook) {\r
181                 var trs = hook.rows,\r
182                     cnt = 0,\r
183                     reg = /(^|\s)invisibleRow(\s|$)/;\r
184                 \r
185                 for(var i = 0, tr; tr = trs[i]; i++) {\r
186                         if(tr.parentNode != hook || tr.getElementsByTagName("th").length || (tr.parentNode && tr.parentNode.tagName.toLowerCase().search(/thead|tfoot/) != -1)) continue;\r
187                         if(tr.className.search(reg) == -1) { cnt++; };\r
188                 };\r
189                 \r
190                 return cnt;\r
191         };\r
192         \r
193         var createButton = function(details, ul, pseudo) {\r
194                 var li   = document.createElement("li"),\r
195                     but  = document.createElement(pseudo ? "div" : "a"),\r
196                     span = document.createElement("span");\r
197 \r
198                 if(!pseudo) { \r
199                         but.href = "#"; \r
200                         but.title = details.title; \r
201                 };\r
202                 \r
203                 but.className = details.className;\r
204 \r
205                 ul.appendChild(li);\r
206                 li.appendChild(but);\r
207                 but.appendChild(span);\r
208                 span.appendChild(document.createTextNode(details.text));\r
209 \r
210                 if(!pseudo) { \r
211                         li.onclick = but.onclick = buttonClick; \r
212                         if(details.id) { but.id = details.id; };\r
213                 };                 \r
214                 \r
215                 li = but = span = null;\r
216         };\r
217         var removePagination = function(tableId) {\r
218                 var wrapT = document.getElementById(tableId + "-fdtablePaginaterWrapTop"),\r
219                     wrapB = document.getElementById(tableId + "-fdtablePaginaterWrapBottom");\r
220                 if(wrapT) { wrapT.parentNode.removeChild(wrapT); };\r
221                 if(wrapB) { wrapB.parentNode.removeChild(wrapB); };\r
222         };\r
223         var buildPagination = function(tblId) {\r
224                 if(!(tblId in tableInfo)) { return; };\r
225 \r
226                 removePagination(tblId);\r
227 \r
228                 var details = tableInfo[tblId];\r
229                 \r
230                 if(details.numPages < 2) return;\r
231                 \r
232                 function resolveText(txt, curr) {\r
233                         curr = curr || details.currentPage;\r
234                         return txt.replace("%p", curr).replace("%t", details.numPages);\r
235                 };\r
236 \r
237                 if(details.maxPages) {\r
238                         findex = Math.max(0, Math.floor(Number(details.currentPage - 1) - (Number(details.maxPages - 1) / 2)));\r
239                         lindex = findex + Number(details.maxPages);\r
240                         if(lindex > details.numPages) {\r
241                                 lindex = details.numPages;\r
242                                 findex = Math.max(0, details.numPages - Number(details.maxPages));\r
243                         };\r
244                 } else {\r
245                         findex = 0;\r
246                         lindex = details.numPages;\r
247                 };\r
248                 \r
249 \r
250                 var wrapT = document.createElement("div");\r
251                 wrapT.className = "fdtablePaginaterWrap";\r
252                 wrapT.id = tblId + "-fdtablePaginaterWrapTop";\r
253 \r
254                 var wrapB = document.createElement("div");\r
255                 wrapB.className = "fdtablePaginaterWrap";\r
256                 wrapB.id = tblId + "-fdtablePaginaterWrapBottom";\r
257 \r
258                 // Create list scaffold\r
259                 var ulT = document.createElement("ul");\r
260                 ulT.id  = tblId + "-tablePaginater";\r
261 \r
262                 var ulB = document.createElement("ul");\r
263                 ulB.id  = tblId + "-tablePaginaterClone";\r
264                 ulT.className = ulB.className = "fdtablePaginater";\r
265 \r
266                 // Add to the wrapper DIVs\r
267                 wrapT.appendChild(ulT);\r
268                 wrapB.appendChild(ulB);\r
269 \r
270                 // FIRST (only created if maxPages set)\r
271                 if(details.maxPages) {\r
272                         createButton({title:text[0], className:"first-page", text:"\u00ab"}, ulT, !findex);\r
273                         createButton({title:text[0], className:"first-page", text:"\u00ab"}, ulB, !findex);\r
274                 };\r
275                 \r
276                 // PREVIOUS (only created if there are more than two pages)\r
277                 if(details.numPages > 2) {\r
278                         createButton({title:resolveText(text[1], details.currentPage-1), className:"previous-page", text:"\u2039", id:tblId+"-previousPage"}, ulT, details.currentPage == 1);\r
279                         createButton({title:resolveText(text[1], details.currentPage-1), className:"previous-page", text:"\u2039", id:tblId+"-previousPageC"}, ulB, details.currentPage == 1);\r
280                 };\r
281                 \r
282                 // NUMBERED\r
283                 for(var i = findex; i < lindex; i++) {\r
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);\r
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);\r
286                 };\r
287 \r
288                 // NEXT (only created if there are more than two pages)\r
289                 if(details.numPages > 2) {\r
290                         createButton({title:resolveText(text[2], details.currentPage + 1), className:"next-page", text:"\u203a", id:tblId+"-nextPage"}, ulT, details.currentPage == details.numPages);\r
291                         createButton({title:resolveText(text[2], details.currentPage + 1), className:"next-page", text:"\u203a", id:tblId+"-nextPageC"}, ulB, details.currentPage == details.numPages);\r
292                 };\r
293                 \r
294                 // LAST (only created if maxPages set)\r
295                 if(details.maxPages) {\r
296                         createButton({title:resolveText(text[3], details.numPages), className:"last-page", text:"\u00bb"}, ulT, lindex == details.numPages);\r
297                         createButton({title:resolveText(text[3], details.numPages), className:"last-page", text:"\u00bb"}, ulB, lindex == details.numPages);\r
298                 };\r
299                 \r
300                 // DOM inject wrapper DIVs (FireFox 2.x Bug: this has to be done here if you use display:table)\r
301                 if(document.getElementById(tblId+"-paginationListWrapTop")) {\r
302                         document.getElementById(tblId+"-paginationListWrapTop").appendChild(wrapT);\r
303                 } else {\r
304                         document.getElementById(tblId).parentNode.insertBefore(wrapT, document.getElementById(tblId));\r
305                 };\r
306 \r
307                 if(document.getElementById(tblId+"-paginationListWrapBottom")) {\r
308                         document.getElementById(tblId+"-paginationListWrapBottom").appendChild(wrapB);\r
309                 } else {\r
310                         document.getElementById(tblId).parentNode.insertBefore(wrapB, document.getElementById(tblId).nextSibling);\r
311                 };\r
312         };\r
313         \r
314         // The tableSort script uses this function to redraw.\r
315         var tableSortRedraw = function(tableid, identical) {\r
316                 if(!tableid || !(tableid in fdTableSort.tableCache) || !(tableid in tableInfo)) { return; };\r
317                 \r
318                 var dataObj     = fdTableSort.tableCache[tableid],\r
319                     data        = dataObj.data,\r
320                     len1        = data.length,\r
321                     len2        = len1 ? data[0].length - 1 : 0,\r
322                     hook        = dataObj.hook,\r
323                     colStyle    = dataObj.colStyle,\r
324                     rowStyle    = dataObj.rowStyle,\r
325                     colOrder    = dataObj.colOrder,                 \r
326                     page        = tableInfo[tableid].currentPage - 1,\r
327                     d1          = tableInfo[tableid].rowsPerPage * page,\r
328                     d2          = Math.min(tableInfo[tableid].totalRows, d1 + tableInfo[tableid].rowsPerPage), \r
329                     cnt         = 0,\r
330                     rs          = 0,\r
331                     reg         = /(^|\s)invisibleRow(\s|$)/,                \r
332                     tr, tds, cell, pos;\r
333                 \r
334                 for(var i = 0; i < len1; i++) {\r
335                         tr = data[i][len2];\r
336                         \r
337                         if(colStyle) {\r
338                                 tds = tr.cells;\r
339                                 for(thPos in colOrder) {\r
340                                         if(!colOrder[thPos]) removeClass(tds[thPos], colStyle);\r
341                                         else addClass(tds[thPos], colStyle);\r
342                                 };\r
343                         };\r
344                         \r
345                         if(tr.className.search(reg) != -1) { continue; };\r
346                         \r
347                         if(!identical) {\r
348                                 cnt++;\r
349 \r
350                                 if(cnt > d1 && cnt <= d2) {\r
351                                         if(rowStyle) {\r
352                                                 if(rs++ & 1) addClass(tr, rowStyle);\r
353                                                 else removeClass(tr, rowStyle);\r
354                                         };\r
355                                         tr.style.display = "";\r
356                                 } else {\r
357                                         tr.style.display = "none";\r
358                                 };\r
359 \r
360                                 // Netscape 8.1.2 requires the removeChild call or it freaks out, so add the line if you want to support this browser\r
361                                 // hook.removeChild(tr);\r
362                                 hook.appendChild(tr);\r
363                         };\r
364                 };\r
365 \r
366                 tr = tds = hook = null;\r
367         };\r
368         \r
369         var showPage = function(tblId, pageNum) {\r
370                 if(!(tblId in tableInfo)) { return; };\r
371 \r
372                 var page = Math.max(0, !pageNum ? tableInfo[tblId].currentPage - 1 : pageNum - 1),\r
373                     d1  = tableInfo[tblId].rowsPerPage * page,\r
374                     d2  = Math.min(tableInfo[tblId].totalRows, d1 + tableInfo[tblId].rowsPerPage),\r
375                     trs = tableInfo[tblId].hook.rows,\r
376                     cnt = 0,\r
377                     rc  = 0,\r
378                     len = trs.length,\r
379                     rs  = tableInfo[tblId].rowStyle,\r
380                     reg = /(^|\s)invisibleRow(\s|$)/,\r
381                     row = [];\r
382                 \r
383                 for(var i = 0; i < len; i++) {\r
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; };\r
385                         \r
386                         cnt++;\r
387                         \r
388                         if(cnt > d1 && cnt <= d2) {\r
389                                 if(rs) {\r
390                                         if(rc++ & 1) {\r
391                                                 addClass(trs[i], rs);\r
392                                         } else {\r
393                                                 removeClass(trs[i], rs);\r
394                                         }\r
395                                 };\r
396                                 trs[i].style.display = "";\r
397                                 row[row.length] = trs[i];\r
398                         } else {\r
399                                 trs[i].style.display = "none";\r
400                         };\r
401                 };\r
402 \r
403                 buildPagination(tblId);\r
404                 callback(tblId, {"totalRows":len, "currentPage":(page+1), "rowsPerPage":tableInfo[tblId].rowsPerPage, "visibleRows":row});\r
405         };\r
406         \r
407         var buttonClick = function(e) {\r
408                 e = e || window.event;\r
409 \r
410                 var a = this.tagName.toLowerCase() == "a" ? this : this.getElementsByTagName("a")[0];\r
411 \r
412                 if(a.className.search("currentPage") != -1) return false;\r
413 \r
414                 var ul = this;\r
415                 while(ul.tagName.toLowerCase() != "ul") ul = ul.parentNode;\r
416 \r
417                 var tblId = ul.id.replace("-tablePaginaterClone","").replace("-tablePaginater", "");\r
418 \r
419                 tableInfo[tblId].lastPage = tableInfo[tblId].currentPage;\r
420                 \r
421                 var showPrevNext = 0;\r
422                 \r
423                 if(a.className.search("previous-page") != -1) {\r
424                         tableInfo[tblId].currentPage = tableInfo[tblId].currentPage > 1 ? tableInfo[tblId].currentPage - 1 : tableInfo[tblId].numPages;\r
425                         showPrevNext = 1;\r
426                 } else if(a.className.search("next-page") != -1) {\r
427                         tableInfo[tblId].currentPage = tableInfo[tblId].currentPage < tableInfo[tblId].numPages ? tableInfo[tblId].currentPage + 1 : 1;\r
428                         showPrevNext = 2;\r
429                 } else if(a.className.search("first-page") != -1) {\r
430                         tableInfo[tblId].currentPage = 1;\r
431                 } else if(a.className.search("last-page") != -1) {\r
432                         tableInfo[tblId].currentPage = tableInfo[tblId].numPages;\r
433                 } else {\r
434                         tableInfo[tblId].currentPage = parseInt(a.className.match(/page-([0-9]+)/)[1]) || 1;\r
435                 };\r
436 \r
437                 showPage(tblId);\r
438 \r
439                 // Focus on the appropriate button (previous, next or the current page)\r
440                 // I'm hoping screen readers are savvy enough to indicate the focus event to the user\r
441                 if(showPrevNext == 1) {\r
442                         var elem = document.getElementById(ul.id.search("-tablePaginaterClone") != -1 ? tblId + "-previousPageC" : tblId + "-previousPage");\r
443                 } else if(showPrevNext == 2) {\r
444                         var elem = document.getElementById(ul.id.search("-tablePaginaterClone") != -1 ? tblId + "-nextPageC" : tblId + "-nextPage");\r
445                 } else {\r
446                         var elem = document.getElementById(ul.id.search("-tablePaginaterClone") != -1 ? tblId + "-currentPageC" : tblId + "-currentPage");\r
447                 };\r
448                 \r
449                 if(elem && elem.tagName.toLowerCase() == "a") { elem.focus(); };   \r
450                 \r
451                 return stopEvent(e);\r
452         };\r
453         \r
454         var onUnLoad = function(e) {\r
455                 var tbl, lis, pagination, uls;\r
456                 for(tblId in tableInfo) {\r
457                         uls = [tblId + "-tablePaginater", tblId + "-tablePaginaterClone"];\r
458                         for(var z = 0; z < 2; z++) {\r
459                                 pagination = document.getElementById(uls[z]);\r
460                                 if(!pagination) { continue; };\r
461                                 lis = pagination.getElementsByTagName("li");\r
462                                 for(var i = 0, li; li = lis[i]; i++) {\r
463                                         li.onclick = null;\r
464                                         if(li.getElementsByTagName("a").length) { li.getElementsByTagName("a")[0].onclick = null; };\r
465                                 };\r
466                         };\r
467                 };\r
468         };\r
469         \r
470         addEvent(window, "load",   init);\r
471         addEvent(window, "unload", onUnLoad); \r
472         \r
473         return {                  \r
474                 init:                   function(tableId) { init(tableId); },                 \r
475                 redraw:                 function(tableid, identical) { tableSortRedraw(tableid, identical); },\r
476                 tableIsPaginated:       function(tableId) { return (tableId in tableInfo); },\r
477                 changeTranslations:     function(translations) { text = translations; }\r
478         };        \r
479 })();