// Storage for the alert data that the server gives us. var alerts = [] // Storage for the options that the server gives us. This will define our filters. var options = {}; // Last time we updated the alerts list on display. var lastUpdateTime; // Load alerts and populate the table. function loadAlerts() { $.getJSON('/api/alerts' + buildQueryString(), function(jsonData) { // Present loaded time $("#timing-container").text("Data loaded at " + moment.utc().format('HH:mm') + " UTC."); // Store data alerts = jsonData; // Update table updateTable(); }); } // Build a query string for the API, based on the filters that the user has selected. function buildQueryString() { var str = "?"; ["dx_continent", "source"].forEach(fn => { if (!allFilterOptionsSelected(fn)) { str = str + getQueryStringFor(fn) + "&"; } }); str = str + "limit=" + $("#alerts-to-fetch option:selected").val(); return str; } // For a parameter, such as dx_continent, get the query string for the current filter options. function getQueryStringFor(parameter) { return parameter + "=" + encodeURIComponent(getSelectedFilterOptions(parameter)); } // For a parameter, such as dx_continent, get the filter options that are currently selected in the UI. function getSelectedFilterOptions(parameter) { return $(".filter-button-" + parameter).filter(function() { return this.checked; }).map(function() { return this.value; }).get().join(","); } // For a parameter, such as dx_continent, return true if all possible options are enabled. (In this case, we don't need // to bother sending this as one of the query parameters to the API; no parameter provided implies "send everything".) function allFilterOptionsSelected(parameter) { var filter = $(".filter-button-" + parameter).filter(function() { return !this.checked; }).get(); return filter.length == 0; } // Update the alerts table function updateTable() { // Populate table with headers let table = $('').append(''); table.find('thead tr').append(``); table.find('thead tr').append(``); table.find('thead tr').append(``); table.find('thead tr').append(``); table.find('thead tr').append(``); table.find('thead tr').append(``); table.find('thead tr').append(``); if (alerts.length == 0) { table.find('tbody').append(''); } alerts.forEach(s => { // Create row let $tr = $(''); // Format UTC times for display var start_time = moment.unix(s["start_time"]).utc(); var start_time_formatted = start_time.format("YYYY-MM-DD HH:mm"); var end_time = moment.unix(s["start_time"]).utc(); var end_time_formatted = (end_time != null) ? end_time.format("YYYY-MM-DD HH:mm") : "Not specified"; // Format dx country var dx_country = s["dx_country"] if (dx_country == null) { dx_country = "Unknown or not a country" } // Format freqs & modes var freqsModesText = ""; if (s["freqs_modes"] != null) { freqsModesText = escapeHtml(s["freqs_modes"]); } // Format comment var commentText = ""; if (s["comment"] != null) { commentText = escapeHtml(s["comment"]); } // Sig or fallback to source var sigSourceText = s["source"]; if (s["sig"]) { sigSourceText = s["sig"]; } // Format sig_refs var sig_refs = "" if (s["sig_refs"]) { sig_refs = s["sig_refs"].join(", ") } // Populate the row $tr.append(``); $tr.append(``); $tr.append(``); $tr.append(``); $tr.append(``); $tr.append(``); $tr.append(``); table.find('tbody').append($tr); // Second row for mobile view only, containing source, ref, freqs/modes & comment $tr2 = $(""); if (s["qrt"] == true) { $tr2.addClass("table-faded"); } $tr2.append(``); table.find('tbody').append($tr2); }); // Update DOM $('#table-container').html(table); } // Load server options. Once a successful callback is made from this, we then query alerts. function loadOptions() { $.getJSON('/api/options', function(jsonData) { // Store options options = jsonData; // Populate the filters panel $("#settings-container").append(generateFilterCard("DX Continent", "dx_continent", options["continents"])); $("#settings-container").append(generateFilterCard("Sources", "source", options["spot_sources"])); // Load settings from settings storage loadSettings(); // Load alerts loadAlerts(); }); } // Generate filter card function generateFilterCard(displayName, filterQuery, options) { let $col = $("
") let $card = $("
"); let $card_body = $("
"); $card_body.append(`
${displayName}
`); $p = $("

"); // Create a button for each option options.forEach(o => { $p.append(` `); }); // Create All/None buttons $p.append(`  `); // Compile HTML elements to return $card_body.append($p); $card.append($card_body); $col.append($card); return $col; } // Method called when "All" or "None" is clicked function toggleFilterButtons(filterQuery, state) { $(".filter-button-" + filterQuery).each(function() { $(this).prop('checked', state); }); filtersUpdated(); } // Method called when any filter is changed to reload the alerts and persist the filter settings. function filtersUpdated() { loadAlerts(); saveSettings(); } // Utility function to escape HTML characters from a string. function escapeHtml(str) { if (typeof str !== 'string') { return ''; } const escapeCharacter = (match) => { switch (match) { case '&': return '&'; case '<': return '<'; case '>': return '>'; case '"': return '"'; case '\'': return '''; case '`': return '`'; default: return match; } }; return str.replace(/[&<>"'`]/g, escapeCharacter); } // Save settings to local storage function saveSettings() { // Find all storeable UI elements, store a key of "element id:property name" mapped to the value of that // property. For a checkbox, that's the "checked" property. $(".storeable-checkbox").each(function() { localStorage.setItem($(this)[0].id + ":checked", $(this)[0].checked); }); $(".storeable-select").each(function() { localStorage.setItem($(this)[0].id + ":value", $(this)[0].value); }); } // Load settings from local storage and set up the filter selectors function loadSettings() { // Find all local storage entries and push their data to the corresponding UI element Object.keys(localStorage).forEach(function(key) { // Split the key back into an element ID and a property var split = key.split(":"); $("#" + split[0]).prop(split[1], JSON.parse(localStorage.getItem(key))); }); } // Set up UI element event listeners, after the document is ready function setUpEventListeners() { $("#settings-button").click(function() { $("#settings-area").toggle(); }); $("#close-settings-button").click(function() { $("#settings-button").button("toggle"); $("#settings-area").hide(); }); $("#alerts-to-fetch").click(function() { filtersUpdated(); }); } // Startup $(document).ready(function() { // Call loadOptions(), this will then trigger loading alerts and setting up timers. loadOptions(); // Set up event listeners setUpEventListeners(); });

Start UTCEnd UTCDXFrequencies & ModesCommentSourceRef.
No alerts match your filters.
${start_time_formatted}${end_time_formatted}${s["dx_flag"]}${s["dx_call"]}${freqsModesText}${commentText} ${sigSourceText}${sig_refs}
${sig_refs} ${freqsModesText}
${commentText}