File: D:/web/hyperflight/2026-07-23b/new/inc-template-search.asp
<%
' --- START OF SEARCH TEMPLATES --- '
' (SS,17/1/13) added ending / to input tag of cbsubcat
' (SS,13/8/14) bootstrap version
' (SS,1/3/14) replaced form tags with div (because there's already the frmSearch form outside this,
' had to adjust padding and margin and add margin-top of 10px to make it behave as before
' (SS,30/6/17) added value = ASearchDefault
' (SS,21/5/26) Bootstrap 5 version for HyperFlight
' (SS,20/7/26) change to placeholder text from "Search..." to "Search products..."
' (SS,21/7/26) modified for incremental search
' (SS,23/7/26) removed <input type="hidden" name="cbsubcat" value=""> (not used here?)
Sub ShowSearchMain(ASearchDefault, ASelectComboHTML)
%>
<!-- START OF SEARCH -->
<div class="header-search-wrapper">
<div class="header-search-form">
<div class="input-group">
<input type="search"
class="form-control rounded-start"
placeholder="Search products..."
name="search"
id="search"
value="<%=ASearchDefault%>"
aria-label="Search"
autocomplete="off"
spellcheck="false">
<button class="btn btn-outline-secondary rounded-end"
type="button"
onclick="itp_search()"
aria-label="Search">
<i class="fa-regular fa-magnifying-glass" aria-hidden="true"></i>
</button>
</div>
</div>
<div class="search-suggest-panel" hidden></div>
</div>
<%
AddScript("ScriptSearchSuggest")
End Sub
' (SS,21/7/26)
' (SS,23/7/26) changed minimum search length from 3 to 2 characters
' (SS,23/7/26) log product clicks from incremental search
Sub ScriptSearchSuggest
%>
<script>
document.addEventListener('DOMContentLoaded', function () {
const wrappers = document.querySelectorAll('.header-search-wrapper');
wrappers.forEach(function (wrapper) {
const input = wrapper.querySelector('input[name="search"]');
const panel = wrapper.querySelector('.search-suggest-panel');
if (!input || !panel) return;
let timer = null;
let controller = null;
function hidePanel() {
panel.hidden = true;
panel.innerHTML = '';
delete panel.dataset.query;
}
function logSuggestionClick(searchText, productCode) {
const url = '/ajax.asp?cmd=product_search_suggest_click';
const data = new URLSearchParams();
data.set('search', searchText);
data.set('productcode', productCode);
let sent = false;
if (navigator.sendBeacon) {
sent = navigator.sendBeacon(url, data);
}
if (!sent) {
fetch(url, {
method: 'POST',
body: data,
keepalive: true,
headers: {
'X-Requested-With': 'fetch'
}
}).catch(function () {
/* Logging failure must not prevent navigation */
});
}
}
input.addEventListener('input', function () {
const q = input.value.trim();
clearTimeout(timer);
if (q.length < 2) {
hidePanel();
return;
}
timer = setTimeout(function () {
if (controller) {
controller.abort();
}
controller = new AbortController();
fetch('/ajax.asp?cmd=product_search_suggest&q=' + encodeURIComponent(q), {
signal: controller.signal,
headers: {
'X-Requested-With': 'fetch'
}
})
.then(function (response) {
if (!response.ok) {
throw new Error('Search suggest failed');
}
return response.text();
})
.then(function (html) {
/* Ignore stale results if the user has typed more */
if (input.value.trim() !== q) return;
html = html.trim();
if (html === '') {
hidePanel();
} else {
panel.innerHTML = html;
panel.dataset.query = q;
panel.hidden = false;
}
})
.catch(function (err) {
if (err.name === 'AbortError') return;
hidePanel();
});
}, 275);
});
panel.addEventListener('click', function (event) {
const link = event.target.closest(
'.search-suggest-item[data-product-code]'
);
if (!link || !panel.contains(link)) return;
const q = (panel.dataset.query || '').trim();
const productCode = link.dataset.productCode || '';
if (q.length < 2 || productCode === '') return;
logSuggestionClick(q, productCode);
});
input.addEventListener('keydown', function (event) {
if (event.key === 'Escape') {
hidePanel();
}
});
document.addEventListener('click', function (event) {
if (!wrapper.contains(event.target)) {
panel.hidden = true;
}
});
});
});
</script>
<%
End Sub
' --- END OF SEARCH TEMPLATES --- '
ShowSearch
%>