[fix](trx-frontend-http): fix about tab data and sub-tab navigation broken by deferred templates

About-tab element refs were cached at script load time but the elements
live inside a <template> that hasn't been cloned yet, so all refs were
null. Convert to lazy resolution via _resolveAboutEls() called on first
about-tab render. Also extract _wireSubTabBar() so sub-tab click
listeners are attached after template cloning (the about Client sub-tab
was unreachable). Decoder status elements use the same lazy pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-04-04 15:04:09 +02:00
parent bed49ce13b
commit 806a66b8c6
@@ -435,29 +435,57 @@ const headerRigSwitchSelect = document.getElementById("header-rig-switch-select"
const headerStylePickSelect = document.getElementById("header-style-pick-select"); const headerStylePickSelect = document.getElementById("header-style-pick-select");
const rdsPsOverlay = document.getElementById("rds-ps-overlay"); const rdsPsOverlay = document.getElementById("rds-ps-overlay");
const tabMainEl = document.getElementById("tab-main"); const tabMainEl = document.getElementById("tab-main");
// Cached About-tab elements (avoid getElementById on every SSE render) // About-tab elements — resolved lazily after the about template is cloned.
const aboutServerVerEl = document.getElementById("about-server-ver"); let aboutServerVerEl = null;
const aboutServerBuildDateEl = document.getElementById("about-server-build-date"); let aboutServerBuildDateEl = null;
const aboutServerAddrEl = document.getElementById("about-server-addr"); let aboutServerAddrEl = null;
const aboutServerCallEl = document.getElementById("about-server-call"); let aboutServerCallEl = null;
const aboutServerLocationEl = document.getElementById("about-server-location"); let aboutServerLocationEl = null;
const aboutRigInfoEl = document.getElementById("about-rig-info"); let aboutRigInfoEl = null;
const aboutRigAccessEl = document.getElementById("about-rig-access"); let aboutRigAccessEl = null;
const aboutModesEl = document.getElementById("about-modes"); let aboutModesEl = null;
const aboutVfosEl = document.getElementById("about-vfos"); let aboutVfosEl = null;
const aboutActiveRigEl = document.getElementById("about-active-rig"); let aboutActiveRigEl = null;
const aboutAudioCodecEl = document.getElementById("about-audio-codec"); let aboutAudioCodecEl = null;
const aboutAudioSamplerateEl = document.getElementById("about-audio-samplerate"); let aboutAudioSamplerateEl = null;
const aboutAudioChannelsEl = document.getElementById("about-audio-channels"); let aboutAudioChannelsEl = null;
const aboutAudioBitrateEl = document.getElementById("about-audio-bitrate"); let aboutAudioBitrateEl = null;
const aboutAudioFrameEl = document.getElementById("about-audio-frame"); let aboutAudioFrameEl = null;
const aboutAudioRxEl = document.getElementById("about-audio-rx"); let aboutAudioRxEl = null;
const aboutAudioStreamsEl = document.getElementById("about-audio-streams"); let aboutAudioStreamsEl = null;
const aboutPskreporterEl = document.getElementById("about-pskreporter"); let aboutPskreporterEl = null;
const aboutAprsIsEl = document.getElementById("about-aprs-is"); let aboutAprsIsEl = null;
const aboutRigctlClientsEl = document.getElementById("about-rigctl-clients"); let aboutRigctlClientsEl = null;
const aboutRigctlEndpointEl = document.getElementById("about-rigctl-endpoint"); let aboutRigctlEndpointEl = null;
const aboutClientsEl = document.getElementById("about-clients"); let aboutClientsEl = null;
let _aboutElsResolved = false;
function _resolveAboutEls() {
if (_aboutElsResolved) return;
aboutServerVerEl = document.getElementById("about-server-ver");
if (!aboutServerVerEl) return; // template not cloned yet
_aboutElsResolved = true;
aboutServerBuildDateEl = document.getElementById("about-server-build-date");
aboutServerAddrEl = document.getElementById("about-server-addr");
aboutServerCallEl = document.getElementById("about-server-call");
aboutServerLocationEl = document.getElementById("about-server-location");
aboutRigInfoEl = document.getElementById("about-rig-info");
aboutRigAccessEl = document.getElementById("about-rig-access");
aboutModesEl = document.getElementById("about-modes");
aboutVfosEl = document.getElementById("about-vfos");
aboutActiveRigEl = document.getElementById("about-active-rig");
aboutAudioCodecEl = document.getElementById("about-audio-codec");
aboutAudioSamplerateEl = document.getElementById("about-audio-samplerate");
aboutAudioChannelsEl = document.getElementById("about-audio-channels");
aboutAudioBitrateEl = document.getElementById("about-audio-bitrate");
aboutAudioFrameEl = document.getElementById("about-audio-frame");
aboutAudioRxEl = document.getElementById("about-audio-rx");
aboutAudioStreamsEl = document.getElementById("about-audio-streams");
aboutPskreporterEl = document.getElementById("about-pskreporter");
aboutAprsIsEl = document.getElementById("about-aprs-is");
aboutRigctlClientsEl = document.getElementById("about-rigctl-clients");
aboutRigctlEndpointEl = document.getElementById("about-rigctl-endpoint");
aboutClientsEl = document.getElementById("about-clients");
}
// Cached CW elements (avoid getElementById on every SSE render) // Cached CW elements (avoid getElementById on every SSE render)
const cwAutoEl = document.getElementById("cw-auto"); const cwAutoEl = document.getElementById("cw-auto");
const cwWpmEl = document.getElementById("cw-wpm"); const cwWpmEl = document.getElementById("cw-wpm");
@@ -488,11 +516,18 @@ function syncDecoderToggle(entry, enabled, label) {
entry.el.style.color = enabled ? "#00d17f" : ""; entry.el.style.color = enabled ? "#00d17f" : "";
} }
// Cached About-tab decoder status elements — avoids 8× getElementById per render(). // About-tab decoder status elements — resolved lazily after template clone.
const _aboutDecEls = [ const _aboutDecIds = [
"about-dec-ft8", "about-dec-ft4", "about-dec-ft2", "about-dec-wspr", "about-dec-ft8", "about-dec-ft4", "about-dec-ft2", "about-dec-wspr",
"about-dec-cw", "about-dec-aprs", "about-dec-lrpt", "about-dec-cw", "about-dec-aprs", "about-dec-lrpt",
].map((id) => ({ el: document.getElementById(id), last: null })); ];
let _aboutDecEls = _aboutDecIds.map(() => ({ el: null, last: null }));
function _resolveAboutDecEls() {
if (_aboutDecEls[0].el) return;
for (let i = 0; i < _aboutDecIds.length; i++) {
_aboutDecEls[i].el = document.getElementById(_aboutDecIds[i]);
}
}
function syncAboutDecoder(idx, enabled) { function syncAboutDecoder(idx, enabled) {
const entry = _aboutDecEls[idx]; const entry = _aboutDecEls[idx];
@@ -3511,7 +3546,9 @@ function render(update) {
if (typeof update.clients === "number") lastClientCount = update.clients; if (typeof update.clients === "number") lastClientCount = update.clients;
// Populate About tab — only update DOM when the about tab is visible // Populate About tab — only update DOM when the about tab is visible
if (_activeTab === "about") { if (_activeTab === "about") {
// About — Server card (uses cached DOM refs) _resolveAboutEls();
_resolveAboutDecEls();
// About — Server card
if (update.server_version && aboutServerVerEl) { if (update.server_version && aboutServerVerEl) {
aboutServerVerEl.textContent = `trx-server v${update.server_version}`; aboutServerVerEl.textContent = `trx-server v${update.server_version}`;
} }
@@ -4451,6 +4488,10 @@ function navigateToTab(name, options = {}) {
if (tmpl) { if (tmpl) {
panel.appendChild(tmpl.content.cloneNode(true)); panel.appendChild(tmpl.content.cloneNode(true));
tmpl.remove(); tmpl.remove();
// Wire sub-tab bars inside the freshly cloned content.
panel.querySelectorAll(".sub-tab-bar").forEach(_wireSubTabBar);
// Re-run decoder visibility for about-tab elements now in the DOM.
if (decoderRegistry.length) hideUnsupportedDecoderTabs();
} }
if (updateHistory) { if (updateHistory) {
updateTabHistory(name, replaceHistory); updateTabHistory(name, replaceHistory);
@@ -4825,7 +4866,9 @@ function latLonToMaidenhead(lat, lon) {
// --- Sub-tab navigation --- // --- Sub-tab navigation ---
document.querySelectorAll(".sub-tab-bar").forEach((bar) => { function _wireSubTabBar(bar) {
if (bar._subtabWired) return;
bar._subtabWired = true;
bar.addEventListener("click", (e) => { bar.addEventListener("click", (e) => {
const btn = e.target.closest(".sub-tab[data-subtab]"); const btn = e.target.closest(".sub-tab[data-subtab]");
if (!btn) return; if (!btn) return;
@@ -4845,7 +4888,8 @@ document.querySelectorAll(".sub-tab-bar").forEach((bar) => {
window.clearSatPredictionDom(); window.clearSatPredictionDom();
} }
}); });
}); }
document.querySelectorAll(".sub-tab-bar").forEach(_wireSubTabBar);
window.addEventListener("resize", () => { window.addEventListener("resize", () => {
const mapTab = document.getElementById("tab-map"); const mapTab = document.getElementById("tab-map");