1function autoCompleteJsOLD(language) { 2 let dataset = []; 3 4 const compressedData = localStorage.getItem(`products_${language}`); 5 6 const cartDataElement = document.getElementById("cart-data"); 7 const defaultTacCode = !cartDataElement ? "" : cartDataElement.getAttribute("data-default-tac-code") || ""; 8 9// Vérifie si des données sont présentes dans localStorage 10 if (compressedData) { 11 // Décompresser les données et les parser en JSON 12 const decompressedData = LZString.decompressFromUTF16(compressedData); 13 14 if (decompressedData) { 15 dataset = JSON.parse(decompressedData); 16 } 17 } 18 let base_url = "product"; 19 20 let searchingEngine = []; 21 try { 22 let searchingEngineParam = $('#searching-engine').text(); 23 if (searchingEngineParam) { 24 searchingEngine = JSON.parse(searchingEngineParam); 25 } 26 } catch (e) { 27 console.warn("searching_engine non défini ou invalide, fallback"); 28 searchingEngine = [ 29 {key: "code_article"}, 30 {key: "libelle_court"}, 31 {key: "libelle_long"} 32 ]; 33 } 34 const keysToDisplay = searchingEngine.map(cfg => cfg.key); 35 36 const autoCompleteJS = new autoComplete({ 37 selector: ".autoComplete", 38 threshold: 3, 39 searchEngine: (query, record) => { 40 return searchingEngineFullRegex(query, record); 41 }, 42 render: false, 43 data: { 44 src: dataset, 45 cache: true, 46 keys: ["search_text"], 47 }, 48 resultsList: { 49 element: (list, data) => { 50 if (!data.results.length) { 51 const message = document.createElement("div"); 52 message.setAttribute("class", "no_result"); 53 message.innerHTML = `<span>Aucun résultat pour "${data.query}"</span>`;
54 list.prepend(message); 55 } 56 }, 57 id: "autoComplete_list", 58 class: "results_list", 59 destination: ".autoComplete", 60 position: "afterend", 61 maxResults: 1000, 62 overflow: true, 63 height: 1000, 64 noResults: false, 65 }, 66 resultItem: { 67 class: "autoComplete_result", 68 element: (item, data) => { 69 const ard_guid = data.value.ard_guid; 70 // const image_thumb = data.value.image_thumb && data.value.image_thumb !== 'null' ? `${data.value.image_thumb}` : ''; 71 const fieldsHtml = keysToDisplay.map(key => data.value[key] || '').join(' - '); 72 73 item.innerHTML = ` 74 <a href="/${base_url}/${ard_guid}/" style="display: flex; justify-content: space-between; align-items: center; color: #29244a !important; padding: 10px; text-decoration: none;"> 75 <div style="display: flex; align-items: center;"> 76 <div> 77 <span style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden; font-size: 13px; display: block;"> 78 ${data.match || data.value[keysToDisplay[1]]} 79 </span> 80 <span style="font-size: 12px; font-weight: 200; color: #000;"> 81 ${fieldsHtml} 82 </span> 83 </div> 84 </div> 85 <span class="hidden-xs" style="font-size: 12px; font-weight: 200; color: #000;"> 86 ${data.value.code_article} 87 </span> 88 </a> 89 <hr> <!-- Ajoute un séparateur entre chaque item --> 90 `; 91 }, 92 highlight: "autoComplete_highlight", 93 selected: "autoComplete_selected" 94 }, 95 events: { 96 input: { 97 selection: (event) => { 98 const selection = event.detail.selection.value; 99 autoCompleteJS.input.value = selection.code_article; 100 } 101 } 102 } 103 }); 104 105 const autoCompleteJSCART = new autoComplete({ 106 selector: ".autoComplete-cart", 107 threshold: 3, 108 searchEngine: (query, record) => { 109 return searchingEngineFullRegex(query, record); 110 }, 111 render: false, 112 data: { 113 src: dataset, 114 cache: true, 115 keys: ["search_text"], 116 }, 117 resultsList: { 118 element: (list, data) => { 119 if (!data.results.length) { 120 const message = document.createElement("div"); 121 message.setAttribute("class", "no_result"); 122 message.innerHTML = `<span>Aucun résultat pour "${data.query}"</span>`;
123 list.prepend(message); 124 } 125 }, 126 id: "autoComplete_list", 127 class: "results_list", 128 destination: ".autoComplete-cart", 129 position: "afterend", 130 maxResults: 1000, 131 overflow: true, 132 height: 1000, 133 noResults: false, 134 }, 135 resultItem: { 136 class: "autoComplete_result", 137 element: (item, data) => { 138 const ard_guid = data.value.ard_guid; 139 // const image_thumb = data.value.image_thumb && data.value.image_thumb !== 'null' ? `${data.value.image_thumb}` : '/static/assets/images/webLinks/default.jpg'; 140 const fieldsHtml = keysToDisplay.map(key => data.value[key] || '').join(' - '); 141 142 item.innerHTML = ` 143 <a onclick="quickAddToCartOnClick('${data.value.code_article}', '${language}', '${defaultTacCode}')" 144 style="display: flex; justify-content: space-between; align-items: center; color: #29244a !important; padding: 10px; text-decoration: none;"> 145 <div style="display: flex; align-items: center;"> 146 <div> 147 <span style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden; font-size: 13px; display: block;"> 148 ${data.match || data.value[keysToDisplay[1]]} 149 </span> 150 <span style="font-size: 12px; font-weight: 200; color: #000;"> 151 ${fieldsHtml} 152 </span> 153 </div> 154 </div> 155 <span class="hidden-xs" style="font-size: 12px; font-weight: 200; color: #000;"> 156 ${data.value.code_article} 157 </span> 158 </a> 159 <hr> <!-- Ajoute un séparateur entre chaque item --> 160 `; 161 }, 162 highlight: "autoComplete_highlight", 163 selected: "autoComplete_selected" 164 }, 165 events: { 166 input: { 167 selection: (event) => { 168 const selection = event.detail.selection.value; 169 autoCompleteJSCART.input.value = selection.code_article; 170 } 171 } 172 } 173 }); 174 175 // Style custom pour la liste des suggestions 176 $("#autoComplete_list").css({ 177 "max-height": "400px", 178 "overflow-y": "auto", 179 "border": "1px solid #ccc", 180 }); 181 182} 183 184function searchingEngineFullRegexOL(query, record){ 185 const keywords = query.toLowerCase().split(" "); // Divise la requête en mots-clés 186 187 if (typeof record === "string") { 188 // Vérifie si tous les mots-clés sont présents dans le texte 189 const allKeywordsMatch = keywords.every(keyword => record.toLowerCase().includes(keyword)); 190 191 if (allKeywordsMatch) { 192 // Applique la surbrillance sur le texte complet 193 let highlightedText = record; 194 195 keywords.forEach(keyword => { 196 // Crée une regex pour surligner le mot-clé dans le texte 197 const regex = new RegExp(`(${keyword})(?![^<]*>)`, "gi"); 198 highlightedText = highlightedText.replace(regex, (match) => { 199 return `<span style="color: red;">${match}</span>`; // Applique la surbrillance 200 }); 201 }); 202 203 return highlightedText; // Retourne le texte avec les mots-clés en surbrillance 204 } 205 } 206 207 return null; // Retourne null si tous les mots-clés ne sont pas présents 208} 209 210function searchingEngineOnCurrentIndexRegex(query, record){ 211 const keywords = query.toLowerCase().split(" "); // Divise la requête en mots-clés 212 213 if (typeof record === "string") { 214 const recordText = record.toLowerCase(); 215 216 // Vérifie si les mots-clés apparaissent dans le bon ordre 217 let index = 0; 218 const allKeywordsInOrder = keywords.every(keyword => {
219 const keywordIndex = recordText.indexOf(keyword, index); // Cherche le mot-clé à partir de la position actuelle 220 if (keywordIndex !== -1) { 221 index = keywordIndex + keyword.length; // Met à jour l'index pour vérifier les prochains mots après le mot trouvé 222 return true; 223 } 224 return false; 225 }); 226 227 if (allKeywordsInOrder) { 228 // Applique la surbrillance sur le texte complet 229 let highlightedText = record; 230 231 keywords.forEach(keyword => { 232 // Crée une regex pour surligner le mot-clé dans le texte 233 const regex = new RegExp(`(${keyword})(?![^<]*>)`, "gi"); 234 highlightedText = highlightedText.replace(regex, (match) => { 235 return `<span style="color: red;">${match}</span>`; // Applique la surbrillance 236 }); 237 }); 238 239 return highlightedText; // Retourne le texte avec les mots-clés en surbrillance 240 } 241 } 242 243 return null; // Retourne null si tous les mots-clés ne sont pas présents dans l'ordre 244 245} 246 247//******************************************************************************************* 248// --- ÃTAT GLOBAL PRIVà (Mémoire persistante pour éviter les re-calculs) --- 249let isSearchInitialized = false; 250let globalDataset = []; 251let globalKeysToDisplay = []; 252let defaultTacCodeSearchingEngine = ""; 253const BASE_URL = "product"; 254 255/** 256 * 1. INITIALISATION UNIQUE (L'optimisation clé) 257 * Charge les données une seule fois au premier besoin. 258 */ 259function initSearchContext(language) { 260 if (isSearchInitialized) return; // Déjà initialisé, on ne fait rien. 261 262 // 1. Récupération et parsing unique du dataset 263 try { 264 const compressed = localStorage.getItem(`products_${language}`); 265 if (compressed) { 266 const decompressed = LZString.decompressFromUTF16(compressed); 267 globalDataset = decompressed ? JSON.parse(decompressed) : []; 268 } 269 } catch (e) { 270 console.error("Ãchec de la préparation du dataset", e); 271 globalDataset = []; 272 } 273 274 // 2. Extraction unique des clés de recherche 275 const defaultEngine = [{ key: "code_article" }, { key: "libelle_court" }, { key: "libelle_long" }]; 276 const engineElement = document.getElementById('searching-engine'); 277 let engineConfig = defaultEngine; 278 279 if (engineElement?.textContent) { 280 try { 281 engineConfig = JSON.parse(engineElement.textContent); 282 } catch (e) { 283 console.warn("Fallback sur searching_engine par défaut."); 284 } 285 } 286 globalKeysToDisplay = engineConfig.map(cfg => cfg.key); 287 288 // 3. Cache des données du panier 289 const cartDataElement = document.getElementById("cart-data"); 290 defaultTacCodeSearchingEngine = cartDataElement?.getAttribute("data-default-tac-code") || ""; 291 292 isSearchInitialized = true; 293} 294 295/** 296 * 2. MOTEUR DE RECHERCHE OPTIMISà 297 * Utilise des méthodes de chaînes natives (indexOf/includes) ultra-rapides pour filtrer, 298 * et ne génère la Regex de surbrillance QUE pour les résultats finaux retenus (et pas sur tout le dataset). 299 */ 300function searchingEngineFullRegex(query, record) { 301 if (typeof record !== "string") return null; 302 303 // Nettoyage de la requête (micro-optimisation : on évite le split/loop si vide) 304 const cleanedQuery = query.trim().toLowerCase(); 305 if (!cleanedQuery) return null; 306 307 const keywords = cleanedQuery.split(" ").filter(Boolean); 308 const recordLower = record.toLowerCase(); 309 310 // PERFORMANCE CRITIQUE : "Early Exit" avec .includes(). 311 // C'est 10x à 50x plus rapide qu'une Regex sur de gros volumes. 312 for (let i = 0; i < keywords.length; i++) { 313 if (!recordLower.includes(keywords[i])) { 314 return null; // On élimine le produit instantanément sans perdre de CPU 315 } 316 } 317 318 // Si on arrive ici, le produit correspond. On applique la surbrillance. 319 let highlightedText = record; 320 for (let i = 0; i < keywords.length; i++) { 321 const regex = new RegExp(`(${keywords[i]}
321)(?![^<]*>)`, "gi"); 322 highlightedText = highlightedText.replace(regex, '<span style="color: red;">$1</span>'); 323 } 324 325 return highlightedText; 326} 327 328/** 329 * 3. FACTORY DE CONFIGURATION SANS EFFETS DE BORD 330 */ 331function createAutoCompleteConfig({ selector, onRenderItem, onSelection }) { 332 return { 333 selector, 334 threshold: 3, 335 debounce: 150, // Micro-optimisation : Attend 150ms de pause dans la frappe avant de chercher 336 searchEngine: (query, record) => searchingEngineFullRegex(query, record), 337 resultsList: { 338 id: "autoComplete_list", 339 class: "results_list", 340 destination: selector, 341 position: "afterend", 342 maxResults: 50, // UI/UX Optimisation : Afficher 1000 items bloque le thread principal du navigateur. 50 suffisent largement. 343 element: (list, data) => { 344 if (!data.results.length) { 345 const message = document.createElement("div"); 346 message.className = "no_result"; 347 message.innerHTML = `<span>Aucun résultat pour "${data.query}"</span>`;
348 list.prepend(message); 349 } 350 }, 351 }, 352 data: { 353 src: globalDataset, 354 cache: true, 355 keys: ["search_text"], 356 }, 357 resultItem: { 358 class: "autoComplete_result", 359 highlight: "autoComplete_highlight", 360 selected: "autoComplete_selected", 361 element: onRenderItem 362 }, 363 events: { 364 input: { selection: onSelection } 365 } 366 }; 367} 368 369/** 370 * 4. POINT D'ENTRÃE DE L'INITIALISATION DES INPUTS 371 */ 372function setupAutoCompleteInputs(language) { 373 // Initialise le contexte (si ce n'est pas déjà fait) 374 initSearchContext(language); 375 376 // Instance Globale 377 const instanceStandard = new autoComplete(createAutoCompleteConfig({ 378 selector: ".autoComplete", 379 onSelection: (event) => { 380 instanceStandard.input.value = event.detail.selection.value.code_article; 381 }, 382 onRenderItem: (item, data) => { 383 const fieldsHtml = globalKeysToDisplay.map(key => data.value[key] || '').join(' - '); 384 const mainLabel = data.match || data.value[globalKeysToDisplay[1]] || ''; 385 386 item.innerHTML = ` 387 <a href="/${BASE_URL}/${data.value.ard_guid}/" style="display: flex; justify-content: space-between; align-items: center; color: #29244a !important; padding: 10px; text-decoration: none;"> 388 <div> 389 <span style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden; font-size: 13px; display: block;">${mainLabel}</span> 390 <span style="font-size: 12px; font-weight: 200; color: #000;">${fieldsHtml}</span> 391 </div> 392 <span class="hidden-xs" style="font-size: 12px; font-weight: 200; color: #000;">${data.value.code_article}</span> 393 </a> 394 <hr> 395 `; 396 } 397 })); 398 399 // Instance Panier 400 const instanceCart = new autoComplete(createAutoCompleteConfig({ 401 selector: ".autoComplete-cart", 402 onSelection: (event) => { 403 instanceCart.input.value = event.detail.selection.value.code_article; 404 }, 405 onRenderItem: (item, data) => { 406 const fieldsHtml = globalKeysToDisplay.map(key => data.value[key] || '').join(' - '); 407 const mainLabel = data.match || data.value[globalKeysToDisplay[1]] || ''; 408 409 item.innerHTML = ` 410 <a onclick="quickAddToCartOnClick('${data.value.code_article}', '${language}', '${defaultTacCodeSearchingEngine}')" style="cursor: pointer; display: flex; justify-content: space-between; align-items: center; color: #29244a !important; padding: 10px; text-decoration: none;"> 411 <div> 412 <span style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden; font-size: 13px; display: block;">${mainLabel}</span> 413 <span style="font-size: 12px; font-weight: 200; color: #000;">${fieldsHtml}</span> 414 </div> 415 <span class="hidden-xs" style="font-size: 12px; font-weight: 200; color: #000;">${data.value.code_article}</span> 416 </a> 417 <hr> 418 `; 419 } 420 })); 421}
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.