PageSourceSearch

https://fpt.vn/frontend_layout_2025_vibecode/assets/js/search.js?v=v1.50.16

js fpt.vn collected 2026-09-24 09:39:23 UTC 14,681 bytes, 429 lines download raw bytes

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.