How to Implement Search Functionality That Spans Across Exercise Names in Vanilla JavaScript
You can implement client-side search across exercise names by creating a pre-computed index string for each exercise that concatenates the name, category, target muscle, equipment, and muscle group, then filtering the array using String.prototype.includes() with a debounced input handler.
The hasaneyldrm/exercises-dataset repository demonstrates a pure front-end approach to implementing search functionality that spans across exercise names. By leveraging an in-memory index and vanilla JavaScript, the implementation delivers instant results across 1,324 exercises without requiring server requests. The following guide breaks down the exact patterns used in index.html to build, filter, and reset the search interface.
Building the Searchable Index
The search capability relies on a pre-computed text index stored in each exercise object. During initialization, the code concatenates multiple searchable fields into a single lowercase string assigned to a private _idx property.
In index.html, lines 24–28, the index construction works as follows:
state.exercises.forEach(ex => {
ex._idx = `${ex.name} ${ex.category} ${ex.target} ${ex.equipment} ${ex.muscle_group}`.toLowerCase();
});
This approach allows the search to span beyond just exercise names, including categories, target muscles, equipment types, and muscle groups in a single query. The concatenation happens once during app startup, ensuring O(1) lookup performance during filtering.
Debouncing Search Input
To prevent excessive filtering while typing, the search input uses a 250ms debounce. The search box (<input id="search">) captures user input and updates the state.search property only after the user pauses typing.
The implementation in index.html, lines 90–96, wires the event listener with a debounce utility:
searchEl.addEventListener('input', debounce(() => {
state.search = searchEl.value;
searchClearEl.classList.toggle('visible', state.search.length > 0);
applyFilters();
}, 250));
The debounce function delays execution until 250 milliseconds after the last keystroke, reducing the number of filter operations and keeping the UI responsive.
Filtering Exercises by Name
The core filtering logic resides in the applyFilters() function, which checks the searchable index against the user's query. This function lower-cases the input and uses String.prototype.includes() to determine matches while respecting additional active filters for category, equipment, and target.
As implemented in index.html, lines 92–99:
function applyFilters() {
const q = state.search.toLowerCase().trim();
const { category, equipment, target } = state.filters;
state.filtered = state.exercises.filter(ex => {
if (q && !ex._idx.includes(q)) return false; // Name search across index
if (category.size && !category.has(ex.category)) return false;
if (equipment.size && !equipment.has(ex.equipment)) return false;
if (target.size && !target.has(ex.target)) return false;
return true;
});
// Rendering logic follows...
}
The ex._idx.includes(q) check enables substring matching across all indexed fields, meaning a query for "chest" returns exercises with "chest" in the name, category, or target muscle fields.
Clearing Search Results
The interface includes a clear button (marked with "×") that resets the search state and restores the full dataset. When clicked, the button clears the input value, hides itself, and re-applies filters to show all exercises.
The implementation in index.html, lines 97–102:
searchClearEl.addEventListener('click', () => {
searchEl.value = '';
state.search = '';
searchClearEl.classList.remove('visible');
applyFilters();
});
This pattern ensures the UI remains synchronized with the internal state, immediately displaying all 1,324 exercises when the search is cleared.
Summary
- Pre-computed indices improve performance by concatenating searchable fields (
name,category,target,equipment,muscle_group) intoex._idxduring initialization. - Debounced input (250ms) prevents excessive filter operations while maintaining responsive feedback.
- Client-side filtering uses
String.prototype.includes()on the index string, eliminating server latency entirely. - State synchronization between the search input, clear button visibility, and filtered results ensures consistent UI behavior.
Frequently Asked Questions
How does the search handle case sensitivity?
The search is case-insensitive. Both the index (ex._idx) and the user query (q) are converted to lowercase using toLowerCase() before comparison, ensuring "Push", "PUSH", and "push" all match the same exercises.
Can I extend the search to include additional exercise fields?
Yes. Modify the index construction in index.html (lines 24–28) to include additional properties in the template literal. For example, add ${ex.instructions} or ${ex.difficulty} to the concatenation string to expand the searchable content.
Why is the search implemented client-side instead of using a server API?
The hasaneyldrm/exercises-dataset repository stores all 1,324 exercises in a global EXERCISES array within index.html. By building an in-memory index and filtering via JavaScript, the implementation eliminates network latency, works offline, and requires no backend infrastructure while remaining instantaneously responsive.
What is the performance impact of searching 1,324 exercises?
The impact is negligible in modern browsers. The O(n) filter operation runs against a pre-computed string index, and the 250ms debounce ensures the function executes at most once per typing pause. Even with the full dataset, filtering completes in milliseconds because it performs simple string inclusion checks on cached property values.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →