Overview
The Search module provides real-time search functionality that filters reviews as the user types. It integrates with the UI module to update the display and manages search state across the application.
Module Structure
Currently active search query
Methods
init()
Initializes the search module and sets up event listeners.
Sets up:
- Search input event listener
- Clear button click handler
Required DOM Elements:
<input id="searchInput" type="text" placeholder="Search..." />
<button id="clearSearch" class="hidden">×</button>
Example:
import { Search } from './modules/search.js';
Search.init();
handleSearch(query)
Handles search input and updates the UI.
Search.handleSearch('pizza');
Behavior:
- Trims whitespace from query
- Updates
currentQuery property
- Shows/hides clear button based on query
- Triggers
UI.renderReviews() to update display
Example:
// Programmatically trigger search
Search.handleSearch('margherita');
// Empty search shows all results
Search.handleSearch('');
Clear Button Logic:
if (this.currentQuery) {
clearBtn.classList.remove('hidden');
} else {
clearBtn.classList.add('hidden');
}
clearSearch()
Clears the search query and resets the display.
Behavior:
- Clears search input value
- Resets
currentQuery to empty string
- Hides clear button
- Triggers
UI.renderReviews() to show all reviews
Example:
// Clear search and show all reviews
Search.clearSearch();
Event Listeners
searchInput.addEventListener('input', (e) => this.handleSearch(e.target.value));
Behavior:
- Fires on every keystroke
- Updates results in real-time
- No debouncing (immediate feedback)
clearBtn.addEventListener('click', () => this.clearSearch());
Behavior:
- Resets search to initial state
- Shows all reviews
Integration with DataStore
The Search module works with DataStore.searchReviews() through the UI module:
// In UI.renderReviews()
let reviews = DataStore.reviews;
if (Search.currentQuery) {
reviews = DataStore.searchReviews(Search.currentQuery);
}
Search Functionality
Search is performed by DataStore.searchReviews() which searches:
Case-insensitive match in restaurant name
Case-insensitive match in dish name
Case-insensitive match in any reviewer’s review text
DataStore Search Implementation:
searchReviews(query) {
if (!query) return this.reviews;
const searchTerm = query.toLowerCase();
return this.reviews.filter(review =>
review.restaurant.toLowerCase().includes(searchTerm) ||
review.dish.toLowerCase().includes(searchTerm) ||
Object.values(review.reviewers).some(reviewer =>
reviewer.review.toLowerCase().includes(searchTerm)
)
);
}
HTML Structure
Complete Search Component
<div class="search-container">
<!-- Search Input -->
<div class="relative">
<input
id="searchInput"
type="text"
placeholder="Buscar por restaurante, plato o reseña..."
class="w-full px-4 py-2 pl-10 pr-10 border rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"
/>
<!-- Search Icon -->
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400">
<i class="fas fa-search"></i>
</div>
<!-- Clear Button -->
<button
id="clearSearch"
class="hidden absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
>
<i class="fas fa-times"></i>
</button>
</div>
</div>
CSS Classes
Tailwind CSS Example
<!-- Container -->
<div class="relative w-full max-w-md">
<!-- Input -->
<input
id="searchInput"
class="w-full px-4 py-2 pl-10 pr-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent"
type="text"
placeholder="Search..."
/>
<!-- Clear Button (initially hidden) -->
<button
id="clearSearch"
class="hidden absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 flex items-center justify-center text-gray-400 hover:text-gray-600 cursor-pointer"
>
×
</button>
</div>
State Management
Current Query Access
// Check if search is active
if (Search.currentQuery) {
console.log('Searching for:', Search.currentQuery);
}
// Get current search term
const searchTerm = Search.currentQuery;
UI Updates
The Search module triggers UI updates through:
This causes the UI module to:
- Get all reviews from DataStore
- Apply search filter if
Search.currentQuery exists
- Apply restaurant filter if active
- Render filtered results
Real-Time Search Flow
User types → Input Event → handleSearch() →
→ Update currentQuery → Show/hide clear button →
→ UI.renderReviews() → DataStore.searchReviews() →
→ Filtered results displayed
Complete Usage Example
import { Search } from './modules/search.js';
import { UI } from './modules/ui.js';
import { DataStore } from './modules/datastore.js';
// Initialize search
Search.init();
// Programmatic search
Search.handleSearch('pizza');
console.log('Current query:', Search.currentQuery); // "pizza"
// Clear search
Search.clearSearch();
console.log('Current query:', Search.currentQuery); // ""
// Check search state
if (Search.currentQuery) {
console.log(`Searching for: ${Search.currentQuery}`);
} else {
console.log('No active search');
}
Integration Example
// Initialize all modules together
await DataStore.init();
Filters.init();
Search.init();
Stats.init();
UI.renderReviews();
// Listen for search changes
const searchInput = document.getElementById('searchInput');
searchInput.addEventListener('input', () => {
console.log('Search query:', Search.currentQuery);
console.log('Results count:', DataStore.searchReviews(Search.currentQuery).length);
});
Advanced Features
Search with Filter Combination
// Search works alongside restaurant filters
// Both are applied in UI.renderReviews():
let reviews = DataStore.reviews;
// Apply search first
if (Search.currentQuery) {
reviews = DataStore.searchReviews(Search.currentQuery);
}
// Then apply restaurant filter
if (Filters.currentFilter !== 'all') {
reviews = reviews.filter(r => r.restaurant === Filters.currentFilter);
}
Add Search Debouncing (Optional)
For performance with large datasets:
let searchTimeout;
searchInput.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
Search.handleSearch(e.target.value);
}, 300); // 300ms debounce
});
Accessibility
Enhanced Accessibility
<label for="searchInput" class="sr-only">Search reviews</label>
<input
id="searchInput"
type="search"
role="searchbox"
aria-label="Search reviews by restaurant, dish, or content"
placeholder="Search..."
/>
<button
id="clearSearch"
class="hidden"
aria-label="Clear search"
title="Clear search"
>
<i class="fas fa-times" aria-hidden="true"></i>
</button>
- Search executes on every keystroke (no debouncing by default)
- For large datasets (100+ reviews), consider adding debouncing
- Search is case-insensitive for better UX
- Empty queries return all reviews (fast path)
Dependencies
For triggering review re-rendering
For accessing searchReviews() method (via UI)
Source Code
Source: workspace/source/src/js/modules/search.js:1