(function ($) { "use strict"; /* =============================== INIT =============================== */ function initNaviTables(scope) { const $scope = scope ? $(scope) : $(".nsb2"); if (!$scope.length) return; $scope.find("table.tablesorter").each(function () { if (this.dataset.hasSum === "0") return; const table = this; const $table = $(table); formatNumericCells($table); initTableSorter($table); buildFooterSumRow($table); updateStickyHeaderHeight($table); }); } $(function () { initNaviTables(); }); // eksponer for manuell re-init ved behov window.initNaviTables = initNaviTables; /* =============================== HELPERS =============================== */ function parseNo(v) { if (v == null || v === "") return 0; return Number(String(v).trim().replace(/\s/g, "")) || 0; } function fmtNo(v) { return v ? v.toLocaleString("no-NO", { minimumFractionDigits: 4, maximumFractionDigits: 4 }) : ""; } /* =============================== FORMAT DATA CELLS =============================== */ function formatNumericCells($table) { $table.find("tbody td.num").each(function () { const txt = this.textContent.trim(); if (!txt) return; const n = Math.round(parseNo(txt) * 10000) / 10000; this.setAttribute("data-sort", n); if (Math.abs(n) < 0.00005) { this.textContent = ""; return; } this.textContent = fmtNo(n); this.style.textAlign = "right"; }); } /* =============================== TABLESORTER =============================== */ function initTableSorter($table) { if (!$table.hasClass("tablesorter")) return; if ($table.data("tablesorter-initialized")) return; $table.tablesorter({ textAttribute: "data-sort", selectorHeaders: "thead th", widgets: ["zebra"], sortRestart: true }); $table.data("tablesorter-initialized", true); } /* =============================== BUILD TFOOT SUM ROW =============================== */ function buildFooterSumRow($table) { const table = $table[0]; const thead = table.tHead; const tbody = table.tBodies[0]; if (!thead || !tbody) return; const headerRow = thead.rows[thead.rows.length - 1]; const headers = [...headerRow.cells]; const rows = [...tbody.rows].filter(r => r.querySelector("td.num") ); if (!rows.length) return; let tfoot = table.tFoot; if (!tfoot) tfoot = table.createTFoot(); tfoot.innerHTML = ""; const sumRow = document.createElement("tr"); sumRow.className = "sumrow"; const offset = rows[0].cells.length - headers.length; headers.forEach((th, i) => { const agg = th.dataset.agg; const cell = document.createElement("td"); if (th.className) cell.className = th.className; if (agg === "label") { cell.textContent = `Antall: ${rows.length}`; sumRow.appendChild(cell); return; } if (!agg || agg === "none" || agg === "derived") { sumRow.appendChild(cell); return; } const colIndex = i + offset; const vals = rows.map(r => parseNo(r.cells[colIndex]?.getAttribute("data-sort")) ); let v = 0; if (agg === "sum") v = vals.reduce((a, b) => a + b, 0); if (agg === "avg") v = vals.reduce((a, b) => a + b, 0) / vals.length; if (agg === "min") v = Math.min(...vals); if (agg === "max") v = Math.max(...vals); cell.textContent = fmtNo(v); cell.classList.add("num"); cell.style.textAlign = "right"; sumRow.appendChild(cell); }); tfoot.appendChild(sumRow); // informer tablesorter om endring $table.trigger("updateAll", [true]); } /* =============================== STICKY HEADER + FOOTER OFFSET =============================== */ function updateStickyHeaderHeight($table) { const thead = $table.find("thead")[0]; if (!thead) return; const h = thead.getBoundingClientRect().height; document.documentElement.style.setProperty( "--thead-height", h + "px" ); } })(jQuery); (function () { function applySticky(scope) { const table = scope.querySelector("table.tablesorter"); if (!table) return; const thead = table.querySelector("thead"); const tfoot = table.querySelector("tfoot"); if (!thead) return; let offset = 0; // ---- THEAD ---- [...thead.rows].forEach(row => { const h = row.getBoundingClientRect().height; [...row.cells].forEach(th => { th.style.top = offset + "px"; }); offset += h; }); // ---- TFOOT (SUM) ---- if (tfoot) { [...tfoot.rows].forEach(row => { [...row.cells].forEach(th => { th.style.top = offset + "px"; }); }); } } function initSticky() { document.querySelectorAll(".nsb2").forEach(applySticky); } window.addEventListener("load", initSticky); window.addEventListener("resize", initSticky); })();