Search/static/js/autocomplete.js
2024-11-20 14:57:55 +01:00

127 lines
4.8 KiB
JavaScript

/*
This script fetches and caches search suggestions to reduce server requests with an LRU cache.
*/
document.addEventListener('DOMContentLoaded', () => {
const searchInput = document.getElementById('search-input');
const searchWrapper = document.querySelector('.wrapper, .wrapper-results');
const resultsWrapper = document.querySelector('.autocomplete');
let suggestions = [];
let currentIndex = -1;
const cacheLimit = 10000; // This should take about 3.6 MB of memory
const suggestionCache = new Map(); // Map to store suggestions with LRU capability
// Fetch suggestions from server or cache
async function getSuggestions(query) {
if (suggestionCache.has(query)) {
// Move the recently accessed item to the end of Map to mark it as most recently used
const cachedResult = suggestionCache.get(query);
suggestionCache.delete(query);
suggestionCache.set(query, cachedResult);
return cachedResult;
}
try {
const response = await fetch(`/suggestions?q=${encodeURIComponent(query)}`);
const data = await response.json();
// Add result to cache and enforce cache limit
suggestionCache.set(query, data[1]);
if (suggestionCache.size > cacheLimit) {
// Remove the oldest (first) item in the Map
const firstKey = suggestionCache.keys().next().value;
suggestionCache.delete(firstKey);
}
return data[1];
} catch (error) {
console.error('Error fetching suggestions:', error);
return [];
}
}
// Function to render results
function renderSuggestions(results) {
if (!results || !results.length) {
searchWrapper.classList.remove('wrapper-searching');
resultsWrapper.innerHTML = '';
return;
}
let content = '';
results.forEach((item, index) => {
content += `<li data-index="${index}">${item}</li>`;
});
resultsWrapper.innerHTML = `<ul>${content}</ul>`;
searchWrapper.classList.add('wrapper-searching');
}
// Fetch suggestions when input is focused
searchInput.addEventListener('focus', async () => {
const query = searchInput.value.trim();
suggestions = await getSuggestions(query);
renderSuggestions(suggestions);
currentIndex = -1;
});
// Handle input event
searchInput.addEventListener('input', async () => {
const query = searchInput.value.trim();
if (query.length > 0) {
suggestions = await getSuggestions(query);
} else {
suggestions = [];
}
currentIndex = -1; // Reset index when new results come in
renderSuggestions(suggestions);
});
// Handle keydown events for navigation
searchInput.addEventListener('keydown', (event) => {
const items = resultsWrapper.querySelectorAll('li');
if (event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'Tab') {
event.preventDefault();
if (items[currentIndex]) items[currentIndex].classList.remove('selected');
currentIndex = event.key === 'ArrowUp'
? (currentIndex > 0 ? currentIndex - 1 : items.length - 1)
: (currentIndex + 1) % items.length;
if (items[currentIndex]) {
items[currentIndex].classList.add('selected');
searchInput.value = items[currentIndex].textContent;
}
} else if (event.key === 'Enter' && currentIndex > -1 && items[currentIndex]) {
event.preventDefault();
selectSuggestion(items[currentIndex]);
}
});
// Function to handle suggestion selection
function selectSuggestion(item) {
const query = item.textContent;
searchInput.value = query;
resultsWrapper.innerHTML = '';
searchWrapper.classList.remove('wrapper-searching');
// Retrieve the `t` parameter from the hidden input or set a default
const currentTemplateType = document.querySelector('input[name="t"]').value || 'web';
// Redirect to the appropriate results page
window.location.href = `/search?q=${encodeURIComponent(query)}&t=${encodeURIComponent(currentTemplateType)}`;
}
// Handle clicks on search suggestions
resultsWrapper.addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
selectSuggestion(event.target);
}
});
// Close the suggestions when clicking outside
document.addEventListener('click', (event) => {
if (!searchWrapper.contains(event.target)) {
searchWrapper.classList.remove('wrapper-searching');
resultsWrapper.innerHTML = '';
currentIndex = -1;
}
});
});