Files
zoneminder/web/js/table-helpers.js
T
Isaac ConnorandClaude Opus 5 9f69694f67 fix: define the bootstrap-table icons before the view scripts refs #5153
icons is the toolbar icon map that thirteen view scripts, and logpanel.js,
pass to bootstrapTable(). It was defined in skin.js, which xhtmlFooter()
loads last of all, after every view's own script. A client that loads the
whole page is fine, because skin.js still runs before DOMContentLoaded,
but one that stops short -- a crawler that caps resource loading, an
aborted load -- runs the view's ready handler against an undefined icons
and the table never initialises:

  Uncaught ReferenceError: icons is not defined
      at HTMLDocument.initPage (.../views_js_console-....js:458:12)

Move it to web/js/table-helpers.js, which already exists to hold what the
bootstrap-table views share and is loaded well before them, and export it
alongside the other helpers for the node tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-26 11:23:38 -04:00

95 lines
3.9 KiB
JavaScript

'use strict';
// Helpers shared by the bootstrap-table views (events, console, log, frames,
// reports, snapshots, watch). Loaded as a plain browser script before the view
// scripts (web/skins/classic/includes/functions.php), so the functions below are
// globals by the time any of them runs. Also CommonJS-exported for node unit
// tests (tests/js/table-helpers.test.js).
// The icons the bootstrap-table top-right toolbar is built with. Every view
// that makes a table passes these to bootstrapTable(), so they have to exist
// before the view scripts run. They used to live in skin.js, which the footer
// loads last of all: a client that never got that far -- a crawler that stops
// short, an aborted load -- ran the view's ready handler against an undefined
// icons and threw.
var icons = {
paginationSwitchDown: 'fa-caret-square-o-down',
paginationSwitchUp: 'fa-caret-square-o-up',
export: 'fa-download',
refresh: 'fa-retweet',
autoRefresh: 'fa-clock-o',
advancedSearchIcon: 'fa-chevron-down',
toggleOff: 'fa-toggle-off',
toggleOn: 'fa-toggle-on',
columns: 'fa-th-list',
fullscreen: 'fa-arrows-alt',
detailOpen: 'fa-plus',
detailClose: 'fa-minus'
};
// Tables whose ajax request was skipped because the page was hidden, waiting to
// be refreshed once it is shown again.
const tablesPendingVisibility = [];
// Decide whether to skip a bootstrap-table ajax request because the page is
// hidden. Returns true when the caller should return without issuing it.
//
// Skipping alone is not enough. Bootstrap-table calls its ajax function on init
// as well as on refresh, and a skipped request is never re-issued, so the table
// renders "No matching records found" over a result it never asked for. Nothing
// brings it back and the user has to refresh by hand (issue #5026). Recording
// the table here means it is refreshed the moment the page becomes visible.
//
// A page can be hidden for the whole of its load - opened in a background tab,
// restored, or simply behind another window - so this is not only about a tab
// the user switched away from later.
function deferTableRequestWhileHidden(table) {
if (document.visibilityState !== 'hidden') return false;
table.bootstrapTable('hideLoading');
// A table can be deferred repeatedly (init, then auto-refresh ticks) and only
// needs refreshing once.
if (tablesPendingVisibility.indexOf(table) === -1) {
tablesPendingVisibility.push(table);
}
return true;
}
// Refresh every table whose request was skipped while hidden. The auth hash we
// were holding may have expired during the hide, so wait for a confirmed one
// rather than have every deferred table 403 (auth-helpers.js). whenAuthFresh is
// absent under node; refresh directly there.
function refreshTablesPendingVisibility() {
if (document.visibilityState === 'hidden') return;
// Drain before refreshing: refresh() calls the ajax function synchronously,
// which would otherwise re-add the table while we are still iterating.
const tables = tablesPendingVisibility.splice(0, tablesPendingVisibility.length);
// Nothing was deferred, so there is nothing to authenticate for. This handler
// is bound on every classic page including the unauthenticated ones, and
// whenAuthFresh() can send a probe, so becoming visible must stay a no-op
// when there is no work.
if (!tables.length) return;
const refresh = function() {
for (let i = 0; i < tables.length; i++) {
tables[i].bootstrapTable('refresh');
}
};
if (typeof whenAuthFresh === 'function') {
whenAuthFresh(refresh);
} else {
refresh();
}
}
if (typeof document !== 'undefined' && document.addEventListener) {
document.addEventListener('visibilitychange', refreshTablesPendingVisibility);
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
icons,
deferTableRequestWhileHidden,
refreshTablesPendingVisibility,
tablesPendingVisibility,
};
}