1// Search functionality 2document.addEventListener('DOMContentLoaded', function () { 3 const searchInput = document.getElementById('searchOverlayInput'); 4 const btnClose = document.getElementById('searchOverlayClose'); 5 const searchOverlay = document.getElementById('searchOverlay'); 6 const keywordChips = document.querySelectorAll('.keyword-chip'); 7 const searchSuggestions = document.getElementById('searchSuggestions'); 8 const searchResults = document.getElementById('searchResults'); 9 const searchEmptyState = document.getElementById('searchEmptyState'); 10 const searchDropdownSuggest = document.getElementById('searchDropdownSuggest'); 11 const searchClearBtn = document.getElementById('searchClearBtn'); // â NEW 12 13 // Debounce timer 14 let debounceTimer = null; // â NEW 15 const DEBOUNCE_DELAY = 3000; // â NEW: 3 seconds 16 17 let suggestDebounceTimer = null; 18 const SUGGEST_DELAY = 1000; 19 20 // Add loading spinner to search input box 21 const searchInputBox = document.querySelector('.search-input-box'); 22 if (searchInputBox && !searchInputBox.querySelector('.search-loading-spinner')) { 23 const spinner = document.createElement('div'); 24 spinner.className = 'search-loading-spinner'; 25 searchInputBox.appendChild(spinner); 26 } 27 28 // Sample suggestions data 29 const allSuggestions = [ 30 'Gói Internet', 31 'Gói Internet Doanh nghiá»p', 32 'Gói Internet Truyá»n hình', 33 'Gói Internet Camera', 34 'Gói Internet Game thá»§', 35 'Gói Internet Gia Äình', 36 'Gói Internet Siêu tá»c', 37 'Gói Internet CÆ¡ bản' 38 ]; 39 40 // Close overlay function 41 function closeSearchOverlay() { 42 if (searchOverlay) { 43 searchOverlay.style.display = 'none'; 44 // Restore body scroll 45 document.body.style.overflow = ''; 46 // Remove search overlay open class 47 document.body.classList.remove('search-overlay-open'); 48 // Clear search input 49 if (searchInput) { 50 searchInput.value = ''; 51 } 52 // Reset to show suggestions 53 showSuggestions(); 54 // â Clear has-value class 55 if (searchInputBox) { 56 searchInputBox.classList.remove('has-value'); 57 } 58 59 if (searchResults) { 60 searchResults.innerHTML = ''; 61 } 62 63 } 64 } 65 66 /** 67 * â NEW: Toggle hiá»n thá» nút X 68 */ 69 function toggleClearButton() { 70 if (!searchInput || !searchInputBox) return; 71 72 const hasValue = searchInput.value.trim().length > 0; 73 74 if (hasValue) { 75 searchInputBox.classList.add('has-value'); 76 } else { 77 searchInputBox.classList.remove('has-value'); 78 } 79 } 80 81 /** 82 * â NEW: Xóa ná»i dung input 83 */ 84 function clearInput() { 85 if (!searchInput) return; 86 87 searchInput.value = ''; 88 89 if (searchInputBox) { 90 searchInputBox.classList.remove('has-value'); 91 searchInputBox.classList.remove('loading'); 92 } 93 94 searchInput.focus(); 95 96 // Hide dropdown suggest 97 if (searchDropdownSuggest) { 98 searchDropdownSuggest.style.display = 'none'; 99 } 100 101 // Reset vá» suggestions 102 showSuggestions(); 103 104 // Clear debounce timer 105 if (debounceTimer) { 106 clearTimeout(debounceTimer); 107 debounceTimer = null; 108 } 109 } 110 111 // Close search overlay 112 if (btnClose) { 113 btnClose.addEventListener('click', function () { 114 closeSearchOverlay(); 115 }); 116 } 117 118 // Handle keyword chip clicks 119 keywordChips.forEach(function (chip) { 120 chip.addEventListener('click', function () { 121 // Remove active class from all chips 122 keywordChips.forEach(function (c) { 123 c.classList.remove('active'); 124 }); 125 126 // Add active class to clicked chip 127 this.classList.add('active'); 128 129 // Set search input value 130 if (searchInput) { 131 searchInput.value = this.textContent.trim(); 132 searchInput.focus(); 133 134 // â Toggle clear button khi click chip 135 toggleClearButton(); 136 137 // Trigger search 138 performSearch(this.textContent.trim()); 139 } 140 }); 141 }); 142 143 // Filter and display dropdown suggestions 144 function updateDropdownSuggestions(query) { 145 if (!searchDropdownSuggest) return; 146 147 if (!query) { 148 searchDropdownSuggest.style.display = 'none'; 149 return; 150 } 151 152 let allSuggestions = null; 153 154 // Gá»i API lấy danh sách các gói Äá» xuất theo kà tá»± Äã nháºp 155 $.ajax({ 156 url: $('#searchOverlayInput').data('route'), 157 type: "POST", 158 data: { 159 _token: $('meta[name="csrf-token"]').attr('content'), 160 Query: $('#searchOverlayInput').val(), 161 SearchType: 'suggest' 162 }, 163 timeout: 10000 164 }) 165 .done(function (res) { 166 console.log(res); 167 allSuggestions = res; 168 169 // Filter suggestions based on query 170 const filteredSuggestions = allSuggestions.filter(function (item) { 171 return item.toLowerCase().includes(query.toLowerCase()); 172 }); 173 174 if (filteredSuggestions.length === 0) { 175 searchDropdownSuggest.style.display = 'none'; 176 return; 177 } 178 179 // Update dropdown content 180 searchDropdownSuggest.innerHTML = ''; 181 filteredSuggestions.forEach(function (suggestion) { 182 const suggestionItem = document.createElement('div'); 183 suggestionItem.className = 'suggest-item';
184 suggestionItem.textContent = suggestion; 185 186 // Click handler for suggestion item 187 suggestionItem.addEventListener('click', function () { 188 searchInput.value = suggestion; 189 searchDropdownSuggest.style.display = 'none'; 190 // â Toggle clear button khi chá»n suggestion 191 toggleClearButton(); 192 performSearch(suggestion); 193 }); 194 195 searchDropdownSuggest.appendChild(suggestionItem); 196 }); 197 198 searchDropdownSuggest.style.display = 'block'; 199 }) 200 .fail(function (xhr, status, error) { 201 console.log(error); 202 }); 203 } 204 205 // Handle search input 206 if (searchInput) { 207 // Hiá»n thá» các gói Äá» xuất theo kà tá»± Äã nháºp 208 searchInput.addEventListener('input', function () { 209 const value = this.value.trim(); 210 211 // â Toggle clear button 212 toggleClearButton(); 213 214 // â Debounce dropdown suggest 215 if (suggestDebounceTimer) clearTimeout(suggestDebounceTimer); 216 217 suggestDebounceTimer = setTimeout(() => { 218 updateDropdownSuggestions(value); 219 }, SUGGEST_DELAY); 220 221 // Clear active state if input doesn't match any chip 222 if (value === '') { 223 keywordChips.forEach(function (chip) { 224 chip.classList.remove('active'); 225 }); 226 // Show suggestions, hide results 227 showSuggestions(); 228 return; 229 } 230 }); 231 232 // Gá»i Äến hà m tìm kiếm gói dá»ch vụ khi nhấn enter sau khi nháºp kà tá»± 233 searchInput.addEventListener('keypress', function (e) { 234 if (e.key === 'Enter') { 235 const searchQuery = this.value.trim(); 236 if (searchQuery) { 237 // â Clear debounce timer khi enter 238 if (debounceTimer) { 239 clearTimeout(debounceTimer); 240 debounceTimer = null; 241 } 242 243 searchDropdownSuggest.style.display = 'none'; 244 performSearch(searchQuery); 245 } 246 } 247 }); 248 } 249 250 /** 251 * â NEW: Xá» lý click nút X 252 */ 253 if (searchClearBtn) { 254 searchClearBtn.addEventListener('click', function (e) { 255 e.preventDefault(); 256 clearInput(); 257 }); 258 } 259 260 // Close dropdown when clicking outside 261 document.addEventListener('click', function (e) { 262 if (searchDropdownSuggest && 263 !searchInput.contains(e.target) && 264 !searchDropdownSuggest.contains(e.target)) { 265 searchDropdownSuggest.style.display = 'none'; 266 } 267 }); 268 269 /** 270 * Hà m xá» lý tìm kiếm gói dá»ch vụ 271 * @param {*} query 272 */ 273 function performSearch(query) { 274 // â Clear debounce timer nếu search manually 275 if (debounceTimer) { 276 clearTimeout(debounceTimer); 277 debounceTimer = null; 278 } 279 280 // Show loading state 281 if (searchInputBox) { 282 searchInputBox.classList.add('loading'); 283 } 284 285 // Disable input during loading 286 if (searchInput) { 287 searchInput.disabled = true; 288 } 289 290 // Hide suggestions container immediately 291 if (searchSuggestions) { 292 searchSuggestions.style.display = 'none'; 293 } 294 295 $.ajax({ 296 url: $('#searchOverlayInput').data('route'), 297 type: "POST", 298 dataType: "html", 299 data: { 300 _token: $('meta[name="csrf-token"]').attr('content'), 301 Query: $('#searchOverlayInput').val(), 302 SearchType: 'result' 303 }, 304 timeout: 10000 305 }) 306 .done(function (res) { 307 if (res && res.trim() !== '') { 308 $('#searchEmptyState').hide(); 309 $('#searchResults').html(res).show(); 310 searchInputBox.classList.remove('loading'); 311 searchInput.disabled = false;
312 313 // Check if search results contain promotion products and add appropriate class 314 const searchResultsGrid = document.querySelector('.search-results-grid'); 315 if (searchResultsGrid) { 316 // â PHÃN LOẠI CARD Dá»°A TRÃN DATA ATTRIBUTE 317 const allCards = searchResultsGrid.querySelectorAll('[data-card-type]'); 318 319 allCards.forEach(card => { 320 const cardType = card.getAttribute('data-card-type'); 321 322 if (cardType === 'promotion') { 323 // Äá»i class cho promotion cards 324 card.classList.remove('package-card'); 325 card.classList.add('promotion-products-card'); 326 } else { 327 // Giữ nguyên package-card 328 card.classList.add('package-card'); 329 } 330 }); 331 } 332 333 initLoadMore(); 334 } else { 335 searchInputBox.classList.remove('loading'); 336 searchInput.disabled = false; 337 // Update empty keyword text 338 $('.empty-keyword').text(query); 339 $('#searchEmptyState').show(); 340 $('#searchResults').empty(); 341 } 342 }) 343 .fail(function (xhr, status, error) { 344 searchInputBox.classList.remove('loading'); 345 searchInput.disabled = false; 346 $('#searchResults').empty(); 347 $('.empty-keyword').text($('#searchOverlayInput').val()); 348 $('#searchEmptyState').show(); 349 }); 350 } 351 352 // Helper function to check if search has results 353 // This should be replaced with actual API call 354 function checkIfHasResults(query) { 355 // Simulate: return false if query doesn't match any known keywords 356 const validKeywords = [ 357 'internet', 'gói internet', 'speedx', 'camera', 358 'truyá»n hình', 'combo', 'mesh', 'wifi', 'modem' 359 ]; 360 361 const lowerQuery = query.toLowerCase(); 362 return validKeywords.some(keyword => lowerQuery.includes(keyword)); 363 } 364 365 // Show suggestions function 366 function showSuggestions() { 367 if (searchSuggestions) { 368 searchSuggestions.style.display = 'block'; 369 } 370 if (searchResults) { 371 searchResults.style.display = 'none'; 372 } 373 if (searchEmptyState) { 374 searchEmptyState.style.display = 'none'; 375 } 376 } 377 378 // Close on Escape key 379 document.addEventListener('keydown', function (e) { 380 if (e.key === 'Escape') { 381 closeSearchOverlay(); 382 } 383 }); 384 385 // Close overlay when clicking outside search content 386 if (searchOverlay) { 387 searchOverlay.addEventListener('click', function (e) { 388 // Only close if clicking on the overlay background, not its children 389 if (e.target === searchOverlay) { 390 closeSearchOverlay(); 391 } 392 }); 393 } 394 395 function initLoadMore() { 396 const itemsPerPage = 8; 397 const grid = document.getElementById('searchResultsGrid'); 398 const cards = grid.querySelectorAll('.package-card'); 399 const btnLoadMore = document.getElementById('btnLoadMore'); 400 let visibleCount = itemsPerPage; 401 402 // Ẩn tất cả ngoà i 8 item Äầu 403 cards.forEach((card, index) => { 404 card.style.display = index < visibleCount ? 'block' : 'none'; 405 }); 406 407 // Gắn event click 408 btnLoadMore.addEventListener('click', function () { 409 const hiddenCards = Array.from(cards).slice(visibleCount, visibleCount + itemsPerPage); 410 411 hiddenCards.forEach(card => card.style.display = 'block'); 412 visibleCount += itemsPerPage; 413 414 if (visibleCount >= cards.length) { 415 btnLoadMore.style.display = 'none'; 416 } 417 }); 418 419 // Ẩn nút nếu Ãt hÆ¡n 8 kết quả 420 if (cards.length <= itemsPerPage) { 421 btnLoadMore.style.display = 'none'; 422 } 423 } 424 425 // â Initialize: Check input value on page load and toggle clear button 426 if (searchInput) { 427 toggleClearButton(); 428 } 429});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.