// Load spots and populate the table. function loadSpots() { $.getJSON('/api/v1/spots' + buildQueryString(), function(jsonData) { // Store last updated time lastUpdateTime = moment.utc(); updateRefreshDisplay(); // Store data spots = 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", "de_continent", "mode_type", "source", "band"].forEach(fn => { if (!allFilterOptionsSelected(fn)) { str = str + getQueryStringFor(fn) + "&"; } }); str = str + "limit=" + $("#spots-to-fetch option:selected").val(); return str; } // Update the spots table function updateTable() { // Use local time instead of UTC? var useLocalTime = $("#timeZone")[0].value == "local"; // Get user grid if valid, this will be null if it's not. var userPos = latLonForGridCentre($("#userGrid").val()); // Table data toggles var showTime = $("#tableShowTime")[0].checked; var showDX = $("#tableShowDX")[0].checked; var showFreq = $("#tableShowFreq")[0].checked; var showMode = $("#tableShowMode")[0].checked; var showComment = $("#tableShowComment")[0].checked; var showBearing = $("#tableShowBearing")[0].checked && userPos != null; var showType = $("#tableShowType")[0].checked; var showRef = $("#tableShowRef")[0].checked; var showDE = $("#tableShowDE")[0].checked; // Populate table with headers let table = $('').append(''); if (showTime) { table.find('thead tr').append(``); } if (showDX) { table.find('thead tr').append(``); } if (showFreq) { table.find('thead tr').append(``); } if (showMode) { table.find('thead tr').append(``); } if (showComment) { table.find('thead tr').append(``); } if (showBearing) { table.find('thead tr').append(``); } if (showType) { table.find('thead tr').append(``); } if (showRef) { table.find('thead tr').append(``); } if (showDE) { table.find('thead tr').append(``); } if (spots.length == 0) { table.find('tbody').append(''); } spots.forEach(s => { // Create row let $tr = $(''); // Show faded out if QRT if (s["qrt"] == true) { $tr.addClass("table-faded"); } // Format a UTC or local time for display var time = moment.unix(s["time"]).utc(); if (useLocalTime) { time = time.local(); } var time_formatted = time.format("HH:mm"); // Format DX flag var dx_flag = ""; if (s["dx_flag"] && s["dx_flag"] != null && s["dx_flag"] != "") { dx_flag = s["dx_flag"]; } // Format dx country var dx_country = s["dx_country"]; if (dx_country == null) { dx_country = "Unknown or not a country"; } // Format the frequency var mhz = Math.floor(s["freq"] / 1000000.0); var khz = Math.floor((s["freq"] - (mhz * 1000000.0)) / 1000.0); var hz = Math.floor(s["freq"] - (mhz * 1000000.0) - (khz * 1000.0)); var hz_string = (hz > 0) ? hz.toFixed(0)[0] : ""; var freq_string = `${mhz.toFixed(0)}${khz.toFixed(0).padStart(3, '0')}${hz_string}` // Format the mode mode_string = s["mode"]; if (s["mode"] == null) { mode_string = "???"; } if (s["mode_source"] == "BANDPLAN") { mode_string = mode_string + ""; } // Format comment var commentText = ""; if (s["comment"] != null) { commentText = escapeHtml(s["comment"]); } // Format bearing text var bearingText = "---"; if (userPos != null && s["dx_latitude"] != null && s["dx_longitude"] != null) { var bearing = calcBearing(userPos[0], userPos[1], s["dx_latitude"], s["dx_longitude"]); bearingText = bearing.toFixed(0).padStart(3, '0') + "°"; if (s["dx_location_good"] == null || s["dx_location_good"] == false) { if (s["dx_location_source"] == "QRZ") { bearingText = bearingText + ""; } else { bearingText = bearingText + ""; } } } // Format "type" (Sig or fallback to source) var typeText = s["source"]; if (s["sig"]) { typeText = s["sig"]; } // Format sig_refs var sig_refs = ""; if (s["sig_refs"] && s["sig_refs_urls"] && s["sig_refs"].length == s["sig_refs_urls"].length && s["sig_refs"].length == s["sig_refs_names"].length) { items = s["sig_refs"].map(s => `${s}`) for (var i = 0; i < items.length; i++) { items[i] = `${items[i]}` } sig_refs = items.join(", "); } else if (s["sig_refs"]) { sig_refs = s["sig_refs"].map(s => `${s}`).join(", "); } // Format DE flag var de_flag = ""; if (s["de_flag"] && s["de_flag"] != null && s["de_flag"] != "") { de_flag = s["de_flag"]; } // Format de country var de_country = s["de_country"]; if (de_country == null) { de_country = "Unknown or not a country"; } // Format de call var de_call = s["de_call"]; if (de_call == null) { de_call = ""; de_flag = ""; } // Format band name var bandFullName = s['band'] ? s['band'] + " band": "Unknown band"; // Populate the row if (showTime) { $tr.append(``); } if (showDX) { $tr.append(``); } if (showFreq) { $tr.append(``); } if (showMode) { $tr.append(``); } if (showComment) { $tr.append(``); } if (showBearing) { $tr.append(``); } if (showType) { $tr.append(``); } if (showRef) { $tr.append(``); } if (showDE) { $tr.append(``); } table.find('tbody').append($tr); // Second row for mobile view only, containing type, ref & comment $tr2 = $(""); if (s["qrt"] == true) { $tr2.addClass("table-faded"); } $td2 = $("
${useLocalTime ? "Local" : "UTC"}DXFrequencyModeCommentBearingTypeRef.DE
No spots match your filters.
${time_formatted}${dx_flag}${s["dx_call"]}${freq_string}${mode_string}${commentText}${bearingText} ${typeText}${sig_refs}${de_flag}${de_call}
"); if (showType) { $td2.append(` ${typeText} `); } if (showRef) { $td2.append(`${sig_refs} `); } if (showBearing) { $td2.append(`   Bearing: ${bearingText} `); } if (showComment) { $td2.append(`
${commentText}`); } $tr2.append($td2); 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 spots and set up the timer to query // spots repeatedly. function loadOptions() { $.getJSON('/api/v1/options', function(jsonData) { // Store options options = jsonData; // Add CSS for band toggle buttons addBandToggleColourCSS(options["bands"]); // Populate the filters panel generateBandsMultiToggleFilterCard(options["bands"]); generateMultiToggleFilterCard("#dx-continent-options", "dx_continent", options["continents"]); generateMultiToggleFilterCard("#de-continent-options", "de_continent", options["continents"]); generateMultiToggleFilterCard("#mode-options", "mode_type", options["mode_types"]); generateMultiToggleFilterCard("#source-options", "source", options["spot_sources"]); // Load settings from settings storage now all the controls are available loadSettings(); // Extra setting - the toggle for the "bearing" column is disabled if the user has not entered a valid grid, and // normally this logic is handled on user input to the grid field, but we might have just loaded a value direct // into the field, so apply the same logic here. $("#tableShowBearing").prop('disabled', !isUserGridValid()); if (!isUserGridValid()) { $("#tableShowBearing").prop('checked', false); } // Show the Add Spot button if spotting is allowed if (options["spot_allowed"]) { $("#add-spot-button").show(); } // Load spots and set up the timer loadSpots(); setInterval(loadSpots, REFRESH_INTERVAL_SEC * 1000); }); } // Work out if the user's entered grid is a valid Maidenhead grid function isUserGridValid() { userGrid = $("#userGrid").val().toUpperCase(); return latLonForGridCentre(userGrid) != null; } // Method called when the user's grid input is changed. function userGridUpdated() { var userGridValid = isUserGridValid(); if (userGridValid) { updateTable(); } // Enable/disable bearing column depending on grid validity $("#tableShowBearing").prop('disabled', !userGridValid); if (!userGridValid) { $("#tableShowBearing").prop('checked', false); } // Save settings even if not a valid grid, this allows the user to clear their grid and have it save. saveSettings(); } // Method called to add a spot to the server function addSpot() { try { var dx = $("#add-spot-dx-call").val().toUpperCase(); var freqStr = $("#add-spot-freq").val(); var mode = $("#add-spot-mode").val().toUpperCase(); var comment = $("#add-spot-comment").val(); var de = $("#add-spot-de-call").val().toUpperCase(); var spot = {} if (dx != "") { spot["dx_call"] = dx; } else { showAddSpotError("A DX callsign is required in order to spot."); return; } if (freqStr != "") { spot["freq"] = parseFloat(freqStr) * 1000; } else { showAddSpotError("A frequency is required in order to spot."); return; } if (mode != "") { spot["mode"] = mode; } if (comment != "") { spot["comment"] = comment; } if (de != "") { spot["de_call"] = de; } spot["time"] = moment.utc().valueOf() / 1000.0; $.ajax("/api/v1/spot", { data : JSON.stringify(spot), contentType : 'application/json', type : 'POST', timeout: 10000, success: async function (result) { $("#post-spot-result-good").html(""); setTimeout(() => $("#post-spot-result-good").hide(), 2000); loadSpots(); }, error: function (result) { showAddSpotError(result.responseText); } }); } catch (error) { showAddSpotError(error); } return false; } // Show an "add spot" error. function showAddSpotError(text) { $("#post-spot-result-bad").html(""); } // React to toggling/closing panels function toggleFiltersPanel() { // If we are going to show the filters panel, hide the display and add spot panels if (!$("#filters-area").is(":visible") && $("#display-area").is(":visible")) { $("#display-area").hide(); $("#display-button").button("toggle"); } if (!$("#filters-area").is(":visible") && $("#add-spot-area").is(":visible")) { $("#add-spot-area").hide(); $("#add-spot-button").button("toggle"); } $("#filters-area").toggle(); } function closeFiltersPanel() { $("#filters-button").button("toggle"); $("#filters-area").hide(); } function toggleDisplayPanel() { // If we are going to show the display panel, hide the filters and add spot panels if (!$("#display-area").is(":visible") && $("#filters-area").is(":visible")) { $("#filters-area").hide(); $("#filters-button").button("toggle"); } if (!$("#display-area").is(":visible") && $("#add-spot-area").is(":visible")) { $("#add-spot-area").hide(); $("#add-spot-button").button("toggle"); } $("#display-area").toggle(); } function closeDisplayPanel() { $("#display-button").button("toggle"); $("#display-area").hide(); } function toggleAddSpotPanel() { // If we are going to show the add spot panel, hide the filters and display panels if (!$("#add-spot-area").is(":visible") && $("#filters-area").is(":visible")) { $("#filters-area").hide(); $("#filters-button").button("toggle"); } if (!$("#add-spot-area").is(":visible") && $("#display-area").is(":visible")) { $("#display-area").hide(); $("#display-button").button("toggle"); } $("#add-spot-area").toggle(); } function closeAddSpotPanel() { $("#add-spot-button").button("toggle"); $("#add-spot-area").hide(); } // Display the intro box, unless the user has already dismissed it once. function displayIntroBox() { if (localStorage.getItem("intro-box-dismissed") == null) { $("#intro-box").show(); } $("#intro-box-dismiss").click(function() { localStorage.setItem("intro-box-dismissed", true); }); } // Startup $(document).ready(function() { // Call loadOptions(), this will then trigger loading spots and setting up timers. loadOptions(); // Update the refresh timing display every second setInterval(updateRefreshDisplay, 1000); // Display intro box displayIntroBox(); });