Update jquery.qunit from upstream v1.8.0 to v1.9.0
[lhc/web/wiklou.git] / resources / jquery / jquery.tablesorter.js
index 14c6b0a..0edc8ee 100644 (file)
@@ -8,8 +8,9 @@
  * http://www.opensource.org/licenses/mit-license.php
  * http://www.gnu.org/licenses/gpl.html
  *
- * @depends on mw.config (wgDigitTransformTable, wgMonthNames, wgMonthNamesShort,
+ * Depends on mw.config (wgDigitTransformTable, wgMonthNames, wgMonthNamesShort,
  * wgDefaultDateFormat, wgContentLanguage)
+ * Uses 'tableSorterCollation' in mw.config (if available)
  */
 /**
  *
  * @author Christian Bach/christian.bach@polyester.se
  */
 
-( function( $ ) {
+( function ( $, mw ) {
 
        /* Local scope */
 
-       var     ts,
+       var ts,
                parsers = [];
 
        /* Parser utility functions */
        }
 
        function getElementText( node ) {
-               if ( node.hasAttribute && node.hasAttribute( 'data-sort-value' ) ) {
-                       return node.getAttribute( 'data-sort-value' );
+               var $node = $( node ),
+                       // Use data-sort-value attribute.
+                       // Use data() instead of attr() so that live value changes
+                       // are processed as well (bug 38152).
+                       data = $node.data( 'sortValue' );
+
+               if ( data !== null && data !== undefined ) {
+                       // Cast any numbers or other stuff to a string, methods
+                       // like charAt, toLowerCase and split are expected.
+                       return String( data );
                } else {
-                       return $( node ).text();
+                       return $node.text();
                }
        }
 
        }
 
        function detectParserForColumn( table, rows, cellIndex ) {
-               var     l = parsers.length,
+               var l = parsers.length,
                        nodeValue,
                        // Start with 1 because 0 is the fallback parser
                        i = 1,
        }
 
        function buildParserCache( table, $headers ) {
-               var     rows = table.tBodies[0].rows,
+               var rows = table.tBodies[0].rows,
                        sortType,
                        parsers = [];
 
                if ( rows[0] ) {
 
-                       var     cells = rows[0].cells,
+                       var cells = rows[0].cells,
                                len = cells.length,
                                i, parser;
 
        /* Other utility functions */
 
        function buildCache( table ) {
-               var     totalRows = ( table.tBodies[0] && table.tBodies[0].rows.length ) || 0,
+               var totalRows = ( table.tBodies[0] && table.tBodies[0].rows.length ) || 0,
                        totalCells = ( table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length ) || 0,
                        parsers = table.config.parsers,
                        cache = {
                for ( var i = 0; i < totalRows; ++i ) {
 
                        // Add the table data to main data array
-                       var     $row = $( table.tBodies[0].rows[i] ),
+                       var $row = $( table.tBodies[0].rows[i] ),
                                cols = [];
 
                        // if this is a child row, add it to the last row's children and
        }
 
        function appendToTable( table, cache ) {
-               var     row = cache.row,
+               var row = cache.row,
                        normalized = cache.normalized,
                        totalRows = normalized.length,
                        checkCell = ( normalized[0].length - 1 ),
                table.tBodies[0].appendChild( fragment );
        }
 
+       /**
+        * Find all header rows in a thead-less table and put them in a <thead> tag.
+        * This only treats a row as a header row if it contains only <th>s (no <td>s)
+        * and if it is preceded entirely by header rows. The algorithm stops when
+        * it encounters the first non-header row.
+        *
+        * After this, it will look at all rows at the bottom for footer rows
+        * And place these in a tfoot using similar rules.
+        * @param $table jQuery object for a <table>
+        */
+       function emulateTHeadAndFoot( $table ) {
+               var $rows = $table.find( '> tbody > tr' );
+               if( !$table.get(0).tHead ) {
+                       var $thead = $( '<thead>' );
+                       $rows.each( function () {
+                               if ( $(this).children( 'td' ).length > 0 ) {
+                                       // This row contains a <td>, so it's not a header row
+                                       // Stop here
+                                       return false;
+                               }
+                               $thead.append( this );
+                       } );
+                       $table.find(' > tbody:first').before( $thead );
+               }
+               if( !$table.get(0).tFoot ) {
+                       var $tfoot = $( '<tfoot>' );
+                       var len = $rows.length;
+                       for ( var i = len-1; i >= 0; i-- ) {
+                               if( $( $rows[i] ).children( 'td' ).length > 0 ){
+                                       break;
+                               }
+                               $tfoot.prepend( $( $rows[i] ));
+                       }
+                       $table.append( $tfoot );
+               }
+       }
+
        function buildHeaders( table, msg ) {
-               var     maxSeen = 0,
+               var maxSeen = 0,
                        longest,
                        realCellIndex = 0,
-                       $tableHeaders = $( 'thead:eq(0) tr', table );
+                       $tableHeaders = $( 'thead:eq(0) tr', table );
                if ( $tableHeaders.length > 1 ) {
-                       $tableHeaders.each(function() {
+                       $tableHeaders.each( function () {
                                if ( this.cells.length > maxSeen ) {
                                        maxSeen = this.cells.length;
                                        longest = this;
                        });
                        $tableHeaders = $( longest );
                }
-               $tableHeaders = $tableHeaders.find( 'th' ).each( function( index ) {
+               $tableHeaders = $tableHeaders.children( 'th' ).each( function ( index ) {
                        this.column = realCellIndex;
 
                        var colspan = this.colspan;
                        this.order = 0;
                        this.count = 0;
 
-                       if ( $( this ).is( '.unsortable, .mw-unsortable' ) ) {
+                       if ( $( this ).is( '.unsortable' ) ) {
                                this.sortDisabled = true;
                        }
 
        function isValueInArray( v, a ) {
                var l = a.length;
                for ( var i = 0; i < l; i++ ) {
-                       if ( a[i][0] == v ) {
+                       if ( a[i][0] === v ) {
                                return true;
                        }
                }
                $headers.removeClass( css[0] ).removeClass( css[1] );
 
                var h = [];
-               $headers.each( function( offset ) {
+               $headers.each( function ( offset ) {
                        if ( !this.sortDisabled ) {
                                h[this.column] = $( this );
                        }
 
        function buildDateTable() {
                var regex = [];
-               ts.monthNames = [
-                       [],
-                       []
-               ];
+               ts.monthNames = {};
 
                for ( var i = 1; i < 13; i++ ) {
-                       ts.monthNames[0][i] = mw.config.get( 'wgMonthNames' )[i].toLowerCase();
-                       ts.monthNames[1][i] = mw.config.get( 'wgMonthNamesShort' )[i].toLowerCase().replace( '.', '' );
-                       regex.push( $.escapeRE( ts.monthNames[0][i] ) );
-                       regex.push( $.escapeRE( ts.monthNames[1][i] ) );
+                       var name = mw.config.get( 'wgMonthNames' )[i].toLowerCase();
+                       ts.monthNames[name] = i;
+                       regex.push( $.escapeRE( name ) );
+                       name = mw.config.get( 'wgMonthNamesShort' )[i].toLowerCase().replace( '.', '' );
+                       ts.monthNames[name] = i;
+                       regex.push( $.escapeRE( name ) );
                }
 
                // Build piped string
 
                // Build RegEx
                // Any date formated with . , ' - or /
-               ts.dateRegex[0] = new RegExp( /^\s*\d{1,2}[\,\.\-\/'\s]{1,2}\d{1,2}[\,\.\-\/'\s]{1,2}\d{2,4}\s*?/i);
+               ts.dateRegex[0] = new RegExp( /^\s*(\d{1,2})[\,\.\-\/'\s]{1,2}(\d{1,2})[\,\.\-\/'\s]{1,2}(\d{2,4})\s*?/i);
 
                // Written Month name, dmy
-               ts.dateRegex[1] = new RegExp( '^\\s*\\d{1,2}[\\,\\.\\-\\/\'\\s]*(' + regex + ')' + '[\\,\\.\\-\\/\'\\s]*\\d{2,4}\\s*$', 'i' );
+               ts.dateRegex[1] = new RegExp( '^\\s*(\\d{1,2})[\\,\\.\\-\\/\'\\s]*(' + regex + ')' + '[\\,\\.\\-\\/\'\\s]*(\\d{2,4})\\s*$', 'i' );
 
                // Written Month name, mdy
-               ts.dateRegex[2] = new RegExp( '^\\s*(' + regex + ')' + '[\\,\\.\\-\\/\'\\s]*\\d{1,2}[\\,\\.\\-\\/\'\\s]*\\d{2,4}\\s*$', 'i' );
+               ts.dateRegex[2] = new RegExp( '^\\s*(' + regex + ')' + '[\\,\\.\\-\\/\'\\s]*(\\d{1,2})[\\,\\.\\-\\/\'\\s]*(\\d{2,4})\\s*$', 'i' );
 
        }
 
        function explodeRowspans( $table ) {
                // Split multi row cells into multiple cells with the same content
-               $table.find( '[rowspan]' ).each(function() {
+               $table.find( '> tbody > tr > [rowspan]' ).each(function () {
                        var rowSpan = this.rowSpan;
                        this.rowSpan = 1;
                        var cell = $( this );
                        var next = cell.parent().nextAll();
                        for ( var i = 0; i < rowSpan - 1; i++ ) {
-                               var td = next.eq( i ).find( 'td' );
+                               var td = next.eq( i ).children( 'td' );
                                if ( !td.length ) {
                                        next.eq( i ).append( cell.clone() );
                                } else if ( this.cellIndex === 0 ) {
                        },
 
                        dateRegex: [],
-                       monthNames: [],
+                       monthNames: {},
 
                        /**
                         * @param $tables {jQuery}
                         * @param settings {Object} (optional)
                         */
-                       construct: function( $tables, settings ) {
-                               return $tables.each( function( i, table ) {
-
-                                       // Quit if no thead or tbody.
-                                       if ( !table.tHead || !table.tBodies ) {
-                                               return;
-                                       }
+                       construct: function ( $tables, settings ) {
+                               return $tables.each( function ( i, table ) {
                                        // Declare and cache.
-                                       var     $document, $headers, cache, config, sortOrder,
+                                       var $document, $headers, cache, config, sortOrder,
                                                $table = $( table ),
                                                shiftDown = 0,
                                                firstTime = true;
 
+                                       // Quit if no tbody
+                                       if ( !table.tBodies ) {
+                                               return;
+                                       }
+                                       if ( !table.tHead ) {
+                                               // No thead found. Look for rows with <th>s and
+                                               // move them into a <thead> tag or a <tfoot> tag
+                                               emulateTHeadAndFoot( $table );
+
+                                               // Still no thead? Then quit
+                                               if ( !table.tHead ) {
+                                                       return;
+                                               }
+                                       }
+                                       $table.addClass( "jquery-tablesorter" );
+
                                        // New config object.
                                        table.config = {};
 
                                        cacheRegexs();
 
                                        // Apply event handling to headers
-                                       // this is to big, perhaps break it out?
-                                       $headers.click( function( e ) {
+                                       // this is too big, perhaps break it out?
+                                       $headers.click( function ( e ) {
+                                               if ( e.target.nodeName.toLowerCase() === 'a' ) {
+                                                       // The user clicked on a link inside a table header
+                                                       // Do nothing and let the default link click action continue
+                                                       return true;
+                                               }
 
                                                if ( firstTime ) {
                                                        firstTime = false;
-                                                       
+
                                                        // Legacy fix of .sortbottoms
                                                        // Wrap them inside inside a tfoot (because that's what they actually want to be) &
-                                                       // Move them up one level in the DOM
-                                                       var sortbottoms = $table.find('tr.sortbottom').wrap('<tfoot>');
-                                                       sortbottoms.parents('table').append(sortbottoms.parent());
-                                                       
+                                                       // and put the <tfoot> at the end of the <table>
+                                                       var $sortbottoms = $table.find( '> tbody > tr.sortbottom' );
+                                                       if ( $sortbottoms.length ) {
+                                                               var $tfoot = $table.children( 'tfoot' );
+                                                               if ( $tfoot.length ) {
+                                                                       $tfoot.eq(0).prepend( $sortbottoms );
+                                                               } else {
+                                                                       $table.append( $( '<tfoot>' ).append( $sortbottoms ) );
+                                                               }
+                                                       }
+
                                                        explodeRowspans( $table );
                                                        // try to auto detect column type, and store in tables config
                                                        table.config.parsers = buildParserCache( table, $headers );
-                                                       // build the cache for the tbody cells
-                                                       cache = buildCache( table );
                                                }
+
+                                               // Build the cache for the tbody cells
+                                               // to share between calculations for this sort action.
+                                               // Re-calculated each time a sort action is performed due to possiblity
+                                               // that sort values change. Shouldn't be too expensive, but if it becomes
+                                               // too slow an event based system should be implemented somehow where
+                                               // cells get event .change() and bubbles up to the <table> here
+                                               cache = buildCache( table );
+
                                                var totalRows = ( $table[0].tBodies[0] && $table[0].tBodies[0].rows.length ) || 0;
                                                if ( !table.sortDisabled && totalRows > 0 ) {
 
                                                                        for ( var j = 0; j < config.sortList.length; j++ ) {
                                                                                var s = config.sortList[j],
                                                                                        o = config.headerList[s[0]];
-                                                                               if ( s[0] == i ) {
+                                                                               if ( s[0] === i ) {
                                                                                        o.count = s[1];
                                                                                        o.count++;
                                                                                        s[1] = o.count % 2;
                                                }
 
                                        // Cancel selection
-                                       } ).mousedown( function() {
+                                       } ).mousedown( function () {
                                                if ( config.cancelSelection ) {
-                                                       this.onselectstart = function() {
+                                                       this.onselectstart = function () {
                                                                return false;
                                                        };
                                                        return false;
                                                }
                                        } );
-
                                } );
                        },
 
-                       addParser: function( parser ) {
-                               var     l = parsers.length,
+                       addParser: function ( parser ) {
+                               var l = parsers.length,
                                        a = true;
                                for ( var i = 0; i < l; i++ ) {
-                                       if ( parsers[i].id.toLowerCase() == parser.id.toLowerCase() ) {
+                                       if ( parsers[i].id.toLowerCase() === parser.id.toLowerCase() ) {
                                                a = false;
                                        }
                                }
                                }
                        },
 
-                       formatDigit: function( s ) {
+                       formatDigit: function ( s ) {
                                if ( ts.transformTable !== false ) {
-                                       var     out = '',
+                                       var out = '',
                                                c;
                                        for ( var p = 0; p < s.length; p++ ) {
                                                c = s.charAt(p);
                                return ( isNaN(i)) ? 0 : i;
                        },
 
-                       formatFloat: function( s ) {
+                       formatFloat: function ( s ) {
                                var i = parseFloat(s);
                                return ( isNaN(i)) ? 0 : i;
                        },
 
-                       formatInt: function( s ) {
+                       formatInt: function ( s ) {
                                var i = parseInt( s, 10 );
                                return ( isNaN(i)) ? 0 : i;
                        },
 
-                       clearTableBody: function( table ) {
+                       clearTableBody: function ( table ) {
                                if ( $.browser.msie ) {
-                                       var empty = function( el ) {
+                                       var empty = function ( el ) {
                                                while ( el.firstChild ) {
                                                        el.removeChild( el.firstChild );
                                                }
        ts = $.tablesorter;
 
        // Register as jQuery prototype method
-       $.fn.tablesorter = function( settings ) {
+       $.fn.tablesorter = function ( settings ) {
                return ts.construct( this, settings );
        };
 
        // Add default parsers
        ts.addParser( {
                id: 'text',
-               is: function( s ) {
+               is: function ( s ) {
                        return true;
                },
-               format: function( s ) {
+               format: function ( s ) {
                        s = $.trim( s.toLowerCase() );
                        if ( ts.collationRegex ) {
                                var tsc = ts.collationTable;
-                               s = s.replace( ts.collationRegex, function( match ) {
+                               s = s.replace( ts.collationRegex, function ( match ) {
                                        var r = tsc[match] ? tsc[match] : tsc[match.toUpperCase()];
                                        return r.toLowerCase();
                                } );
 
        ts.addParser( {
                id: 'IPAddress',
-               is: function( s ) {
+               is: function ( s ) {
                        return ts.rgx.IPAddress[0].test(s);
                },
-               format: function( s ) {
-                       var     a = s.split( '.' ),
+               format: function ( s ) {
+                       var a = s.split( '.' ),
                                r = '',
                                l = a.length;
                        for ( var i = 0; i < l; i++ ) {
                                var item = a[i];
-                               if ( item.length == 1 ) {
+                               if ( item.length === 1 ) {
                                        r += '00' + item;
-                               } else if ( item.length == 2 ) {
+                               } else if ( item.length === 2 ) {
                                        r += '0' + item;
                                } else {
                                        r += item;
 
        ts.addParser( {
                id: 'currency',
-               is: function( s ) {
+               is: function ( s ) {
                        return ts.rgx.currency[0].test(s);
                },
-               format: function( s ) {
+               format: function ( s ) {
                        return $.tablesorter.formatDigit( s.replace( ts.rgx.currency[1], '' ) );
                },
                type: 'numeric'
 
        ts.addParser( {
                id: 'url',
-               is: function( s ) {
+               is: function ( s ) {
                        return ts.rgx.url[0].test(s);
                },
-               format: function( s ) {
+               format: function ( s ) {
                        return $.trim( s.replace( ts.rgx.url[1], '' ) );
                },
                type: 'text'
 
        ts.addParser( {
                id: 'isoDate',
-               is: function( s ) {
+               is: function ( s ) {
                        return ts.rgx.isoDate[0].test(s);
                },
-               format: function( s ) {
+               format: function ( s ) {
                        return $.tablesorter.formatFloat((s !== '') ? new Date(s.replace(
                        new RegExp( /-/g), '/')).getTime() : '0' );
                },
 
        ts.addParser( {
                id: 'usLongDate',
-               is: function( s ) {
+               is: function ( s ) {
                        return ts.rgx.usLongDate[0].test(s);
                },
-               format: function( s ) {
+               format: function ( s ) {
                        return $.tablesorter.formatFloat( new Date(s).getTime() );
                },
                type: 'numeric'
 
        ts.addParser( {
                id: 'date',
-               is: function( s ) {
+               is: function ( s ) {
                        return ( ts.dateRegex[0].test(s) || ts.dateRegex[1].test(s) || ts.dateRegex[2].test(s ));
                },
-               format: function( s, table ) {
+               format: function ( s, table ) {
+                       var match;
                        s = $.trim( s.toLowerCase() );
 
-                       for ( var i = 1, j = 0; i < 13 && j < 2; i++ ) {
-                               s = s.replace( ts.monthNames[j][i], i );
-                               if ( i == 12 ) {
-                                       j++;
-                                       i = 0;
+                       if ( ( match = s.match( ts.dateRegex[0] ) ) !== null ) {
+                               if ( mw.config.get( 'wgDefaultDateFormat' ) === 'mdy' || mw.config.get( 'wgContentLanguage' ) === 'en' ) {
+                                       s = [ match[3], match[1], match[2] ];
+                               } else if ( mw.config.get( 'wgDefaultDateFormat' ) === 'dmy' ) {
+                                       s = [ match[3], match[2], match[1] ];
                                }
+                       } else if ( ( match = s.match( ts.dateRegex[1] ) ) !== null ) {
+                               s = [ match[3], '' + ts.monthNames[match[2]], match[1] ];
+                       } else if ( ( match = s.match( ts.dateRegex[2] ) ) !== null ) {
+                               s = [ match[3], '' + ts.monthNames[match[1]], match[2] ];
+                       } else {
+                               // Should never get here
+                               return '99999999';
                        }
 
-                       s = s.replace( /[\-\.\,' ]/g, '/' );
-
-                       // Replace double slashes
-                       s = s.replace( /\/\//g, '/' );
-                       s = s.replace( /\/\//g, '/' );
-                       s = s.split( '/' );
-
                        // Pad Month and Day
-                       if ( s[0] && s[0].length == 1 ) {
-                               s[0] = '0' + s[0];
-                       }
-                       if ( s[1] && s[1].length == 1 ) {
+                       if ( s[1].length === 1 ) {
                                s[1] = '0' + s[1];
                        }
-                       var y;
+                       if ( s[2].length === 1 ) {
+                               s[2] = '0' + s[2];
+                       }
 
-                       if ( !s[2] ) {
-                               // Fix yearless dates
-                               s[2] = 2000;
-                       } else if ( ( y = parseInt( s[2], 10) ) < 100 ) {
+                       var y;
+                       if ( ( y = parseInt( s[0], 10) ) < 100 ) {
                                // Guestimate years without centuries
                                if ( y < 30 ) {
-                                       s[2] = 2000 + y;
+                                       s[0] = 2000 + y;
                                } else {
-                                       s[2] = 1900 + y;
+                                       s[0] = 1900 + y;
                                }
                        }
-                       // Resort array depending on preferences
-                       if ( mw.config.get( 'wgDefaultDateFormat' ) == 'mdy' || mw.config.get( 'wgContentLanguage' ) == 'en' ) {
-                               s.push( s.shift() );
-                               s.push( s.shift() );
-                       } else if ( mw.config.get( 'wgDefaultDateFormat' ) == 'dmy' ) {
-                               var d = s.shift();
-                               s.push( s.shift() );
-                               s.push(d);
+                       while ( s[0].length < 4 ) {
+                               s[0] = '0' + s[0];
                        }
                        return parseInt( s.join( '' ), 10 );
                },
 
        ts.addParser( {
                id: 'time',
-               is: function( s ) {
+               is: function ( s ) {
                        return ts.rgx.time[0].test(s);
                },
-               format: function( s ) {
+               format: function ( s ) {
                        return $.tablesorter.formatFloat( new Date( '2000/01/01 ' + s ).getTime() );
                },
                type: 'numeric'
 
        ts.addParser( {
                id: 'number',
-               is: function( s, table ) {
+               is: function ( s, table ) {
                        return $.tablesorter.numberRegex.test( $.trim( s ));
                },
-               format: function( s ) {
+               format: function ( s ) {
                        return $.tablesorter.formatDigit(s);
                },
                type: 'numeric'
        } );
 
-} )( jQuery );
+}( jQuery, mediaWiki ) );