From 2f65d04dda9afc097111850467f36f408dae8f03 Mon Sep 17 00:00:00 2001 From: partisan Date: Thu, 31 Oct 2024 11:32:28 +0100 Subject: [PATCH] added caching to search suggestions on client side --- static/js/autocomplete.js | 55 ++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/static/js/autocomplete.js b/static/js/autocomplete.js index bcafac1..6f1c19b 100644 --- a/static/js/autocomplete.js +++ b/static/js/autocomplete.js @@ -1,5 +1,5 @@ /* - This script is responsible for fetching search suggestions when the user types in the search bar. It also shows and hides the search suggestions wrapper. + This script fetches and caches search suggestions to reduce server requests with an LRU cache. */ document.addEventListener('DOMContentLoaded', () => { const searchInput = document.getElementById('search-input'); @@ -7,14 +7,30 @@ document.addEventListener('DOMContentLoaded', () => { const resultsWrapper = document.querySelector('.autocomplete'); let suggestions = []; - let currentIndex = -1; // Keep track of the currently selected suggestion + 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 the server + // 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(); - return data[1]; // Return only the array of suggestion strings + // 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 []; @@ -63,28 +79,19 @@ document.addEventListener('DOMContentLoaded', () => { 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'); - // Remove 'selected' class from the current item - if (items[currentIndex]) { - items[currentIndex].classList.remove('selected'); - } + currentIndex = event.key === 'ArrowUp' + ? (currentIndex > 0 ? currentIndex - 1 : items.length - 1) + : (currentIndex + 1) % items.length; - if (event.key === 'ArrowUp') { - currentIndex = (currentIndex > 0) ? currentIndex - 1 : items.length - 1; - } else { - currentIndex = (currentIndex + 1) % items.length; - } - - // Add 'selected' class to the new item and update the input value if (items[currentIndex]) { items[currentIndex].classList.add('selected'); searchInput.value = items[currentIndex].textContent; } - } else if (event.key === 'Enter') { - if (currentIndex > -1 && items[currentIndex]) { - event.preventDefault(); - selectSuggestion(items[currentIndex]); - } + } else if (event.key === 'Enter' && currentIndex > -1 && items[currentIndex]) { + event.preventDefault(); + selectSuggestion(items[currentIndex]); } }); @@ -94,14 +101,8 @@ document.addEventListener('DOMContentLoaded', () => { searchInput.value = query; resultsWrapper.innerHTML = ''; searchWrapper.classList.remove('wrapper-searching'); - - // Submit the form or navigate to search results const form = searchInput.closest('form'); - if (form) { - form.submit(); - } else { - window.location.href = `/search?q=${encodeURIComponent(query)}&t=web`; - } + form ? form.submit() : window.location.href = `/search?q=${encodeURIComponent(query)}&t=web`; } // Handle clicks on search suggestions