(bug 41886) make $.tablesorter support headers with colspan
authorMatmaRex <matma.rex@gmail.com>
Fri, 9 Nov 2012 16:47:29 +0000 (17:47 +0100)
committerMatmaRex <matma.rex@gmail.com>
Sat, 24 Nov 2012 17:19:29 +0000 (18:19 +0100)
Previously, if a header had colspan set, all headers after it would sort
wrong columns (shifted by the cumulating colspans). This fixes the issue,
from now on every header sorts on all columns it spans over (using the
multi-column sorting funcitonality already present in $.tablesorter).

Change-Id: I89766c9640b83ca38f41d698d1ef052a9e3f68d3

resources/jquery/jquery.tablesorter.js
tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js

index 7a1bd27..6297f9a 100644 (file)
                return false;
        }
 
-       function setHeadersCss( table, $headers, list, css, msg ) {
+       function setHeadersCss( table, $headers, list, css, msg, columnToHeader ) {
                // Remove all header information and reset titles to default message
                $headers.removeClass( css[0] ).removeClass( css[1] ).attr( 'title', msg[1] );
 
-               var h = [];
-               $headers.each( function () {
-                       if ( !this.sortDisabled ) {
-                               h[this.column] = $( this );
-                       }
-               } );
-
-               var l = list.length;
-               for ( var i = 0; i < l; i++ ) {
-                       h[ list[i][0] ].addClass( css[ list[i][1] ] ).attr( 'title', msg[ list[i][1] ] );
+               for ( var i = 0; i < list.length; i++ ) {
+                       $headers.eq( columnToHeader[ list[i][0] ] )
+                               .addClass( css[ list[i][1] ] )
+                               .attr( 'title', msg[ list[i][1] ] );
                }
        }
 
                                return $tables.each( function ( i, table ) {
                                        // Declare and cache.
                                        var $headers, cache, config,
+                                               headerToColumns, columnToHeader, colspanOffset,
                                                $table = $( table ),
                                                firstTime = true;
 
                                                table.config.parsers = buildParserCache( table, $headers );
                                        }
 
+                                       // as each header can span over multiple columns (using colspan=N),
+                                       // we have to bidirectionally map headers to their columns and columns to their headers
+                                       headerToColumns = [];
+                                       columnToHeader = [];
+                                       colspanOffset = 0;
+                                       $headers.each( function ( headerIndex ) {
+                                               var columns = [];
+                                               for ( var i = 0; i < this.colSpan; i++ ) {
+                                                       columnToHeader[ colspanOffset + i ] = headerIndex;
+                                                       columns.push( colspanOffset + i );
+                                               }
+
+                                               headerToColumns[ headerIndex ] = columns;
+                                               colspanOffset += this.colSpan;
+                                       } );
+
                                        // Apply event handling to headers
                                        // this is too big, perhaps break it out?
                                        $headers.filter( ':not(.unsortable)' ).click( function ( e ) {
 
                                                var totalRows = ( $table[0].tBodies[0] && $table[0].tBodies[0].rows.length ) || 0;
                                                if ( !table.sortDisabled && totalRows > 0 ) {
-
-                                                       // Get current column index
-                                                       var i = this.column;
-
                                                        // Get current column sort order
                                                        this.order = this.count % 2;
                                                        this.count++;
 
-                                                       // User only wants to sort on one column
-                                                       if ( !e[config.sortMultiSortKey] ) {
-                                                               // Flush the sort list
-                                                               config.sortList = [];
-                                                               // Add column to sort list
-                                                               config.sortList.push( [i, this.order] );
+                                                       var cell = this;
+                                                       // Get current column index
+                                                       var columns = headerToColumns[this.column];
+                                                       var newSortList = $.map( columns, function (c) {
+                                                               // jQuery "helpfully" flattens the arrays...
+                                                               return [[c, cell.order]];
+                                                       });
+                                                       // Index of first column belonging to this header
+                                                       var i = columns[0];
 
-                                                       // Multi column sorting
+                                                       if ( !e[config.sortMultiSortKey] ) {
+                                                               // User only wants to sort on one column set
+                                                               // Flush the sort list and add new columns
+                                                               config.sortList = newSortList;
                                                        } else {
-                                                               // The user has clicked on an already sorted column.
+                                                               // Multi column sorting
+                                                               // It is not possible for one column to belong to multiple headers,
+                                                               // so this is okay - we don't need to check for every value in the columns array
                                                                if ( isValueInArray( i, config.sortList ) ) {
+                                                                       // The user has clicked on an already sorted column.
                                                                        // Reverse the sorting direction for all tables.
                                                                        for ( var j = 0; j < config.sortList.length; j++ ) {
                                                                                var s = config.sortList[j],
                                                                                        o = config.headerList[s[0]];
-                                                                               if ( s[0] === i ) {
+                                                                               if ( isValueInArray( s[0], newSortList ) ) {
                                                                                        o.count = s[1];
                                                                                        o.count++;
                                                                                        s[1] = o.count % 2;
                                                                                }
                                                                        }
                                                                } else {
-                                                                       // Add column to sort list array
-                                                                       config.sortList.push( [i, this.order] );
+                                                                       // Add columns to sort list array
+                                                                       config.sortList = config.sortList.concat( newSortList );
                                                                }
                                                        }
 
                                                        // Set CSS for headers
-                                                       setHeadersCss( $table[0], $headers, config.sortList, sortCSS, sortMsg );
+                                                       setHeadersCss( $table[0], $headers, config.sortList, sortCSS, sortMsg, columnToHeader );
                                                        appendToTable(
                                                                $table[0], multisort( $table[0], config.sortList, cache )
                                                        );
                                                cache = buildCache( table );
 
                                                // set css for headers
-                                               setHeadersCss( table, $headers, sortList, sortCSS, sortMsg );
+                                               setHeadersCss( table, $headers, sortList, sortCSS, sortMsg, columnToHeader );
 
                                                // sort the table and append it to the dom
                                                appendToTable( table, multisort( table, sortList, cache ) );
index ef71dd0..0000f0c 100644 (file)
                );
        } );
 
+       // Sorting with colspans
+       header = [ 'column1a' , 'column1b', 'column1c', 'column2' ];
+       var
+               aaa1 = [ 'A', 'A', 'A', '1' ],
+               aab5 = [ 'A', 'A', 'B', '5' ],
+               abc3 = [ 'A', 'B', 'C', '3' ],
+               bbc2 = [ 'B', 'B', 'C', '2' ],
+               caa4 = [ 'C', 'A', 'A', '4' ];
+       // initial is already declared above
+       initial = [ aab5, aaa1, abc3, bbc2, caa4 ];
+       tableTest( 'Sorting with colspanned headers: spanned column',
+               header,
+               initial,
+               [ aaa1, aab5, abc3, bbc2, caa4 ],
+               function ( $table ) {
+                       // Make colspanned header for test
+                       $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
+                       $table.find( 'tr:eq(0) th:eq(0)' ).prop( 'colspan', '3' );
+
+                       $table.tablesorter();
+                       $table.find( '.headerSort:eq(0)' ).click();
+               }
+       );
+       tableTest( 'Sorting with colspanned headers: subsequent column',
+               header,
+               initial,
+               [ aaa1, bbc2, abc3, caa4, aab5 ],
+               function ( $table ) {
+                       // Make colspanned header for test
+                       $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
+                       $table.find( 'tr:eq(0) th:eq(0)' ).prop( 'colspan', '3' );
+
+                       $table.tablesorter();
+                       $table.find( '.headerSort:eq(1)' ).click();
+               }
+       );
+
        // Regression tests!
        tableTest(
                'Bug 28775: German-style (dmy) short numeric dates',