PageSourceSearch

https://volleybretagne.fr/media/mod_oui_search/js/mod_oui_search.js?87c2d1

js volleybretagne.fr collected 2026-10-02 00:02:39 UTC 15,154 bytes, 383 lines download raw bytes

1/**
2 * @package     OUI Search Module
3 * @subpackage  mod_oui_search
4 *
5 * @copyright   (C) 2025 Pros.bzh. All rights reserved.
6 * @license     GNU General Public License version 2 or later; see LICENSE.txt
7 */
8
9(function() {
10    'use strict';
11    
12    // Wait for DOM to be ready
13    document.addEventListener('DOMContentLoaded', function() {
14        initializeOuiSearch();
15    });
16    
17    function initializeOuiSearch() {
18        // Find all OUI Search modules on the page
19        const searchModules = document.querySelectorAll('.mod-oui-search');
20        
21        searchModules.forEach(function(module) {
22            const moduleId = module.id;
23            const paramsData = module.getAttribute('data-params');
24            
25            if (!paramsData) {
26                console.warn('OUI Search: No parameters found for module', moduleId);
27                return;
28            }
29            
30            try {
31                const params = JSON.parse(paramsData);
32                initializeModule(module, params);
33            } catch (error) {
34                console.error('OUI Search: Error parsing parameters for module', moduleId, error);
35            }
36        });
37    }
38    
39    function initializeModule(module, params) {
40        const input = module.querySelector('.oui-search-input');
41        const resultsContainer = module.querySelector('.oui-search-results');
42        const resultsList = module.querySelector('.oui-search-results-list');
43        const loadingIndicator = module.querySelector('.oui-search-loading');
44        
45        if (!input || !resultsContainer || !resultsList || !loadingIndicator) {
46            console.error('OUI Search: Required elements not found in module', module.id);
47            return;
48        }
49        
50        let searchTimeout;
51        let currentRequest;
52        
53        // Input event handler
54        input.addEventListener('input', function(e) {
55            const query = e.target.value.trim();
56            
57            // Console log every keystroke
58            console.log('User typed:', e.target.value, 'Query after trim:', query);
59            
60            // Clear previous timeout
61            if (searchTimeout) {
62                clearTimeout(searchTimeout);
63            }
64            
65            // Show looking message immediately when user starts typing
66            if (e.target.value.length > 0) {
67                const lookingElement = document.getElementById('oui-search-looking-' + module.id);
68                if (lookingElement) {
69                    lookingElement.style.display = 'block';
70                }
71            }
72            
73            // Cancel previous request
74            if (currentRequest) {
75                currentRequest.abort();
76                currentRequest = null;
77            }
78            
79            // Hide results if query is too short
80            if (query.length < params.minChars) {
81                console.log('Query too short, hiding results');
82                hideResults();
83                // Hide looking message when query is too short
84                const lookingElement = document.getElementById('oui-search-looking-' + module.id.replace('mod-oui-search-', ''));
85                if (lookingElement) {
86                    lookingElement.style.display = 'none';
87                }
88                return;
89            }
90            
91            // Show looking message immediately when typing starts
92            showLoading();
93            
94            // Show loading and delay search
95            console.log('Query meets minimum characters, scheduling search...');
96            
97            searchTimeout = setTimeout(function() {
98                console.log('Executing search for query:', query);
99                performSearch(query);
100            }, 300); // 300ms delay
101        });
102        
103        // Hide results when clicking outside
104        document.addEventListener('click', function(e) {
105            if (!module.contains(e.target)) {
106                hideResults();
107            }
108        });
109        
110        // Show results when focusing on input (if there are results)
111        input.addEventListener('focus', function() {
112            if (resultsList.children.length > 0) {
113                showResults();
114            }
115        });
116        
117        // Keyboard navigation
118        input.addEventListener('keydown', function(e) {
119            const items = resultsList.querySelectorAll('.oui-search-result-item');
120            const activeItem = resultsList.querySelector('.oui-search-result-item.active');
121            
122            if (e.key === 'ArrowDown') {
123                e.preventDefault();
124                navigateResults(items, activeItem, 'down');
125            } else if (e.key === 'ArrowUp') {
126                e.preventDefault();
127                navigateResults(items, activeItem, 'up');
128            } else if (e.key === 'Enter') {
129                e.preventDefault();
130                const activeLink = resultsList.querySelector('.oui-search-result-item.active');
131                if (activeLink) {
132                    window.location.href = activeLink.href;
133                }
134            } else if (e.key === 'Escape') {
135                hideResults();
136                input.blur();
137            }
138        });
139        
140        function performSearch(query) {
141            // Create FormData for the request
142            const formData = new FormData();
143            formData.append('option', 'com_ajax');
144            formData.append('module', 'oui_search');
145            formData.append('method', 'getAjaxSearch');
146            formData.append('format', 'json');
147            formData.append('query', query);
148            formData.append('max_results', params.maxResults);
149            formData.append('show_featured_first', params.showFeaturedFirst ? '1' : '0');
150            formData.append('show_introtext', params.showIntrotext ? '1' : '0');
151            formData.append('show_images', params.showImages ? '1' : '0');
152            formData.append('introtext_limit', params.introtextLimit);
153            
154            // Add category filter parameters
155            if (params.categoryFilterType) {
156                formData.append('category_filter_type', params.categoryFilterType);
157            }
158            if (params.categories) {
159                formData.append('categories', params.categories);
160            }
161            
162            // Create and send request
163            console.log('=== AJAX CALL INITIATED ===');
164            console.log('Sending AJAX request to:', params.ajaxUrl || 'index.php');
165            console.log('Query being searched:', query);
166            console.log('Module ID:', module.id);
167            currentRequest = new XMLHttpRequest();
168            currentRequest.open('POST', params.ajaxUrl || 'index.php', true);
169            
170            currentRequest.onreadystatechange = function() {
171                if (currentRequest.readyState === 4) {
172                    hideLoading();
173                    
174                    if (currentRequest.status === 200) {
175                        try {
176                            console.log('Raw response:', currentRequest.responseText);
177                            
178                            // Extract JSON from response (handle PHP warnings/notices)
179                            let jsonText = currentRequest.responseText;
180                            const jsonStart = jsonText.indexOf('{');
181                            const jsonEnd = jsonText.lastIndexOf('}') + 1;
182                            
183                            if (jsonStart !== -1 && jsonEnd > jsonStart) {
184                                jsonText = jsonText.substring(jsonStart, jsonEnd);
185                                console.log('Extracted JSON:', jsonText);
186                            }
187                            
188                            const response = JSON.parse(jsonText);
189                            console.log('Parsed response:', response);
190                            
191                            if (response.success && response.data) {
192                                console.log('Displaying results:', response.data);
193                                displayResults(response.data, query);
194                            } else {
195                                console.log('No results found');
196                                displayNoResults();
197                            }
198                        } catch (error) {
199                            console.error('JSON parsing error:', error);
200                            console.error('Response text:', currentRequest.responseText);
201                            displayError();
202                        }
203                    } else if (currentRequest.status !== 0) { // 0 means aborted
204                        console.error('HTTP error:', currentRequest.status);
205                        displayError();
206                    }
207                    
208                    currentRequest = null;
209                }
210            };
211            
212            currentRequest.send(formData);
213        }
214        
215        function displayResults(results, query) {
216            resultsList.innerHTML = '';
217            
218            if (results.length === 0) {
219                displayNoResults();
220                return;
221            }
222            
223            results.forEach(function(result) {
224                const item = createResultItem(result, query);
225                resultsList.appendChild(item);
226            });
227            
228            showResults();
229        }
230        
231        function createResultItem(result, query) {
232            console.log('Creating result item:', result);
233            
234            const item = document.createElement('a');
235            item.className = 'oui-search-result-item';
236            item.href = result.link;
237            
238            if (result.featured) {
239                item.classList.add('featured');
240            }
241            
242            const content = document.createElement('div');
243            content.className = 'oui-search-result-content';
244            
245            // Add intro image if available
246            if (result.image) {
247                console.log('Adding image:', result.image);
248                const imageDiv = document.createElement('div');
249                imageDiv.className = 'oui-search-result-image';
250                
251                const img = document.createElement('img');
252                img.src = result.image;
253                img.alt = result.title || 'Article image';
254                img.loading = 'lazy';
255                img.onerror = function() {
256                    console.log('Image failed to load:', result.image);
257                    imageDiv.style.display = 'none';
258                };
259                
260                imageDiv.appendChild(img);
261                content.appendChild(imageDiv);
262            }
263            
264            // Text content
265            const textDiv = document.createElement('div');
266            textDiv.className = 'oui-search-result-text';
267            
268            // Title (always show)
269            const title = document.createElement('h4');
270            title.className = 'oui-search-result-title';
271            title.innerHTML = highlightSearchTerms(result.title || 'Untitled', query);
272            textDiv.appendChild(title);
273            console.log('Added title:', result.title);
274            
275            // Category
276            if (result.category) {
277                const category = document.createElement('div');
278                category.className = 'oui-search-result-category';
279                category.textContent = result.category;
280                textDiv.appendChild(category);
281            }
282            
283            // Introtext (always show if available)
284            if (result.introtext) {
285                console.log('Adding introtext:', result.introtext);
286                const introtext = document.createElement('p');
287                introtext.className = 'oui-search-result-introtext';
288                introtext.innerHTML = highlightSearchTerms(result.introtext, query);
289                textDiv.appendChild(introtext);
290            }
291            
292            content.appendChild(textDiv);
293            item.appendChild(content);
294            
295            // Add click handler
296            item.addEventListener('mouseenter', function() {
297                clearActiveItems();
298                item.classList.add('active');
299            });
300            
301            return item;
302        }
303        
304        function highlightSearchTerms(text, query) {
305            if (!query || query.length < 2) {
306                return text;
307            }
308            
309            const regex = new RegExp('(' + escapeRegExp(query) + ')', 'gi');
310            return text.replace(regex, '<span class="oui-search-highlight">$1</span>');
311        }
312        
313        function escapeRegExp(string) {
314            return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
315        }
316        
317        function displayNoResults() {
318            resultsList.innerHTML = '<div class="oui-search-no-results">' + params.textNoResults + '</div>';
319            showResults();
320        }
321        
322        function displayError() {
323            resultsList.innerHTML = '<div class="oui-search-no-results">Search error occurred. Please try again.</div>';
324            showResults();
325        }
326        
327        function showLoading() {
328            loadingIndicator.style.display = 'block';
329        }
330        
331        function hideLoading() {
332            loadingIndicator.style.display = 'none';
333            
334            // Also hide the looking message
335            const lookingElement = document.getElementById('oui-search-looking-' + module.id);
336            if (lookingElement) {
337                lookingElement.style.display = 'none';
338            }
339        }
340        
341        function showResults() {
342            resultsContainer.style.display = 'block';
343        }
344        
345        function hideResults() {
346            resultsContainer.style.display = 'none';
347            clearActiveItems();
348        }
349        
350        function navigateResults(items, activeItem, direction) {
351            if (items.length === 0) return;
352            
353            let newIndex = -1;
354            
355            if (activeItem) {
356                const currentIndex = Array.from(items).indexOf(activeItem);
357                newIndex = direction === 'down' ? currentIndex + 1 : currentIndex - 1;
358            } else {
359                newIndex = direction === 'down' ? 0 : items.length - 1;
360            }
361            
362            // Wrap around
363            if (newIndex >= items.length) newIndex = 0;
364            if (newIndex < 0) newIndex = items.length - 1;
365            
366            clearActiveItems();
367            items[newIndex].classList.add('active');
368            
369            // Scroll into view if needed
370            items[newIndex].scrollIntoView({
371                block: 'nearest',
372                behavior: 'smooth'
373            });
374        }
375        
376        function clearActiveItems() {
377            const activeItems = resultsList.querySelectorAll('.oui-search-result-item.active');
378            activeItems.forEach(function(item) {
379                item.classList.remove('active');
380            });
381        }
382    }
383})();

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.