UNPKG

advanced-search-library

Version:

Intelligent search library with typo correction, autocomplete, and flexible data structure support

365 lines (307 loc) 12.7 kB
/** * Advanced Search Demo Application * * @author Rıdvan Sevindik <sevindikbusiness@gmail.com> * @github https://github.com/Ridvan0 * @linkedin https://www.linkedin.com/in/ridvansevindik/ */ class AdvancedSearchDemo { constructor() { this.search = new AdvancedSearch({ maxResults: 50, minQueryLength: 1, typoThreshold: 2, historyLimit: 50 }); this.initializeElements(); this.bindEvents(); this.loadSampleData(); this.displayAllProducts(); } initializeElements() { this.searchInput = document.getElementById('searchInput'); this.searchButton = document.getElementById('searchButton'); this.resultsContainer = document.getElementById('resultsContainer'); this.autocompleteContainer = document.getElementById('autocompleteContainer'); this.historyContainer = document.getElementById('historyContainer'); this.statsContainer = document.getElementById('statsContainer'); this.resultsCount = document.getElementById('resultsCount'); this.resultsTitle = document.getElementById('resultsTitle'); // Filter elements this.categoryFilter = document.getElementById('categoryFilter'); this.priceMinInput = document.getElementById('priceMin'); this.priceMaxInput = document.getElementById('priceMax'); this.sortSelect = document.getElementById('sortSelect'); this.clearFiltersButton = document.getElementById('clearFilters'); this.currentQuery = ''; } bindEvents() { this.searchInput.addEventListener('input', (e) => { this.handleSearchInput(e.target.value); }); this.searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.performSearch(); } }); this.searchButton.addEventListener('click', () => { this.performSearch(); }); // Filter events this.categoryFilter.addEventListener('change', () => { this.performSearch(); }); this.priceMinInput.addEventListener('input', () => { this.performSearch(); }); this.priceMaxInput.addEventListener('input', () => { this.performSearch(); }); this.sortSelect.addEventListener('change', () => { this.performSearch(); }); this.clearFiltersButton.addEventListener('click', () => { this.clearFilters(); }); // Click outside to hide autocomplete document.addEventListener('click', (e) => { if (!this.searchInput.contains(e.target) && !this.autocompleteContainer.contains(e.target)) { this.autocompleteContainer.style.display = 'none'; } }); } loadSampleData() { this.search.addData(sampleData); this.populateFilters(); this.updateStats(); } populateFilters() { const categories = [...new Set(this.search.data.map(item => item.category))]; this.categoryFilter.innerHTML = '<option value="">All Categories</option>'; categories.forEach(category => { const option = document.createElement('option'); option.value = category; option.textContent = category; this.categoryFilter.appendChild(option); }); } handleSearchInput(query) { this.currentQuery = query; if (query.length >= 3) { this.showAutocomplete(query); } else { this.autocompleteContainer.style.display = 'none'; } if (query.length >= 1) { this.performSearch(); } else { this.displayAllProducts(); } } showAutocomplete(query) { const suggestions = this.search.autocomplete(query); if (suggestions.length > 0) { this.autocompleteContainer.innerHTML = ''; suggestions.forEach(suggestion => { const item = document.createElement('div'); item.className = 'autocomplete-item'; item.textContent = suggestion; item.addEventListener('click', () => { this.searchInput.value = suggestion; this.currentQuery = suggestion; this.performSearch(); this.autocompleteContainer.style.display = 'none'; }); this.autocompleteContainer.appendChild(item); }); this.autocompleteContainer.style.display = 'block'; } else { this.autocompleteContainer.style.display = 'none'; } } performSearch() { const query = this.currentQuery; const filters = this.getFilters(); let results; if (query.trim() === '') { results = this.getAllProductsFiltered(filters); } else { results = this.search.search(query, filters); } this.displayResults(results); this.updateResultsCount(results.length); this.updateStats(); this.updateSearchHistory(); this.autocompleteContainer.style.display = 'none'; } getFilters() { const filters = {}; if (this.categoryFilter.value) { filters.category = this.categoryFilter.value; } const minPrice = parseFloat(this.priceMinInput.value); const maxPrice = parseFloat(this.priceMaxInput.value); if (!isNaN(minPrice) || !isNaN(maxPrice)) { filters.priceRange = [ isNaN(minPrice) ? 0 : minPrice, isNaN(maxPrice) ? 999999 : maxPrice ]; } if (this.sortSelect.value) { filters.sortBy = this.sortSelect.value; } return filters; } getAllProductsFiltered(filters) { // Get all products and apply filters manually for non-search results let results = this.search.data.map(item => ({...item})); // Apply filters if (filters.category) { results = results.filter(item => item.category && item.category.toLowerCase().includes(filters.category.toLowerCase()) ); } if (filters.priceRange) { const [min, max] = filters.priceRange; results = results.filter(item => item.price >= min && item.price <= max ); } // Sort results const sortFunctions = { 'relevance': (a, b) => b.viewCount - a.viewCount, // Use viewCount as relevance for all products 'price_asc': (a, b) => (a.price || 0) - (b.price || 0), 'price_desc': (a, b) => (b.price || 0) - (a.price || 0), 'name_asc': (a, b) => (a.name || '').localeCompare(b.name || ''), 'rating_desc': (a, b) => (b.rating || 0) - (a.rating || 0) }; const sortFn = sortFunctions[filters.sortBy] || sortFunctions['relevance']; return results.sort(sortFn); } displayAllProducts() { const filters = this.getFilters(); const results = this.getAllProductsFiltered(filters); this.displayResults(results); this.updateResultsCount(results.length); this.currentQuery = ''; this.updateResultsTitle(); } displayResults(results) { this.resultsContainer.innerHTML = ''; if (results.length === 0) { const noResults = document.createElement('div'); noResults.className = 'no-results'; noResults.innerHTML = ` <h3>No results found</h3> <p>Try different search terms or check your filters.</p> `; this.resultsContainer.appendChild(noResults); return; } results.forEach(item => { const productCard = this.createProductCard(item); this.resultsContainer.appendChild(productCard); }); } createProductCard(item) { const card = document.createElement('div'); card.className = 'product-card'; const relevanceScore = item.relevanceScore ? ` (${item.relevanceScore.toFixed(1)})` : ''; card.innerHTML = ` <div class="product-header"> <h3 class="product-name">${item.name}${relevanceScore}</h3> <div class="product-category">${item.category}</div> </div> <div class="product-details"> <div class="product-brand">Brand: ${item.brand}</div> <div class="product-price">$${item.price}</div> <div class="product-rating"> <span class="stars">${this.generateStars(item.rating)}</span> <span class="rating-value">(${item.rating})</span> </div> </div> <div class="product-description">${item.description}</div> <div class="product-tags"> ${item.tags.map(tag => `<span class="tag">${tag}</span>`).join('')} </div> <div class="product-stats"> <small>Views: ${item.viewCount || 0}</small> </div> `; return card; } generateStars(rating) { const fullStars = Math.floor(rating); const hasHalfStar = rating % 1 >= 0.5; let stars = ''; for (let i = 0; i < fullStars; i++) { stars += '★'; } if (hasHalfStar) { stars += '☆'; } return stars; } clearFilters() { this.categoryFilter.value = ''; this.priceMinInput.value = ''; this.priceMaxInput.value = ''; this.sortSelect.value = 'relevance'; this.performSearch(); } updateResultsCount(count) { this.resultsCount.textContent = `${count} results`; this.updateResultsTitle(); } updateResultsTitle() { if (this.currentQuery) { this.resultsTitle.textContent = `Results for "${this.currentQuery}"`; } else { this.resultsTitle.textContent = 'All Products'; } } updateStats() { const stats = this.search.getStats(); this.statsContainer.innerHTML = ` <div class="stat-item"> <span class="stat-label">Total Items:</span> <span class="stat-value">${stats.totalItems}</span> </div> <div class="stat-item"> <span class="stat-label">Total Searches:</span> <span class="stat-value">${stats.totalSearches}</span> </div> <div class="stat-item"> <span class="stat-label">Unique Searches:</span> <span class="stat-value">${stats.uniqueSearches}</span> </div> `; } updateSearchHistory() { const history = this.search.getSearchHistory().slice(0, 10); if (history.length === 0) { this.historyContainer.innerHTML = '<p>No search history yet</p>'; return; } this.historyContainer.innerHTML = ''; history.forEach(entry => { const historyItem = document.createElement('div'); historyItem.className = 'history-item'; historyItem.innerHTML = ` <span class="history-query">${entry.query}</span> <span class="history-time">${new Date(entry.timestamp).toLocaleTimeString()}</span> `; historyItem.addEventListener('click', () => { this.searchInput.value = entry.query; this.currentQuery = entry.query; this.performSearch(); }); this.historyContainer.appendChild(historyItem); }); } } // Initialize the demo when the page loads document.addEventListener('DOMContentLoaded', () => { window.demo = new AdvancedSearchDemo(); console.log('✅ Advanced Search Demo loaded successfully'); });