Skip to content

refactor: search result handling with better event listener cleanup #677

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 21, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 68 additions & 21 deletions src/assets/js/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,36 @@ function fetchSearchResults(query) {
}

/**
* Removes any current search results from the display.
* @returns {void}
* Clears the search results from the display.
* If the removeEventListener flag is true, removes the click event listener from the document.
* @param {boolean} [removeEventListener=false] - Optional flag to indicate if the click event listener should be removed. Default is false.
* @returns {void} - This function doesn't return anything.
*/
function clearSearchResults() {
while (resultsElement.firstChild) {
resultsElement.removeChild(resultsElement.firstChild);
function clearSearchResults(removeEventListener = false) {
resultsElement.innerHTML = "";
if (removeEventListener && document.clickEventAdded) {
document.removeEventListener("click", handleDocumentClick);
document.clickEventAdded = false;
}
}

/**
* Displays a "No results found" message in both the live region and results display area.
* This is typically used when no matching results are found in the search.
* @returns {void} - This function doesn't return anything.
*/
function showNoResults() {
resultsLiveRegion.innerHTML = "No results found.";
resultsElement.innerHTML = "No results found.";
resultsElement.setAttribute("data-results", "false");
}

/**
* Clears any "No results found" message from the live region and results display area.
* @returns {void} - This function doesn't return anything.
*/
function clearNoResults() {
resultsLiveRegion.innerHTML = "";
resultsElement.innerHTML = "";
}

Expand Down Expand Up @@ -86,9 +109,7 @@ function displaySearchResults(results) {
list.append(listItem);
}
} else {
resultsLiveRegion.innerHTML = "No results found.";
resultsElement.innerHTML = "No results found.";
resultsElement.setAttribute("data-results", "false");
showNoResults();
}
}

Expand All @@ -111,6 +132,13 @@ function maintainScrollVisibility(activeElement, scrollParent) {
scrollParent.scrollTo(0, offsetTop - parentOffsetHeight + offsetHeight);
}
}

/**
* Debounces the provided callback with a given delay.
* @param {Function} callback The callback that needs to be debounced.
* @param {Number} delay Time in ms that the timer should wait before the callback is executed.
* @returns {Function} Returns the new debounced function.
*/
function debounce(callback, delay) {
let timerId;

Expand All @@ -122,18 +150,39 @@ function debounce(callback, delay) {
};
}

/**
* Debounced function to fetch search results after 300ms of inactivity.
* Calls `fetchSearchResults` to retrieve data and `displaySearchResults` to show them.
* If an error occurs, clears the search results.
* @param {string} query - The search query.
* @returns {void} - No return value.
* @see debounce - Limits the number of requests during rapid typing.
*/
const debouncedFetchSearchResults = debounce(query => {
fetchSearchResults(query)
.then(displaySearchResults)
.catch(clearSearchResults);
.catch(() => {
clearSearchResults(true);
});
}, 300);

/**
* Handles the document click event to clear search results if the user clicks outside of the search input or results element.
* @param {MouseEvent} e - The event object representing the click event.
* @returns {void} - This function does not return any value. It directly interacts with the UI by clearing search results.
*/
const handleDocumentClick = e => {
if (e.target !== resultsElement && e.target !== searchInput) {
clearSearchResults(true);
}
};

//-----------------------------------------------------------------------------
// Event Handlers
//-----------------------------------------------------------------------------

// listen for input changes
searchInput.addEventListener("keyup", function (e) {
searchInput.addEventListener("keyup", function () {
const query = searchInput.value;

if (query === searchQuery) return;
Expand All @@ -143,12 +192,12 @@ searchInput.addEventListener("keyup", function (e) {

if (query.length > 2) {
debouncedFetchSearchResults(query);

document.addEventListener("click", function (e) {
if (e.target !== resultsElement) clearSearchResults();
});
if (!document.clickEventAdded) {
document.addEventListener("click", handleDocumentClick);
document.clickEventAdded = true;
}
} else {
clearSearchResults();
clearSearchResults(true);
}

searchQuery = query;
Expand All @@ -157,7 +206,7 @@ searchInput.addEventListener("keyup", function (e) {
searchClearBtn.addEventListener("click", function (e) {
searchInput.value = "";
searchInput.focus();
clearSearchResults();
clearSearchResults(true);
searchClearBtn.setAttribute("hidden", "");
});

Expand All @@ -169,12 +218,10 @@ document.addEventListener("keydown", function (e) {
if (e.key === "Escape") {
e.preventDefault();
if (searchResults.length) {
clearSearchResults();
clearSearchResults(true);
searchInput.focus();
} else if (
!searchResults.length &&
document.activeElement === searchInput
) {
} else if (document.activeElement === searchInput) {
clearNoResults();
searchInput.blur();
}
}
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy