From: Leo Koppelkamm Date: Thu, 14 Apr 2011 21:47:00 +0000 (+0000) Subject: Completely rewritten table sorting script. X-Git-Tag: 1.31.0-rc.0~30830 X-Git-Url: http://git.cyclocoop.org/%22.%24h.%22?a=commitdiff_plain;h=7d6ddfe836310c89469b339e18fef21114d80e70;p=lhc%2Fweb%2Fwiklou.git Completely rewritten table sorting script. Fixes Bug 8028, Bug 8115, Bug 15406, Bug 17141, Bug 8732 1. Sites can specify custom collations. The script accepts an object "tableSorterCollation" which contains a lookup table, how specific characters should be treated. For example, after setting "tableSorterCollation={'ä':'ae', 'ß':'ss'};" in the site's common.js any string containing an ä or Ä will be sorted as if it were a 'ae'. 2. Table rows can be forced to use a specific data type. By setting class="sort-{Parsername}", the row will be parsed with the specified algorithm. class="sort-date" would force date sorting etc. The following parsers are available: text, IPAddress, number, url, currency, date, isoDate, usLongDate, time 3. Execution time is reduced by half or more. Sorting a 935 row * 8 columns table: Browser Before After -------- ------ ----- Chrome 10 90ms 42ms Safari 5 115ms 48ms Firefox 4 412ms 87ms IE8 720ms 115ms 4. Based on the content language and the mdy vs dmy preference, the parser can understand dates such as "17. März '11". wgMonthNames=[] and wgMonthNamesShort=[] in the content language and the mdy vs dmy preference are exported to js; A table containing the following dates would be sorted correctly: 17. Jan. 01 23 Feb 1992 9.02.05 13 November 2001 14 Oktober '76 Was tested in ie6-8, chrome, safari 5, ff3 & ff4 --- diff --git a/includes/resourceloader/ResourceLoaderStartUpModule.php b/includes/resourceloader/ResourceLoaderStartUpModule.php index 266dcb1616..6e0357e9a2 100644 --- a/includes/resourceloader/ResourceLoaderStartUpModule.php +++ b/includes/resourceloader/ResourceLoaderStartUpModule.php @@ -54,6 +54,17 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { ); $mainPage = Title::newMainPage(); + #$localDateFormats = $wgContLang->getDateFormats(); + #$localPreferedFormat = $localDateFormats[$wgContLang->getDefaultDateFormat().' date']; + + $monthNames = array(''); + $monthNamesShort = array(''); + for ($i=1; $i < 13; $i++) { + $monthNames[]=$wgContLang->getMonthName($i); + $monthNamesShort[]=$wgContLang->getMonthAbbreviation($i); + } + + #$localPreferedFormat = $localDateFormats['dmy date']; // Build list of variables $vars = array( 'wgLoadScript' => $wgLoadScript, @@ -73,6 +84,9 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { 'wgVersion' => $wgVersion, 'wgEnableAPI' => $wgEnableAPI, 'wgEnableWriteAPI' => $wgEnableWriteAPI, + 'wgDefaultDateFormat' => $wgContLang->getDefaultDateFormat(), + 'wgMonthNames' => $monthNames, + 'wgMonthNamesShort' => $monthNamesShort, 'wgSeparatorTransformTable' => $compactSeparatorTransTable, 'wgDigitTransformTable' => $compactDigitTransTable, 'wgMainPageTitle' => $mainPage ? $mainPage->getPrefixedText() : null, diff --git a/resources/Resources.php b/resources/Resources.php index c2253cd14e..37f8fc08fb 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -129,6 +129,9 @@ return array( 'jquery.tabIndex' => array( 'scripts' => 'resources/jquery/jquery.tabIndex.js', ), + 'jquery.tablesorter' => array( + 'scripts' => 'resources/jquery/jquery.tablesorter.js' + ), 'jquery.textSelection' => array( 'scripts' => 'resources/jquery/jquery.textSelection.js', ), @@ -395,6 +398,7 @@ return array( 'jquery.messageBox', 'jquery.makeCollapsible', 'jquery.placeholder', + 'jquery.tablesorter', ), 'debugScripts' => 'resources/mediawiki.util/mediawiki.util.test.js', ), diff --git a/resources/jquery/jquery.tablesorter.js b/resources/jquery/jquery.tablesorter.js new file mode 100644 index 0000000000..31da6d4795 --- /dev/null +++ b/resources/jquery/jquery.tablesorter.js @@ -0,0 +1,937 @@ +/* + * + * TableSorter for MediaWiki + * + * Written 2011 Leo Koppelkamm + * Based on tablesorter.com plugin, written (c) 2007 Christian Bach. + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + */ +/** + * + * @description Create a sortable table with multi-column sorting capabilitys + * + * @example $( 'table' ).tablesorter(); + * @desc Create a simple tablesorter interface. + * + * @option String cssHeader ( optional ) A string of the class name to be appended + * to sortable tr elements in the thead of the table. Default value: + * "header" + * + * @option String cssAsc ( optional ) A string of the class name to be appended to + * sortable tr elements in the thead on a ascending sort. Default value: + * "headerSortUp" + * + * @option String cssDesc ( optional ) A string of the class name to be appended + * to sortable tr elements in the thead on a descending sort. Default + * value: "headerSortDown" + * + * @option String sortInitialOrder ( optional ) A string of the inital sorting + * order can be asc or desc. Default value: "asc" + * + * @option String sortMultisortKey ( optional ) A string of the multi-column sort + * key. Default value: "shiftKey" + * + * @option Boolean sortLocaleCompare ( optional ) Boolean flag indicating whatever + * to use String.localeCampare method or not. Set to false. + * + * @option Boolean cancelSelection ( optional ) Boolean flag indicating if + * tablesorter should cancel selection of the table headers text. + * Default value: true + * + * @option Boolean debug ( optional ) Boolean flag indicating if tablesorter + * should display debuging information usefull for development. + * + * @type jQuery + * + * @name tablesorter + * + * @cat Plugins/Tablesorter + * + * @author Christian Bach/christian.bach@polyester.se + */ + +( function ($) { + $.extend( { + tablesorter: new + + function () { + + var parsers = []; + + this.defaults = { + cssHeader: "headerSort", + cssAsc: "headerSortUp", + cssDesc: "headerSortDown", + cssChildRow: "expand-child", + sortInitialOrder: "asc", + sortMultiSortKey: "shiftKey", + sortLocaleCompare: false, + parsers: {}, + widgets: [], + headers: {}, + cancelSelection: true, + sortList: [], + headerList: [], + selectorHeaders: 'thead tr:eq(0) th', + debug: false + }; + + /* debuging utils */ + // + // function benchmark( s, d ) { + // alert( s + "," + ( new Date().getTime() - d.getTime() ) + "ms" ); + // } + // + // this.benchmark = benchmark; + // + // + /* parsers utils */ + + function buildParserCache( table, $headers ) { + var rows = table.tBodies[0].rows; + + if ( rows[0] ) { + + var list = [], + cells = rows[0].cells, + l = cells.length; + + for ( var i = 0; i < l; i++ ) { + + if ( $headers.eq(i).is( '[class*="sort-"]' ) ) { + p = getParserById($headers.eq(i).attr('class').replace(/.*?sort-(.*?) .*/, '$1')); + } else { + p = detectParserForColumn( table, rows, -1, i ); + } + // if ( table.config.debug ) { + // console.log( "column:" + i + " parser:" + p.id + "\n" ); + // } + list.push(p); + } + } + return list; + } + + function detectParserForColumn( table, rows, rowIndex, cellIndex ) { + var l = parsers.length, + node = false, + nodeValue = false, + keepLooking = true; + while ( nodeValue == '' && keepLooking ) { + rowIndex++; + if ( rows[rowIndex] ) { + node = getNodeFromRowAndCellIndex( rows, rowIndex, cellIndex ); + nodeValue = trimAndGetNodeText( table.config, node ); + // if ( table.config.debug ) { + // console.log( 'Checking if value was empty on row:' + rowIndex ); + // } + } else { + keepLooking = false; + } + } + for ( var i = 1; i < l; i++ ) { + if ( parsers[i].is( nodeValue, table, node ) ) { + return parsers[i]; + } + } + // 0 is always the generic parser ( text ) + return parsers[0]; + } + + function getNodeFromRowAndCellIndex( rows, rowIndex, cellIndex ) { + return rows[rowIndex].cells[cellIndex]; + } + + function trimAndGetNodeText( config, node ) { + return $.trim( getElementText( config, node ) ); + } + + function getParserById( name ) { + var l = parsers.length; + for ( var i = 0; i < l; i++ ) { + if ( parsers[i].id.toLowerCase() == name.toLowerCase() ) { + return parsers[i]; + } + } + return false; + } + + /* utils */ + + function buildCache( table ) { + + // if ( table.config.debug ) { + // var cacheTime = new Date(); + // } + 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 = { + row: [], + normalized: [] + }; + + for ( var i = 0; i < totalRows; ++i ) { + + // Add the table data to main data array + var c = $( table.tBodies[0].rows[i] ), + cols = []; + + // if this is a child row, add it to the last row's children and + // continue to the next row + if ( c.hasClass( table.config.cssChildRow ) ) { + cache.row[cache.row.length - 1] = cache.row[cache.row.length - 1].add(c); + // go to the next for loop + continue; + } + + cache.row.push(c); + + for ( var j = 0; j < totalCells; ++j ) { + cols.push( parsers[j].format( getElementText( table.config, c[0].cells[j] ), table, c[0].cells[j] ) ); + } + + cols.push( cache.normalized.length ); // add position for rowCache + cache.normalized.push( cols ); + cols = null; + } + + // if ( table.config.debug ) { + // benchmark( "Building cache for " + totalRows + " rows:", cacheTime ); + // } + return cache; + } + + function getElementText( config, node ) { + return $( node ).text(); + } + + function appendToTable( table, cache ) { + + // if ( table.config.debug ) { + // var appendTime = new Date() + // } + var c = cache, + r = c.row, + n = c.normalized, + totalRows = n.length, + checkCell = (n[0].length - 1), + tableBody = $( table.tBodies[0] ), + fragment = document.createDocumentFragment(); + + for ( var i = 0; i < totalRows; i++ ) { + var pos = n[i][checkCell]; + + var l = r[pos].length; + + for ( var j = 0; j < l; j++ ) { + fragment.appendChild( r[pos][j] ); + } + + } + tableBody[0].appendChild( fragment ); + // if ( table.config.debug ) { + // benchmark( "Rebuilt table:", appendTime ); + // } + } + + function buildHeaders( table ) { + + // if ( table.config.debug ) { + // var time = new Date(); + // } + //var header_index = computeTableHeaderCellIndexes( table ); + var realCellIndex = 0; + + $tableHeaders = $( table.config.selectorHeaders, table ).each( function ( index ) { + //var normalIndex = allCells.index( this ); + //var realCellIndex = 0; + this.column = realCellIndex; + + var colspan = this.colspan; + colspan = colspan ? parseInt( colspan ) : 1; + realCellIndex += colspan; + + //this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex]; + this.order = 0; + this.count = 0; + + if ( $( this ).is( '.unsortable' ) ) this.sortDisabled = true; + + if ( !this.sortDisabled ) { + var $th = $( this ).addClass( table.config.cssHeader ); + //if ( table.config.onRenderHeader ) table.config.onRenderHeader.apply($th); + } + + // add cell to headerList + table.config.headerList[index] = this; + } ); + + // if ( table.config.debug ) { + // benchmark( "Built headers:", time ); + // console.log( $tableHeaders ); + // } + // + return $tableHeaders; + + } + + // // from: + // // http://www.javascripttoolbox.com/lib/table/examples.php + // // http://www.javascripttoolbox.com/temp/table_cellindex.html + // + // function computeTableHeaderCellIndexes(t) { + // var matrix = []; + // var lookup = {}; + // var thead = t.getElementsByTagName( 'THEAD' )[0]; + // var trs = thead.getElementsByTagName( 'TR' ); + // + // for ( var i = 0; i < trs.length; i++ ) { + // var cells = trs[i].cells; + // for ( var j = 0; j < cells.length; j++ ) { + // var c = cells[j]; + // + // var rowIndex = c.parentNode.rowIndex; + // var cellId = rowIndex + "-" + c.cellIndex; + // var rowSpan = c.rowSpan || 1; + // var colSpan = c.colSpan || 1; + // var firstAvailCol; + // if ( typeof( matrix[rowIndex] ) == "undefined" ) { + // matrix[rowIndex] = []; + // } + // // Find first available column in the first row + // for ( var k = 0; k < matrix[rowIndex].length + 1; k++ ) { + // if ( typeof( matrix[rowIndex][k] ) == "undefined" ) { + // firstAvailCol = k; + // break; + // } + // } + // lookup[cellId] = firstAvailCol; + // for ( var k = rowIndex; k < rowIndex + rowSpan; k++ ) { + // if ( typeof( matrix[k] ) == "undefined" ) { + // matrix[k] = []; + // } + // var matrixrow = matrix[k]; + // for ( var l = firstAvailCol; l < firstAvailCol + colSpan; l++ ) { + // matrixrow[l] = "x"; + // } + // } + // } + // } + // return lookup; + // } + // function checkCellColSpan( table, rows, row ) { + // var arr = [], + // r = table.tHead.rows, + // c = r[row].cells; + // + // for ( var i = 0; i < c.length; i++ ) { + // var cell = c[i]; + // + // if ( cell.colSpan > 1 ) { + // arr = arr.concat( checkCellColSpan( table, headerArr, row++ ) ); + // } else { + // if ( table.tHead.length == 1 || ( cell.rowSpan > 1 || !r[row + 1] ) ) { + // arr.push( cell ); + // } + // // headerArr[row] = ( i+row ); + // } + // } + // return arr; + // } + // + // function checkHeaderOptions( table, i ) { + // if ( ( table.config.headers[i] ) && ( table.config.headers[i].sorter === false ) ) { + // return true; + // } + // return false; + // } + // function formatSortingOrder(v) { + // if ( typeof(v) != "Number" ) { + // return ( v.toLowerCase() == "desc" ) ? 1 : 0; + // } else { + // return ( v == 1 ) ? 1 : 0; + // } + // } + + function isValueInArray( v, a ) { + var l = a.length; + for ( var i = 0; i < l; i++ ) { + if ( a[i][0] == v ) { + return true; + } + } + return false; + } + + function setHeadersCss( table, $headers, list, css ) { + // remove all header information + $headers.removeClass( css[0] ).removeClass( css[1] ); + + var h = []; + $headers.each( function ( offset ) { + 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]] ); + } + } + + // function updateHeaderSortCount( table, sortList ) { + // var c = table.config, + // l = sortList.length; + // for ( var i = 0; i < l; i++ ) { + // var s = sortList[i], + // o = c.headerList[s[0]]; + // o.count = s[1]; + // o.count++; + // } + // } + /* sorting methods */ + + function multisort( table, sortList, cache ) { + // if ( table.config.debug ) { + // var sortTime = new Date(); + // } + var dynamicExp = "var sortWrapper = function(a,b) {", + l = sortList.length; + + // TODO: inline functions. + for ( var i = 0; i < l; i++ ) { + + var c = sortList[i][0]; + var order = sortList[i][1]; + var s = ""; + if ( table.config.parsers[c].type == "text" ) { + if ( order == 0 ) { + s = makeSortFunction( "text", "asc", c ); + } else { + s = makeSortFunction( "text", "desc", c ); + } + } else { + if ( order == 0 ) { + s = makeSortFunction( "numeric", "asc", c ); + } else { + s = makeSortFunction( "numeric", "desc", c ); + } + } + var e = "e" + i; + + dynamicExp += "var " + e + " = " + s; // + "(a[" + c + "],b[" + c + "]); "; + dynamicExp += "if(" + e + ") { return " + e + "; } "; + dynamicExp += "else { "; + } + + // if value is the same keep original order + var orgOrderCol = cache.normalized[0].length - 1; + dynamicExp += "return a[" + orgOrderCol + "]-b[" + orgOrderCol + "];"; + + for ( var i = 0; i < l; i++ ) { + dynamicExp += "}; "; + } + + dynamicExp += "return 0; "; + dynamicExp += "}; "; + + // if ( table.config.debug ) { + // benchmark( "Evaling expression:" + dynamicExp, new Date() ); + // } + eval( dynamicExp ); + cache.normalized.sort( sortWrapper ); + + // if ( table.config.debug ) { + // benchmark( "Sorting on " + sortList.toString() + " and dir " + order + " time:", sortTime ); + // } + return cache; + } + + function makeSortFunction( type, direction, index ) { + var a = "a[" + index + "]", + b = "b[" + index + "]"; + if (type == 'text' && direction == 'asc') { + return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + a + " < " + b + ") ? -1 : 1 )));"; + } else if (type == 'text' && direction == 'desc') { + return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + b + " < " + a + ") ? -1 : 1 )));"; + } else if (type == 'numeric' && direction == 'asc') { + return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + a + " - " + b + "));"; + } else if (type == 'numeric' && direction == 'desc') { + return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + b + " - " + a + "));"; + } + } + + function makeSortText(i) { + return "((a[" + i + "] < b[" + i + "]) ? -1 : ((a[" + i + "] > b[" + i + "]) ? 1 : 0));"; + } + + function makeSortTextDesc(i) { + return "((b[" + i + "] < a[" + i + "]) ? -1 : ((b[" + i + "] > a[" + i + "]) ? 1 : 0));"; + } + + function makeSortNumeric(i) { + return "a[" + i + "]-b[" + i + "];"; + } + + function makeSortNumericDesc(i) { + return "b[" + i + "]-a[" + i + "];"; + } + + function sortText( a, b ) { + if ( table.config.sortLocaleCompare ) return a.localeCompare(b); + return ((a < b) ? -1 : ((a > b) ? 1 : 0)); + } + + function sortTextDesc( a, b ) { + if ( table.config.sortLocaleCompare ) return b.localeCompare(a); + return ((b < a) ? -1 : ((b > a) ? 1 : 0)); + } + + function sortNumeric( a, b ) { + return a - b; + } + + function sortNumericDesc( a, b ) { + return b - a; + } + + function buildTransformTable() { + var digits = '0123456789,.'.split(''); + + if ( typeof wgSeparatorTransformTable == 'undefined' || ( wgSeparatorTransformTable[0] == '' && wgDigitTransformTable[2] == '' ) ) { + ts.transformTable = false; + } else { + ts.transformTable = {}; + + // Unpack the transform table + var ascii = wgSeparatorTransformTable[0].split( "\t" ).concat( wgDigitTransformTable[0].split( "\t" ) ); + var localised = wgSeparatorTransformTable[1].split( "\t" ).concat( wgDigitTransformTable[1].split( "\t" ) ); + + // Construct regex for number identification + for ( var i = 0; i < ascii.length; i++ ) { + ts.transformTable[localised[i]] = ascii[i]; + digits.push( $.escapeRE( localised[i] ) ); + } + } + var digitClass = '[' + digits.join( '', digits ) + ']'; + + // We allow a trailing percent sign, which we just strip. This works fine + // if percents and regular numbers aren't being mixed. + ts.numberRegex = new RegExp("^(" + "[-+\u2212]?[0-9][0-9,]*(\\.[0-9,]*)?(E[-+\u2212]?[0-9][0-9,]*)?" + // Fortran-style scientific + "|" + "[-+\u2212]?" + digitClass + "+%?" + // Generic localised + ")$", "i"); + } + + function buildDateTable() { + var r = ''; + ts.monthNames = [ + [], + [] + ]; + ts.dateRegex = []; + + for ( i = 1; i < 13; i++ ) { + ts.monthNames[0][i] = wgMonthNames[i].toLowerCase(); + ts.monthNames[1][i] = wgMonthNamesShort[i].toLowerCase().replace( '.', '' ); + r += $.escapeRE( ts.monthNames[0][i] ) + '|'; + r += $.escapeRE( ts.monthNames[1][i] ) + '|'; + } + + //Remove trailing pipe + r = r.slice( 0, -1 ); + + //Build RegEx + //Any date formated with . , ' - or / + ts.dateRegex[0] = new RegExp(/^\s*\d{1,2}[\,\.\-\/'\s]*\d{1,2}[\,\.\-\/'\s]*\d{2,4}\s*?/i); + + //Written Month name, dmy + ts.dateRegex[1] = new RegExp('^\\s*\\d{1,2}[\\,\\.\\-\\/\'\\s]*(' + r + ')' + '[\\,\\.\\-\\/\'\\s]*\\d{2,4}\\s*$', 'i'); + + //Written Month name, mdy + ts.dateRegex[2] = new RegExp('^\\s*(' + r + ')' + '[\\,\\.\\-\\/\'\\s]*\\d{1,2}[\\,\\.\\-\\/\'\\s]*\\d{2,4}\\s*$', 'i'); + + } + + function buildCollationTable() { + if ( typeof tableSorterCollation == "object" ) { + ts.collationRegex = []; + + //Build array of key names + for ( var key in tableSorterCollation ) { + if ( tableSorterCollation.hasOwnProperty(key) ) { //to be safe + ts.collationRegex.push(key); + } + } + ts.collationRegex = new RegExp( '[' + ts.collationRegex.join('') + ']', 'ig' ); + } + } + + function cacheRegexs() { + ts.rgx = { + IPAddress: [new RegExp(/^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/)], + currency: [new RegExp(/^[£$€?.]/), new RegExp(/[£$€]/g)], + url: [new RegExp(/^(https?|ftp|file):\/\/$/), new RegExp(/(https?|ftp|file):\/\//)], + isoDate: [new RegExp(/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/)], + 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)))$/)], + time: [new RegExp(/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/)] + }; + } /* public methods */ + this.construct = function ( settings ) { + return this.each( function () { + // if no thead or tbody quit. + if ( !this.tHead || !this.tBodies ) return; + // declare + var $this, $document, $headers, cache, config, shiftDown = 0, + sortOrder; + + // new blank config object + this.config = {}; + // merge and extend. + config = $.extend( this.config, $.tablesorter.defaults, settings ); + + // store common expression for speed + $this = $( this ); + // save the settings where they read + $.data( this, "tablesorter", config ); + // build headers + $headers = buildHeaders( this ); + // Grab and process locale settings + buildTransformTable(); + buildDateTable(); + buildCollationTable(); + + //Precaching regexps can bring 10 fold + //performance improvements in some browsers + cacheRegexs(); + + // try to auto detect column type, and store in tables config + this.config.parsers = buildParserCache( this, $headers ); + // build the cache for the tbody cells + cache = buildCache( this ); + // get the css class names, could be done else where. + var sortCSS = [config.cssDesc, config.cssAsc]; + // apply event handling to headers + // this is to big, perhaps break it out? + $headers.click( + + function (e) { + //var clickTime= new Date(); + var totalRows = ( $this[0].tBodies[0] && $this[0].tBodies[0].rows.length ) || 0; + if ( !this.sortDisabled && totalRows > 0 ) { + // Only call sortStart if sorting is + // enabled. + //$this.trigger( "sortStart" ); + // store exp, for speed + var $cell = $( this ); + // get current column index + var i = this.column; + // get current column sort order + this.order = this.count % 2; + this.count++; + // user only whants 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] ); + // multi column sorting + } else { + // the user has clicked on an all + // ready sortet column. + if ( isValueInArray( i, config.sortList ) ) { + // revers 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 ) { + o.count = s[1]; + o.count++; + s[1] = o.count % 2; + } + } + } else { + // add column to sort list array + config.sortList.push( [i, this.order] ); + } + } + setTimeout( function () { + // set css for headers + setHeadersCss( $this[0], $headers, config.sortList, sortCSS ); + appendToTable( + $this[0], multisort( + $this[0], config.sortList, cache ) ); + //benchmark( "Sorting " + totalRows + " rows:", clickTime ); + }, 1 ); + // stop normal event by returning false + return false; + } + // cancel selection + } ).mousedown( function () { + if ( config.cancelSelection ) { + this.onselectstart = function () { + return false; + }; + return false; + } + } ); + // apply easy methods that trigger binded events + //Can't think of any use for these in a mw context + // $this.bind( "update", function () { + // var me = this; + // setTimeout( function () { + // // rebuild parsers. + // me.config.parsers = buildParserCache( + // me, $headers ); + // // rebuild the cache map + // cache = buildCache(me); + // }, 1 ); + // } ).bind( "updateCell", function ( e, cell ) { + // var config = this.config; + // // get position from the dom. + // var pos = [( cell.parentNode.rowIndex - 1 ), cell.cellIndex]; + // // update cache + // cache.normalized[pos[0]][pos[1]] = config.parsers[pos[1]].format( + // getElementText( config, cell ), cell ); + // } ).bind( "sorton", function ( e, list ) { + // $( this ).trigger( "sortStart" ); + // config.sortList = list; + // // update and store the sortlist + // var sortList = config.sortList; + // // update header count index + // updateHeaderSortCount( this, sortList ); + // // set css for headers + // setHeadersCss( this, $headers, sortList, sortCSS ); + // // sort the table and append it to the dom + // appendToTable( this, multisort( this, sortList, cache ) ); + // } ).bind( "appendCache", function () { + // appendToTable( this, cache ); + // } ); + } ); + }; + this.addParser = function ( parser ) { + var l = parsers.length, + a = true; + for ( var i = 0; i < l; i++ ) { + if ( parsers[i].id.toLowerCase() == parser.id.toLowerCase() ) { + a = false; + } + } + if (a) { + parsers.push( parser ); + } + }; + this.formatDigit = function (s) { + if ( ts.transformTable != false ) { + var out = '', + c; + for ( var p = 0; p < s.length; p++ ) { + c = s.charAt(p); + if ( c in ts.transformTable ) { + out += ts.transformTable[c]; + } else { + out += c; + } + } + s = out; + } + var i = parseFloat( s.replace(/[, ]/g, '').replace( "\u2212", '-' ) ); + return ( isNaN(i)) ? 0 : i; + }; + this.formatFloat = function (s) { + var i = parseFloat(s); + return ( isNaN(i)) ? 0 : i; + }; + this.formatInt = function (s) { + var i = parseInt(s); + return ( isNaN(i)) ? 0 : i; + }; + this.clearTableBody = function ( table ) { + if ( $.browser.msie ) { + function empty() { + while ( this.firstChild ) + this.removeChild( this.firstChild ); + } + empty.apply( table.tBodies[0] ); + } else { + table.tBodies[0].innerHTML = ""; + } + }; + } + } ); + + // extend plugin scope + $.fn.extend( { + tablesorter: $.tablesorter.construct + } ); + + // make shortcut + var ts = $.tablesorter; + + // add default parsers + ts.addParser( { + id: "text", + is: function (s) { + return true; + }, + format: function (s) { + s = $.trim( s.toLowerCase() ); + if ( ts.collationRegex ) { + var tsc = tableSorterCollation; + s = s.replace( ts.collationRegex, function ( match ) { + var r = tsc[match] ? tsc[match] : tsc[match.toUpperCase()]; + return r.toLowerCase(); + } ); + } + return s; + }, + type: "text" + } ); + + ts.addParser( { + id: "IPAddress", + is: function (s) { + return ts.rgx.IPAddress[0].test(s); + }, + 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 == 2 ) { + r += "0" + item; + } else { + r += item; + } + } + return $.tablesorter.formatFloat(r); + }, + type: "numeric" + } ); + + ts.addParser( { + id: "number", + is: function ( s, table ) { + return $.tablesorter.numberRegex.test( $.trim(s )); + }, + format: function (s) { + return $.tablesorter.formatDigit(s); + }, + type: "numeric" + } ); + + ts.addParser( { + id: "currency", + is: function (s) { + return ts.rgx.currency[0].test(s); + }, + format: function (s) { + return $.tablesorter.formatDigit( s.replace( ts.rgx.currency[1], "" ) ); + }, + type: "numeric" + } ); + + ts.addParser( { + id: "url", + is: function (s) { + return ts.rgx.url[0].test(s); + }, + format: function (s) { + return $.trim( s.replace( ts.rgx.url[1], '' ) ); + }, + type: "text" + } ); + + ts.addParser( { + id: "isoDate", + is: function (s) { + return ts.rgx.isoDate[0].test(s); + }, + format: function (s) { + return $.tablesorter.formatFloat((s != "") ? new Date(s.replace( + new RegExp(/-/g), "/")).getTime() : "0"); + }, + type: "numeric" + } ); + + ts.addParser( { + id: "usLongDate", + is: function (s) { + return ts.rgx.usLongDate[0].test(s); + }, + format: function (s) { + return $.tablesorter.formatFloat( new Date(s).getTime() ); + }, + type: "numeric" + } ); + + ts.addParser( { + id: "date", + is: function (s) { + return ( ts.dateRegex[0].test(s) || ts.dateRegex[1].test(s) || ts.dateRegex[2].test(s )); + }, + format: function ( s, table ) { + s = s.toLowerCase(); + + for ( i = 1, j = 0; i < 13 && j < 2; i++ ) { + s = s.replace( ts.monthNames[j][i], i ); + if ( i == 12 ) { + j++; + i = 0; + } + } + + 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].length == 1 ) s[0] = "0" + s[0]; + if ( s[1].length == 1 ) s[1] = "0" + s[1]; + + if ( !s[2] ) { + //Fix yearless dates + s[2] = 2000; + } else if ( ( y = parseInt( s[2] ) ) < 100 ) { + //Guestimate years without centuries + if ( y < 30 ) { + s[2] = 2000 + y; + } else { + s[2] = 1900 + y; + } + } + //Resort array depending on preferences + if ( wgDefaultDateFormat == "mdy" ) { + s.push( s.shift() ); + s.push( s.shift() ); + } else if ( wgDefaultDateFormat == "dmy" ) { + var d = s.shift(); + s.push( s.shift() ); + s.push(d); + } + return parseInt( s.join('') ); + }, + type: "numeric" + } ); + ts.addParser( { + id: "time", + is: function (s) { + return ts.rgx.time[0].test(s); + }, + format: function (s) { + return $.tablesorter.formatFloat( new Date( "2000/01/01 " + s ).getTime() ); + }, + type: "numeric" + } ); +} )( jQuery ); \ No newline at end of file diff --git a/resources/mediawiki.util/mediawiki.util.js b/resources/mediawiki.util/mediawiki.util.js index 16e2aed196..5d2b5f912b 100644 --- a/resources/mediawiki.util/mediawiki.util.js +++ b/resources/mediawiki.util/mediawiki.util.js @@ -57,6 +57,9 @@ /* Enable CheckboxShiftClick */ $( 'input[type=checkbox]:not(.noshiftselect)' ).checkboxShiftClick(); + /* Enable Tablesorting */ + $( 'table.sortable' ).tablesorter(); + /* Emulate placeholder if not supported by browser */ if ( !( 'placeholder' in document.createElement( 'input' ) ) ) { $( 'input[placeholder]' ).placeholder(); diff --git a/skins/common/images/sort_both.gif b/skins/common/images/sort_both.gif new file mode 100644 index 0000000000..50ad15a0a9 Binary files /dev/null and b/skins/common/images/sort_both.gif differ diff --git a/skins/common/images/sort_down.gif b/skins/common/images/sort_down.gif index d97e8285b5..ec4f41b001 100644 Binary files a/skins/common/images/sort_down.gif and b/skins/common/images/sort_down.gif differ diff --git a/skins/common/images/sort_up.gif b/skins/common/images/sort_up.gif index 488cf2790a..8018918541 100644 Binary files a/skins/common/images/sort_up.gif and b/skins/common/images/sort_up.gif differ diff --git a/skins/common/shared.css b/skins/common/shared.css index 10abbcd0f5..5de18f51d8 100644 --- a/skins/common/shared.css +++ b/skins/common/shared.css @@ -567,11 +567,6 @@ div.gallerytext { background-repeat: no-repeat; } -/* Sort arrows added by SortableTables */ -a.sortheader { - margin: 0 0.3em; -} - /* Localised ordered list numbering for some languages */ ol:lang(bcc) li, ol:lang(bqi) li, @@ -652,3 +647,17 @@ ol:lang(or) li { right: 10px; background-position: 0% 100%; } +/* Table Sorting */ +th.headerSort { + background-image: url(images/sort_both.gif); + cursor: pointer; + background-repeat: no-repeat; + background-position: center right; + padding-right: 21px; +} +th.headerSortUp { + background-image: url(images/sort_up.gif); +} +th.headerSortDown { + background-image: url(images/sort_down.gif); +} \ No newline at end of file diff --git a/skins/common/wikibits.js b/skins/common/wikibits.js index 1db54f27fb..50dd9dc569 100644 --- a/skins/common/wikibits.js +++ b/skins/common/wikibits.js @@ -981,7 +981,7 @@ window.runOnloadHook = function() { updateTooltipAccessKeys( null ); setupCheckboxShiftClick(); - jQuery( document ).ready( sortables_init ); + //jQuery( document ).ready( sortables_init ); // Run any added-on functions for ( var i = 0; i < onloadFuncts.length; i++ ) {