From 9d5255c7c0db2350e090ac3a66df6354f9383997 Mon Sep 17 00:00:00 2001 From: Andreas Kristiansen <6977712+AndreasK79@users.noreply.github.com> Date: Mon, 22 Sep 2025 09:00:17 +0200 Subject: [PATCH 1/7] [Advanced Logbook] Enabled csv button + tweak datatable --- assets/js/sections/logbookadvanced.js | 204 +++++++++++++++++++++++++- 1 file changed, 203 insertions(+), 1 deletion(-) diff --git a/assets/js/sections/logbookadvanced.js b/assets/js/sections/logbookadvanced.js index a70279d88..549d647c1 100644 --- a/assets/js/sections/logbookadvanced.js +++ b/assets/js/sections/logbookadvanced.js @@ -174,6 +174,205 @@ function updateRow(qso) { function loadQSOTable(rows) { const $table = $('#qsoList'); + if (!$table.length) return; + + // Safety destroy if an old instance exists + if ($.fn.dataTable && $.fn.dataTable.isDataTable($table)) { + try { + const old = $table.DataTable(); + if (typeof old.scroller === 'function') { + try { old.scroller().destroy(); } catch (e) {} + } + try { old.off(); old.clear(); old.destroy(); } catch (e) {} + } catch (e) {} + $table.find('tbody').empty(); + $table.removeClass('dataTable'); // remove leftover class + } + + const langUrl = getDataTablesLanguageUrl(); + + const initTable = function(language) { + // make sure moment plugin (if used) is set + if ($.fn.dataTable && $.fn.dataTable.moment) { + $.fn.dataTable.moment(custom_date_format + ' HH:mm'); + } + + // Build the full data array (array-of-arrays). Keep metadata properties on the array. + const allData = []; + for (let i = 0; i < rows.length; i++) { + const qso = rows[i]; + const data = []; + + data.push('
'); + + if ((user_options.datetime.show ?? 'true') == "true") { + data.push(qso.datetime === '' ? 'Missing date' : qso.qsoDateTime); + } + if ((user_options.de.show ?? 'true') == "true") { + data.push(qso.de.replaceAll('0', 'Ø')); + } + if ((user_options.dx.show ?? 'true') == "true") { + if (qso.dx === '') { + data.push('Missing callsign'); + } else { + let dxHtml = '' + qso.dx.replaceAll('0', 'Ø') + ''; + if (qso.callsign != '') { + dxHtml += ' L'; + } + dxHtml += ' Lookup ' + qso.dx.replaceAll('0', 'Ø') + ' on QRZ.com' + + ' Lookup ' + qso.dx.replaceAll('0', 'Ø') + ' on HamQTH' + + ' Clublog Log Search' + + ''; + data.push(dxHtml); + } + } + + // The rest of the user_options fields (kept exactly like your original) + if ((user_options.mode.show ?? 'true') == "true") data.push(qso.mode === '' ? 'Missing mode' : qso.mode); + if ((user_options.rsts.show ?? 'true') == "true") data.push(qso.rstS); + if ((user_options.rstr.show ?? 'true') == "true") data.push(qso.rstR); + if ((user_options.band.show ?? 'true') == "true") data.push(qso.band === '' ? 'Missing band' : qso.band); + if ((user_options.frequency.show ?? 'true') == "true") data.push(qso.frequency); + if ((user_options.gridsquare.show ?? 'true') == "true") data.push(qso.gridsquare); + if ((user_options.name.show ?? 'true') == "true") data.push(qso.name); + if ((user_options.qth.show ?? 'true') == "true") data.push(qso.qth); + if ((user_options.qslvia.show ?? 'true') == "true") data.push(qso.qslVia); + if ((user_options.clublog.show ?? 'true') == "true") data.push(qso.clublog); + if ((user_options.qsl.show ?? 'true') == "true") data.push(qso.qsl); + if ($(".eqslconfirmation")[0] && (user_options.eqsl.show ?? 'true') == "true") data.push(qso.eqsl); + if ($(".lotwconfirmation")[0] && (user_options.lotw.show ?? 'true') == "true") data.push(qso.lotw); + if ((user_options.qrz.show ?? 'true') == "true") data.push(qso.qrz); + if ((user_options.dcl.show ?? 'true') == "true") data.push(qso.dcl); + if ((user_options.qslmsgs.show ?? 'true') == "true") data.push(qso.qslMessage); + if ((user_options.qslmsgr.show ?? 'true') == "true") data.push(qso.qslMessageR); + if ((user_options.dxcc.show ?? 'true') == "true") data.push(qso.dxcc + qso.flag + (qso.end == null ? '' : ' Deleted DXCC')); + if ((user_options.state.show ?? 'true') == "true") data.push(qso.state); + if ((user_options.county.show ?? 'true') == "true") data.push(qso.county); + if ((user_options.cqzone.show ?? 'true') == "true") data.push(qso.cqzone); + if ((user_options.ituzone.show ?? 'true') == "true") data.push(qso.ituzone); + if ((user_options.iota.show ?? 'true') == "true") data.push(qso.iota); + if ((user_options.pota.show ?? 'true') == "true") data.push(qso.pota); + if ((user_options.sota.show ?? 'true') == "true") data.push(qso.sota); + if ((user_options.dok.show ?? 'true') == "true") data.push(qso.dok); + if ((user_options.wwff.show ?? 'true') == "true") data.push(qso.wwff); + if ((user_options.sig.show ?? 'true') == "true") data.push(qso.sig); + if ((user_options.region.show ?? 'true') == "true") data.push(qso.region); + if ((user_options.operator.show ?? 'true') == "true") data.push(qso.operator); + if ((user_options.comment.show ?? 'true') == "true") data.push(qso.comment); + if ((user_options.propagation.show ?? 'true') == "true") data.push(qso.propagation); + if ((user_options.contest.show ?? 'true') == "true") data.push(qso.contest); + if ((user_options.myrefs.show ?? 'true') == "true") data.push(qso.deRefs); + if ((user_options.continent.show ?? 'true') == "true") data.push(qso.continent); + if ((user_options.distance.show ?? 'true') == "true") data.push(qso.distance); + if ((user_options.antennaazimuth.show ?? 'true') == "true") data.push(qso.antennaazimuth); + if ((user_options.antennaelevation.show ?? 'true') == "true") data.push(qso.antennaelevation); + if ((user_options.profilename.show ?? 'true') == "true") data.push(qso.profilename); + if ((user_options.stationpower.show ?? 'true') == "true") data.push(qso.stationpower); + + // metadata accessible inside createdRow + data.id = 'qsoID-' + qso.qsoID; + data._qsoID = qso.qsoID; + + allData.push(data); + } + + // cache header index lookups (avoid repeated DOM queries) + const distanceIdx = $(".distance-column-sort").index(); + const azIdx = $(".antennaazimuth-column-sort").index(); + const elIdx = $(".antennaelevation-column-sort").index(); + const powerIdx = $(".stationpower-column-sort").index(); + + // DataTables init (data provided up-front) + const table = $table.DataTable({ + data: allData, + deferRender: true, + scrollY: window.innerHeight - $('#searchForm').innerHeight() - 250, + scrollCollapse: true, + paging: true, // required for Scroller + scroller: true, + displayStart: 0, + language: language, + ordering: true, + orderClasses: false, + responsive: false, + createdRow: function(row, data) { + if (data && data.id) $(row).attr('id', data.id); + if (data && data._qsoID) $(row).data('qsoID', data._qsoID); + }, + // robust info text so "259 to 250" can't appear + infoCallback: function(settings) { + const api = new $.fn.dataTable.Api(settings); + const info = api.page.info(); + if (info.recordsDisplay === 0) return 'Showing 0 to 0 of 0 entries'; + const dispStart = info.start + 1; + const dispEnd = Math.min(info.recordsDisplay, info.start + info.length); + return 'Showing ' + dispStart + ' to ' + dispEnd + ' of ' + info.recordsDisplay + ' entries'; + }, + columnDefs: [ + { orderable: false, targets: 0 }, + { targets: distanceIdx, type: "numbersort" }, + { targets: azIdx, type: "numbersort" }, + { targets: elIdx, type: "numbersort" }, + { targets: powerIdx, type: "numbersort" }, + ], + dom: 'Bfrtip', + buttons: [ + { + extend: 'csv', + className: 'mb-1 btn btn-sm btn-primary', // Bootstrap classes + init: function(api, node, config) { + $(node).removeClass('dt-button').addClass('btn btn-primary'); // Ensure Bootstrap class applies + }, + } + ] + }); + + // Ensure scroller measured and page reset + try { if (table.scroller) table.scroller().measure(); } catch (e) {} + try { table.page('first').draw(false); } catch (e) { table.draw(false); } + + // Delegate tooltip creation (create on demand) + $table.off('mouseenter', '[data-bs-toggle="tooltip"]').on('mouseenter', '[data-bs-toggle="tooltip"]', function () { + if (!$(this).data('bs.tooltip')) { + $(this).tooltip(); + } + }); + + // Delegate checkbox handling (shift-click selection) — no per-row listeners + let lastChecked = null; + $table.off('click', '.row-check').on('click', '.row-check', function (e) { + const checkboxes = $table.find('.row-check').toArray(); + if (e.shiftKey && lastChecked) { + let start = checkboxes.indexOf(this); + let end = checkboxes.indexOf(lastChecked); + [start, end] = [Math.min(start, end), Math.max(start, end)]; + for (let i = start; i <= end; i++) { + checkboxes[i].checked = lastChecked.checked; + $(checkboxes[i]).closest('tr').toggleClass('activeRow', lastChecked.checked); + } + } else { + $(this).closest('tr').toggleClass('activeRow', this.checked); + } + lastChecked = this; + }); + + // rebind other UI bits after table init (if function exists) + if (typeof rebind_checkbox_trigger === 'function') rebind_checkbox_trigger(); + }; + + // load language then init + if (langUrl) { + $.getJSON(langUrl) + .done(function(language) { initTable(language); }) + .fail(function() { console.error("Failed to load DataTables language file at " + langUrl); initTable({}); }); + } else { + initTable({}); + } +} + + +function loadQSOTable2(rows) { + const $table = $('#qsoList'); // Prevent initializing if already a DataTable if ($.fn.DataTable.isDataTable($table)) { @@ -191,8 +390,11 @@ function loadQSOTable(rows) { ordering: true, scrollY: window.innerHeight - $('#searchForm').innerHeight() - 250, scrollCollapse: true, - paging: false, language: language, + deferRender: true, // delay DOM creation until needed + ordering: true, + paging: true, + scroller: true, createdRow: function (row, data, dataIndex) { $(row).attr('id', data.id); }, From 3e9ef5d684032c506c323fba25cdf41d7e557635 Mon Sep 17 00:00:00 2001 From: Andreas Kristiansen <6977712+AndreasK79@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:37:30 +0200 Subject: [PATCH 2/7] Replace slashed zero, remove LoTW L and trim whitespace in callsign --- assets/js/sections/logbookadvanced.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/assets/js/sections/logbookadvanced.js b/assets/js/sections/logbookadvanced.js index 549d647c1..3225c0173 100644 --- a/assets/js/sections/logbookadvanced.js +++ b/assets/js/sections/logbookadvanced.js @@ -323,6 +323,26 @@ function loadQSOTable(rows) { init: function(api, node, config) { $(node).removeClass('dt-button').addClass('btn btn-primary'); // Ensure Bootstrap class applies }, + exportOptions: { + columns: ':visible', + format: { + body: function (data, row, column, node) { + // strip HTML tags first (like DataTables does by default) + if (typeof data === 'string') { + data = data.replace(/<[^>]*>/g, ''); + } + // then replace Ø with 0 in specific columns + if (column === 1 || column === 2 || column === 3) { + // remove a trailing "L" and trim whitespaces + data = data.replace(/\s*L\s*$/, '').trim(); + if (typeof data === 'string' && data.includes('Ø')) { + data = data.replace(/Ø/g, '0'); + } + } + return data; + } + } + } } ] }); From 2c839bb621ef8ca22ca9f9df02dfdb90d5f8a59c Mon Sep 17 00:00:00 2001 From: Andreas Kristiansen <6977712+AndreasK79@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:42:14 +0200 Subject: [PATCH 3/7] Remove checkbox in csv export --- assets/js/sections/logbookadvanced.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/sections/logbookadvanced.js b/assets/js/sections/logbookadvanced.js index 3225c0173..30c5ebdb3 100644 --- a/assets/js/sections/logbookadvanced.js +++ b/assets/js/sections/logbookadvanced.js @@ -324,7 +324,7 @@ function loadQSOTable(rows) { $(node).removeClass('dt-button').addClass('btn btn-primary'); // Ensure Bootstrap class applies }, exportOptions: { - columns: ':visible', + columns: ':visible:not(:eq(0))', // export all visible except column 4 format: { body: function (data, row, column, node) { // strip HTML tags first (like DataTables does by default) From 5c9a58d6171fe8cff0e8b6c0977270730a901ba5 Mon Sep 17 00:00:00 2001 From: Andreas Kristiansen <6977712+AndreasK79@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:50:18 +0200 Subject: [PATCH 4/7] Replace arrows --- assets/js/sections/logbookadvanced.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/assets/js/sections/logbookadvanced.js b/assets/js/sections/logbookadvanced.js index 30c5ebdb3..0149f03fc 100644 --- a/assets/js/sections/logbookadvanced.js +++ b/assets/js/sections/logbookadvanced.js @@ -339,6 +339,12 @@ function loadQSOTable(rows) { data = data.replace(/Ø/g, '0'); } } + if (typeof data === 'string' && data.includes('▲')) { + data = data.replace(/▲/g, ''); + } + if (typeof data === 'string' && data.includes('▼')) { + data = data.replace(/▼/g, ''); + } return data; } } From 83616c3722df6e9d2bd74a5870938a7fdf0980f2 Mon Sep 17 00:00:00 2001 From: Andreas Kristiansen <6977712+AndreasK79@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:08:19 +0200 Subject: [PATCH 5/7] Get rid of tooltip --- assets/js/sections/logbookadvanced.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/assets/js/sections/logbookadvanced.js b/assets/js/sections/logbookadvanced.js index 0149f03fc..25397e8ec 100644 --- a/assets/js/sections/logbookadvanced.js +++ b/assets/js/sections/logbookadvanced.js @@ -340,11 +340,13 @@ function loadQSOTable(rows) { } } if (typeof data === 'string' && data.includes('▲')) { - data = data.replace(/▲/g, ''); + data = data.replace(/▲/g, ''); } if (typeof data === 'string' && data.includes('▼')) { - data = data.replace(/▼/g, ''); + data = data.replace(/▼/g, ''); } + + data = data.replace(/ data-bs-toggle="tooltip" data-bs-html="true" class="[^"]*">/g, ''); return data; } } From af86ffc58a3408c1f89e90dcc65d3d3dd0d9192a Mon Sep 17 00:00:00 2001 From: phl0 Date: Mon, 22 Sep 2025 15:20:15 +0200 Subject: [PATCH 6/7] Remove extra br inside HTML --- assets/js/sections/logbookadvanced.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assets/js/sections/logbookadvanced.js b/assets/js/sections/logbookadvanced.js index 25397e8ec..7df8f2eaf 100644 --- a/assets/js/sections/logbookadvanced.js +++ b/assets/js/sections/logbookadvanced.js @@ -328,6 +328,9 @@ function loadQSOTable(rows) { format: { body: function (data, row, column, node) { // strip HTML tags first (like DataTables does by default) + if (typeof data === 'string' && data.includes('
')) { + data = data.replace(/
/g, ''); + } if (typeof data === 'string') { data = data.replace(/<[^>]*>/g, ''); } From aca174394691ae4433f88584efe22b8e759c8602 Mon Sep 17 00:00:00 2001 From: Andreas Kristiansen <6977712+AndreasK79@users.noreply.github.com> Date: Tue, 23 Sep 2025 10:40:31 +0200 Subject: [PATCH 7/7] Removed tweaking, not happy with it --- assets/js/sections/logbookadvanced.js | 219 ++------------------------ 1 file changed, 14 insertions(+), 205 deletions(-) diff --git a/assets/js/sections/logbookadvanced.js b/assets/js/sections/logbookadvanced.js index 7df8f2eaf..d9c81742e 100644 --- a/assets/js/sections/logbookadvanced.js +++ b/assets/js/sections/logbookadvanced.js @@ -174,146 +174,35 @@ function updateRow(qso) { function loadQSOTable(rows) { const $table = $('#qsoList'); - if (!$table.length) return; - // Safety destroy if an old instance exists - if ($.fn.dataTable && $.fn.dataTable.isDataTable($table)) { - try { - const old = $table.DataTable(); - if (typeof old.scroller === 'function') { - try { old.scroller().destroy(); } catch (e) {} - } - try { old.off(); old.clear(); old.destroy(); } catch (e) {} - } catch (e) {} - $table.find('tbody').empty(); - $table.removeClass('dataTable'); // remove leftover class + // Prevent initializing if already a DataTable + if ($.fn.DataTable.isDataTable($table)) { + $table.DataTable().clear().destroy(); } const langUrl = getDataTablesLanguageUrl(); const initTable = function(language) { - // make sure moment plugin (if used) is set - if ($.fn.dataTable && $.fn.dataTable.moment) { - $.fn.dataTable.moment(custom_date_format + ' HH:mm'); - } + $.fn.dataTable.moment(custom_date_format + ' HH:mm'); - // Build the full data array (array-of-arrays). Keep metadata properties on the array. - const allData = []; - for (let i = 0; i < rows.length; i++) { - const qso = rows[i]; - const data = []; - - data.push('
'); - - if ((user_options.datetime.show ?? 'true') == "true") { - data.push(qso.datetime === '' ? 'Missing date' : qso.qsoDateTime); - } - if ((user_options.de.show ?? 'true') == "true") { - data.push(qso.de.replaceAll('0', 'Ø')); - } - if ((user_options.dx.show ?? 'true') == "true") { - if (qso.dx === '') { - data.push('Missing callsign'); - } else { - let dxHtml = '' + qso.dx.replaceAll('0', 'Ø') + ''; - if (qso.callsign != '') { - dxHtml += ' L'; - } - dxHtml += ' Lookup ' + qso.dx.replaceAll('0', 'Ø') + ' on QRZ.com' + - ' Lookup ' + qso.dx.replaceAll('0', 'Ø') + ' on HamQTH' + - ' Clublog Log Search' + - ''; - data.push(dxHtml); - } - } - - // The rest of the user_options fields (kept exactly like your original) - if ((user_options.mode.show ?? 'true') == "true") data.push(qso.mode === '' ? 'Missing mode' : qso.mode); - if ((user_options.rsts.show ?? 'true') == "true") data.push(qso.rstS); - if ((user_options.rstr.show ?? 'true') == "true") data.push(qso.rstR); - if ((user_options.band.show ?? 'true') == "true") data.push(qso.band === '' ? 'Missing band' : qso.band); - if ((user_options.frequency.show ?? 'true') == "true") data.push(qso.frequency); - if ((user_options.gridsquare.show ?? 'true') == "true") data.push(qso.gridsquare); - if ((user_options.name.show ?? 'true') == "true") data.push(qso.name); - if ((user_options.qth.show ?? 'true') == "true") data.push(qso.qth); - if ((user_options.qslvia.show ?? 'true') == "true") data.push(qso.qslVia); - if ((user_options.clublog.show ?? 'true') == "true") data.push(qso.clublog); - if ((user_options.qsl.show ?? 'true') == "true") data.push(qso.qsl); - if ($(".eqslconfirmation")[0] && (user_options.eqsl.show ?? 'true') == "true") data.push(qso.eqsl); - if ($(".lotwconfirmation")[0] && (user_options.lotw.show ?? 'true') == "true") data.push(qso.lotw); - if ((user_options.qrz.show ?? 'true') == "true") data.push(qso.qrz); - if ((user_options.dcl.show ?? 'true') == "true") data.push(qso.dcl); - if ((user_options.qslmsgs.show ?? 'true') == "true") data.push(qso.qslMessage); - if ((user_options.qslmsgr.show ?? 'true') == "true") data.push(qso.qslMessageR); - if ((user_options.dxcc.show ?? 'true') == "true") data.push(qso.dxcc + qso.flag + (qso.end == null ? '' : ' Deleted DXCC')); - if ((user_options.state.show ?? 'true') == "true") data.push(qso.state); - if ((user_options.county.show ?? 'true') == "true") data.push(qso.county); - if ((user_options.cqzone.show ?? 'true') == "true") data.push(qso.cqzone); - if ((user_options.ituzone.show ?? 'true') == "true") data.push(qso.ituzone); - if ((user_options.iota.show ?? 'true') == "true") data.push(qso.iota); - if ((user_options.pota.show ?? 'true') == "true") data.push(qso.pota); - if ((user_options.sota.show ?? 'true') == "true") data.push(qso.sota); - if ((user_options.dok.show ?? 'true') == "true") data.push(qso.dok); - if ((user_options.wwff.show ?? 'true') == "true") data.push(qso.wwff); - if ((user_options.sig.show ?? 'true') == "true") data.push(qso.sig); - if ((user_options.region.show ?? 'true') == "true") data.push(qso.region); - if ((user_options.operator.show ?? 'true') == "true") data.push(qso.operator); - if ((user_options.comment.show ?? 'true') == "true") data.push(qso.comment); - if ((user_options.propagation.show ?? 'true') == "true") data.push(qso.propagation); - if ((user_options.contest.show ?? 'true') == "true") data.push(qso.contest); - if ((user_options.myrefs.show ?? 'true') == "true") data.push(qso.deRefs); - if ((user_options.continent.show ?? 'true') == "true") data.push(qso.continent); - if ((user_options.distance.show ?? 'true') == "true") data.push(qso.distance); - if ((user_options.antennaazimuth.show ?? 'true') == "true") data.push(qso.antennaazimuth); - if ((user_options.antennaelevation.show ?? 'true') == "true") data.push(qso.antennaelevation); - if ((user_options.profilename.show ?? 'true') == "true") data.push(qso.profilename); - if ((user_options.stationpower.show ?? 'true') == "true") data.push(qso.stationpower); - - // metadata accessible inside createdRow - data.id = 'qsoID-' + qso.qsoID; - data._qsoID = qso.qsoID; - - allData.push(data); - } - - // cache header index lookups (avoid repeated DOM queries) - const distanceIdx = $(".distance-column-sort").index(); - const azIdx = $(".antennaazimuth-column-sort").index(); - const elIdx = $(".antennaelevation-column-sort").index(); - const powerIdx = $(".stationpower-column-sort").index(); - - // DataTables init (data provided up-front) const table = $table.DataTable({ - data: allData, - deferRender: true, + searching: true, + responsive: false, + ordering: true, scrollY: window.innerHeight - $('#searchForm').innerHeight() - 250, scrollCollapse: true, - paging: true, // required for Scroller - scroller: true, - displayStart: 0, language: language, ordering: true, - orderClasses: false, - responsive: false, - createdRow: function(row, data) { - if (data && data.id) $(row).attr('id', data.id); - if (data && data._qsoID) $(row).data('qsoID', data._qsoID); - }, - // robust info text so "259 to 250" can't appear - infoCallback: function(settings) { - const api = new $.fn.dataTable.Api(settings); - const info = api.page.info(); - if (info.recordsDisplay === 0) return 'Showing 0 to 0 of 0 entries'; - const dispStart = info.start + 1; - const dispEnd = Math.min(info.recordsDisplay, info.start + info.length); - return 'Showing ' + dispStart + ' to ' + dispEnd + ' of ' + info.recordsDisplay + ' entries'; + paging: false, + createdRow: function (row, data, dataIndex) { + $(row).attr('id', data.id); }, columnDefs: [ { orderable: false, targets: 0 }, - { targets: distanceIdx, type: "numbersort" }, - { targets: azIdx, type: "numbersort" }, - { targets: elIdx, type: "numbersort" }, - { targets: powerIdx, type: "numbersort" }, + { targets: $(".distance-column-sort").index(), type: "numbersort" }, + { targets: $(".antennaazimuth-column-sort").index(), type: "numbersort" }, + { targets: $(".antennaelevation-column-sort").index(), type: "numbersort" }, + { targets: $(".stationpower-column-sort").index(), type: "numbersort" }, ], dom: 'Bfrtip', buttons: [ @@ -358,86 +247,6 @@ function loadQSOTable(rows) { ] }); - // Ensure scroller measured and page reset - try { if (table.scroller) table.scroller().measure(); } catch (e) {} - try { table.page('first').draw(false); } catch (e) { table.draw(false); } - - // Delegate tooltip creation (create on demand) - $table.off('mouseenter', '[data-bs-toggle="tooltip"]').on('mouseenter', '[data-bs-toggle="tooltip"]', function () { - if (!$(this).data('bs.tooltip')) { - $(this).tooltip(); - } - }); - - // Delegate checkbox handling (shift-click selection) — no per-row listeners - let lastChecked = null; - $table.off('click', '.row-check').on('click', '.row-check', function (e) { - const checkboxes = $table.find('.row-check').toArray(); - if (e.shiftKey && lastChecked) { - let start = checkboxes.indexOf(this); - let end = checkboxes.indexOf(lastChecked); - [start, end] = [Math.min(start, end), Math.max(start, end)]; - for (let i = start; i <= end; i++) { - checkboxes[i].checked = lastChecked.checked; - $(checkboxes[i]).closest('tr').toggleClass('activeRow', lastChecked.checked); - } - } else { - $(this).closest('tr').toggleClass('activeRow', this.checked); - } - lastChecked = this; - }); - - // rebind other UI bits after table init (if function exists) - if (typeof rebind_checkbox_trigger === 'function') rebind_checkbox_trigger(); - }; - - // load language then init - if (langUrl) { - $.getJSON(langUrl) - .done(function(language) { initTable(language); }) - .fail(function() { console.error("Failed to load DataTables language file at " + langUrl); initTable({}); }); - } else { - initTable({}); - } -} - - -function loadQSOTable2(rows) { - const $table = $('#qsoList'); - - // Prevent initializing if already a DataTable - if ($.fn.DataTable.isDataTable($table)) { - $table.DataTable().clear().destroy(); - } - - const langUrl = getDataTablesLanguageUrl(); - - const initTable = function(language) { - $.fn.dataTable.moment(custom_date_format + ' HH:mm'); - - const table = $table.DataTable({ - searching: false, - responsive: false, - ordering: true, - scrollY: window.innerHeight - $('#searchForm').innerHeight() - 250, - scrollCollapse: true, - language: language, - deferRender: true, // delay DOM creation until needed - ordering: true, - paging: true, - scroller: true, - createdRow: function (row, data, dataIndex) { - $(row).attr('id', data.id); - }, - columnDefs: [ - { orderable: false, targets: 0 }, - { targets: $(".distance-column-sort").index(), type: "numbersort" }, - { targets: $(".antennaazimuth-column-sort").index(), type: "numbersort" }, - { targets: $(".antennaelevation-column-sort").index(), type: "numbersort" }, - { targets: $(".stationpower-column-sort").index(), type: "numbersort" }, - ] - }); - for (i = 0; i < rows.length; i++) { let qso = rows[i];