Geizkragen/assets/js/search-autocomplete.js

223 lines
5.7 KiB
JavaScript

(function () {
'use strict';
function debounce(fn, delay) {
var t;
return function () {
var ctx = this;
var args = arguments;
clearTimeout(t);
t = setTimeout(function () { fn.apply(ctx, args); }, delay);
};
}
function createDropdownEl() {
var el = document.createElement('div');
el.className = 'searchDropdown';
el.hidden = true;
el.setAttribute('role', 'listbox');
return el;
}
function attachToInput(input) {
if (!input || input.dataset.autocompleteBound === '1') return;
input.dataset.autocompleteBound = '1';
// wrapper for positioning
var wrapper = input.closest('.nav__searchField');
if (!wrapper) return;
wrapper.classList.add('nav__searchField--hasDropdown');
// dropdown
var dropdown = createDropdownEl();
wrapper.appendChild(dropdown);
var activeIndex = -1;
var items = [];
var abortController = null;
function close() {
dropdown.hidden = true;
dropdown.innerHTML = '';
dropdown.classList.remove('is-open');
activeIndex = -1;
items = [];
}
function open() {
dropdown.hidden = false;
dropdown.classList.add('is-open');
}
function setStatus(text) {
dropdown.innerHTML = '';
var row = document.createElement('div');
row.className = 'searchDropdown__status';
row.textContent = text;
dropdown.appendChild(row);
open();
}
function render(list) {
dropdown.innerHTML = '';
items = list || [];
activeIndex = -1;
if (!items.length) {
setStatus('Keine Produkte gefunden.');
return;
}
for (var i = 0; i < items.length; i++) {
(function (idx) {
var it = items[idx];
var a = document.createElement('a');
a.className = 'searchDropdown__item';
a.href = it.url;
a.setAttribute('role', 'option');
a.tabIndex = -1;
var img = document.createElement('img');
img.className = 'searchDropdown__img';
img.alt = '';
img.loading = 'lazy';
img.decoding = 'async';
img.src = it.imagePath && it.imagePath.length ? it.imagePath : 'assets/images/placeholder.png';
var meta = document.createElement('div');
meta.className = 'searchDropdown__meta';
var title = document.createElement('div');
title.className = 'searchDropdown__title';
title.textContent = it.model || '';
var desc = document.createElement('div');
desc.className = 'searchDropdown__desc';
desc.textContent = it.description || '';
meta.appendChild(title);
meta.appendChild(desc);
a.appendChild(img);
a.appendChild(meta);
a.addEventListener('mouseenter', function () {
setActive(idx);
});
dropdown.appendChild(a);
})(i);
}
open();
}
function setActive(idx) {
var children = dropdown.querySelectorAll('.searchDropdown__item');
for (var i = 0; i < children.length; i++) {
children[i].classList.toggle('is-active', i === idx);
}
activeIndex = idx;
}
function fetchResults(q) {
if (abortController) {
abortController.abort();
}
abortController = new AbortController();
var url = 'api/search_products.php?q=' + encodeURIComponent(q) + '&limit=8';
setStatus('Suche…');
fetch(url, { signal: abortController.signal, headers: { 'Accept': 'application/json' } })
.then(function (r) {
if (!r.ok) throw new Error('HTTP ' + r.status);
return r.json();
})
.then(function (data) {
render((data && data.items) ? data.items : []);
})
.catch(function (err) {
if (err && err.name === 'AbortError') return;
setStatus('Fehler bei der Suche.');
});
}
var debounced = debounce(function () {
var q = (input.value || '').trim();
if (q.length < 1) {
close();
return;
}
fetchResults(q);
}, 180);
input.addEventListener('input', debounced);
input.addEventListener('focus', function () {
var q = (input.value || '').trim();
if (q.length >= 1) {
fetchResults(q);
}
});
input.addEventListener('keydown', function (e) {
if (dropdown.hidden) return;
if (e.key === 'Escape') {
close();
return;
}
var max = items.length - 1;
if (max < 0) return;
if (e.key === 'ArrowDown') {
e.preventDefault();
var next = activeIndex + 1;
if (next > max) next = 0;
setActive(next);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
var prev = activeIndex - 1;
if (prev < 0) prev = max;
setActive(prev);
} else if (e.key === 'Enter') {
if (activeIndex >= 0) {
var links = dropdown.querySelectorAll('.searchDropdown__item');
if (links[activeIndex]) {
e.preventDefault();
window.location.href = links[activeIndex].href;
}
}
}
});
document.addEventListener('click', function (e) {
if (wrapper.contains(e.target)) return;
close();
});
// prevent blur-close when clicking inside dropdown until navigation happens
dropdown.addEventListener('mousedown', function (e) {
e.preventDefault();
});
}
function init() {
var inputs = document.querySelectorAll('input.nav__searchInput[name="search"]');
for (var i = 0; i < inputs.length; i++) {
attachToInput(inputs[i]);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();