UNPKG

iobroker.viessmann

Version:

Connect a viessmann heating system over vcontrold

1,381 lines (1,332 loc) 84.6 kB
/* 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 =