diff --git a/docs/index.html b/docs/index.html index dc5cc9d6..79825119 100644 --- a/docs/index.html +++ b/docs/index.html @@ -5294,6 +5294,43 @@ $('table').trigger( 'search', [['', '', '', '', 'orange']] ); // find orange in + + + Object + This is an object of all instance methods of config object. They can be added using the addInstanceMethods function before table initialization. +
+
+$.tablesorter.addInstanceMethods({
+  columnSum: function(colNumber) {
+    var sum = 0, tbodyIndex, normalizedRows, rowIndex;
+    // `this` refer to config object
+    for (tbodyIndex = 0; tbodyIndex < this.$tbodies.length; ++tbodyIndex){
+      normalizedRows = this.cache[tbodyIndex].normalized;
+      for (rowIndex = 0; rowIndex < normalizedRows.length; ++rowIndex) {
+        sum += normalizedRows[rowIndex][colNumber];
+      }
+    }
+    return sum;
+  },
+  columnMean: function(colNumber) {
+    return this.columnSum(colNumber) / this.totalRows;
+  },
+});
+
+$('table').tablesorter();
+c = $('table')[0].config;
+console.log('sum of third column: ' + c.columnSum(2));
+console.log('mean of third column: ' + c.columnMean(2));
+            
+ Some predefined instance methods are already defined: + +
+ + + + @@ -6173,6 +6210,20 @@ $.tablesorter.isValueInArray(2, sortList); + + + This function allows to add custom methods for config object. +

+ Access it as follows: +
$.tablesorter.addInstanceMethods(methods);
+ + Take a look at instanceMethods variable description for more details. +
+ + + This function returns the named parser object. diff --git a/js/jquery.tablesorter.js b/js/jquery.tablesorter.js index 868b746d..881983be 100644 --- a/js/jquery.tablesorter.js +++ b/js/jquery.tablesorter.js @@ -154,6 +154,18 @@ nextNone : 'activate to remove the sort' }; + // These methods can be applied on table.config instance + ts.instanceMethods = { + // Returns a jQuery object of n-th column header + $columnHeader: function(n, options) { + var $headers, lastOnly; + options = options || {}, + $headers = (options.headers !== undefined) ? $(options.headers) : this.$headers; + lastOnly = (options.lastOnly !== undefined) ? options.lastOnly : true; + return lastOnly ? $headers.filter('[data-column="' + n + '"]:last') : $headers.filter('[data-column="' + n + '"]'); + }, + }; + /* debuging utils */ function log() { var a = arguments[0], @@ -256,7 +268,7 @@ if (rows.length) { l = c.columns; // rows[j].cells.length; for (i = 0; i < l; i++) { - h = c.$headers.filter('[data-column="' + i + '"]:last'); + h = c.$columnHeader(i); // get column indexed table cell ch = ts.getColumnData( table, c.headers, i ); // get column parser/extractor @@ -561,7 +573,7 @@ // direction = 2 means reset! if (list[i][1] !== 2) { // multicolumn sorting updating - choose the :last in case there are nested columns - f = c.$headers.not('.sorter-false').filter('[data-column="' + list[i][0] + '"]' + (len === 1 ? ':last' : '') ); + f = c.$columnHeader(list[i][0], {lastOnly: (len === 1)}).not('.sorter-false'); if (f.length) { for (j = 0; j < f.length; j++) { if (!f[j].sortDisabled) { @@ -601,7 +613,7 @@ // ensure all sortList values are numeric - fixes #127 col = parseInt(v[0], 10); // make sure header exists - o = c.$headers.filter('[data-column="' + col + '"]:last')[0]; + o = c.$columnHeader(col)[0]; if (o) { // prevents error if sorton array is wrong // o.count = o.count + 1; t = ('' + v[1]).match(/^(1|d|s|o|n)/); @@ -705,7 +717,7 @@ // reverse the sorting direction for (col = 0; col < c.sortList.length; col++) { s = c.sortList[col]; - order = c.$headers.filter('[data-column="' + s[0] + '"]:last')[0]; + order = c.$columnHeader( s[0] )[0]; if (s[0] === indx) { // order.count seems to be incorrect when compared to cell.count s[1] = order.order[cell.count]; @@ -1051,7 +1063,7 @@ return this.each(function() { var table = this, // merge & extend config options - c = $.extend(true, {}, ts.defaults, settings); + c = $.extend(true, {}, ts.defaults, settings, ts.instanceMethods); // save initial settings c.originalSettings = settings; // create a table from data (build table widget) @@ -1600,6 +1612,12 @@ } }; + // Use it to add a set of methods to table.config which will be available for all tables. + // This should be done before table initialization + ts.addInstanceMethods = function(methods) { + $.extend(ts.instanceMethods, methods); + }; + ts.getParserById = function(name) { /*jshint eqeqeq:false */ if (name == 'false') { return false; } @@ -1955,7 +1973,7 @@ if (s) { var date, d, c = table.config, - ci = c.$headers.filter('[data-column="' + cellIndex + '"]:last'), + ci = c.$columnHeader(cellIndex), format = ci.length && ci[0].dateFormat || ts.getData( ci, ts.getColumnData( table, c.headers, cellIndex ), 'dateFormat') || c.dateFormat; d = s.replace(/\s+/g, ' ').replace(/[\-.,]/g, '/'); // escaped - because JSHint in Firefox was showing it as an error if (format === 'mmddyyyy') { diff --git a/js/parsers/parser-metric.js b/js/parsers/parser-metric.js index db1f85ad..622a265f 100644 --- a/js/parsers/parser-metric.js +++ b/js/parsers/parser-metric.js @@ -47,7 +47,7 @@ b, t, // process number here to get a numerical format (us or eu) n = $.tablesorter.formatFloat(s.replace(/[^\w,. \-()]/g, ""), table), - $t = table.config.$headers.filter('[data-column="' + cellIndex + '"]'), + $t = table.config.$columnHeader(cellIndex, {lastOnly: false}), m = $t.data('metric'); if (!m) { // stored values diff --git a/js/widgets/widget-filter-formatter-select2.js b/js/widgets/widget-filter-formatter-select2.js index 9b1ad25c..d8f3e3c9 100644 --- a/js/widgets/widget-filter-formatter-select2.js +++ b/js/widgets/widget-filter-formatter-select2.js @@ -36,7 +36,7 @@ ts.filterFormatter.select2 = function($cell, indx, select2Def) { $cell.find('.select2').select2('val', val); updateSelect2(); }), - $header = c.$headers.filter('[data-column="' + indx + '"]:last'), + $header = c.$columnHeader(indx), onlyAvail = $header.hasClass(wo.filter_onlyAvail), $shcell = [], matchPrefix = o.match ? '' : '^', diff --git a/js/widgets/widget-filter.js b/js/widgets/widget-filter.js index 1b0b1635..d289cd68 100644 --- a/js/widgets/widget-filter.js +++ b/js/widgets/widget-filter.js @@ -209,7 +209,7 @@ ts.filter = { parsed = data.parsed[index], query = ts.filter.parseFilter(c, data.iFilter.replace(ts.filter.regex.orReplace, "|"), index, parsed); // look for an exact match with the "or" unless the "filter-match" class is found - if (!c.$headers.filter('[data-column="' + index + '"]:last').hasClass('filter-match') && /\|/.test(query)) { + if (!c.$columnHeader(index).hasClass('filter-match') && /\|/.test(query)) { // show all results while using filter match. Fixes #727 if (query[ query.length - 1 ] === '|') { query += '*'; } query = data.anyMatch && $.isArray(data.rowArray) ? '(' + query + ')' : '^(' + query + ')$'; @@ -330,7 +330,7 @@ ts.filter = { fxn = ts.getColumnData( table, wo.filter_functions, column ); if (fxn) { // remove "filter-select" from header otherwise the options added here are replaced with all options - $header = c.$headers.filter('[data-column="' + column + '"]:last').removeClass('filter-select'); + $header = c.$columnHeader(column).removeClass('filter-select'); // don't build select if "filter-false" or "parser-false" set noSelect = !($header.hasClass('filter-false') || $header.hasClass('parser-false')); options = ''; @@ -468,7 +468,7 @@ ts.filter = { // if no filters saved, then check default settings if (filters.join('') === '') { for (indx = 0; indx < c.columns; indx++) { - filters[indx] = c.$headers.filter('[data-column="' + indx + '"]:last').attr(wo.filter_defaultAttrib) || filters[indx]; + filters[indx] = c.$columnHeader(indx).attr(wo.filter_defaultAttrib) || filters[indx]; } } c.$table.data('lastSearch', filters); @@ -497,7 +497,7 @@ ts.filter = { for (column = 0; column < columns; column++) { disabled = false; // assuming last cell of a column is the main column - $header = c.$headers.filter('[data-column="' + column + '"]:last'); + $header = c.$columnHeader(column); ffxn = ts.getColumnData( table, wo.filter_functions, column ); buildSelect = (wo.filter_functions && ffxn && typeof ffxn !== "function" ) || $header.hasClass('filter-select'); @@ -784,7 +784,7 @@ ts.filter = { data.parsed = c.$headers.map(function(columnIndex) { return c.parsers && c.parsers[columnIndex] && c.parsers[columnIndex].parsed || // getData won't return "parsed" if other "filter-" class names exist (e.g. ) - ts.getData && ts.getData(c.$headers.filter('[data-column="' + columnIndex + '"]:last'), ts.getColumnData( table, c.headers, columnIndex ), 'filter') === 'parsed' || + ts.getData && ts.getData(c.$columnHeader(columnIndex), ts.getColumnData( table, c.headers, columnIndex ), 'filter') === 'parsed' || $(this).hasClass('filter-parsed'); }).get(); @@ -860,7 +860,7 @@ ts.filter = { // don't search only filtered if the value is negative ('> -10' => '> -100' will ignore hidden rows) !(/(>=?\s*-\d)/.test(val) || /(<=?\s*\d)/.test(val)) && // if filtering using a select without a "filter-match" class (exact match) - fixes #593 - !( val !== '' && c.$filters && c.$filters.eq(indx).find('select').length && !c.$headers.filter('[data-column="' + indx + '"]:last').hasClass('filter-match') ); + !( val !== '' && c.$filters && c.$filters.eq(indx).find('select').length && !c.$columnHeader(indx).hasClass('filter-match') ); } } notFiltered = $rows.not('.' + wo.filter_filteredRow).length; @@ -990,7 +990,7 @@ ts.filter = { // data.iFilter = case insensitive (if wo.filter_ignoreCase is true), data.filter = case sensitive data.iFilter = wo.filter_ignoreCase ? (data.filter || '').toLocaleLowerCase() : data.filter; fxn = ts.getColumnData( table, wo.filter_functions, columnIndex ); - $cell = c.$headers.filter('[data-column="' + columnIndex + '"]:last'); + $cell = c.$columnHeader(columnIndex); hasSelect = $cell.hasClass('filter-select'); if ( fxn || ( hasSelect && val ) ) { if (fxn === true || hasSelect) { @@ -1093,7 +1093,7 @@ ts.filter = { return $.inArray(value, arry) === indx; }); - if (c.$headers.filter('[data-column="' + column + '"]:last').hasClass('filter-select-nosort')) { + if (c.$columnHeader(column).hasClass('filter-select-nosort')) { // unsorted select options return arry; } else { @@ -1146,7 +1146,7 @@ ts.filter = { // check if has class filtered if (onlyAvail && row.className.match(wo.filter_filteredRow)) { continue; } // get non-normalized cell content - if (wo.filter_useParsedData || c.parsers[column].parsed || c.$headers.filter('[data-column="' + column + '"]:last').hasClass('filter-parsed')) { + if (wo.filter_useParsedData || c.parsers[column].parsed || c.$columnHeader(column).hasClass('filter-parsed')) { arry.push( '' + cache.normalized[rowIndex][column] ); } else { cell = row.cells[column]; @@ -1165,7 +1165,7 @@ ts.filter = { var indx, val, txt, t, $filters, $filter, c = table.config, wo = c.widgetOptions, - node = c.$headers.filter('[data-column="' + column + '"]:last'), + node = c.$columnHeader(column), // t.data('placeholder') won't work in jQuery older than 1.4.3 options = '', // Get curent filter value @@ -1220,7 +1220,7 @@ ts.filter = { columns = c.columns; // build default select dropdown for (columnIndex = 0; columnIndex < columns; columnIndex++) { - $header = c.$headers.filter('[data-column="' + columnIndex + '"]:last'); + $header = c.$columnHeader(columnIndex); noSelect = !($header.hasClass('filter-false') || $header.hasClass('parser-false')); // look for the filter-select class; build/update it if found if (($header.hasClass('filter-select') || ts.getColumnData( table, wo.filter_functions, columnIndex ) === true) && noSelect) { diff --git a/js/widgets/widget-formatter.js b/js/widgets/widget-formatter.js index 8b8cd4fe..ee2b8df3 100644 --- a/js/widgets/widget-formatter.js +++ b/js/widgets/widget-formatter.js @@ -29,7 +29,7 @@ $headers = []; // set up variables for ( column = 0; column < c.columns; column++ ) { - $headers[ column ] = c.$headers.filter('[data-column="' + column + '"]:last'); + $headers[ column ] = c.$columnHeader(column); formatter[ column ] = ts.getColumnData( c.table, wo.formatter_column, column ) || false; } // main loop diff --git a/js/widgets/widget-grouping.js b/js/widgets/widget-grouping.js index 546743dc..530cde8a 100644 --- a/js/widgets/widget-grouping.js +++ b/js/widgets/widget-grouping.js @@ -66,12 +66,12 @@ ts.grouping = { // clear pager saved spacer height (in case the rows are collapsed) c.$table.data('pagerSavedHeight', 0); } - if (column >= 0 && !c.$headers.filter('[data-column="' + column + '"]:last').hasClass('group-false')) { + if (column >= 0 && !c.$columnHeader(column).hasClass('group-false')) { wo.group_currentGroup = ''; // save current groups wo.group_currentGroups = {}; // group class finds "group-{word/separator/letter/number/date/false}-{optional:#/year/month/day/week/time}" - groupClass = (c.$headers.filter('[data-column="' + column + '"]:last').attr('class') || '').match(/(group-\w+(-\w+)?)/g); + groupClass = (c.$columnHeader(column).attr('class') || '').match(/(group-\w+(-\w+)?)/g); // grouping = [ 'group', '{word/separator/letter/number/date/false}', '{#/year/month/day/week/time}' ] grouping = groupClass ? groupClass[0].split('-') : ['group','letter',1]; // default to letter 1 @@ -97,14 +97,14 @@ ts.grouping = { // fixes #438 if (ts.grouping.types[grouping[1]]) { currentGroup = norm_rows[rowIndex] ? - ts.grouping.types[grouping[1]]( c, c.$headers.filter('[data-column="' + column + '"]:last'), norm_rows[rowIndex][column], /date/.test(groupClass) ? + ts.grouping.types[grouping[1]]( c, c.$columnHeader(column), norm_rows[rowIndex][column], /date/.test(groupClass) ? grouping[2] : parseInt(grouping[2] || 1, 10) || 1, group, lang ) : currentGroup; if (group !== currentGroup) { group = currentGroup; // show range if number > 1 if (grouping[1] === 'number' && grouping[2] > 1 && currentGroup !== '') { currentGroup += ' - ' + (parseInt(currentGroup, 10) + - ((parseInt(grouping[2],10) - 1) * (c.$headers.filter('[data-column="' + column + '"]:last').hasClass(ts.css.sortAsc) ? 1 : -1))); + ((parseInt(grouping[2],10) - 1) * (c.$columnHeader(column).hasClass(ts.css.sortAsc) ? 1 : -1))); } if ($.isFunction(wo.group_formatter)) { currentGroup = wo.group_formatter((currentGroup || '').toString(), column, table, c, wo) || currentGroup; diff --git a/js/widgets/widget-reflow.js b/js/widgets/widget-reflow.js index 07bec5bf..7e8f9a25 100644 --- a/js/widgets/widget-reflow.js +++ b/js/widgets/widget-reflow.js @@ -97,7 +97,7 @@ tablereflow = { // add to every table cell with thead cell contents for (i = 0; i < cols; i++) { - $hdr = c.$headers.filter('[data-column="' + i + '"]'); + $hdr = c.$columnHeader(i, {lastOnly: false}); if ($hdr.length > 1) { txt = []; /*jshint loopfunc:true */ diff --git a/js/widgets/widget-resizable.js b/js/widgets/widget-resizable.js index fd8e8fca..0f4fb8b4 100644 --- a/js/widgets/widget-resizable.js +++ b/js/widgets/widget-resizable.js @@ -109,7 +109,7 @@ ts.addWidget({ .bind('mousedown', function(event) { // save header cell and mouse position $target = $(event.target).closest('th'); - var $header = c.$headers.filter('[data-column="' + $target.attr('data-column') + '"]'); + var $header = c.$columnHeader($target.attr('data-column'), {lastOnly: false}); if ($header.length > 1) { $target = $target.add($header); } // if table is not as wide as it's parent, then resize the table $next = event.shiftKey ? $target.parent().find('th').not('.resizable-false').filter(':last') : $target.nextAll(':not(.resizable-false)').eq(0); diff --git a/js/widgets/widget-uitheme.js b/js/widgets/widget-uitheme.js index 90e046dd..3b495fd1 100644 --- a/js/widgets/widget-uitheme.js +++ b/js/widgets/widget-uitheme.js @@ -130,9 +130,9 @@ ts.addWidget({ } } for (i = 0; i < c.columns; i++) { - $header = c.$headers.add(c.$extraHeaders).not('.sorter-false').filter('[data-column="' + i + '"]'); + $header = c.$columnHeader(i, {headers: c.$headers.add(c.$extraHeaders), lastOnly: false}).not('.sorter-false'); $icon = (ts.css.icon) ? $header.find('.' + ts.css.icon) : $(); - $h = $headers.not('.sorter-false').filter('[data-column="' + i + '"]:last'); + $h = c.$columnHeader(i, {headers: $headers}).not('.sorter-false'); if ($h.length) { $header.removeClass(remove); $icon.removeClass(iconRmv);