PageSourceSearch

https://www.techniques-ingenieur.fr/assets/controllers/edito_contact_controller-6xrQjES.js

js techniques-ingenieur.fr collected 2026-09-24 09:15:10 UTC 6,793 bytes, 215 lines download raw bytes

1import {Controller} from '@hotwired/stimulus'
2
3const POSTAL_CODE_LENGTH = 5;
4const FULL_PAGE_HASH = '#assistantIAfullPage';
5
6export default class extends Controller {
7    static targets = ['postalCode', 'postalCodeError', 'contactName', 'provinceCta', 'countryCta', 'countrySelect']
8    static values = {
9        contactNameUrl: String,
10        franceId: String,
11    }
12
13    connect() {
14        this.updateProvinceCta('');
15        if (this.hasCountrySelectTarget) {
16            this.selectCountry();
17        }
18
19        // Le bouton d'agrandissement rendu par l'app CDN appelle cette fonction globale.
20        window.toggleChatbotExpand = (forceExpand = null) => this.toggleChatbotExpand(forceExpand);
21
22        if (window.location.hash === FULL_PAGE_HASH) {
23            this.waitForChatbot(() => this.toggleChatbotExpand(true));
24        }
25    }
26
27    disconnect() {
28        if (this.chatbotWait) {
29            clearInterval(this.chatbotWait);
30        }
31        delete window.toggleChatbotExpand;
32    }
33
34    lookupContact() {
35        const postalCode = this.postalCodeTarget.value.trim();
36
37        this.updateProvinceCta(postalCode);
38
39        if (postalCode.length !== POSTAL_CODE_LENGTH) {
40            this.contactNameTarget.textContent = '';
41
42            return;
43        }
44
45        fetch(`${this.contactNameUrlValue}?postalCode=${encodeURIComponent(postalCode)}`, {
46            headers: {Accept: 'application/json'},
47        })
48            .then((response) => (response.ok ? response.text() : ''))
49            .then((body) => {
50                this.contactNameTarget.textContent = this.readContactName(body);
51                this.postalCodeErrorTarget.textContent = '';
52            })
53            .catch(() => {
54                this.contactNameTarget.textContent = '';
55            });
56    }
57
58    /** Tolère une réponse JSON ({name|contactName|...}) ou du texte brut « Prénom Nom ». */
59    readContactName(body) {
60        if (!body) {
61            return '';
62        }
63
64        try {
65            const parsed = JSON.parse(body);
66
67            if (typeof parsed === 'string') {
68                return parsed;
69            }
70
71            return parsed?.name ?? parsed?.contactName ?? parsed?.fullName
72                ?? [parsed?.firstName, parsed?.lastName].filter(Boolean).join(' ');
73        } catch {
74            return body.trim();
75        }
76    }
77
78    /**
79     * L'ouverture passe par ce contrôleur plutôt que par une seconde action `popin#open` :
80     * Stimulus déclenche les actions dans l'ordre de connexion des contrôleurs, pas dans
81     * celui de l'attribut `data-action`, donc la validation ne pourrait pas la précéder.
82     */
83    openPopin(event) {
84        this.applyPopinParams(event);
85        this.popinController?.open();
86    }
87
88    openProvincePopin(event) {
89        if (this.postalCodeTarget.value.trim().length !== POSTAL_CODE_LENGTH) {
90            this.postalCodeErrorTarget.textContent = this.postalCodeErrorTarget.dataset.message
91                || 'Code postal invalide !';
92
93            return;
94        }
95
96        this.postalCodeErrorTarget.textContent = '';
97        this.openPopin(event);
98    }
99
100    get popinController() {
101        return this.application.getControllerForElementAndIdentifier(this.element, 'popin');
102    }
103
104    selectCountry() {
105        const country = this.countrySelectTarget.value;
106        const params = new URLSearchParams();
107
108        if (country !== '') {
109            params.set('country', country);
110        }
111
112        this.setCtaParams(this.countryCtaTarget, params);
113    }
114
115    updateProvinceCta(postalCode) {
116        if (!this.hasProvinceCtaTarget) {
117            return;
118        }
119
120        const params = new URLSearchParams({country: this.franceIdValue});
121
122        if (postalCode !== '') {
123            params.set('postalCode', postalCode);
124        }
125
126        this.setCtaParams(this.provinceCtaTarget, params);
127    }
128
129    /**
130     * Les CTA ouvrent la popin ; on garde les paramètres sur le lien pour que la popin les
131     * transmette au formulaire de demande d'informations.
132     */
133    setCtaParams(cta, params) {
134        if (!cta) {
135            return;
136        }
137
138        const query = params.toString();
139        cta.dataset.switchPopinParams = query;
140        cta.setAttribute('href', query === '' ? '#' : `#${query}`);
141    }
142
143    /**
144     * Recopie les paramètres du CTA cliqué (country / postalCode) sur les deux liens de la
145     * popin, afin que la zone du chargé d'affaire suive l'utilisateur dans le formulaire.
146     */
147    applyPopinParams(event) {
148        const popin = document.getElementById(this.element.dataset.popinModalIdValue);
149
150        if (!popin) {
151            return;
152        }
153
154        const extra = new URLSearchParams(event.currentTarget.dataset.switchPopinParams || '');
155
156        for (const cta of popin.querySelectorAll('[data-switch-popin-target]')) {
157            cta.dataset.baseHref = cta.dataset.baseHref || cta.getAttribute('href');
158
159            const url = new URL(cta.dataset.baseHref, window.location.origin);
160
161            for (const [name, value] of extra) {
162                url.searchParams.set(name, value);
163            }
164
165            cta.setAttribute('href', url.pathname + (url.search === '' ? '' : url.search));
166        }
167    }
168
169    toggleChatbotExpand(forceExpand = null) {
170        const header = document.querySelector('#chatbot .block-header .block-right .fa-minus-close');
171        const icon = header?.querySelector('.expand, .compress');
172
173        if (!icon) {
174            return;
175        }
176
177        const isExpand = forceExpand !== null ? forceExpand : icon.classList.contains('expand');
178
179        icon.classList.toggle('fa-expand');
180        icon.classList.toggle('fa-compress');
181        icon.classList.toggle('expand');
182        icon.classList.toggle('compress');
183
184        if (typeof window.callChatbotDl === 'function') {
185            window.callChatbotDl({event: 'expand_cb'});
186        }
187
188        const contactOptions = document.getElementById('contact-options');
189
190        if (isExpand) {
191            const top = contactOptions?.getBoundingClientRect().top ?? 0;
192            window.scrollTo({top: window.scrollY + top - 58});
193        }
194
195        contactOptions?.classList.toggle('hide', isExpand);
196    }
197
198    /** L'app chatbot est montée par un bundle CDN : on attend que son en-tête existe. */
199    waitForChatbot(callback, attempts = 40) {
200        let remaining = attempts;
201
202        this.chatbotWait = setInterval(() => {
203            const icon = document.querySelector('#chatbot .block-header .block-right .fa-minus-close .expand');
204
205            if (icon || remaining-- <= 0) {
206                clearInterval(this.chatbotWait);
207                this.chatbotWait = null;
208
209                if (icon) {
210                    callback();
211                }
212            }
213        }, 250);
214    }
215}

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.