mediawiki.util.test.js: IE7 doesn't expose the CSSStyleSheet publicly (like it doesn...
[mediawiki.git] / resources / jquery / jquery.tablesorter.js
blob500447ba17f07bef8f568e6cdfb39d297a171162
1 /*
2  * 
3  * TableSorter for MediaWiki
4  * 
5  * Written 2011 Leo Koppelkamm
6  * Based on tablesorter.com plugin, written (c) 2007 Christian Bach.
7  *
8  * Dual licensed under the MIT and GPL licenses:
9  * http://www.opensource.org/licenses/mit-license.php
10  * http://www.gnu.org/licenses/gpl.html
11  * 
12  */
13 /**
14  * 
15  * @description Create a sortable table with multi-column sorting capabilitys
16  * 
17  * @example $( 'table' ).tablesorter();
18  * @desc Create a simple tablesorter interface.
19  *
20  * @option String cssHeader ( optional ) A string of the class name to be appended
21  *         to sortable tr elements in the thead of the table. Default value:
22  *         "header"
23  * 
24  * @option String cssAsc ( optional ) A string of the class name to be appended to
25  *         sortable tr elements in the thead on a ascending sort. Default value:
26  *         "headerSortUp"
27  * 
28  * @option String cssDesc ( optional ) A string of the class name to be appended
29  *         to sortable tr elements in the thead on a descending sort. Default
30  *         value: "headerSortDown"
31  * 
32  * @option String sortInitialOrder ( optional ) A string of the inital sorting
33  *         order can be asc or desc. Default value: "asc"
34  * 
35  * @option String sortMultisortKey ( optional ) A string of the multi-column sort
36  *         key. Default value: "shiftKey"
37  *
38  * @option Boolean sortLocaleCompare ( optional ) Boolean flag indicating whatever
39  *         to use String.localeCampare method or not. Set to false.
40  *
41  * @option Boolean cancelSelection ( optional ) Boolean flag indicating if
42  *         tablesorter should cancel selection of the table headers text.
43  *         Default value: true
44  * 
45  * @option Boolean debug ( optional ) Boolean flag indicating if tablesorter
46  *         should display debuging information usefull for development.
47  * 
48  * @type jQuery
49  * 
50  * @name tablesorter
51  * 
52  * @cat Plugins/Tablesorter
53  * 
54  * @author Christian Bach/christian.bach@polyester.se
55  */
57 ( function ($) {
58         $.extend( {
59                 tablesorter: new
61                 function () {
63                         var parsers = [];
65                         this.defaults = {
66                                 cssHeader: "headerSort",
67                                 cssAsc: "headerSortUp",
68                                 cssDesc: "headerSortDown",
69                                 cssChildRow: "expand-child",
70                                 sortInitialOrder: "asc",
71                                 sortMultiSortKey: "shiftKey",
72                                 sortLocaleCompare: false,
73                                 parsers: {},
74                                 widgets: [],
75                                 headers: {},
76                                 cancelSelection: true,
77                                 sortList: [],
78                                 headerList: [],
79                                 selectorHeaders: 'thead tr:eq(0) th',
80                                 debug: false
81                         };
83                         /* debuging utils */
84                         // 
85                         // function benchmark( s, d ) {
86                         //     console.log( s + " " + ( new Date().getTime() - d.getTime() ) + "ms" );
87                         // }
88                         // 
89                         // this.benchmark = benchmark;
90                         // 
91                         /* parsers utils */
93                         function buildParserCache( table, $headers ) {
94                                 var rows = table.tBodies[0].rows,
95                                         sortType;
97                                 if ( rows[0] ) {
99                                         var list = [],
100                                                 cells = rows[0].cells,
101                                                 l = cells.length;
103                                         for ( var i = 0; i < l; i++ ) {
104                                                 p = false;
105                                                 sortType = $headers.eq(i).data('sort-type');
106                                                 if ( typeof sortType != 'undefined' ) {
107                                                         p = getParserById( sortType );
108                                                 }
110                                                 if (p === false) {
111                                                         p = detectParserForColumn( table, rows, i );
112                                                 }
113                                                 // if ( table.config.debug ) {
114                                                 //     console.log( "column:" + i + " parser:" + p.id + "\n" );
115                                                 // }
116                                                 list.push(p);
117                                         }
118                                 }
119                                 return list;
120                         }
122                         function detectParserForColumn( table, rows, cellIndex ) {
123                                 var l = parsers.length,
124                                         nodeValue,
125                                         // Start with 1 because 0 is the fallback parser
126                                         i = 1,
127                                         rowIndex = 0,
128                                         concurrent = 0,
129                                         needed = (rows.length > 4 ) ? 5 : rows.length;
130                                 while( i<l ) {
131                                         nodeValue = getTextFromRowAndCellIndex( rows, rowIndex, cellIndex );
132                                         if ( nodeValue != '') {
133                                                 if ( parsers[i].is( nodeValue, table ) ) {
134                                                         concurrent++;
135                                                         rowIndex++;
136                                                         if (concurrent >= needed ) {
137                                                                 // Confirmed the parser for multiple cells, let's return it
138                                                                 return parsers[i];
139                                                         }
140                                                 } else {
141                                                         // Check next parser, reset rows
142                                                         i++;
143                                                         rowIndex = 0;
144                                                         concurrent = 0;
145                                                 }
146                                         } else {
147                                                 // Empty cell
148                                                 rowIndex++;
149                                                 if ( rowIndex > rows.length ) {
150                                                         rowIndex = 0;
151                                                         i++;
152                                                 }
153                                         }
154                                 }
155                                 
156                                 // 0 is always the generic parser ( text )
157                                 return parsers[0];
158                         }
159                         
160                         function getTextFromRowAndCellIndex( rows, rowIndex, cellIndex ) {
161                                 if ( rows[rowIndex] && rows[rowIndex].cells[cellIndex] ) {
162                                         return $.trim( getElementText( rows[rowIndex].cells[cellIndex] ) );
163                                 } else {
164                                         return '';
165                                 }
166                         }
167                         
168                         function getParserById( name ) {
169                                 var l = parsers.length;
170                                 for ( var i = 0; i < l; i++ ) {
171                                         if ( parsers[i].id.toLowerCase() == name.toLowerCase() ) {
172                                                 return parsers[i];
173                                         }
174                                 }
175                                 return false;
176                         }
178                         /* utils */
180                         function buildCache( table ) {
181                                 // if ( table.config.debug ) {
182                                 //     var cacheTime = new Date();
183                                 // }
184                                 var totalRows = ( table.tBodies[0] && table.tBodies[0].rows.length ) || 0,
185                                         totalCells = ( table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length ) || 0,
186                                         parsers = table.config.parsers,
187                                         cache = {
188                                                 row: [],
189                                                 normalized: []
190                                         };
192                                 for ( var i = 0; i < totalRows; ++i ) {
194                                         // Add the table data to main data array
195                                         var c = $( table.tBodies[0].rows[i] ),
196                                                 cols = [];
198                                         // if this is a child row, add it to the last row's children and
199                                         // continue to the next row
200                                         if ( c.hasClass( table.config.cssChildRow ) ) {
201                                                 cache.row[cache.row.length - 1] = cache.row[cache.row.length - 1].add(c);
202                                                 // go to the next for loop
203                                                 continue;
204                                         }
206                                         cache.row.push(c);
208                                         for ( var j = 0; j < totalCells; ++j ) {
209                                                 cols.push( parsers[j].format( getElementText( c[0].cells[j] ), table, c[0].cells[j] ) );
210                                         }
212                                         cols.push( cache.normalized.length ); // add position for rowCache
213                                         cache.normalized.push( cols );
214                                         cols = null;
215                                 }
217                                 // if ( table.config.debug ) {
218                                 //     benchmark( "Building cache for " + totalRows + " rows:", cacheTime );
219                                 // }
220                                 return cache;
221                         }
223                         function getElementText( node ) {
224                                 if ( node.hasAttribute && node.hasAttribute( "data-sort-value" ) ) {
225                                         return node.getAttribute( "data-sort-value" );
226                                 } else {
227                                         return $( node ).text();
228                                 }
229                         }
231                         function appendToTable( table, cache ) {
232                                 // if ( table.config.debug ) {
233                                 //      var appendTime = new Date()
234                                 //  }
235                                 var c = cache,
236                                         r = c.row,
237                                         n = c.normalized,
238                                         totalRows = n.length,
239                                         checkCell = (n[0].length - 1),
240                                         tableBody = $( table.tBodies[0] ),
241                                         fragment = document.createDocumentFragment();
243                                 for ( var i = 0; i < totalRows; i++ ) {
244                                         var pos = n[i][checkCell];
246                                         var l = r[pos].length;
248                                         for ( var j = 0; j < l; j++ ) {
249                                                 fragment.appendChild( r[pos][j] );
250                                         }
252                                 }
253                                 tableBody[0].appendChild( fragment );
254                                 // if ( table.config.debug ) {
255                                 //      benchmark( "Rebuilt table:", appendTime );
256                                 //  }
257                         }
259                         function buildHeaders( table, msg ) {
260                                 var maxSeen = 0;
261                                 var longest;
262                                 // if ( table.config.debug ) {
263                                 //     var time = new Date();
264                                 // }
265                                 //var header_index = computeTableHeaderCellIndexes( table );
266                                 var realCellIndex = 0;
267                                 $tableHeaders = $( "thead:eq(0) tr", table );
268                                 if ( $tableHeaders.length > 1 ) {
269                                         $tableHeaders.each(function() {
270                                                 if (this.cells.length > maxSeen) {
271                                                         maxSeen = this.cells.length;
272                                                         longest = this;
273                                                 }
274                                         });
275                                         $tableHeaders = $( longest );
276                                 }
277                                 $tableHeaders = $tableHeaders.find('th').each( function ( index ) {
278                                         //var normalIndex = allCells.index( this );
279                                         //var realCellIndex = 0;
280                                         this.column = realCellIndex;
282                                         var colspan = this.colspan;
283                                         colspan = colspan ? parseInt( colspan, 10 ) : 1;
284                                         realCellIndex += colspan;
286                                         //this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex];
287                                         this.order = 0;
288                                         this.count = 0;
290                                         if ( $( this ).is( '.unsortable' ) ) this.sortDisabled = true;
292                                         if ( !this.sortDisabled ) {
293                                                 var $th = $( this ).addClass( table.config.cssHeader ).attr( 'title', msg[1] );
294                                                 
295                                                 //if ( table.config.onRenderHeader ) table.config.onRenderHeader.apply($th);
296                                         }
298                                         // add cell to headerList
299                                         table.config.headerList[index] = this;
300                                 } );
302                                 // if ( table.config.debug ) {
303                                 //     benchmark( "Built headers:", time );
304                                 //     console.log( $tableHeaders );
305                                 // }
306                                 // 
307                                 return $tableHeaders;
309                         }
311                         function isValueInArray( v, a ) {
312                                 var l = a.length;
313                                 for ( var i = 0; i < l; i++ ) {
314                                         if ( a[i][0] == v ) {
315                                                 return true;
316                                         }
317                                 }
318                                 return false;
319                         }
321                         function setHeadersCss( table, $headers, list, css, msg ) {
322                                 // remove all header information
323                                 $headers.removeClass( css[0] ).removeClass( css[1] );
325                                 var h = [];
326                                 $headers.each( function ( offset ) {
327                                         if ( !this.sortDisabled ) {
328                                                 h[this.column] = $( this );
329                                         }
330                                 } );
332                                 var l = list.length;
333                                 for ( var i = 0; i < l; i++ ) {
334                                         h[ list[i][0] ].addClass( css[ list[i][1] ] ).attr( 'title', msg[ list[i][1] ] );
335                                 }
336                         }
338                         function checkSorting (array1, array2, sortList) {
339                                 var col, fn, ret;
340                                 for ( var i = 0, len = sortList.length; i < len; i++ ) {
341                                         col = sortList[i][0];
342                                         fn = ( sortList[i][1] ) ? sortTextDesc : sortText;
343                                         ret = fn.call( this, array1[col], array2[col] );
344                                         if ( ret !== 0 ) {
345                                                 return ret;
346                                         }
347                                 }
348                                 return ret;
349                         }
351                         // Merge sort algorithm
352                         // Based on http://en.literateprograms.org/Merge_sort_(JavaScript)
353                         function mergeSortHelper(array, begin, beginRight, end, sortList) {
354                                 for (; begin < beginRight; ++begin) {
355                                         if (checkSorting( array[begin], array[beginRight], sortList )) {
356                                                 var v = array[begin];
357                                                 array[begin] = array[beginRight];
358                                                 var begin2 = beginRight;
359                                                 while ( begin2 + 1 < end && checkSorting( v, array[begin2 + 1], sortList ) ) {
360                                                         var tmp = array[begin2];
361                                                         array[begin2] = array[begin2 + 1];
362                                                         array[begin2 + 1] = tmp;
363                                                         ++begin2;
364                                                 }
365                                                 array[begin2] = v;
366                                         }
367                                 }
368                         }
370                         function mergeSort(array, begin, end, sortList) {
371                                 var size = end - begin;
372                                 if (size < 2) return;
374                                 var beginRight = begin + Math.floor(size / 2);
376                                 mergeSort(array, begin, beginRight, sortList);
377                                 mergeSort(array, beginRight, end, sortList);
378                                 mergeSortHelper(array, begin, beginRight, end, sortList);
379                         }
381                         var lastSort = '';
382                         
383                         function multisort( table, sortList, cache ) {
384                                 //var sortTime = new Date();
385                     
386                                 var i = sortList.length;
387                                 if ( i == 1 && sortList[0][0] === lastSort) {
388                                         // Special case a simple reverse
389                                         cache.normalized.reverse();
390                                 } else {
391                                         mergeSort(cache.normalized, 0, cache.normalized.length, sortList);
392                                 }
393                                 lastSort = ( sortList.length == 1 ) ? sortList[0][0] : '';
395                                 //benchmark( "Sorting in dir " + order + " time:", sortTime );
396                     
397                                 return cache;
398                         }
400                         function sortText( a, b ) {
401                                 return ((a < b) ? false : ((a > b) ? true : 0));
402                         }
404                         function sortTextDesc( a, b ) {
405                                 return ((b < a) ? false : ((b > a) ? true : 0));
406                         }
408                         function buildTransformTable() {
409                                 var digits = '0123456789,.'.split('');
410                                 var separatorTransformTable = mw.config.get( 'wgSeparatorTransformTable' );
411                                 var digitTransformTable = mw.config.get( 'wgDigitTransformTable' );
412                                 if ( separatorTransformTable == null || ( separatorTransformTable[0] == '' && digitTransformTable[2] == '' ) ) {
413                                         ts.transformTable = false;
414                                 } else {
415                                         ts.transformTable = {};
417                                         // Unpack the transform table
418                                         var ascii = separatorTransformTable[0].split( "\t" ).concat( digitTransformTable[0].split( "\t" ) );
419                                         var localised = separatorTransformTable[1].split( "\t" ).concat( digitTransformTable[1].split( "\t" ) );
421                                         // Construct regex for number identification
422                                         for ( var i = 0; i < ascii.length; i++ ) {
423                                                 ts.transformTable[localised[i]] = ascii[i];
424                                                 digits.push( $.escapeRE( localised[i] ) );
425                                         }
426                                 }
427                                 var digitClass = '[' + digits.join( '', digits ) + ']';
429                                 // We allow a trailing percent sign, which we just strip.  This works fine
430                                 // if percents and regular numbers aren't being mixed.
431                                 ts.numberRegex = new RegExp("^(" + "[-+\u2212]?[0-9][0-9,]*(\\.[0-9,]*)?(E[-+\u2212]?[0-9][0-9,]*)?" + // Fortran-style scientific
432                                 "|" + "[-+\u2212]?" + digitClass + "+[\\s\\xa0]*%?" + // Generic localised
433                                 ")$", "i");
434                         }
436                         function buildDateTable() {
437                                 var r = '';
438                                 ts.monthNames = [
439                                         [],
440                                         []
441                                 ];
442                                 ts.dateRegex = [];
444                                 for ( var i = 1; i < 13; i++ ) {
445                                         ts.monthNames[0][i] = mw.config.get( 'wgMonthNames' )[i].toLowerCase();
446                                         ts.monthNames[1][i] = mw.config.get( 'wgMonthNamesShort' )[i].toLowerCase().replace( '.', '' );
447                                         r += $.escapeRE( ts.monthNames[0][i] ) + '|';
448                                         r += $.escapeRE( ts.monthNames[1][i] ) + '|';
449                                 }
451                                 //Remove trailing pipe
452                                 r = r.slice( 0, -1 );
454                                 //Build RegEx
455                                 //Any date formated with . , ' - or /
456                                 ts.dateRegex[0] = new RegExp(/^\s*\d{1,2}[\,\.\-\/'\s]*\d{1,2}[\,\.\-\/'\s]*\d{2,4}\s*?/i);
458                                 //Written Month name, dmy
459                                 ts.dateRegex[1] = new RegExp('^\\s*\\d{1,2}[\\,\\.\\-\\/\'\\s]*(' + r + ')' + '[\\,\\.\\-\\/\'\\s]*\\d{2,4}\\s*$', 'i');
461                                 //Written Month name, mdy
462                                 ts.dateRegex[2] = new RegExp('^\\s*(' + r + ')' + '[\\,\\.\\-\\/\'\\s]*\\d{1,2}[\\,\\.\\-\\/\'\\s]*\\d{2,4}\\s*$', 'i');
464                         }
466                         function explodeRowspans( $table ) {
467                                 // Split multi row cells into multiple cells with the same content
468                                 $table.find( '[rowspan]' ).each(function() {
469                                         var rowSpan = this.rowSpan;
470                                         this.rowSpan = 1;
471                                         var cell = $( this );
472                                         var next = cell.parent().nextAll();
473                                         for ( var i = 0; i < rowSpan - 1; i++ ) {
474                                                 next.eq(0).find( 'td' ).eq( this.cellIndex ).before( cell.clone() );
475                                         }
476                                 });
477                         }
479                         function buildCollationTable() {
480                                 ts.collationTable = mw.config.get('tableSorterCollation');
481                                 if ( typeof ts.collationTable === "object" ) {
482                                         ts.collationRegex = [];
484                                         //Build array of key names
485                                         for ( var key in ts.collationTable ) {
486                                                 if ( ts.collationTable.hasOwnProperty(key) ) { //to be safe
487                                                         ts.collationRegex.push(key);
488                                                 }
489                                         }
490                                         ts.collationRegex = new RegExp( '[' + ts.collationRegex.join('') + ']', 'ig' );
491                                 }
492                         }
494                         function cacheRegexs() {
495                                 ts.rgx = {
496                                         IPAddress: [new RegExp(/^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/)],
497                                         currency: [new RegExp(/^[£$€?.]/), new RegExp(/[£$€]/g)],
498                                         url: [new RegExp(/^(https?|ftp|file):\/\/$/), new RegExp(/(https?|ftp|file):\/\//)],
499                                         isoDate: [new RegExp(/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/)],
500                                         usLongDate: [new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/)],
501                                         time: [new RegExp(/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/)]
502                                 };
503                         } /* public methods */
504                         this.construct = function ( settings ) {
505                                 return this.each( function () {
506                                         // if no thead or tbody quit.
507                                         if ( !this.tHead || !this.tBodies ) return;
508                                         // declare
509                                         var $this, $document, $headers, cache, config, shiftDown = 0,
510                                                 sortOrder, firstTime = true, that = this;
511                                         // new blank config object
512                                         this.config = {};
513                                         // merge and extend.
514                                         config = $.extend( this.config, $.tablesorter.defaults, settings );
516                                         // store common expression for speed
517                                         $this = $( this );
518                                         // save the settings where they read
519                                         $.data( this, "tablesorter", config );
521                                         // get the css class names, could be done else where.
522                                         var sortCSS = [ config.cssDesc, config.cssAsc ];
523                                         var sortMsg = [ mw.msg( 'sort-descending' ), mw.msg( 'sort-ascending' ) ];
525                                         // build headers
526                                         $headers = buildHeaders( this, sortMsg );
527                                         // Grab and process locale settings
528                                         buildTransformTable();
529                                         buildDateTable();
530                                         buildCollationTable();
532                                         //Precaching regexps can bring 10 fold 
533                                         //performance improvements in some browsers
534                                         cacheRegexs();
536                                         // apply event handling to headers
537                                         // this is to big, perhaps break it out?
538                                         $headers.click( 
540                                         function (e) {
541                                                 //var clickTime= new Date();
542                                                 if (firstTime) {
543                                                         firstTime = false;
544                                                         explodeRowspans( $this );
545                                                         // try to auto detect column type, and store in tables config
546                                                         that.config.parsers = buildParserCache( that, $headers );
547                                                         // build the cache for the tbody cells
548                                                         cache = buildCache( that );
549                                                 }
550                                                 var totalRows = ( $this[0].tBodies[0] && $this[0].tBodies[0].rows.length ) || 0;
551                                                 if ( !this.sortDisabled && totalRows > 0 ) {
552                                                         // Only call sortStart if sorting is
553                                                         // enabled.
554                                                         //$this.trigger( "sortStart" );
556                                                         // store exp, for speed
557                                                         var $cell = $( this );
558                                                         // get current column index
559                                                         var i = this.column;
560                                                         // get current column sort order
561                                                         this.order = this.count % 2;
562                                                         this.count++;
563                                                         // user only whants to sort on one
564                                                         // column
565                                                         if ( !e[config.sortMultiSortKey] ) {
566                                                                 // flush the sort list
567                                                                 config.sortList = [];
568                                                                 // add column to sort list
569                                                                 config.sortList.push( [i, this.order] );
570                                                                 // multi column sorting
571                                                         } else {
572                                                                 // the user has clicked on an already sorted column.
573                                                                 if ( isValueInArray( i, config.sortList ) ) {
574                                                                         // revers the sorting direction
575                                                                         // for all tables.
576                                                                         for ( var j = 0; j < config.sortList.length; j++ ) {
577                                                                                 var s = config.sortList[j],
578                                                                                         o = config.headerList[s[0]];
579                                                                                 if ( s[0] == i ) {
580                                                                                         o.count = s[1];
581                                                                                         o.count++;
582                                                                                         s[1] = o.count % 2;
583                                                                                 }
584                                                                         }
585                                                                 } else {
586                                                                         // add column to sort list array
587                                                                         config.sortList.push( [i, this.order] );
588                                                                 }
589                                                         }
590                                                         setTimeout( function () {
591                                                                 // set css for headers
592                                                                 setHeadersCss( $this[0], $headers, config.sortList, sortCSS, sortMsg );
593                                                                 appendToTable( 
594                                                                 $this[0], multisort( 
595                                                                 $this[0], config.sortList, cache ) );
596                                                                 //benchmark( "Sorting " + totalRows + " rows:", clickTime );
597                                                         }, 1 );
598                                                         // stop normal event by returning false
599                                                         return false;
600                                                 }
601                                                 // cancel selection
602                                         } ).mousedown( function () {
603                                                 if ( config.cancelSelection ) {
604                                                         this.onselectstart = function () {
605                                                                 return false;
606                                                         };
607                                                         return false;
608                                                 }
609                                         } );
610                                         // apply easy methods that trigger binded events
611                                         //Can't think of any use for these in a mw context
612                                         // $this.bind( "update", function () {
613                                         //     var me = this;
614                                         //     setTimeout( function () {
615                                         //         // rebuild parsers.
616                                         //         me.config.parsers = buildParserCache( 
617                                         //         me, $headers );
618                                         //         // rebuild the cache map
619                                         //         cache = buildCache(me);
620                                         //     }, 1 );
621                                         // } ).bind( "updateCell", function ( e, cell ) {
622                                         //     var config = this.config;
623                                         //     // get position from the dom.
624                                         //     var pos = [( cell.parentNode.rowIndex - 1 ), cell.cellIndex];
625                                         //     // update cache
626                                         //     cache.normalized[pos[0]][pos[1]] = config.parsers[pos[1]].format( 
627                                         //     getElementText( cell ), cell );
628                                         // } ).bind( "sorton", function ( e, list ) {
629                                         //     $( this ).trigger( "sortStart" );
630                                         //     config.sortList = list;
631                                         //     // update and store the sortlist
632                                         //     var sortList = config.sortList;
633                                         //     // update header count index
634                                         //     updateHeaderSortCount( this, sortList );
635                                         //     // set css for headers
636                                         //     setHeadersCss( this, $headers, sortList, sortCSS );
637                                         //     // sort the table and append it to the dom
638                                         //     appendToTable( this, multisort( this, sortList, cache ) );
639                                         // } ).bind( "appendCache", function () {
640                                         //     appendToTable( this, cache );
641                                         // } );
642                                 } );
643                         };
644                         this.addParser = function ( parser ) {
645                                 var l = parsers.length,
646                                         a = true;
647                                 for ( var i = 0; i < l; i++ ) {
648                                         if ( parsers[i].id.toLowerCase() == parser.id.toLowerCase() ) {
649                                                 a = false;
650                                         }
651                                 }
652                                 if (a) {
653                                         parsers.push( parser );
654                                 }
655                         };
656                         this.formatDigit = function (s) {
657                                 if ( ts.transformTable != false ) {
658                                         var out = '',
659                                                 c;
660                                         for ( var p = 0; p < s.length; p++ ) {
661                                                 c = s.charAt(p);
662                                                 if ( c in ts.transformTable ) {
663                                                         out += ts.transformTable[c];
664                                                 } else {
665                                                         out += c;
666                                                 }
667                                         }
668                                         s = out;
669                                 }
670                                 var i = parseFloat( s.replace(/[, ]/g, '').replace( "\u2212", '-' ) );
671                                 return ( isNaN(i)) ? 0 : i;
672                         };
673                         this.formatFloat = function (s) {
674                                 var i = parseFloat(s);
675                                 return ( isNaN(i)) ? 0 : i;
676                         };
677                         this.formatInt = function (s) {
678                                 var i = parseInt( s, 10 );
679                                 return ( isNaN(i)) ? 0 : i;
680                         };
681                         this.clearTableBody = function ( table ) {
682                                 if ( $.browser.msie ) {
683                                         function empty() {
684                                                 while ( this.firstChild )
685                                                 this.removeChild( this.firstChild );
686                                         }
687                                         empty.apply( table.tBodies[0] );
688                                 } else {
689                                         table.tBodies[0].innerHTML = "";
690                                 }
691                         };
692                 }
693         } );
695         // extend plugin scope
696         $.fn.extend( {
697                 tablesorter: $.tablesorter.construct
698         } );
700         // make shortcut
701         var ts = $.tablesorter;
703         // add default parsers
704         ts.addParser( {
705                 id: "text",
706                 is: function (s) {
707                         return true;
708                 },
709                 format: function (s) {
710                         s = $.trim( s.toLowerCase() );
711                         if ( ts.collationRegex ) {
712                                 var tsc = ts.collationTable;
713                                 s = s.replace( ts.collationRegex, function ( match ) {
714                                         var r = tsc[match] ? tsc[match] : tsc[match.toUpperCase()];
715                                         return r.toLowerCase();
716                                 } );
717                         }
718                         return s;
719                 },
720                 type: "text"
721         } );
723         ts.addParser( {
724                 id: "IPAddress",
725                 is: function (s) {
726                         return ts.rgx.IPAddress[0].test(s);
727                 },
728                 format: function (s) {
729                         var a = s.split("."),
730                                 r = "",
731                                 l = a.length;
732                         for ( var i = 0; i < l; i++ ) {
733                                 var item = a[i];
734                                 if ( item.length == 2 ) {
735                                         r += "0" + item;
736                                 } else {
737                                         r += item;
738                                 }
739                         }
740                         return $.tablesorter.formatFloat(r);
741                 },
742                 type: "numeric"
743         } );
745         ts.addParser( {
746                 id: "currency",
747                 is: function (s) {
748                         return ts.rgx.currency[0].test(s);
749                 },
750                 format: function (s) {
751                         return $.tablesorter.formatDigit( s.replace( ts.rgx.currency[1], "" ) );
752                 },
753                 type: "numeric"
754         } );
756         ts.addParser( {
757                 id: "url",
758                 is: function (s) {
759                         return ts.rgx.url[0].test(s);
760                 },
761                 format: function (s) {
762                         return $.trim( s.replace( ts.rgx.url[1], '' ) );
763                 },
764                 type: "text"
765         } );
767         ts.addParser( {
768                 id: "isoDate",
769                 is: function (s) {
770                         return ts.rgx.isoDate[0].test(s);
771                 },
772                 format: function (s) {
773                         return $.tablesorter.formatFloat((s != "") ? new Date(s.replace(
774                         new RegExp(/-/g), "/")).getTime() : "0");
775                 },
776                 type: "numeric"
777         } );
779         ts.addParser( {
780                 id: "usLongDate",
781                 is: function (s) {
782                         return ts.rgx.usLongDate[0].test(s);
783                 },
784                 format: function (s) {
785                         return $.tablesorter.formatFloat( new Date(s).getTime() );
786                 },
787                 type: "numeric"
788         } );
790         ts.addParser( {
791                 id: "date",
792                 is: function (s) {
793                         return ( ts.dateRegex[0].test(s) || ts.dateRegex[1].test(s) || ts.dateRegex[2].test(s ));
794                 },
795                 format: function ( s, table ) {
796                         s = $.trim( s.toLowerCase() );
798                         for ( i = 1, j = 0; i < 13 && j < 2; i++ ) {
799                                 s = s.replace( ts.monthNames[j][i], i );
800                                 if ( i == 12 ) {
801                                         j++;
802                                         i = 0;
803                                 }
804                         }
806                         s = s.replace(/[\-\.\,' ]/g, "/");
808                         //Replace double slashes
809                         s = s.replace(/\/\//g, "/");
810                         s = s.replace(/\/\//g, "/");
811                         s = s.split('/');
813                         //Pad Month and Day
814                         if ( s[0] && s[0].length == 1 ) s[0] = "0" + s[0];
815                         if ( s[1] && s[1].length == 1 ) s[1] = "0" + s[1];
817                         if ( !s[2] ) {
818                                 //Fix yearless dates
819                                 s[2] = 2000;
820                         } else if ( ( y = parseInt( s[2], 10) ) < 100 ) {
821                                 //Guestimate years without centuries
822                                 if ( y < 30 ) {
823                                         s[2] = 2000 + y;
824                                 } else {
825                                         s[2] = 1900 + y;
826                                 }
827                         }
828                         //Resort array depending on preferences
829                         if ( mw.config.get( 'wgDefaultDateFormat' ) == "mdy" || mw.config.get('wgContentLanguage') == 'en' ) {
830                                 s.push( s.shift() );
831                                 s.push( s.shift() );
832                         } else if ( mw.config.get( 'wgDefaultDateFormat' ) == "dmy" ) {
833                                 var d = s.shift();
834                                 s.push( s.shift() );
835                                 s.push(d);
836                         }
837                         return parseInt( s.join(''), 10 );
838                 },
839                 type: "numeric"
840         } );
841         ts.addParser( {
842                 id: "time",
843                 is: function (s) {
844                         return ts.rgx.time[0].test(s);
845                 },
846                 format: function (s) {
847                         return $.tablesorter.formatFloat( new Date( "2000/01/01 " + s ).getTime() );
848                 },
849                 type: "numeric"
850         } );
851         ts.addParser( {
852                 id: "number",
853                 is: function ( s, table ) {
854                         return $.tablesorter.numberRegex.test( $.trim(s ));
855                 },
856                 format: function (s) {
857                         return $.tablesorter.formatDigit(s);
858                 },
859                 type: "numeric"
860         } );
861         
862 } )( jQuery );