PageSourceSearch

https://www.techniques-ingenieur.fr/assets/controllers/search/autocomplete_controller-ormGTZu.js

js techniques-ingenieur.fr collected 2026-09-24 09:15:58 UTC 3,735 bytes, 112 lines download raw bytes

1import {ApplicationController, useDebounce} from 'stimulus-use';
2
3/**
4 * Champ de recherche : suggestions à la volée + bouton de réinitialisation (croix).
5 *
6 * Monté sur le `<form>` de la barre de recherche. Reprend les blocs
7 * « Suggestions de recherche » et « gestion de la croix » de l'ancien search.js.
8 *
9 * L'appel réseau à `/suggest` est debouncé (via stimulus-use) pour ne pas tirer
10 * une requête à chaque frappe ; la croix de clear, elle, reste instantanée.
11 */
12/* stimulusFetch: 'lazy' */
13export default class extends ApplicationController {
14    static debounces = ['fetchSuggestions'];
15    static targets = ['input', 'clearIcon', 'suggestions'];
16    static values = { suggestUrl: String };
17
18    connect() {
19        useDebounce(this, { wait: 330 });
20        this.applySuggestionBoxStyles();
21        this.updateClearButton();
22
23        this.onDocumentClick = this.onDocumentClick.bind(this);
24        document.addEventListener('click', this.onDocumentClick);
25    }
26
27    disconnect() {
28        document.removeEventListener('click', this.onDocumentClick);
29    }
30
31    // action : input->search--autocomplete#input
32    input() {
33        this.updateClearButton(); // immédiat
34        this.fetchSuggestions(); // debouncé (cf. static debounces)
35    }
36
37    async fetchSuggestions() {
38        const query = this.inputTarget.value.trim();
39
40        if (query.length < 2) {
41            this.hideSuggestions();
42            return;
43        }
44
45        try {
46            const response = await fetch(`${this.suggestUrlValue}?prefix=${encodeURIComponent(query)}`);
47            const suggestions = await response.json();
48            this.renderSuggestions(suggestions);
49        } catch (error) {
50            console.error('Erreur lors de la récupération des suggestions:', error);
51        }
52    }
53
54    // action : click->search--autocomplete#clear (sur la croix)
55    clear() {
56        this.inputTarget.value = '';
57        this.updateClearButton();
58        this.inputTarget.focus();
59    }
60
61    renderSuggestions(suggestions) {
62        const box = this.suggestionsTarget;
63
64        if (!suggestions.length) {
65            this.hideSuggestions();
66            return;
67        }
68
69        box.innerHTML = suggestions
70            .map((item) => `<div class="suggestion-item">${item}</div>`)
71            .join('');
72        box.style.display = 'block';
73        box.style.width = `${this.inputTarget.offsetWidth}px`;
74        box.style.left = `${this.inputTarget.offsetLeft}px`;
75        box.style.top = `${this.inputTarget.offsetTop + this.inputTarget.offsetHeight}px`;
76
77        box.querySelectorAll('.suggestion-item').forEach((item) => {
78            item.style.cursor = 'pointer';
79            item.addEventListener('click', () => {
80                this.inputTarget.value = item.textContent;
81                this.hideSuggestions();
82                this.element.submit();
83            });
84        });
85    }
86
87    hideSuggestions() {
88        this.suggestionsTarget.style.display = 'none';
89        this.suggestionsTarget.innerHTML = '';
90    }
91
92    onDocumentClick(event) {
93        if (!this.suggestionsTarget.contains(event.target) && event.target !== this.inputTarget) {
94            this.suggestionsTarget.style.display = 'none';
95        }
96    }
97
98    updateClearButton() {
99        if (!this.hasClearIconTarget) return;
100        const hasValue = this.inputTarget.value.trim().length > 0;
101        this.clearIconTarget.classList.toggle('hide', !hasValue);
102    }
103
104    applySuggestionBoxStyles() {
105        const box = this.suggestionsTarget;
106        box.style.position = 'absolute';
107        box.style.background = '#fff';
108        box.style.border = '1px solid #ccc';
109        box.style.display = 'none';
110        box.style.zIndex = '1000';
111    }
112}

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.