iobroker.viessmann
Version:
Connect a viessmann heating system over vcontrold
1,381 lines (1,332 loc) • 84.6 kB
JavaScript
/* eslint-disable no-undef */
/* eslint-disable no-useless-escape */
/* eslint-disable no-prototype-builtins */
/* eslint-disable no-redeclare */
/**!
* TableSorter 2.17.8 - Client-side table sorting with ease!
* @requires jQuery v1.2.6+
*
* Copyright (c) 2007 Christian Bach
* Examples and docs at: http://tablesorter.com
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* @type jQuery
* @name tablesorter
* @cat Plugins/Tablesorter
* @author Christian Bach/christian.bach@polyester.se
* @contributor Rob Garrison/https://github.com/Mottie/tablesorter
*/
/*jshint browser:true, jquery:true, unused:false, expr: true */
/*global console:false, alert:false */
!(function ($) {
"use strict";
$.extend({
/*jshint supernew:true */
tablesorter: new (function () {
const ts = this;
ts.version = "2.17.8";
ts.parsers = [];
ts.widgets = [];
ts.defaults = {
// *** appearance
theme: "default", // adds tablesorter-{theme} to the table for styling
widthFixed: false, // adds colgroup to fix widths of columns
showProcessing: false, // show an indeterminate timer icon in the header when the table is sorted or filtered.
headerTemplate: "{content}", // header layout template (HTML ok); {content} = innerHTML, {icon} = <i/> (class from cssIcon)
onRenderTemplate: null, // function(index, template){ return template; }, (template is a string)
onRenderHeader: null, // function(index){}, (nothing to return)
// *** functionality
cancelSelection: true, // prevent text selection in the header
tabIndex: true, // add tabindex to header for keyboard accessibility
dateFormat: "mmddyyyy", // other options: "ddmmyyy" or "yyyymmdd"
sortMultiSortKey: "shiftKey", // key used to select additional columns
sortResetKey: "ctrlKey", // key used to remove sorting on a column
usNumberFormat: true, // false for German "1.234.567,89" or French "1 234 567,89"
delayInit: false, // if false, the parsed table contents will not update until the first sort
serverSideSorting: false, // if true, server-side sorting should be performed because client-side sorting will be disabled, but the ui and events will still be used.
// *** sort options
headers: {}, // set sorter, string, empty, locked order, sortInitialOrder, filter, etc.
ignoreCase: true, // ignore case while sorting
sortForce: null, // column(s) first sorted; always applied
sortList: [], // Initial sort order; applied initially; updated when manually sorted
sortAppend: null, // column(s) sorted last; always applied
sortStable: false, // when sorting two rows with exactly the same content, the original sort order is maintained
sortInitialOrder: "asc", // sort direction on first click
sortLocaleCompare: false, // replace equivalent character (accented characters)
sortReset: false, // third click on the header will reset column to default - unsorted
sortRestart: false, // restart sort to "sortInitialOrder" when clicking on previously unsorted columns
emptyTo: "bottom", // sort empty cell to bottom, top, none, zero
stringTo: "max", // sort strings in numerical column as max, min, top, bottom, zero
textExtraction: "basic", // text extraction method/function - function(node, table, cellIndex){}
textAttribute: "data-text", // data-attribute that contains alternate cell text (used in textExtraction function)
textSorter: null, // choose overall or specific column sorter function(a, b, direction, table, columnIndex) [alt: ts.sortText]
numberSorter: null, // choose overall numeric sorter function(a, b, direction, maxColumnValue)
// *** widget options
widgets: [], // method to add widgets, e.g. widgets: ['zebra']
widgetOptions: {
zebra: ["even", "odd"], // zebra widget alternating row class names
},
initWidgets: true, // apply widgets on tablesorter initialization
// *** callbacks
initialized: null, // function(table){},
// *** extra css class names
tableClass: "",
cssAsc: "",
cssDesc: "",
cssNone: "",
cssHeader: "",
cssHeaderRow: "",
cssProcessing: "", // processing icon applied to header during sort/filter
cssChildRow: "tablesorter-childRow", // class name indiciating that a row is to be attached to the its parent
cssIcon: "tablesorter-icon", // if this class exists, a <i> will be added to the header automatically
cssInfoBlock: "tablesorter-infoOnly", // don't sort tbody with this class name (only one class name allowed here!)
// *** selectors
selectorHeaders: "> thead th, > thead td",
selectorSort: "th, td", // jQuery selector of content within selectorHeaders that is clickable to trigger a sort
selectorRemove: ".remove-me",
// *** advanced
debug: false,
// *** Internal variables
headerList: [],
empties: {},
strings: {},
parsers: [],
// deprecated; but retained for backwards compatibility
// widgetZebra: { css: ["even", "odd"] }
};
// internal css classes - these will ALWAYS be added to
// the table and MUST only contain one class name - fixes #381
ts.css = {
table: "tablesorter",
cssHasChild: "tablesorter-hasChildRow",
childRow: "tablesorter-childRow",
header: "tablesorter-header",
headerRow: "tablesorter-headerRow",
headerIn: "tablesorter-header-inner",
icon: "tablesorter-icon",
info: "tablesorter-infoOnly",
processing: "tablesorter-processing",
sortAsc: "tablesorter-headerAsc",
sortDesc: "tablesorter-headerDesc",
sortNone: "tablesorter-headerUnSorted",
};
// labels applied to sortable headers for accessibility (aria) support
ts.language = {
sortAsc: "Ascending sort applied, ",
sortDesc: "Descending sort applied, ",
sortNone: "No sort applied, ",
nextAsc: "activate to apply an ascending sort",
nextDesc: "activate to apply a descending sort",
nextNone: "activate to remove the sort",
};
/* debuging utils */
function log() {
const a = arguments[0],
s = arguments.length > 1 ? Array.prototype.slice.call(arguments) : a;
if (
typeof console !== "undefined" &&
typeof console.log !== "undefined"
) {
console[
/error/i.test(a) ? "error" : /warn/i.test(a) ? "warn" : "log"
](s);
} else {
alert(s);
}
}
function benchmark(s, d) {
log(`${s} (${new Date().getTime() - d.getTime()}ms)`);
}
ts.log = log;
ts.benchmark = benchmark;
// $.isEmptyObject from jQuery v1.4
function isEmptyObject(obj) {
/*jshint forin: false */
for (const name in obj) {
return false;
}
return true;
}
function getElementText(table, node, cellIndex) {
if (!node) {
return "";
}
let te,
c = table.config,
t = c.textExtraction || "",
text = "";
if (t === "basic") {
// check data-attribute first
text =
$(node).attr(c.textAttribute) ||
node.textContent ||
node.innerText ||
$(node).text() ||
"";
} else {
if (typeof t === "function") {
text = t(node, table, cellIndex);
} else if (
typeof (te = ts.getColumnData(table, t, cellIndex)) === "function"
) {
text = te(node, table, cellIndex);
} else {
// previous "simple" method
text = node.textContent || node.innerText || $(node).text() || "";
}
}
return $.trim(text);
}
function detectParserForColumn(table, rows, rowIndex, cellIndex) {
let cur,
i = ts.parsers.length,
node = false,
nodeValue = "",
keepLooking = true;
while (nodeValue === "" && keepLooking) {
rowIndex++;
if (rows[rowIndex]) {
node = rows[rowIndex].cells[cellIndex];
nodeValue = getElementText(table, node, cellIndex);
if (table.config.debug) {
log(
`Checking if value was empty on row ${rowIndex}, column: ${
cellIndex
}: "${nodeValue}"`,
);
}
} else {
keepLooking = false;
}
}
while (--i >= 0) {
cur = ts.parsers[i];
// ignore the default text parser because it will always be true
if (
cur &&
cur.id !== "text" &&
cur.is &&
cur.is(nodeValue, table, node)
) {
return cur;
}
}
// nothing found, return the generic parser (text)
return ts.getParserById("text");
}
function buildParserCache(table) {
let c = table.config,
// update table bodies in case we start with an empty table
tb = (c.$tbodies = c.$table.children(
`tbody:not(.${c.cssInfoBlock})`,
)),
rows,
list,
l,
i,
h,
ch,
np,
p,
e,
time,
j = 0,
parsersDebug = "",
len = tb.length;
if (len === 0) {
return c.debug
? log("Warning: *Empty table!* Not building a parser cache")
: "";
} else if (c.debug) {
time = new Date();
log("Detecting parsers for each column");
}
list = {
extractors: [],
parsers: [],
};
while (j < len) {
rows = tb[j].rows;
if (rows[j]) {
l = c.columns; // rows[j].cells.length;
for (i = 0; i < l; i++) {
h = c.$headers.filter(`[data-column="${i}"]:last`);
// get column indexed table cell
ch = ts.getColumnData(table, c.headers, i);
// get column parser/extractor
e = ts.getParserById(ts.getData(h, ch, "extractor"));
p = ts.getParserById(ts.getData(h, ch, "sorter"));
np = ts.getData(h, ch, "parser") === "false";
// empty cells behaviour - keeping emptyToBottom for backwards compatibility
c.empties[i] = (
ts.getData(h, ch, "empty") ||
c.emptyTo ||
(c.emptyToBottom ? "bottom" : "top")
).toLowerCase();
// text strings behaviour in numerical sorts
c.strings[i] = (
ts.getData(h, ch, "string") ||
c.stringTo ||
"max"
).toLowerCase();
if (np) {
p = ts.getParserById("no-parser");
}
if (!e) {
// For now, maybe detect someday
e = false;
}
if (!p) {
p = detectParserForColumn(table, rows, -1, i);
}
if (c.debug) {
parsersDebug += `column:${i}; extractor:${e.id}; parser:${
p.id
}; string:${c.strings[i]}; empty: ${c.empties[i]}\n`;
}
list.parsers[i] = p;
list.extractors[i] = e;
}
}
j += list.parsers.length ? len : 1;
}
if (c.debug) {
log(parsersDebug ? parsersDebug : "No parsers detected");
benchmark("Completed detecting parsers", time);
}
c.parsers = list.parsers;
c.extractors = list.extractors;
}
/* utils */
function buildCache(table) {
let cc,
t,
tx,
v,
i,
j,
k,
$row,
rows,
cols,
cacheTime,
totalRows,
rowData,
colMax,
c = table.config,
$tb = c.$table.children("tbody"),
extractors = c.extractors,
parsers = c.parsers;
c.cache = {};
c.totalRows = 0;
// if no parsers found, return - it's an empty table.
if (!parsers) {
return c.debug
? log("Warning: *Empty table!* Not building a cache")
: "";
}
if (c.debug) {
cacheTime = new Date();
}
// processing icon
if (c.showProcessing) {
ts.isProcessing(table, true);
}
for (k = 0; k < $tb.length; k++) {
colMax = []; // column max value per tbody
cc = c.cache[k] = {
normalized: [], // array of normalized row data; last entry contains "rowData" above
// colMax: # // added at the end
};
// ignore tbodies with class name from c.cssInfoBlock
if (!$tb.eq(k).hasClass(c.cssInfoBlock)) {
totalRows = ($tb[k] && $tb[k].rows.length) || 0;
for (i = 0; i < totalRows; ++i) {
rowData = {
// order: original row order #
// $row : jQuery Object[]
child: [], // child row text (filter widget)
};
/** Add the table data to main data array */
$row = $($tb[k].rows[i]);
rows = [new Array(c.columns)];
cols = [];
// if this is a child row, add it to the last row's children and continue to the next row
// ignore child row class, if it is the first row
if ($row.hasClass(c.cssChildRow) && i !== 0) {
t = cc.normalized.length - 1;
cc.normalized[t][c.columns].$row =
cc.normalized[t][c.columns].$row.add($row);
// add "hasChild" class name to parent row
if (!$row.prev().hasClass(c.cssChildRow)) {
$row.prev().addClass(ts.css.cssHasChild);
}
// save child row content (un-parsed!)
rowData.child[t] = $.trim(
$row[0].textContent || $row[0].innerText || $row.text() || "",
);
// go to the next for loop
continue;
}
rowData.$row = $row;
rowData.order = i; // add original row position to rowCache
for (j = 0; j < c.columns; ++j) {
if (typeof parsers[j] === "undefined") {
if (c.debug) {
log(
"No parser found for cell:",
$row[0].cells[j],
"does it have a header?",
);
}
continue;
}
t = getElementText(table, $row[0].cells[j], j);
// do extract before parsing if there is one
if (typeof extractors[j].id === "undefined") {
tx = t;
} else {
tx = extractors[j].format(t, table, $row[0].cells[j], j);
}
// allow parsing if the string is empty, previously parsing would change it to zero,
// in case the parser needs to extract data from the table cell attributes
v =
parsers[j].id === "no-parser"
? ""
: parsers[j].format(tx, table, $row[0].cells[j], j);
cols.push(
c.ignoreCase && typeof v === "string" ? v.toLowerCase() : v,
);
if ((parsers[j].type || "").toLowerCase() === "numeric") {
// determine column max value (ignore sign)
colMax[j] = Math.max(Math.abs(v) || 0, colMax[j] || 0);
}
}
// ensure rowData is always in the same location (after the last column)
cols[c.columns] = rowData;
cc.normalized.push(cols);
}
cc.colMax = colMax;
// total up rows, not including child rows
c.totalRows += cc.normalized.length;
}
}
if (c.showProcessing) {
ts.isProcessing(table); // remove processing icon
}
if (c.debug) {
benchmark(`Building cache for ${totalRows} rows`, cacheTime);
}
}
// init flag (true) used by pager plugin to prevent widget application
function appendToTable(table, init) {
let c = table.config,
wo = c.widgetOptions,
b = table.tBodies,
rows = [],
cc = c.cache,
n,
totalRows,
$bk,
$tb,
i,
k,
appendTime;
// empty table - fixes #206/#346
if (isEmptyObject(cc)) {
// run pager appender in case the table was just emptied
return c.appender
? c.appender(table, rows)
: table.isUpdating
? c.$table.trigger("updateComplete", table)
: ""; // Fixes #532
}
if (c.debug) {
appendTime = new Date();
}
for (k = 0; k < b.length; k++) {
$bk = $(b[k]);
if ($bk.length && !$bk.hasClass(c.cssInfoBlock)) {
// get tbody
$tb = ts.processTbody(table, $bk, true);
n = cc[k].normalized;
totalRows = n.length;
for (i = 0; i < totalRows; i++) {
rows.push(n[i][c.columns].$row);
// removeRows used by the pager plugin; don't render if using ajax - fixes #411
if (
!c.appender ||
(c.pager &&
(!c.pager.removeRows || !wo.pager_removeRows) &&
!c.pager.ajax)
) {
$tb.append(n[i][c.columns].$row);
}
}
// restore tbody
ts.processTbody(table, $tb, false);
}
}
if (c.appender) {
c.appender(table, rows);
}
if (c.debug) {
benchmark("Rebuilt table", appendTime);
}
// apply table widgets; but not before ajax completes
if (!init && !c.appender) {
ts.applyWidget(table);
}
if (table.isUpdating) {
c.$table.trigger("updateComplete", table);
}
}
function formatSortingOrder(v) {
// look for "d" in "desc" order; return true
return /^d/i.test(v) || v === 1;
}
function buildHeaders(table) {
let ch,
$t,
h,
i,
t,
lock,
time,
c = table.config;
c.headerList = [];
c.headerContent = [];
if (c.debug) {
time = new Date();
}
// children tr in tfoot - see issue #196 & #547
c.columns = ts.computeColumnIndex(
c.$table.children("thead, tfoot").children("tr"),
);
// add icon if cssIcon option exists
i = c.cssIcon
? `<i class="${
c.cssIcon === ts.css.icon
? ts.css.icon
: `${c.cssIcon} ${ts.css.icon}`
}"></i>`
: "";
// redefine c.$headers here in case of an updateAll that replaces or adds an entire header cell - see #683
c.$headers = $(table)
.find(c.selectorHeaders)
.each(function (index) {
$t = $(this);
// make sure to get header cell & not column indexed cell
ch = ts.getColumnData(table, c.headers, index, true);
// save original header content
c.headerContent[index] = $(this).html();
// if headerTemplate is empty, don't reformat the header cell
if (c.headerTemplate !== "") {
// set up header template
t = c.headerTemplate
.replace(/\{content\}/g, $(this).html())
.replace(/\{icon\}/g, i);
if (c.onRenderTemplate) {
h = c.onRenderTemplate.apply($t, [index, t]);
if (h && typeof h === "string") {
t = h;
} // only change t if something is returned
}
$(this).html(`<div class="${ts.css.headerIn}">${t}</div>`); // faster than wrapInner
}
if (c.onRenderHeader) {
c.onRenderHeader.apply($t, [index]);
}
this.column = parseInt($(this).attr("data-column"), 10);
this.order = formatSortingOrder(
ts.getData($t, ch, "sortInitialOrder") || c.sortInitialOrder,
)
? [1, 0, 2]
: [0, 1, 2];
this.count = -1; // set to -1 because clicking on the header automatically adds one
this.lockedOrder = false;
lock = ts.getData($t, ch, "lockedOrder") || false;
if (typeof lock !== "undefined" && lock !== false) {
this.order = this.lockedOrder = formatSortingOrder(lock)
? [1, 1, 1]
: [0, 0, 0];
}
$t.addClass(`${ts.css.header} ${c.cssHeader}`);
// add cell to headerList
c.headerList[index] = this;
// add to parent in case there are multiple rows
$t.parent()
.addClass(`${ts.css.headerRow} ${c.cssHeaderRow}`)
.attr("role", "row");
// allow keyboard cursor to focus on element
if (c.tabIndex) {
$t.attr("tabindex", 0);
}
})
.attr({
scope: "col",
role: "columnheader",
});
// enable/disable sorting
updateHeader(table);
if (c.debug) {
benchmark("Built headers:", time);
log(c.$headers);
}
}
function commonUpdate(table, resort, callback) {
const c = table.config;
// remove rows/elements before update
c.$table.find(c.selectorRemove).remove();
// rebuild parsers
buildParserCache(table);
// rebuild the cache map
buildCache(table);
checkResort(c.$table, resort, callback);
}
function updateHeader(table) {
let s,
$th,
col,
c = table.config;
c.$headers.each(function (index, th) {
$th = $(th);
col = ts.getColumnData(table, c.headers, index, true);
// add "sorter-false" class if "parser-false" is set
s =
ts.getData(th, col, "sorter") === "false" ||
ts.getData(th, col, "parser") === "false";
th.sortDisabled = s;
$th[s ? "addClass" : "removeClass"]("sorter-false").attr(
"aria-disabled",
`${s}`,
);
// aria-controls - requires table ID
if (table.id) {
if (s) {
$th.removeAttr("aria-controls");
} else {
$th.attr("aria-controls", table.id);
}
}
});
}
function setHeadersCss(table) {
let f,
i,
j,
c = table.config,
list = c.sortList,
len = list.length,
none = `${ts.css.sortNone} ${c.cssNone}`,
css = [
`${ts.css.sortAsc} ${c.cssAsc}`,
`${ts.css.sortDesc} ${c.cssDesc}`,
],
aria = ["ascending", "descending"],
// find the footer
$t = $(table)
.find("tfoot tr")
.children()
.add(c.$extraHeaders)
.removeClass(css.join(" "));
// remove all header information
c.$headers
.removeClass(css.join(" "))
.addClass(none)
.attr("aria-sort", "none");
for (i = 0; i < len; i++) {
// 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" : ""}`,
);
if (f.length) {
for (j = 0; j < f.length; j++) {
if (!f[j].sortDisabled) {
f.eq(j)
.removeClass(none)
.addClass(css[list[i][1]])
.attr("aria-sort", aria[list[i][1]]);
}
}
// add sorted class to footer & extra headers, if they exist
if ($t.length) {
$t.filter(`[data-column="${list[i][0]}"]`)
.removeClass(none)
.addClass(css[list[i][1]]);
}
}
}
}
// add verbose aria labels
c.$headers.not(".sorter-false").each(function () {
const $this = $(this),
nextSort = this.order[(this.count + 1) % (c.sortReset ? 3 : 2)],
txt = `${$this.text()}: ${
ts.language[
$this.hasClass(ts.css.sortAsc)
? "sortAsc"
: $this.hasClass(ts.css.sortDesc)
? "sortDesc"
: "sortNone"
]
}${
ts.language[
nextSort === 0
? "nextAsc"
: nextSort === 1
? "nextDesc"
: "nextNone"
]
}`;
$this.attr("aria-label", txt);
});
}
// automatically add col group, and column sizes if set
function fixColumnWidth(table) {
let colgroup,
overallWidth,
c = table.config;
if (c.widthFixed && c.$table.find("colgroup").length === 0) {
colgroup = $("<colgroup>");
overallWidth = $(table).width();
// only add col for visible columns - fixes #371
$(table.tBodies)
.not(`.${c.cssInfoBlock}`)
.find("tr:first")
.children(":visible")
.each(function () {
colgroup.append(
$("<col>").css(
"width",
`${
parseInt(($(this).width() / overallWidth) * 1000, 10) / 10
}%`,
),
);
});
c.$table.prepend(colgroup);
}
}
function updateHeaderSortCount(table, list) {
let s,
t,
o,
col,
primary,
c = table.config,
sl = list || c.sortList;
c.sortList = [];
$.each(sl, function (i, v) {
// 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];
if (o) {
// prevents error if sorton array is wrong
// o.count = o.count + 1;
t = `${v[1]}`.match(/^(1|d|s|o|n)/);
t = t ? t[0] : "";
// 0/(a)sc (default), 1/(d)esc, (s)ame, (o)pposite, (n)ext
switch (t) {
case "1":
case "d": // descending
t = 1;
break;
case "s": // same direction (as primary column)
// if primary sort is set to "s", make it ascending
t = primary || 0;
break;
case "o":
s = o.order[(primary || 0) % (c.sortReset ? 3 : 2)];
// opposite of primary column; but resets if primary resets
t = s === 0 ? 1 : s === 1 ? 0 : 2;
break;
case "n":
o.count = o.count + 1;
t = o.order[o.count % (c.sortReset ? 3 : 2)];
break;
default: // ascending
t = 0;
break;
}
primary = i === 0 ? t : primary;
s = [col, parseInt(t, 10) || 0];
c.sortList.push(s);
t = $.inArray(s[1], o.order); // fixes issue #167
o.count = t >= 0 ? t : s[1] % (c.sortReset ? 3 : 2);
}
});
}
function getCachedSortType(parsers, i) {
return parsers && parsers[i] ? parsers[i].type || "" : "";
}
function initSort(table, cell, event) {
if (table.isUpdating) {
// let any updates complete before initializing a sort
return setTimeout(function () {
initSort(table, cell, event);
}, 50);
}
let arry,
indx,
col,
order,
s,
c = table.config,
key = !event[c.sortMultiSortKey],
$table = c.$table;
// Only call sortStart if sorting is enabled
$table.trigger("sortStart", table);
// get current column sort order
cell.count = event[c.sortResetKey]
? 2
: (cell.count + 1) % (c.sortReset ? 3 : 2);
// reset all sorts on non-current column - issue #30
if (c.sortRestart) {
indx = cell;
c.$headers.each(function () {
// only reset counts on columns that weren't just clicked on and if not included in a multisort
if (
this !== indx &&
(key || !$(this).is(`.${ts.css.sortDesc},.${ts.css.sortAsc}`))
) {
this.count = -1;
}
});
}
// get current column index
indx = cell.column;
// user only wants to sort on one column
if (key) {
// flush the sort list
c.sortList = [];
if (c.sortForce !== null) {
arry = c.sortForce;
for (col = 0; col < arry.length; col++) {
if (arry[col][0] !== indx) {
c.sortList.push(arry[col]);
}
}
}
// add column to sort list
order = cell.order[cell.count];
if (order < 2) {
c.sortList.push([indx, order]);
// add other columns if header spans across multiple
if (cell.colSpan > 1) {
for (col = 1; col < cell.colSpan; col++) {
c.sortList.push([indx + col, order]);
}
}
}
// multi column sorting
} else {
// get rid of the sortAppend before adding more - fixes issue #115 & #523
if (c.sortAppend && c.sortList.length > 1) {
for (col = 0; col < c.sortAppend.length; col++) {
s = ts.isValueInArray(c.sortAppend[col][0], c.sortList);
if (s >= 0) {
c.sortList.splice(s, 1);
}
}
}
// the user has clicked on an already sorted column
if (ts.isValueInArray(indx, c.sortList) >= 0) {
// 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];
if (s[0] === indx) {
// order.count seems to be incorrect when compared to cell.count
s[1] = order.order[cell.count];
if (s[1] === 2) {
c.sortList.splice(col, 1);
order.count = -1;
}
}
}
} else {
// add column to sort list array
order = cell.order[cell.count];
if (order < 2) {
c.sortList.push([indx, order]);
// add other columns if header spans across multiple
if (cell.colSpan > 1) {
for (col = 1; col < cell.colSpan; col++) {
c.sortList.push([indx + col, order]);
}
}
}
}
}
if (c.sortAppend !== null) {
arry = c.sortAppend;
for (col = 0; col < arry.length; col++) {
if (arry[col][0] !== indx) {
c.sortList.push(arry[col]);
}
}
}
// sortBegin event triggered immediately before the sort
$table.trigger("sortBegin", table);
// setTimeout needed so the processing icon shows up
setTimeout(function () {
// set css for headers
setHeadersCss(table);
multisort(table);
appendToTable(table);
$table.trigger("sortEnd", table);
}, 1);
}
// sort multiple columns
function multisort(table) {
/*jshint loopfunc:true */
let i,
k,
num,
col,
sortTime,
colMax,
cache,
order,
sort,
x,
y,
dir = 0,
c = table.config,
cts = c.textSorter || "",
sortList = c.sortList,
l = sortList.length,
bl = table.tBodies.length;
if (c.serverSideSorting || isEmptyObject(c.cache)) {
// empty table - fixes #206/#346
return;
}
if (c.debug) {
sortTime = new Date();
}
for (k = 0; k < bl; k++) {
colMax = c.cache[k].colMax;
cache = c.cache[k].normalized;
cache.sort(function (a, b) {
// cache is undefined here in IE, so don't use it!
for (i = 0; i < l; i++) {
col = sortList[i][0];
order = sortList[i][1];
// sort direction, true = asc, false = desc
dir = order === 0;
if (c.sortStable && a[col] === b[col] && l === 1) {
return a[c.columns].order - b[c.columns].order;
}
// fallback to natural sort since it is more robust
num = /n/i.test(getCachedSortType(c.parsers, col));
if (num && c.strings[col]) {
// sort strings in numerical columns
if (typeof c.string[c.strings[col]] === "boolean") {
num = (dir ? 1 : -1) * (c.string[c.strings[col]] ? -1 : 1);
} else {
num = c.strings[col] ? c.string[c.strings[col]] || 0 : 0;
}
// fall back to built-in numeric sort
// var sort = $.tablesorter["sort" + s](table, a[c], b[c], c, colMax[c], dir);
sort = c.numberSorter
? c.numberSorter(a[col], b[col], dir, colMax[col], table)
: ts[`sortNumeric${dir ? "Asc" : "Desc"}`](
a[col],
b[col],
num,
colMax[col],
col,
table,
);
} else {
// set a & b depending on sort direction
x = dir ? a : b;
y = dir ? b : a;
// text sort function
if (typeof cts === "function") {
// custom OVERALL text sorter
sort = cts(x[col], y[col], dir, col, table);
} else if (typeof cts === "object" && cts.hasOwnProperty(col)) {
// custom text sorter for a SPECIFIC COLUMN
sort = cts[col](x[col], y[col], dir, col, table);
} else {
// fall back to natural sort
sort = ts[`sortNatural${dir ? "Asc" : "Desc"}`](
a[col],
b[col],
col,
table,
c,
);
}
}
if (sort) {
return sort;
}
}
return a[c.columns].order - b[c.columns].order;
});
}
if (c.debug) {
benchmark(
`Sorting on ${sortList.toString()} and dir ${order} time`,
sortTime,
);
}
}
function resortComplete($table, callback) {
const table = $table[0];
if (table.isUpdating) {
$table.trigger("updateComplete", table);
}
if ($.isFunction(callback)) {
callback($table[0]);
}
}
function checkResort($table, flag, callback) {
const sl = $table[0].config.sortList;
// don't try to resort if the table is still processing
// this will catch spamming of the updateCell method
if (flag !== false && !$table[0].isProcessing && sl.length) {
$table.trigger("sorton", [
sl,
function () {
resortComplete($table, callback);
},
true,
]);
} else {
resortComplete($table, callback);
ts.applyWidget($table[0], false);
}
}
function bindMethods(table) {
let c = table.config,
$table = c.$table;
// apply easy methods that trigger bound events
$table
.unbind(
"sortReset update updateRows updateCell updateAll addRows updateComplete sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave "
.split(" ")
.join(`${c.namespace} `),
)
.bind(`sortReset${c.namespace}`, function (e, callback) {
e.stopPropagation();
c.sortList = [];
setHeadersCss(table);
multisort(table);
appendToTable(table);
if ($.isFunction(callback)) {
callback(table);
}
})
.bind(`updateAll${c.namespace}`, function (e, resort, callback) {
e.stopPropagation();
table.isUpdating = true;
ts.refreshWidgets(table, true, true);
ts.restoreHeaders(table);
buildHeaders(table);
ts.bindEvents(table, c.$headers, true);
bindMethods(table);
commonUpdate(table, resort, callback);
})
.bind(
`update${c.namespace} updateRows${c.namespace}`,
function (e, resort, callback) {
e.stopPropagation();
table.isUpdating = true;
// update sorting (if enabled/disabled)
updateHeader(table);
commonUpdate(table, resort, callback);
},
)
.bind(
`updateCell${c.namespace}`,
function (e, cell, resort, callback) {
e.stopPropagation();
table.isUpdating = true;
$table.find(c.selectorRemove).remove();
// get position from the dom
let v,
t,
row,
icell,
$tb = $table.find("tbody"),
$cell = $(cell),
// update cache - format: function(s, table, cell, cellIndex)
// no closest in jQuery v1.2.6 - tbdy = $tb.index( $(cell).closest('tbody') ),$row = $(cell).closest('tr');
tbdy = $tb.index(
$.fn.closest
? $cell.closest("tbody")
: $cell.parents("tbody").filter(":first"),
),
$row = $.fn.closest
? $cell.closest("tr")
: $cell.parents("tr").filter(":first");
cell = $cell[0]; // in case cell is a jQuery object
// tbody may not exist if update is initialized while tbody is removed for processing
if ($tb.length && tbdy >= 0) {
row = $tb.eq(tbdy).find("tr").index($row);
icell = $cell.index();
c.cache[tbdy].normalized[row][c.columns].$row = $row;
if (typeof c.extractors[icell].id === "undefined") {
t = getElementText(table, cell, icell);
} else {
t = c.extractors[icell].format(
getElementText(table, cell, icell),
table,
cell,
icell,
);
}
v =
c.parsers[icell].id === "no-parser"
? ""
: c.parsers[icell].format(t, table, cell, icell);
c.cache[tbdy].normalized[row][icell] =
c.ignoreCase && typeof v === "string" ? v.toLowerCase() : v;
if ((c.parsers[icell].type || "").toLowerCase() === "numeric") {
// update column max value (ignore sign)
c.cache[tbdy].colMax[icell] = Math.max(
Math.abs(v) || 0,
c.cache[tbdy].colMax[icell] || 0,
);
}
checkResort($table, resort, callback);
}
},
)
.bind(`addRows${c.namespace}`, function (e, $row, resort, callback) {
e.stopPropagation();
table.isUpdating = true;
if (isEmptyObject(c.cache)) {
// empty table, do an update instead - fixes #450
updateHeader(table);
commonUpdate(table, resort, callback);
} else {
$row = $($row).attr("role", "row"); // make sure we're using a jQuery object
let i,
j,
l,
t,
v,
rowData,
cells,
rows = $row.filter("tr").length,
tbdy = $table
.find("tbody")
.index($row.parents("tbody").filter(":first"));
// fixes adding rows to an empty table - see issue #179
if (!(c.parsers && c.parsers.length)) {
buildParserCache(table);
}
// add each row
for (i = 0; i < rows; i++) {
l = $row[i].cells.length;
cells = [];
rowData = {
child: [],
$row: $row.eq(i),
order: c.cache[tbdy].normalized.length,
};
// add each cell
for (j = 0; j < l; j++) {
if (typeof c.extractors[j].id === "undefined") {
t = getElementText(table, $row[i].cells[j], j);
} else {
t = c.extractors[j].format(
getElementText(table, $row[i].cells[j], j),
table,
$row[i].cells[j],
j,
);
}
v =
c.parsers[j].id === "no-parser"
? ""
: c.parsers[j].format(t, table, $row[i].cells[j], j);
cells[j] =
c.ignoreCase && typeof v === "string" ? v.toLowerCase() : v;
if ((c.parsers[j].type || "").toLowerCase() === "numeric") {
// update column max value (ignore sign)
c.cache[tbdy].colMax[j] = Math.max(
Math.abs(cells[j]) || 0,
c.cache[tbdy].colMax[j] || 0,
);
}
}
// add the row data to the end
cells.push(rowData);
// update cache
c.cache[tbdy].normalized.push(cells);
}
// resort using current settings
checkResort($table, resort, callback);
}
})
.bind(`updateComplete${c.namespace}`, function () {
table.isUpdating = false;
})
.bind(`sorton${c.namespace}`, function (e, list, callback, init) {
const c = table.config;
e.stopPropagation();
$table.trigger("sortStart", this);
// update header count index
updateHeaderSortCount(table, list);
// set css for headers
setHeadersCss(table);
// fixes #346
if (c.delayInit && isEmptyObject(c.cache)) {
buildCache(table);
}
$table.trigger("sortBegin", this);
// sort the table and append it to the dom
multisort(table);
appendToTable(table, init);
$table.trigger("sortEnd", this);
ts.applyWidget(table);
if ($.isFunction(callback)) {
callback(table);
}
})
.bind(`appendCache${c.namespace}`, function (e, callback, init) {
e.stopPropagation();
appendToTable(table, init);
if ($.isFunction(callback)) {
callback(table);
}
})
.bind(`updateCache${c.namespace}`, function (e, callback) {
// rebuild parsers
if (!(c.parsers && c.parsers.length)) {
buildParserCache(table);
}
// rebuild the cache map
buildCache(table);
if ($.isFunction(callback)) {
callback(table);
}
})
.bind(`applyWidgetId${c.namespace}`, function (e, id) {
e.stopPropagation();
ts.getWidgetById(id).format(table, c, c.widgetOptions);
})
.bind(`applyWidgets${c.namespace}`, function (e, init) {
e.stopPropagation();
// apply widgets
ts.applyWidget(table, init);
})
.bind(`refreshWidgets${c.namespace}`, function (e, all, dontapply) {
e.stopPropagation();
ts.refreshWidgets(table, all, dontapply);
})
.bind(`destroy${c.namespace}`, function (e, c, cb) {
e.stopPropagation();
ts.destroy(table, c, cb);
})
.bind(`resetToLoadState${c.namespace}`, function () {
// remove all widgets
ts.refreshWidgets(table, true, true);
// restore original settings; this clears out current settings, but does not clear
// values saved to storage.
c = $.extend(true, ts.defaults, c.originalSettings);
table.hasInitialized = false;
// setup the entire table again
ts.setup(table, c);
});
}
/* public methods */
ts.construct = function (settings) {
return this.each(function () {
const table = this,
// merge & extend config options
c = $.extend(true, {}, ts.defaults, settings);
// save initial settings
c.originalSettings = settings;
// create a table from data (build table widget)
if (
!table.hasInitialized &&
ts.buildTable &&
this.tagName !== "TABLE"
) {
// return the table (in case the original target is the table's container)
ts.buildTable(table, c);
} else {
ts.setup(table, c);
}
});
};
ts.setup = function (table, c) {
// if no thead or tbody, or tablesorter is already present, quit
if (
!table ||
!table.tHead ||
table.tBodies.length === 0 ||
table.hasInitialized === true
) {
return c.debug
? log(
"ERROR: stopping initialization! No table, thead, tbody or tablesorter has already been initialized",
)
: "";
}
let k = "",
$table = $(table),
m = $.metadata;
// initialization flag
table.hasInitialized = false;
// table is being processed flag
table.isProcessing = true;
// make sure to store the config object
table.config = c;
// save the settings where they read
$.data(table, "tablesorter", c);
if (c.debug) {
$.data(table, "startoveralltimer", new Date());
}
// removing this in version 3 (only supports jQuery 1.7+)
c.supportsDataObject = (function (version) {
version[0] = parseInt(version[0], 10);
return (
version[0] > 1 ||
(version[0] === 1 && parseInt(version[1], 10) >= 4)
);
})($.fn.jquery.split("."));
// digit sort text location; keeping max+/- for backwards compatibility
c.string = {
max: 1,
min: -1,
emptymin: 1,
emptymax: -1,
zero: 0,
none: 0,
null: 0,
top: true,
bottom: false,
};
// ensure case insensitivity
c.emptyTo = c.emptyTo.toLowerCase();
c.stringTo = c.stringTo.toLowerCase();
// add table theme class only if there isn't already one there
if (!/tablesorter\-/.test($table.attr("class"))) {
k = c.theme !== "" ? ` tablesorter-${c.theme}` : "";
}
c.table = table;
c.$table = $table
.addClass(`${ts.css.table} ${c.tableClass}${k}`)
.attr("role", "grid");
c.$headers = $table.find(c.selectorHeaders);
// give the table a unique id, which will be used in namespace binding
if (!c.namespace) {
c.namespace = `.tablesorter${Math.random().toString(16).slice(2)}`;
} else {
// make sure namespace starts with a period & doesn't have weird characters
c.namespace =