File: D:/web/hyperflight/inc-template-currency.asp
<%
' (SS,26/7/26) Combined delivery country and currency selector.
' The compact header control is rendered with the page. The larger country
' and currency panel is loaded from ajax.asp only when the Bootstrap modal
' is opened for the first time.
' (SS,26/7/26) Created using ChatGPT
' (SS,30/7/26) modified to ensure the wavy flag sprite image is only loaded when this selector is rendered
' to reduce the download bandwidth because the country-flags-24@2x-wavy-q75.webp file is 170K
Sub ShowCountryCurrencySelector
Dim LDeliveryCountry, LCountryCodeA2, LCurrencyCode
GetCountryCurrencyHeaderDetails LDeliveryCountry, LCountryCodeA2, LCurrencyCode
LDeliveryCountry = Server.HTMLEncode(LDeliveryCountry)
LCountryCodeA2 = Server.HTMLEncode(LCountryCodeA2)
LCurrencyCode = Server.HTMLEncode(LCurrencyCode)
%>
<input type="hidden" id="CurrencyCode" value="<%=LCurrencyCode%>">
<div id="countryCurrencySelector"
class="country-currency-selector"
data-country="<%=LDeliveryCountry%>"
data-country-code="<%=LCountryCodeA2%>"
data-currency="<%=LCurrencyCode%>">
<button type="button"
id="countryCurrencyButton"
class="country-currency-button"
data-bs-toggle="modal"
data-bs-target="#countryCurrencyModal"
aria-haspopup="dialog"
aria-controls="countryCurrencyModal"
aria-label="Country <%=LDeliveryCountry%>, currency <%=LCurrencyCode%>">
<img class="country-currency-header-flag"
src="/common/flags/country/wavy-64/<%=LCountryCodeA2%>.png"
width="64"
height="48"
alt=""
aria-hidden="true">
<span class="country-currency-code"><%=LCurrencyCode%></span>
<i class="fa-solid fa-caret-down country-currency-caret" aria-hidden="true"></i>
</button>
</div>
<div class="modal fade"
id="countryCurrencyModal"
tabindex="-1"
aria-labelledby="countryCurrencyModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered country-currency-modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title fs-5" id="countryCurrencyModalLabel">Country and currency</h2>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="countryCurrencyModalContent">
<div class="country-currency-loading text-center py-4" role="status">
<span class="spinner-border spinner-border-sm me-2" aria-hidden="true"></span>
Loading settings...
</div>
</div>
</div>
</div>
</div>
<%
AddScript "ScriptCountryCurrencySelector"
End Sub
ShowCountryCurrencySelector
Sub ScriptCountryCurrencySelector
%>
<script>
document.addEventListener("DOMContentLoaded", function () {
const selector = document.getElementById("countryCurrencySelector");
const openButton = document.getElementById("countryCurrencyButton");
const modalElement = document.getElementById("countryCurrencyModal");
const contentElement = document.getElementById("countryCurrencyModalContent");
if (!selector || !openButton || !modalElement || !contentElement) return;
let panelLoaded = false;
let panelLoading = false;
function showLoadError() {
contentElement.innerHTML =
'<div class="alert alert-danger mb-3" role="alert">' +
'The country and currency settings could not be loaded.' +
'</div>' +
'<button type="button" class="btn btn-outline-primary" id="countryCurrencyRetry">Try again</button>';
}
function loadPanel(forceReload) {
if (panelLoading) return;
if (panelLoaded && !forceReload) return;
panelLoading = true;
panelLoaded = false;
contentElement.innerHTML =
'<div class="country-currency-loading text-center py-4" role="status">' +
'<span class="spinner-border spinner-border-sm me-2" aria-hidden="true"></span>' +
'Loading settings...' +
'</div>';
$.ajax({
url: "/ajax.asp",
method: "GET",
cache: false,
data: {
cmd: "country_currency_panel"
}
})
.done(function (html) {
contentElement.innerHTML = html;
panelLoaded = true;
initialisePanel();
})
.fail(function () {
showLoadError();
})
.always(function () {
panelLoading = false;
});
}
function reloadCurrentPageWithGet() {
const url = new URL(window.location.href);
// Do not allow a currency selection left in the query string by the old
// frmSearch route to overwrite the country/currency just saved by AJAX.
url.searchParams.delete("xcmd");
url.searchParams.delete("xcode");
window.location.replace(
url.pathname + url.search + url.hash
);
}
function initialisePanel() {
const panel = contentElement.querySelector(".country-currency-panel");
if (!panel) return;
const countryToggle = panel.querySelector("#countryCurrencyCountryButton");
const countryMenu = panel.querySelector("#countryCurrencyCountryMenu");
const countrySearch = panel.querySelector("#countryCurrencyCountrySearch");
const noCountries = panel.querySelector("#countryCurrencyNoCountries");
const countryInput = panel.querySelector("#countryCurrencyCountry");
const countryText = panel.querySelector(".country-picker-selected-name");
const countryFlag = panel.querySelector(".country-picker-selected-flag");
const vatMessage = panel.querySelector("#countryCurrencyVatMessage");
const currencySelect = panel.querySelector("#countryCurrencyCurrency");
const saveButton = panel.querySelector("#countryCurrencySave");
const errorElement = panel.querySelector("#countryCurrencyError");
const defaultCurrency = panel.dataset.defaultCurrency || "GBP";
if (!countryToggle || !countryMenu || !countryInput || !countryText ||
!countryFlag || !currencySelect || !saveButton || !errorElement) return;
function setCountryMenuOpen(isOpen) {
countryMenu.classList.toggle("d-none", !isOpen);
countryToggle.setAttribute("aria-expanded", isOpen ? "true" : "false");
if (isOpen && countrySearch) {
window.setTimeout(function () {
countrySearch.focus();
}, 0);
}
}
function clearCountrySearch() {
if (!countrySearch) return;
countrySearch.value = "";
panel.querySelectorAll(".country-picker-option").forEach(function (option) {
option.classList.remove("d-none");
});
panel.querySelectorAll(".country-picker-section").forEach(function (section) {
section.classList.remove("d-none");
});
if (noCountries) noCountries.classList.add("d-none");
}
countryToggle.addEventListener("click", function () {
setCountryMenuOpen(countryMenu.classList.contains("d-none"));
});
function updateVatMessage(country) {
if (!vatMessage) {
return;
}
const isUnitedKingdom =
country.trim().toLowerCase() === "united kingdom";
vatMessage.textContent = isUnitedKingdom
? "Prices will include VAT."
: "Prices will exclude VAT.";
}
updateVatMessage(countryInput.value);
panel.querySelectorAll(".country-picker-option").forEach(function (option) {
option.addEventListener("click", function () {
const country = option.dataset.country;
const countryCode = option.dataset.countryCode;
const recommendedCurrency = option.dataset.currency || defaultCurrency;
countryInput.value = country;
countryText.textContent = country;
countryFlag.className =
"country-picker-selected-flag country-flag country-flag-" + countryCode;
updateVatMessage(country);
if (currencySelect.querySelector('option[value="' + recommendedCurrency + '"]')) {
currencySelect.value = recommendedCurrency;
}
else {
currencySelect.value = defaultCurrency;
}
panel.querySelectorAll(".country-picker-option").forEach(function (item) {
const isSelected = item.dataset.country === country;
item.classList.toggle("selected", isSelected);
item.setAttribute("aria-selected", isSelected ? "true" : "false");
});
clearCountrySearch();
setCountryMenuOpen(false);
countryToggle.focus();
});
});
if (countrySearch) {
countrySearch.addEventListener("input", function () {
const query = countrySearch.value.trim().toLowerCase();
let visibleCount = 0;
panel.querySelectorAll(".country-picker-option").forEach(function (option) {
const matches = !query || option.dataset.search.indexOf(query) !== -1;
option.classList.toggle("d-none", !matches);
if (matches) visibleCount += 1;
});
panel.querySelectorAll(".country-picker-section").forEach(function (section) {
const hasVisibleOption = section.querySelector(".country-picker-option:not(.d-none)");
section.classList.toggle("d-none", !hasVisibleOption);
});
if (noCountries) noCountries.classList.toggle("d-none", visibleCount !== 0);
});
}
panel.addEventListener("click", function (event) {
if (!countryMenu.classList.contains("d-none") &&
!countryMenu.contains(event.target) &&
!countryToggle.contains(event.target)) {
setCountryMenuOpen(false);
}
});
panel.addEventListener("keydown", function (event) {
if (event.key === "Escape" && !countryMenu.classList.contains("d-none")) {
event.preventDefault();
event.stopPropagation();
setCountryMenuOpen(false);
countryToggle.focus();
}
});
saveButton.addEventListener("click", function () {
const selectedCountry = countryInput.value;
const selectedCurrency = currencySelect.value || defaultCurrency;
const originalCountry = panel.dataset.originalCountry;
const originalCurrency = panel.dataset.originalCurrency;
errorElement.classList.add("d-none");
errorElement.textContent = "";
if (selectedCountry === originalCountry && selectedCurrency === originalCurrency) {
const modal = bootstrap.Modal.getInstance(modalElement);
if (modal) modal.hide();
return;
}
saveButton.disabled = true;
saveButton.innerHTML =
'<span class="spinner-border spinner-border-sm me-2" aria-hidden="true"></span>' +
'Applying...';
$.ajax({
url: "/ajax.asp",
method: "POST",
dataType: "json",
data: {
cmd: "country_currency_save",
deliverycountry: selectedCountry,
currency: selectedCurrency
}
})
.done(function (result) {
if (!result || result.success !== true) {
errorElement.textContent =
result && result.message ? result.message : "The settings could not be saved.";
errorElement.classList.remove("d-none");
saveButton.disabled = false;
saveButton.textContent = "Save";
return;
}
// Both values have already been stored by apputils.asp. Perform a
// normal GET so Classic ASP re-renders all prices, VAT, delivery and
// header information from the new session/cookie state.
reloadCurrentPageWithGet();
})
.fail(function () {
errorElement.textContent = "The settings could not be saved. Please try again.";
errorElement.classList.remove("d-none");
saveButton.disabled = false;
saveButton.textContent = "Save";
});
});
}
modalElement.addEventListener("show.bs.modal", function () {
loadPanel(false);
});
modalElement.addEventListener("hidden.bs.modal", function () {
const countryMenu = contentElement.querySelector("#countryCurrencyCountryMenu");
const countryToggle = contentElement.querySelector("#countryCurrencyCountryButton");
if (countryMenu) countryMenu.classList.add("d-none");
if (countryToggle) countryToggle.setAttribute("aria-expanded", "false");
});
contentElement.addEventListener("click", function (event) {
if (event.target && event.target.id === "countryCurrencyRetry") {
loadPanel(true);
}
});
});
</script>
<%
End Sub
%>