223 lines
5.7 KiB
JavaScript
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 < 2) {
|
|
close();
|
|
return;
|
|
}
|
|
fetchResults(q);
|
|
}, 180);
|
|
|
|
input.addEventListener('input', debounced);
|
|
|
|
input.addEventListener('focus', function () {
|
|
var q = (input.value || '').trim();
|
|
if (q.length >= 2) {
|
|
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();
|
|
}
|
|
})();
|
|
|