Last active
March 29, 2025 09:24
-
-
Save teppokoivula/a3c9fe6080ab2207847170559a4b569c to your computer and use it in GitHub Desktop.
Search Engine live AJAX search example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// this is an example of how to use the search engine module for ProcessWire to provide an AJAX | |
// "live search"; likely not a perfect solution, but should give you a general idea of how to | |
// set this type of functionality up :) | |
// see https://processwire.com/talk/topic/21941-searchengine/?do=findComment&comment=248152 for | |
// alternative solution using htmx | |
const searchForm = document.getElementById('se-form') | |
if (searchForm) { | |
const searchInput = searchForm.querySelector('input[name="q"]') | |
const searchCache = {} | |
let searchTimeout | |
let searchResults | |
const findResults = () => { | |
window.clearTimeout(searchTimeout) | |
searchTimeout = window.setTimeout(() => { | |
if (searchResults) { | |
searchResults.setAttribute('hidden', 'true') | |
} | |
if (searchInput.value.length > 2) { | |
if (searchCache[searchInput.value]) { | |
renderResults(searchForm, searchCache[searchInput.value]) | |
return | |
} | |
if (searchInput.hasAttribute('data-request')) { | |
return | |
} | |
searchInput.setAttribute('data-request', 'true') | |
searchInput.setAttribute('disabled', 'true') | |
const searchParams = new URLSearchParams() | |
searchParams.append('q', searchInput.value) | |
fetch(`${searchForm.getAttribute('action')}?${searchParams}`, { | |
headers: { | |
// set the request header to indicate to ProcessWire that this is an AJAX request; this | |
// way we can check $config->ajax in the template file and return JSON instead of HTML | |
// by calling $modules->get('SearchEngine')->renderResultsJSON() | |
'X-Requested-With': 'XMLHttpRequest', | |
}, | |
}) | |
.then((response) => { | |
if (!response.ok) { | |
throw new Error('Network response was not ok') | |
} | |
return response.json() | |
}) | |
.then((data) => { | |
searchCache[searchInput.value] = data | |
renderResults(searchForm, data) | |
searchInput.removeAttribute('data-request') | |
searchInput.removeAttribute('disabled') | |
searchInput.focus() | |
}) | |
.catch((error) => { | |
console.error('Error fetching search results:', error) | |
searchInput.removeAttribute('data-request') | |
searchInput.removeAttribute('disabled') | |
searchInput.focus() | |
}) | |
} | |
}, 300) | |
} | |
const maybeHideResults = () => { | |
if (searchResults) { | |
window.setTimeout(() => { | |
if (!searchResults.querySelector(':focus')) { | |
searchResults.setAttribute('hidden', 'true') | |
} | |
}, 100) | |
} | |
} | |
const hideResults = () => { | |
if (resultsContainer) { | |
resultsContainer.setAttribute('hidden', 'true') | |
} | |
} | |
const renderResults = (form, data) => { | |
searchResults = document.getElementById('search-results') | |
if (!searchResults) { | |
searchResults = document.createElement('ul') | |
searchResults.id = 'search-results' | |
searchResults.addEventListener('focusout', maybeHideResults) | |
form.insertAdjacentElement('afterend', searchResults) | |
} | |
searchResults.innerHTML = '' | |
if (data.results.length > 0) { | |
data.results.forEach((item) => { | |
const resultItem = document.createElement('li') | |
resultItem.innerHTML = `<a href="${item.url}">${item.title}</a>` | |
searchResults.appendChild(resultItem) | |
}) | |
searchResults.removeAttribute('hidden') | |
} else { | |
searchResults.innerHTML = '<li>No results found</li>' | |
searchResults.removeAttribute('hidden') | |
} | |
} | |
searchInput.addEventListener('keyup', findResults) | |
searchInput.addEventListener('focus', (event) => { | |
if (searchInput.value.length > 2) { | |
findResults(event) | |
} | |
}) | |
document.addEventListener('keydown', (event) => { | |
if (event.key === 'Escape') { | |
hideResults(event) | |
} | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment