Skip to content

Commit f76d05d

Browse files
authored
docs: Refactor search result handling with better event listener cleanup (#19252)
1 parent c5f3d7d commit f76d05d

File tree

1 file changed

+64
-18
lines changed

1 file changed

+64
-18
lines changed

docs/src/assets/js/search.js

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,36 @@ function fetchSearchResults(query) {
4141
}
4242

4343
/**
44-
* Removes any current search results from the display.
45-
* @returns {void}
44+
* Clears the search results from the display.
45+
* If the removeEventListener flag is true, removes the click event listener from the document.
46+
* @param {boolean} [removeEventListener=false] - Optional flag to indicate if the click event listener should be removed. Default is false.
47+
* @returns {void} - This function doesn't return anything.
4648
*/
47-
function clearSearchResults() {
48-
while (resultsElement.firstChild) {
49-
resultsElement.removeChild(resultsElement.firstChild);
49+
function clearSearchResults(removeEventListener = false) {
50+
resultsElement.innerHTML = "";
51+
if (removeEventListener && document.clickEventAdded) {
52+
document.removeEventListener('click', handleDocumentClick);
53+
document.clickEventAdded = false;
5054
}
55+
}
56+
57+
/**
58+
* Displays a "No results found" message in both the live region and results display area.
59+
* This is typically used when no matching results are found in the search.
60+
* @returns {void} - This function doesn't return anything.
61+
*/
62+
function showNoResults() {
63+
resultsLiveRegion.innerHTML = "No results found.";
64+
resultsElement.innerHTML = "No results found.";
65+
resultsElement.setAttribute('data-results', 'false');
66+
}
67+
68+
/**
69+
* Clears any "No results found" message from the live region and results display area.
70+
* @returns {void} - This function doesn't return anything.
71+
*/
72+
function clearNoResults() {
73+
resultsLiveRegion.innerHTML = "";
5174
resultsElement.innerHTML = "";
5275
}
5376

@@ -81,9 +104,7 @@ function displaySearchResults(results) {
81104
}
82105

83106
} else {
84-
resultsLiveRegion.innerHTML = "No results found.";
85-
resultsElement.innerHTML = "No results found.";
86-
resultsElement.setAttribute('data-results', 'false');
107+
showNoResults();
87108
}
88109

89110
}
@@ -125,12 +146,33 @@ function debounce(callback, delay) {
125146
}
126147
}
127148

149+
/**
150+
* Debounced function to fetch search results after 300ms of inactivity.
151+
* Calls `fetchSearchResults` to retrieve data and `displaySearchResults` to show them.
152+
* If an error occurs, clears the search results.
153+
* @param {string} query - The search query.
154+
* @returns {void} - No return value.
155+
* @see debounce - Limits the number of requests during rapid typing.
156+
*/
128157
const debouncedFetchSearchResults = debounce((query) => {
129158
fetchSearchResults(query)
130159
.then(displaySearchResults)
131-
.catch(clearSearchResults);
160+
.catch(() => { clearSearchResults(true) });
132161
}, 300);
133162

163+
164+
/**
165+
* Handles the document click event to clear search results if the user clicks outside of the search input or results element.
166+
* @param {MouseEvent} e - The event object representing the click event.
167+
* @returns {void} - This function does not return any value. It directly interacts with the UI by clearing search results.
168+
*/
169+
const handleDocumentClick = (e) => {
170+
if (e.target !== resultsElement && e.target !== searchInput) {
171+
clearSearchResults(true);
172+
}
173+
}
174+
175+
134176
//-----------------------------------------------------------------------------
135177
// Event Handlers
136178
//-----------------------------------------------------------------------------
@@ -146,14 +188,13 @@ if (searchInput)
146188
else searchClearBtn.setAttribute('hidden', '');
147189

148190
if (query.length > 2) {
149-
150191
debouncedFetchSearchResults(query);
151-
152-
document.addEventListener('click', function (e) {
153-
if (e.target !== resultsElement) clearSearchResults();
154-
});
192+
if (!document.clickEventAdded) {
193+
document.addEventListener('click', handleDocumentClick);
194+
document.clickEventAdded = true;
195+
}
155196
} else {
156-
clearSearchResults();
197+
clearSearchResults(true);
157198
}
158199

159200
searchQuery = query
@@ -165,19 +206,24 @@ if (searchClearBtn)
165206
searchClearBtn.addEventListener('click', function (e) {
166207
searchInput.value = '';
167208
searchInput.focus();
168-
clearSearchResults();
209+
clearSearchResults(true);
169210
searchClearBtn.setAttribute('hidden', '');
170211
});
171212

172213
document.addEventListener('keydown', function (e) {
173214

174215
const searchResults = Array.from(document.querySelectorAll('.search-results__item'));
175216

176-
if (e.key === 'Escape') {
217+
if (e.key === "Escape") {
177218
e.preventDefault();
178219
if (searchResults.length) {
179-
clearSearchResults();
220+
clearSearchResults(true);
180221
searchInput.focus();
222+
} else if (
223+
document.activeElement === searchInput
224+
) {
225+
clearNoResults();
226+
searchInput.blur();
181227
}
182228
}
183229

0 commit comments

Comments
 (0)