fix loss of HTML formatting in table header cells
[mediawiki.git] / skins / common / sorttable.js
blob229b4c3a83abe660c93e6958830b2074969a7754
1 /*
2  * Table sorting script  by Joost de Valk, check it out at http://www.joostdevalk.nl/code/sortable-table/.
3  * Based on a script from http://www.kryogenix.org/code/browser/sorttable/.
4  * Distributed under the MIT license: http://www.kryogenix.org/code/browser/licence.html .
5  *
6  * Copyright (c) 1997-2006 Stuart Langridge, Joost de Valk.
7  *
8  * @todo don't break on colspans/rowspans (bug 8028)
9  * @todo language-specific digit grouping/decimals (bug 8063)
10  * @todo support all accepted date formats (bug 8226)
11  */
13 var image_path = stylepath+"/common/images/";
14 var image_up = "sort_up.gif";
15 var image_down = "sort_down.gif";
16 var image_none = "sort_none.gif";
17 var europeandate = wgContentLanguage != "en"; // The non-American-inclined can change to "true"
19 var alternate_row_colors = true;
22 hookEvent( "load", sortables_init);
24 var SORT_COLUMN_INDEX;
25 var thead = false;
27 function sortables_init() {
28         var idnum = 0;
29         // Find all tables with class sortable and make them sortable
30         if (!document.getElementsByTagName) return;
31         tbls = document.getElementsByTagName("table");
32         for (ti=0;ti<tbls.length;ti++) {
33                 thisTbl = tbls[ti];
34                 if ( (' '+thisTbl.className+' ').indexOf("sortable") != -1 ) {
35                         if (!thisTbl.id) {
36                                 thisTbl.setAttribute('id','sortable_table_id_'+idnum);
37                                 ++idnum;
38                         }
39                         ts_makeSortable(thisTbl);
40                 }
41         }
44 function ts_makeSortable(table) {
45         if (table.rows && table.rows.length > 0) {
46                 if (table.tHead && table.tHead.rows.length > 0) {
47                         var firstRow = table.tHead.rows[table.tHead.rows.length-1];
48                         thead = true;
49                 } else {
50                         var firstRow = table.rows[0];
51                 }
52         }
53         if (!firstRow) return;
55         // We have a first row: assume it's the header, and make its contents clickable links
56         for (var i=0;i<firstRow.cells.length;i++) {
57                 var cell = firstRow.cells[i];
58                 if (cell.className != "unsortable" && cell.className.indexOf("unsortable") == -1) {
59                         cell.innerHTML += '&nbsp;&nbsp;<a href="#" class="sortheader" onclick="ts_resortTable(this);return false;"><span class="sortarrow"><img src="'+ image_path + image_none + '" alt="&darr;"/></span></a>';
60                 }
61         }
62         if (alternate_row_colors) {
63                 alternate(table);
64         }
67 function ts_getInnerText(el) {
68         if (typeof el == "string") return el;
69         if (typeof el == "undefined") { return el };
70         if (el.innerText) return el.innerText;  //Not needed but it is faster
71         var str = "";
72         
73         var cs = el.childNodes;
74         var l = cs.length;
75         for (var i = 0; i < l; i++) {
76                 switch (cs[i].nodeType) {
77                         case 1: //ELEMENT_NODE
78                                 str += ts_getInnerText(cs[i]);
79                                 break;
80                         case 3: //TEXT_NODE
81                                 str += cs[i].nodeValue;
82                                 break;
83                 }
84         }
85         return str;
88 function ts_resortTable(lnk) {
89         // get the span
90         var span;
91         for (var ci=0;ci<lnk.childNodes.length;ci++) {
92                 if (lnk.childNodes[ci].tagName && lnk.childNodes[ci].tagName.toLowerCase() == 'span') span = lnk.childNodes[ci];
93         }
94         var spantext = ts_getInnerText(span);
95         var td = lnk.parentNode;
96         var column = td.cellIndex;
97         var table = getParent(td,'TABLE');
99         // Work out a type for the column
100         if (table.rows.length <= 1) return;
102         for( var i = 1, itm = ""; itm.match(/^([\s]|\n|\&nbsp;|<!--[^-]+-->)*$/); i++) {
103                 var itm = ts_getInnerText(table.tBodies[0].rows[i].cells[column]);
104                 itm = trim(itm);
105         }
106         sortfn = ts_sort_caseinsensitive;
107         if (itm.match(/^\d\d[\/. -][a-zA-Z]{3}[\/. -]\d\d\d\d$/)) sortfn = ts_sort_date;
108         if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d\d\d$/)) sortfn = ts_sort_date;
109         if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d$/)) sortfn = ts_sort_date;
110         if (itm.match(/^[£$€Û¢´]/)) sortfn = ts_sort_currency;
111         if (itm.match(/^[\d.,]+\%?$/)) sortfn = ts_sort_numeric;
112         SORT_COLUMN_INDEX = column;
113         var firstRow = new Array();
114         var newRows = new Array();
116         for (k=0;k<table.tBodies.length;k++) {
117                 for (i=0;i<table.tBodies[k].rows[0].length;i++) {
118                         firstRow[i] = table.tBodies[k].rows[0][i];
119                 }
120         }
122         for (k=0;k<table.tBodies.length;k++) {
123                 if (!thead) {
124                         // Skip the first row
125                         for (j=1;j<table.tBodies[k].rows.length;j++) {
126                                 newRows[j-1] = table.tBodies[k].rows[j];
127                         }
128                 } else {
129                         // Do NOT skip the first row
130                         for (j=0;j<table.tBodies[k].rows.length;j++) { 
131                                 newRows[j] = table.tBodies[k].rows[j];
132                         }
133                 }
134         }
136         newRows.sort(sortfn);
138         if (span.getAttribute("sortdir") == 'down') {
139                         ARROW = '<img src="'+ image_path + image_down + '" alt="&darr;"/>';
140                         newRows.reverse();
141                         span.setAttribute('sortdir','up');
142         } else {
143                         ARROW = '<img src="'+ image_path + image_up + '" alt="&uarr;"/>';
144                         span.setAttribute('sortdir','down');
145         } 
146         
147         // We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
148         // don't do sortbottom rows
149         for (i=0; i<newRows.length; i++) {
150                 if (!newRows[i].className || (newRows[i].className && (newRows[i].className.indexOf('sortbottom') == -1))) {
151                         table.tBodies[0].appendChild(newRows[i]);
152                 }
153         }
154         // do sortbottom rows only
155         for (i=0; i<newRows.length; i++) {
156                 if (newRows[i].className && (newRows[i].className.indexOf('sortbottom') != -1))
157                         table.tBodies[0].appendChild(newRows[i]);
158         }
160         // Delete any other arrows there may be showing
161         var allspans = document.getElementsByTagName("span");
162         for (var ci=0;ci<allspans.length;ci++) {
163                 if (allspans[ci].className == 'sortarrow') {
164                         if (getParent(allspans[ci],"table") == getParent(lnk,"table")) { // in the same table as us?
165                                 allspans[ci].innerHTML = '<img src="'+ image_path + image_none + '" alt="&darr;"/>';
166                         }
167                 }
168         }
170         span.innerHTML = ARROW;
171         alternate(table);               
174 function getParent(el, pTagName) {
175         if (el == null) {
176                 return null;
177         } else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase()) {    // Gecko bug, supposed to be uppercase
178                 return el;
179         } else {
180                 return getParent(el.parentNode, pTagName);
181         }
184 function sort_date(date) {      
185         // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
186         dt = "00000000";
187         if (date.length == 11) {
188                 monthstr = date.substr(3,3);
189                 monthstr = monthstr.toLowerCase();
190                 switch(monthstr) {
191                         case "jan": var month = "01"; break;
192                         case "feb": var month = "02"; break;
193                         case "mar": var month = "03"; break;
194                         case "apr": var month = "04"; break;
195                         case "may": var month = "05"; break;
196                         case "jun": var month = "06"; break;
197                         case "jul": var month = "07"; break;
198                         case "aug": var month = "08"; break;
199                         case "sep": var month = "09"; break;
200                         case "oct": var month = "10"; break;
201                         case "nov": var month = "11"; break;
202                         case "dec": var month = "12"; break;
203                         // default: var month = "00";
204                 }
205                 dt = date.substr(7,4)+month+date.substr(0,2);
206                 return dt;
207         } else if (date.length == 10) {
208                 if (europeandate == false) {
209                         dt = date.substr(6,4)+date.substr(0,2)+date.substr(3,2);
210                         return dt;
211                 } else {
212                         dt = date.substr(6,4)+date.substr(3,2)+date.substr(0,2);
213                         return dt;
214                 }
215         } else if (date.length == 8) {
216                 yr = date.substr(6,2);
217                 if (parseInt(yr) < 50) { 
218                         yr = '20'+yr; 
219                 } else { 
220                         yr = '19'+yr; 
221                 }
222                 if (europeandate == true) {
223                         dt = yr+date.substr(3,2)+date.substr(0,2);
224                         return dt;
225                 } else {
226                         dt = yr+date.substr(0,2)+date.substr(3,2);
227                         return dt;
228                 }
229         }
230         return dt;
233 function ts_sort_date(a,b) {
234         dt1 = sort_date(ts_getInnerText(a.cells[SORT_COLUMN_INDEX]));
235         dt2 = sort_date(ts_getInnerText(b.cells[SORT_COLUMN_INDEX]));
237         if (dt1==dt2) {
238                 return 0;
239         }
240         if (dt1<dt2) {
241                 return -1;
242         }
243         return 1;
246 function ts_sort_currency(a,b) {
247         aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
248         bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
249         return compare_numeric(aa,bb);
252 function ts_sort_numeric(a,b) {
253         aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
254         bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
255         return compare_numeric(aa,bb);
258 function compare_numeric(a,b) {
259         a = parseFloat(a.replace(/,/, ""));
260         a = (isNaN(a) ? 0 : a);
261         b = parseFloat(b.replace(/,/, ""));
262         b = (isNaN(b) ? 0 : b);
263         return a - b;
266 function ts_sort_caseinsensitive(a,b) {
267         aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).toLowerCase();
268         bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).toLowerCase();
269         if (aa==bb) {
270                 return 0;
271         }
272         if (aa<bb) {
273                 return -1;
274         }
275         return 1;
278 function ts_sort_default(a,b) {
279         aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
280         bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
281         if (aa==bb) {
282                 return 0;
283         }
284         if (aa<bb) {
285                 return -1;
286         }
287         return 1;
290 function addEvent(elm, evType, fn, useCapture)
291 // addEvent and removeEvent
292 // cross-browser event handling for IE5+,       NS6 and Mozilla
293 // By Scott Andrew
295         if (elm.addEventListener){
296                 elm.addEventListener(evType, fn, useCapture);
297                 return true;
298         } else if (elm.attachEvent){
299                 var r = elm.attachEvent("on"+evType, fn);
300                 return r;
301         } else {
302                 alert("Handler could not be removed");
303         }
306 function replace(s, t, u) {
307         /*
308         **  Replace a token in a string
309         **    s  string to be processed
310         **    t  token to be found and removed
311         **    u  token to be inserted
312         **  returns new String
313         */
314         i = s.indexOf(t);
315         r = "";
316         if (i == -1) return s;
317         r += s.substring(0,i) + u;
318         if ( i + t.length < s.length) {
319                 r += replace(s.substring(i + t.length, s.length), t, u);
320         }
321         return r;
324 function trim(s) {
325         return s.replace(/^([ \t]|\n|\&nbsp;|<!--[^-]+-->)*/, "").replace(/([ \t]|\n|\&nbsp;|<!--[^-]+-->)*$/, "");
328 function alternate(table) {
329         // Take object table and get all it's tbodies.
330         var tableBodies = table.getElementsByTagName("tbody");
331         // Loop through these tbodies
332         for (var i = 0; i < tableBodies.length; i++) {
333                 // Take the tbody, and get all it's rows
334                 var tableRows = tableBodies[i].getElementsByTagName("tr");
335                 // Loop through these rows
336                 // Start at 1 because we want to leave the heading row untouched
337                 for (var j = 0; j < tableRows.length; j++) {
338                         // Check if j is even, and apply classes for both possible results
339                         if ( (j % 2) == 0  ) {
340                                 if ( !(tableRows[j].className.indexOf('odd') == -1) ) {
341                                         tableRows[j].className = replace(tableRows[j].className, 'odd', 'even');
342                                 } else {
343                                         if ( tableRows[j].className.indexOf('even') == -1 ) {
344                                                 tableRows[j].className += " even";
345                                         }
346                                 }
347                         } else {
348                                 if ( !(tableRows[j].className.indexOf('even') == -1) ) {
349                                         tableRows[j].className = replace(tableRows[j].className, 'even', 'odd');
350                                 } else {
351                                         if ( tableRows[j].className.indexOf('odd') == -1 ) {
352                                                 tableRows[j].className += " odd";
353                                         }
354                                 }
355                         } 
356                 }
357         }