PageSourceSearch

https://www.dimension-ingenieur.com/assets/design2023/js/sol.js.pagespeed.ce.V2nFQRefPp.js

js dimension-ingenieur.com collected 2026-09-24 22:02:11 UTC 60,170 bytes, 1,284 lines download raw bytes

1let currentStep = 0;
2const requiredInput = {};
3let bacList = [];
4let filieres = [];
5let formation_types = [];
6const DATE_FORMAT_SETTINGS = {
7  day: 'numeric',
8  month: 'long',
9  year: 'numeric'
10};
11
12function initSol(base_url, json_org_type, post, nb_steps) {
13  const formEl = document.querySelector('form');
14  const stepsEl = formEl.querySelectorAll('form>fieldset');
15  const formResumeEl = document.querySelector('#resume');
16  const resumeElSnapshot = formResumeEl.innerHTML;
17  let sumbited = false;
18
19  formEl.querySelectorAll('input[required],select[required]').forEach((input) => {
20    requiredInput[input.name] = input
21  });
22
23  formEl.addEventListener('change', (e) => {
24    formResumeEl.innerHTML = resumeElSnapshot;
25    setFormResume(formResumeEl, new FormData(formEl));
26    document.querySelectorAll('.choice-fieldset').forEach(fieldset => {
27      updateChoiceSelects(fieldset, filieres, e.target); // formations must be available
28    });
29  });
30  formEl.addEventListener('submit', (e) => {
31    const submitter = e.submitter || stepsEl[currentStep].querySelector('input[type=submit]');
32    const addChoice = submitter.id === 'add-choice';
33    if (!addChoice && currentStep >= 3) {
34      if (sumbited) {
35        e.preventDefault();
36      }
37      sumbited = true;
38      return;
39    }
40    e.preventDefault();
41    
42    if (addChoice) {
43      if (choiceCount < 3) {
44        choiceCount++;
45        renderChoices(formEl.querySelector('#step3'), choiceCount, filieres, window.json_org_type);
46      }
47      return;
48    }
49
50    const values = Object.fromEntries(new FormData(e.target));
51
52    const newStep = currentStep + 1;
53    // Location hash is always newstep + 1 (step 0 is #1 in URL)
54    window.location.hash = 'step' + (newStep + 1);
55    // Save to localStorage so we can load it if the page got reloaded.
56    localStorage.setItem('inscription', JSON.stringify(values));
57
58    submitter.style.display = 'none';
59
60    if (submitter.id === 'submit-choice') {
61      submitter.parentElement.previousElementSibling.childNodes.forEach(e => {
62        e.style.display = 'none';
63      });
64    }
65
66    setStep(newStep, stepsEl, nb_steps);
67  });
68
69  window.addEventListener('hashchange', (e) => {
70    // When hash change in the URL, then we used the navigation in browser.
71    // const stepArr = new URL(e.newURL).hash.substr(5).split('-');
72    // const newStep = stepArr[1] ? stepArr.map(v=>Number(v)-1) : (Number(stepArr[0]) - 1);
73    // setStep(newStep, stepsEl, nb_steps);
74  })
75
76  let currentValues = null;
77  /*if(window.location.hash.length > 0) {
78    currentStep = Number(window.location.hash.substr(1)) - 1;
79    const storageStr = localStorage.getItem('inscription');
80    if(storageStr) {
81      const data = new FormData(formEl);
82      const storage = JSON.parse(storageStr);
83      Object.entries(storage).forEach(([key, value])=>{
84        console.log(key, value);
85        const input = formEl.elements[key];
86        switch(input.type) {
87          case 'checkbox':
88            input.checked = !!value;
89            break;
90          default:
91            input.value = value;
92        }
93      });
94      onStep2ChangeNiveau(storage['steps[1][niveau]'] ?? '', formEl).then(async ()=> {
95        await setStep(currentStep, stepsEl);
96        await setFormResume(formResumeEl, storage);
97      });
98    } else {
99      currentStep = 0;
100      const newUrl = new URL(window.location);
101      newUrl.hash = '1';
102      history.replaceState(null, "", newUrl);
103      setStep(currentStep, stepsEl);
104    }
105} else {*/
106  if (post) {
107    const newUrl = new URL(window.location);
108    newUrl.hash = 'step' + 1;
109    history.replaceState(null, "", newUrl);
110    setStep(currentStep, stepsEl, nb_steps);
111  } else {
112    setStep(0, stepsEl, nb_steps);
113  }
114  /*}*/
115  initStep2(stepsEl[1], currentValues, base_url, json_org_type);
116  if (post) {
117    initStep5(stepsEl[4], post, nb_steps, base_url);
118  }
119  window.json_org_type = json_org_type;
120  attachStep3Listeners();
121}
122
123function setupAdresseAutocomplete() {
124  const adresseInput = document.getElementById('adresse');
125  if (!adresseInput) return;
126  let currentFocus = -1;
127  let suggestionBox;
128
129  adresseInput.setAttribute('autocomplete', 'off');
130
131  adresseInput.addEventListener('input', function () {
132    const val = this.value;
133    if (!val || val.length < 3) return;
134    fetch(`https://api-adresse.data.gouv.fr/search/?q=${encodeURIComponent(val)}&limit=5`)
135      .then(res => res.json())
136      .then(data => {
137        closeAllLists();
138        if (!data.features || !data.features.length) return;
139        suggestionBox = document.createElement('div');
140        suggestionBox.setAttribute('class', 'autocomplete-items');
141        suggestionBox.style.position = 'absolute';
142        suggestionBox.style.zIndex = 1000;
143        suggestionBox.style.background = '#fff';
144        suggestionBox.style.border = '1px solid #ccc';
145        suggestionBox.style.width = this.offsetWidth + 'px';
146        const rect = this.getBoundingClientRect();
147        const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
148        const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
149        suggestionBox.style.left = rect.left + scrollLeft + 'px';
150        suggestionBox.style.top = rect.bottom + scrollTop + 'px';
151        suggestionBox.style.maxHeight = '220px';
152        suggestionBox.style.overflowY = 'auto';
153        document.body.appendChild(suggestionBox);
154        data.features.forEach((feature, idx) => {
155          const item = document.createElement('div');
156          item.innerHTML = feature.properties.label;
157          item.style.cursor = 'pointer';
158          item.style.padding = '4px 8px';
159          item.addEventListener('mousedown', (e) => {
160            e.preventDefault();
161            adresseInput.value = feature.properties.label;
162            closeAllLists();
163          });
164          suggestionBox.appendChild(item);
165        });
166      });
167  });
168
169  adresseInput.addEventListener('keydown', function (e) {
170    let items = suggestionBox ? suggestionBox.getElementsByTagName('div') : [];
171    if (e.key === 'ArrowDown') {
172      currentFocus++;
173      addActive(items);
174    } else if (e.key === 'ArrowUp') {
175      currentFocus--;
176      addActive(items);
177    } else if (e.key === 'Enter') {
178      e.preventDefault();
179      if (currentFocus > -1 && items[currentFocus]) {
180        items[currentFocus].dispatchEvent(new Event('mousedown'));
181      }
182    }
183  });
184
185  function addActive(items) {
186    if (!items) return;
187    removeActive(items);
188    if (currentFocus >= items.length) currentFocus = 0;
189    if (currentFocus < 0) currentFocus = items.length - 1;
190    items[currentFocus].classList.add('autocomplete-active');
191    items[currentFocus].style.background = '#e9e9e9';
192  }
193  function removeActive(items) {
194    for (let i = 0; i < items.length; i++) {
195      items[i].classList.remove('autocomplete-active');
196      items[i].style.background = '#fff';
197    }
198  }
199  function closeAllLists(elmnt) {
200    const items = document.querySelectorAll('.autocomplete-items');
201    items.forEach(item => {
202      if (elmnt !== item && elmnt !== adresseInput) {
203        item.parentNode.removeChild(item);
204      }
205    });
206    currentFocus = -1;
207  }
208  document.addEventListener('click', function (e) {
209    closeAllLists(e.target);
210  });
211}
212
213if (document.readyState === 'loading') {
214  document.addEventListener('DOMContentLoaded', setupAdresseAutocomplete);
215} else {
216  setupAdresseAutocomplete();
217}
218
219const phoneCountries = [
220  { code: 'AF', dial: '+93', name: 'Afghanistan', flag: 'https://flagcdn.com/af.svg' },
221  { code: 'AL', dial: '+355', name: 'Albanie', flag: 'https://flagcdn.com/al.svg' },
222  { code: 'DZ', dial: '+213', name: 'Algérie', flag: 'https://flagcdn.com/dz.svg' },
223  { code: 'AS', dial: '+1-684', name: 'Samoa américaines', flag: 'https://flagcdn.com/as.svg' },
224  { code: 'AD', dial: '+376', name: 'Andorre', flag: 'https://flagcdn.com/ad.svg' },
225  { code: 'AO', dial: '+244', name: 'Angola', flag: 'https://flagcdn.com/ao.svg' },
226  { code: 'AI', dial: '+1-264', name: 'Anguilla', flag: 'https://flagcdn.com/ai.svg' },
227  { code: 'AQ', dial: '+672', name: 'Antarctique', flag: 'https://flagcdn.com/aq.svg' },
228  { code: 'AG', dial: '+1-268', name: 'Antigua-et-Barbuda', flag: 'https://flagcdn.com/ag.svg' },
229  { code: 'AR', dial: '+54', name: 'Argentine', flag: 'https://flagcdn.com/ar.svg' },
230  { code: 'AM', dial: '+374', name: 'Arménie', flag: 'https://flagcdn.com/am.svg' },
231  { code: 'AW', dial: '+297', name: 'Aruba', flag: 'https://flagcdn.com/aw.svg' },
232  { code: 'AU', dial: '+61', name: 'Australie', flag: 'https://flagcdn.com/au.svg' },
233  { code: 'AT', dial: '+43', name: 'Autriche', flag: 'https://flagcdn.com/at.svg' },
234  { code: 'AZ', dial: '+994', name: 'Azerbaïdjan', flag: 'https://flagcdn.com/az.svg' },
235  { code: 'BS', dial: '+1-242', name: 'Bahamas', flag: 'https://flagcdn.com/bs.svg' },
236  { code: 'BH', dial: '+973', name: 'Bahreïn', flag: 'https://flagcdn.com/bh.svg' },
237  { code: 'BD', dial: '+880', name: 'Bangladesh', flag: 'https://flagcdn.com/bd.svg' },
238  { code: 'BB', dial: '+1-246', name: 'Barbade', flag: 'https://flagcdn.com/bb.svg' },
239  { code: 'BY', dial: '+375', name: 'Biélorussie', flag: 'https://flagcdn.com/by.svg' },
240  { code: 'BE', dial: '+32', name: 'Belgique', flag: 'https://flagcdn.com/be.svg' },
241  { code: 'BZ', dial: '+501', name: 'Belize', flag: 'https://flagcdn.com/bz.svg' },
242  { code: 'BJ', dial: '+229', name: 'Bénin', flag: 'https://flagcdn.com/bj.svg' },
243  { code: 'BM', dial: '+1-441', name: 'Bermudes', flag: 'https://flagcdn.com/bm.svg' },
244  { code: 'BT', dial: '+975', name: 'Bhoutan', flag: 'https://flagcdn.com/bt.svg' },
245  { code: 'BO', dial: '+591', name: 'Bolivie', flag: 'https://flagcdn.com/bo.svg' },
246  { code: 'BA', dial: '+387', name: 'Bosnie-Herzégovine', flag: 'https://flagcdn.com/ba.svg' },
247  { code: 'BW', dial: '+267', name: 'Botswana', flag: 'https://flagcdn.com/bw.svg' },
248  { code: 'BR', dial: '+55', name: 'Brésil', flag: 'https://flagcdn.com/br.svg' },
249  { code: 'IO', dial: '+246', name: 'Territoire britannique de l’océan Indien', flag: 'https://flagcdn.com/io.svg' },
250  { code: 'VG', dial: '+1-284', name: 'Îles Vierges britanniques', flag: 'https://flagcdn.com/vg.svg' },
251  { code: 'BN', dial: '+673', name: 'Brunei', flag: 'https://flagcdn.com/bn.svg' },
252  { code: 'BG', dial: '+359', name: 'Bulgarie', flag: 'https://flagcdn.com/bg.svg' },
253  { code: 'BF', dial: '+226', name: 'Burkina Faso', flag: 'https://flagcdn.com/bf.svg' },
254  { code: 'BI', dial: '+257', name: 'Burundi', flag: 'https://flagcdn.com/bi.svg' },
255  { code: 'KH', dial: '+855', name: 'Cambodge', flag: 'https://flagcdn.com/kh.svg' },
256  { code: 'CM', dial: '+237', name: 'Cameroun', flag: 'https://flagcdn.com/cm.svg' },
257  { code: 'CA', dial: '+1', name: 'Canada', flag: 'https://flagcdn.com/ca.svg' },
258  { code: 'CV', dial: '+238', name: 'Cap-Vert', flag: 'https://flagcdn.com/cv.svg' },
259  { code: 'KY', dial: '+1-345', name: 'Îles Caïmans', flag: 'https://flagcdn.com/ky.svg' },
260  { code: 'CF', dial: '+236', name: 'République centrafricaine', flag: 'https://flagcdn.com/cf.svg' },
261  { code: 'TD', dial: '+235', name: 'Tchad', flag: 'https://flagcdn.com/td.svg' },
262  { code: 'CL', dial: '+56', name: 'Chili', flag: 'https://flagcdn.com/cl.svg' },
263  { code: 'CN', dial: '+86', name: 'Chine', flag: 'https://flagcdn.com/cn.svg' },
264  { code: 'CX', dial: '+61', name: 'Île Christmas', flag: 'https://flagcdn.com/cx.svg' },
265  { code: 'CC', dial: '+61', name: 'Îles Cocos', flag: 'https://flagcdn.com/cc.svg' },
266  { code: 'CO', dial: '+57', name: 'Colombie', flag: 'https://flagcdn.com/co.svg' },
267  { code: 'KM', dial: '+269', name: 'Comores', flag: 'https://flagcdn.com/km.svg' },
268  { code: 'CD', dial: '+243', name: 'Congo (RDC)', flag: 'https://flagcdn.com/cd.svg' },
269  { code: 'CG', dial: '+242', name: 'Congo', flag: 'https://flagcdn.com/cg.svg' },
270  { code: 'CK', dial: '+682', name: 'Îles Cook', flag: 'https://flagcdn.com/ck.svg' },
271  { code: 'CR', dial: '+506', name: 'Costa Rica', flag: 'https://flagcdn.com/cr.svg' },
272  { code: 'CI', dial: '+225', name: 'Côte d’Ivoire', flag: 'https://flagcdn.com/ci.svg' },
273  { code: 'HR', dial: '+385', name: 'Croatie', flag: 'https://flagcdn.com/hr.svg' },
274  { code: 'CU', dial: '+53', name: 'Cuba', flag: 'https://flagcdn.com/cu.svg' },
275  { code: 'CW', dial: '+599', name: 'Curaçao', flag: 'https://flagcdn.com/cw.svg' },
276  { code: 'CY', dial: '+357', name: 'Chypre', flag: 'https://flagcdn.com/cy.svg' },
277  { code: 'CZ', dial: '+420', name: 'République tchèque', flag: 'https://flagcdn.com/cz.svg' },
278  { code: 'DK', dial: '+45', name: 'Danemark', flag: 'https://flagcdn.com/dk.svg' },
279  { code: 'DJ', dial: '+253', name: 'Djibouti', flag: 'https://flagcdn.com/dj.svg' },
280  { code: 'DM', dial: '+1-767', name: 'Dominique', flag: 'https://flagcdn.com/dm.svg' },
281  { code: 'DO', dial: '+1-809', name: 'République dominicaine', flag: 'https://flagcdn.com/do.svg' },
282  { code: 'EC', dial: '+593', name: 'Équateur', flag: 'https://flagcdn.com/ec.svg' },
283  { code: 'EG', dial: '+20', name: 'Égypte', flag: 'https://flagcdn.com/eg.svg' },
284  { code: 'SV', dial: '+503', name: 'Salvador', flag: 'https://flagcdn.com/sv.svg' },
285  { code: 'GQ', dial: '+240', name: 'Guinée équatoriale', flag: 'https://flagcdn.com/gq.svg' },
286  { code: 'ER', dial: '+291', name: 'Érythrée', flag: 'https://flagcdn.com/er.svg' },
287  { code: 'EE', dial: '+372', name: 'Estonie', flag: 'https://flagcdn.com/ee.svg' },
288  { code: 'SZ', dial: '+268', name: 'Eswatini', flag: 'https://flagcdn.com/sz.svg' },
289  { code: 'ET', dial: '+251', name: 'Éthiopie', flag: 'https://flagcdn.com/et.svg' },
290  { code: 'FK', dial: '+500', name: 'Îles Malouines', flag: 'https://flagcdn.com/f
290k.svg' },
291  { code: 'FO', dial: '+298', name: 'Îles Féroé', flag: 'https://flagcdn.com/fo.svg' },
292  { code: 'FJ', dial: '+679', name: 'Fidji', flag: 'https://flagcdn.com/fj.svg' },
293  { code: 'FI', dial: '+358', name: 'Finlande', flag: 'https://flagcdn.com/fi.svg' },
294  { code: 'FR', dial: '+33', name: 'France', flag: 'https://flagcdn.com/fr.svg' },
295  { code: 'GF', dial: '+594', name: 'Guyane française', flag: 'https://flagcdn.com/gf.svg' },
296  { code: 'PF', dial: '+689', name: 'Polynésie française', flag: 'https://flagcdn.com/pf.svg' },
297  { code: 'TF', dial: '+262', name: 'Terres australes françaises', flag: 'https://flagcdn.com/tf.svg' },
298  { code: 'GA', dial: '+241', name: 'Gabon', flag: 'https://flagcdn.com/ga.svg' },
299  { code: 'GM', dial: '+220', name: 'Gambie', flag: 'https://flagcdn.com/gm.svg' },
300  { code: 'GE', dial: '+995', name: 'Géorgie', flag: 'https://flagcdn.com/ge.svg' },
301  { code: 'DE', dial: '+49', name: 'Allemagne', flag: 'https://flagcdn.com/de.svg' },
302  { code: 'GH', dial: '+233', name: 'Ghana', flag: 'https://flagcdn.com/gh.svg' },
303  { code: 'GI', dial: '+350', name: 'Gibraltar', flag: 'https://flagcdn.com/gi.svg' },
304  { code: 'GR', dial: '+30', name: 'Grèce', flag: 'https://flagcdn.com/gr.svg' },
305  { code: 'GL', dial: '+299', name: 'Groenland', flag: 'https://flagcdn.com/gl.svg' },
306  { code: 'GD', dial: '+1-473', name: 'Grenade', flag: 'https://flagcdn.com/gd.svg' },
307  { code: 'GP', dial: '+590', name: 'Guadeloupe', flag: 'https://flagcdn.com/gp.svg' },
308  { code: 'GU', dial: '+1-671', name: 'Guam', flag: 'https://flagcdn.com/gu.svg' },
309  { code: 'GT', dial: '+502', name: 'Guatemala', flag: 'https://flagcdn.com/gt.svg' },
310  { code: 'GG', dial: '+44', name: 'Guernesey', flag: 'https://flagcdn.com/gg.svg' },
311  { code: 'GN', dial: '+224', name: 'Guinée', flag: 'https://flagcdn.com/gn.svg' },
312  { code: 'GW', dial: '+245', name: 'Guinée-Bissau', flag: 'https://flagcdn.com/gw.svg' },
313  { code: 'GY', dial: '+592', name: 'Guyana', flag: 'https://flagcdn.com/gy.svg' },
314  { code: 'HT', dial: '+509', name: 'Haïti', flag: 'https://flagcdn.com/ht.svg' },
315  { code: 'HN', dial: '+504', name: 'Honduras', flag: 'https://flagcdn.com/hn.svg' },
316  { code: 'HK', dial: '+852', name: 'Hong Kong', flag: 'https://flagcdn.com/hk.svg' },
317  { code: 'HU', dial: '+36', name: 'Hongrie', flag: 'https://flagcdn.com/hu.svg' },
318  { code: 'IS', dial: '+354', name: 'Islande', flag: 'https://flagcdn.com/is.svg' },
319  { code: 'IN', dial: '+91', name: 'Inde', flag: 'https://flagcdn.com/in.svg' },
320  { code: 'ID', dial: '+62', name: 'Indonésie', flag: 'https://flagcdn.com/id.svg' },
321  { code: 'IR', dial: '+98', name: 'Iran', flag: 'https://flagcdn.com/ir.svg' },
322  { code: 'IQ', dial: '+964', name: 'Irak', flag: 'https://flagcdn.com/iq.svg' },
323  { code: 'IE', dial: '+353', name: 'Irlande', flag: 'https://flagcdn.com/ie.svg' },
324  { code: 'IM', dial: '+44', name: 'Île de Man', flag: 'https://flagcdn.com/im.svg' },
325  { code: 'IL', dial: '+972', name: 'Israël', flag: 'https://flagcdn.com/il.svg' },
326  { code: 'IT', dial: '+39', name: 'Italie', flag: 'https://flagcdn.com/it.svg' },
327  { code: 'JM', dial: '+1-876', name: 'Jamaïque', flag: 'https://flagcdn.com/jm.svg' },
328  { code: 'JP', dial: '+81', name: 'Japon', flag: 'https://flagcdn.com/jp.svg' },
329  { code: 'JE', dial: '+44', name: 'Jersey', flag: 'https://flagcdn.com/je.svg' },
330  { code: 'JO', dial: '+962', name: 'Jordanie', flag: 'https://flagcdn.com/jo.svg' },
331  { code: 'KZ', dial: '+7', name: 'Kazakhstan', flag: 'https://flagcdn.com/kz.svg' },
332  { code: 'KE', dial: '+254', name: 'Kenya', flag: 'https://flagcdn.com/ke.svg' },
333  { code: 'KI', dial: '+686', name: 'Kiribati', flag: 'https://flagcdn.com/ki.svg' },
334  { code: 'KP', dial: '+850', name: 'Corée du Nord', flag: 'https://flagcdn.com/kp.svg' },
335  { code: 'KR', dial: '+82', name: 'Corée du Sud', flag: 'https://flagcdn.com/kr.svg' },
336  { code: 'KW', dial: '+965', name: 'Koweït', flag: 'https://flagcdn.com/kw.svg' },
337  { code: 'KG', dial: '+996', name: 'Kirghizistan', flag: 'https://flagcdn.com/kg.svg' },
338  { code: 'LA', dial: '+856', name: 'Laos', flag: 'https://flagcdn.com/la.svg' },
339  { code: 'LV', dial: '+371', name: 'Lettonie', flag: 'https://flagcdn.com/lv.svg' },
340  { code: 'LB', dial: '+961', name: 'Liban', flag: 'https://flagcdn.com/lb.svg' },
341  { code: 'LS', dial: '+266', name: 'Lesotho', flag: 'https://flagcdn.com/ls.svg' },
342  { code: 'LR', dial: '+231', name: 'Libéria', flag: 'https://flagcdn.com/lr.svg' },
343  { code: 'LY', dial: '+218', name: 'Libye', flag: 'https://flagcdn.com/ly.svg' },
344  { code: 'LI', dial: '+423', name: 'Liechtenstein', flag: 'https://flagcdn.com/li.svg' },
345  { code: 'LT', dial: '+370', name: 'Lituanie', flag: 'https://flagcdn.com/lt.svg' },
346  { code: 'LU', dial: '+352', name: 'Luxembourg', flag: 'https://flagcdn.com/lu.svg' },
347  { code: 'MO', dial: '+853', name: 'Macao', flag: 'https://flagcdn.com/mo.svg' },
348  { code: 'MG', dial: '+261', name: 'Madagascar', flag: 'https://flagcdn.com/mg.svg' },
349  { code: 'MW', dial: '+265', name: 'Malawi', flag: 'https://flagcdn.com/mw.svg' },
350  { code: 'MY', dial: '+60', name: 'Malaisie', flag: 'https://flagcdn.com/my.svg' },
351  { code: 'MV', dial: '+960', name: 'Maldives', flag: 'https://flagcdn.com/mv.
351svg' },
352  { code: 'ML', dial: '+223', name: 'Mali', flag: 'https://flagcdn.com/ml.svg' },
353  { code: 'MT', dial: '+356', name: 'Malte', flag: 'https://flagcdn.com/mt.svg' },
354  { code: 'MH', dial: '+692', name: 'Îles Marshall', flag: 'https://flagcdn.com/mh.svg' },
355  { code: 'MQ', dial: '+596', name: 'Martinique', flag: 'https://flagcdn.com/mq.svg' },
356  { code: 'MR', dial: '+222', name: 'Mauritanie', flag: 'https://flagcdn.com/mr.svg' },
357  { code: 'MU', dial: '+230', name: 'Maurice', flag: 'https://flagcdn.com/mu.svg' },
358  { code: 'YT', dial: '+262', name: 'Mayotte', flag: 'https://flagcdn.com/yt.svg' },
359  { code: 'MX', dial: '+52', name: 'Mexique', flag: 'https://flagcdn.com/mx.svg' },
360  { code: 'FM', dial: '+691', name: 'Micronésie', flag: 'https://flagcdn.com/fm.svg' },
361  { code: 'MD', dial: '+373', name: 'Moldavie', flag: 'https://flagcdn.com/md.svg' },
362  { code: 'MC', dial: '+377', name: 'Monaco', flag: 'https://flagcdn.com/mc.svg' },
363  { code: 'MN', dial: '+976', name: 'Mongolie', flag: 'https://flagcdn.com/mn.svg' },
364  { code: 'ME', dial: '+382', name: 'Monténégro', flag: 'https://flagcdn.com/me.svg' },
365  { code: 'MS', dial: '+1-664', name: 'Montserrat', flag: 'https://flagcdn.com/ms.svg' },
366  { code: 'MA', dial: '+212', name: 'Maroc', flag: 'https://flagcdn.com/ma.svg' },
367  { code: 'MZ', dial: '+258', name: 'Mozambique', flag: 'https://flagcdn.com/mz.svg' },
368  { code: 'MM', dial: '+95', name: 'Myanmar (Birmanie)', flag: 'https://flagcdn.com/mm.svg' },
369  { code: 'NA', dial: '+264', name: 'Namibie', flag: 'https://flagcdn.com/na.svg' },
370  { code: 'NR', dial: '+674', name: 'Nauru', flag: 'https://flagcdn.com/nr.svg' },
371  { code: 'NP', dial: '+977', name: 'Népal', flag: 'https://flagcdn.com/np.svg' },
372  { code: 'NL', dial: '+31', name: 'Pays-Bas', flag: 'https://flagcdn.com/nl.svg' },
373  { code: 'NC', dial: '+687', name: 'Nouvelle-Calédonie', flag: 'https://flagcdn.com/nc.svg' },
374  { code: 'NZ', dial: '+64', name: 'Nouvelle-Zélande', flag: 'https://flagcdn.com/nz.svg' },
375  { code: 'NI', dial: '+505', name: 'Nicaragua', flag: 'https://flagcdn.com/ni.svg' },
376  { code: 'NE', dial: '+227', name: 'Niger', flag: 'https://flagcdn.com/ne.svg' },
377  { code: 'NG', dial: '+234', name: 'Nigéria', flag: 'https://flagcdn.com/ng.svg' },
378  { code: 'NU', dial: '+683', name: 'Niue', flag: 'https://flagcdn.com/nu.svg' },
379  { code: 'NF', dial: '+672', name: 'Île Norfolk', flag: 'https://flagcdn.com/nf.svg' },
380  { code: 'MK', dial: '+389', name: 'Macédoine du Nord', flag: 'https://flagcdn.com/mk.svg' },
381  { code: 'MP', dial: '+1-670', name: 'Îles Mariannes du Nord', flag: 'https://flagcdn.com/mp.svg' },
382  { code: 'NO', dial: '+47', name: 'Norvège', flag: 'https://flagcdn.com/no.svg' },
383  { code: 'OM', dial: '+968', name: 'Oman', flag: 'https://flagcdn.com/om.svg' },
384  { code: 'PK', dial: '+92', name: 'Pakistan', flag: 'https://flagcdn.com/pk.svg' },
385  { code: 'PW', dial: '+680', name: 'Palaos', flag: 'https://flagcdn.com/pw.svg' },
386  { code: 'PS', dial: '+970', name: 'Palestine', flag: 'https://flagcdn.com/ps.svg' },
387  { code: 'PA', dial: '+507', name: 'Panama', flag: 'https://flagcdn.com/pa.svg' },
388  { code: 'PG', dial: '+675', name: 'Papouasie-Nouvelle-Guinée', flag: 'https://flagcdn.com/pg.svg' },
389  { code: 'PY', dial: '+595', name: 'Paraguay', flag: 'https://flagcdn.com/py.svg' },
390  { code: 'PE', dial: '+51', name: 'Pérou', flag: 'https://flagcdn.com/pe.svg' },
391  { code: 'PH', dial: '+63', name: 'Philippines', flag: 'https://flagcdn.com/ph.svg' },
392  { code: 'PL', dial: '+48', name: 'Pologne', flag: 'https://flagcdn.com/pl.svg' },
393  { code: 'PT', dial: '+351', name: 'Portugal', flag: 'https://flagcdn.com/pt.svg' },
394  { code: 'PR', dial: '+1-787', name: 'Porto Rico', flag: 'https://flagcdn.com/pr.svg' },
395  { code: 'QA', dial: '+974', name: 'Qatar', flag: 'https://flagcdn.com/qa.svg' },
396  { code: 'RE', dial: '+262', name: 'La Réunion', flag: 'https://flagcdn.com/re.svg' },
397  { code: 'RO', dial: '+40', name: 'Roumanie', flag: 'https://flagcdn.com/ro.svg' },
398  { code: 'RU', dial: '+7', name: 'Russie', flag: 'https://flagcdn.com/ru.svg' },
399  { code: 'RW', dial: '+250', name: 'Rwanda', flag: 'https://flagcdn.com/rw.svg' },
400  { code: 'BL', dial: '+590', name: 'Saint-Barthélemy', flag: 'https://flagcdn.com/bl.svg' },
401  { code: 'SH', dial: '+290', name: 'Sainte-Hélène', flag: 'https://flagcdn.com/sh.svg' },
402  { code: 'KN', dial: '+1-869', name: 'Saint-Kitts-et-Nevis', flag: 'https://flagcdn.com/kn.svg' },
403  { code: 'LC', dial: '+1-758', name: 'Sainte-Lucie', flag: 'https://flagcdn.com/lc.svg' },
404  { code: 'MF', dial: '+590', name: 'Saint-Martin', flag: 'https://flagcdn.com/mf.svg' },
405  { code: 'PM', dial: '+508', name: 'Saint-Pierre-et-Miquelon', flag: 'https://flagcdn.com/pm.svg' },
406  { code: 'VC', dial: '+1-784', name: 'Saint-Vincent-et-les-Grenadines', flag: 'https://flagcdn.com/vc.svg' },
407  { code: 'WS', dial: '+685', name: 'Samoa', flag: 'https://flagcdn.com/ws.svg' },
408  { code: 'SM', dial: '+378', name: 'Saint-Marin', flag: 'https://flagcdn.com/sm.svg' },
409  { code: 'ST', dial: '+239', name: 'Sao Tomé-et-Principe', flag: 'https://flagcdn.com/st.svg' },
410  { code: 'SA', dial: '+966', name: 'Arabie saoudite', flag: 'https://flagcdn.com/sa.svg' },
411  { code: 'SN', dial: '+221', name: 'Sénégal', flag: 'https://flagcdn.com/sn.svg' },
412  { code: 'RS', dial: '+381', name: 'Serbie', flag: 'https://flagcdn.com/rs.svg' },
413  { code: 'SC', dial: '+248', name: 'Seychelles', flag: 'https://flagcdn.com/sc.svg' },
414  { code: 'SL', dial: '+232', name: 'Sierra Leone', flag: 'https://flagcdn.com/sl.svg' },
415  { code: 'SG', dial: '+65', name: 'Singapour', flag: 'https://flagcdn.com/sg.svg' },
416  { code: 'SX', dial: '+1-721', name: 'Saint-Martin (partie néerlandaise)', flag: 'https://flagcdn.com/sx.svg' },
417  { code: 'SK', dial: '+421', name: 'Slovaquie', flag: 'https://flagcdn.com/sk.svg' },
418  { code: 'SI', dial: '+386', name: 'Slovénie', flag: 'https://flagcdn.com/si.svg' },
419  { code: 'SB', dial: '+677', name: 'Îles Salomon', flag: 'https://flagcdn.com/sb.svg' },
420  { code: 'SO', dial: '+252', name: 'Somalie', flag: 'https://flagcdn.com/so.svg' },
421  { code: 'ZA', dial: '+27', name: 'Afrique du Sud', flag: 'https://flagcdn.com/za.svg' },
422  { code: 'GS', dial: '+500', name: 'Géorgie du Sud-et-les Îles Sandwich du Sud', flag: 'https://flagcdn.com/gs.svg' },
423  { code: 'SS', dial: '+211', name: 'Soudan du Sud', flag: 'https://flagcdn.com/ss.svg' },
424  { code: 'ES', dial: '+34', name: 'Espagne', flag: 'https://flagcdn.com/es.svg' },
425  { code: 'LK', dial: '+94', name: 'Sri Lanka', flag: 'https://flagcdn.com/lk.svg' },
426  { code: 'SD', dial: '+249', name: 'Soudan', flag: 'https://flagcdn.com/sd.svg' },
427  { code: 'SR', dial: '+597', name: 'Suriname', flag: 'https://flagcdn.com/sr.svg' },
428  { code: 'SJ', dial: '+47', name: 'Svalbard et Jan Mayen', flag: 'https://flagcdn.com/sj.svg' },
429  { code: 'SE', dial: '+46', name: 'Suède', flag: 'https://flagcdn.com/se.svg' },
430  { code: 'CH', dial: '+41', name: 'Suisse', flag: 'https://flagcdn.com/ch.svg' },
431  { code: 'SY', dial: '+963', name: 'Syrie', flag: 'https://flagcdn.com/sy.svg' },
432  { code: 'TW', dial: '+886', name: 'Taïwan', flag: 'https://flagcdn.com/tw.svg' },
433  { code: 'TJ', dial: '+992', name: 'Tadjikistan', flag: 'https://flagcdn.com/tj.svg' },
434  { code: 'TZ', dial: '+255', name: 'Tanzanie', flag: 'https://flagcdn.com/tz.svg' },
435  { code: 'TH', dial: '+66', name: 'Thaïlande', flag: 'https://flagcdn.com/th.svg' },
436  { code: 'TL', dial: '+670', name: 'Timor oriental', flag: 'https://flagcdn.com/tl.svg' },
437  { code: 'TG', dial: '+228', name: 'Togo', flag: 'https://flagcdn.com/tg.svg' },
438  { code: 'TK', dial: '+690', name: 'Tokelau', flag: 'https://flagcdn.com/tk.svg' },
439  { code: 'TO', dial: '+676', name: 'Tonga', flag: 'https://flagcdn.com/to.svg' },
440  { code: 'TT', dial: '+1-868', name: 'Trinité-et-Tobago', flag: 'https://flagcdn.com/tt.svg' },
441  { code: 'TN', dial: '+216', name: 'Tunisie', flag: 'https://flagcdn.com/tn.svg' },
442  { code: 'TR', dial: '+90', name: 'Turquie', flag: 'https://flagcdn.com/tr.svg' },
443  { code: 'TM', dial: '+993', name: 'Turkménistan', flag: 'https://flagcdn.com/tm.svg' },
444  { code: 'TC', dial: '+1-649', name: 'Îles Turques-et-Caïques', flag: 'https://flagcdn.com/tc.svg' },
445  { code: 'TV', dial: '+688', name: 'Tuvalu', flag: 'https://flagcdn.com/tv.svg' },
446  { code: 'UG', dial: '+256', name: 'Ouganda', flag: 'https://flagcdn.com/ug.svg' },
447  { code: 'UA', dial: '+380', name: 'Ukraine', flag: 'https://flagcdn.com/ua.svg' },
448  { code: 'AE', dial: '+971', name: 'Émirats arabes unis', flag: 'https://flagcdn.com/ae.svg' },
449  { code: 'GB', dial: '+44', name: 'Royaume-Uni', flag: 'https://flagcdn.com/gb.svg' },
450  { code: 'US', dial: '+1', name: 'États-Unis', flag: 'https://flagcdn.com/us.svg' },
451  { code: 'UY', dial: '+598', name: 'Uruguay', flag: 'https://flagcdn.com/uy.svg' },
452  { code: 'UZ', dial: '+998', name: 'Ouzbékistan', flag: 'https://flagcdn.com/uz.svg' },
453  { code: 'VU', dial: '+678', name: 'Vanuatu', flag: 'https://flagcdn.com/vu.svg' },
454  { code: 'VA', dial: '+39', name: 'Vatican', flag: 'https://flagcdn.com/va.svg' },
455  { code: 'VE', dial: '+58', name: 'Venezuela', flag: 'https://flagcdn.com/ve.svg' },
456  { code: 'VN', dial: '+84', name: 'Viêt Nam', flag: 'https://flagcdn.com/vn.svg' },
457  { code: 'WF', dial: '+681', name: 'Wallis-et-Futuna', flag: 'https://flagcdn.com/wf.svg' },
458  { code: 'EH', dial: '+212', name: 'Sahara occidental', flag: 'https://flagcdn.com/eh.svg' },
459  { code: 'YE', dial: '+967', name: 'Yémen', flag: 'https://flagcdn.com/ye.svg' },
460  { code: 'ZM', dial: '+260', name: 'Zambie', flag: 'https://flagcdn.com/zm.svg' },
461  { code: 'ZW', dial: '+263', name: 'Zimbabwe', flag: 'https://flagcdn.com/zw.svg' }
462];
463
464function setupTelCountryDropdown() {
465  const telInput = document.getElementById('tel');
466  const flagBtn = document.getElementById('tel-flag-btn');
467  const flagImg = document.getElementById('tel-flag');
468  const codeSpan = document.getElementById('tel-country-code');
469  const dropdown = document.getElementById('tel-country-list');
470
471  if (!telInput || !flagBtn || !flagImg || !codeSpan || !dropdown) return;
472
473  dropdown.innerHTML = phoneCountries.map(c =>
474    `<div class="tel-country-option" data-code="${c.code}" data-dial="${c.dial}" data-flag="${c.flag}">
475      <img src="${c.flag}" alt="${c.code}
475" style="width: 20px; height: 15px; vertical-align: middle;"> <span style="font-weight:bold;">${c.dial}</span> <span style="margin-left:0.3em;">${c.name}</span>
476    </div>`
477  ).join('');
478
479  flagBtn.addEventListener('click', e => {
480    e.preventDefault();
481    dropdown.style.display = dropdown.style.display === 'block' ? 'none' : 'block';
482  });
483  document.addEventListener('click', e => {
484    if (!flagBtn.contains(e.target) && !dropdown.contains(e.target)) {
485      dropdown.style.display = 'none';
486    }
487  });
488
489  dropdown.addEventListener('click', e => {
490    const option = e.target.closest('.tel-country-option');
491    if (option) {
492      flagImg.src = option.dataset.flag;
493      flagImg.alt = option.dataset.code;
494      codeSpan.textContent = option.dataset.dial;
495      telInput.value = option.dataset.dial + ' ';
496      telInput.focus();
497      dropdown.style.display = 'none';
498    }
499  });
500
501  telInput.addEventListener('input', () => {
502    const val = telInput.value.trim();
503    const match = phoneCountries.find(c => val.startsWith(c.dial));
504    if (match) {
505      flagImg.src = match.flag;
506      flagImg.alt = match.code;
507      codeSpan.textContent = match.dial;
508    }
509  });
510}
511
512if (document.readyState === 'loading') {
513  document.addEventListener('DOMContentLoaded', setupTelCountryDropdown);
514} else {
515  setupTelCountryDropdown();
516}
517
518function attachStep3Listeners() {
519  const form = document.querySelector('form');
520  const step3Container = form.querySelector('#step3');
521  if (step3ListenerAttached) return;
522  step3ListenerAttached = true;
523  step3Container.addEventListener('click', (e) => {
524
525    if (e.target.matches('#remove-choice') || e.target.closest('#remove-choice')) {
526      e.preventDefault();
527      const idx = Number(e.target.closest('fieldset').dataset.idx);
528      if (choiceCount > 1) {
529        choiceCount--;
530        renderChoices(step3Container, choiceCount, filieres, window.json_org_type, idx);
531      }
532    }
533
534    if (e.target.classList.contains('formationtype-selectall-btn')) {
535      e.preventDefault();
536      const fieldset = e.target.closest('fieldset');
537      const checkboxes = fieldset.querySelectorAll('input[type="checkbox"][name*="[formation_type]"]');
538      const allChecked = Array.from(checkboxes).every(cb => cb.checked);
539      checkboxes.forEach(cb => cb.checked = !allChecked);
540      checkboxes.forEach(cb => cb.dispatchEvent(new Event('change', { bubbles: true })));
541    }
542  });
543
544  step3Container.addEventListener('change', (e) => {
545    if (e.target.matches('input[type="checkbox"][name*="[formation_type]"]')) {
546      e.preventDefault();
547      const fieldset = e.target.closest('fieldset');
548      const checkboxes = Array.from(fieldset.querySelectorAll('input[type="checkbox"][name*="[formation_type]"]'));
549      const selectAllBtn = fieldset.querySelector('.formationtype-selectall-btn');
550      if (selectAllBtn) {
551        const allChecked = checkboxes.every(cb => cb.checked);
552        selectAllBtn.textContent = allChecked ? 'Tout désélectionner' : 'Tout sélectionner';
553      }
554      if (checkboxes.length > 1) {
555        const required = !checkboxes.some(cb => cb.checked);
556        checkboxes.forEach(cb => {
557          cb.required = required;
558        });
559      }
560      updateChoiceSelects(fieldset, filieres, e.target);
561    }
562  });
563}
564
565function setCompletion(step, nb_steps) {
566  answer_progression = Math.round(step * 100 / (nb_steps - 1));
567
568  const colibri = document.querySelector('svg.colibri').parentElement;
569  const completion = document.querySelector('.completion-text');
570  completion.innerText = `${answer_progression}%`;
571  completion.parentElement.style.width = `${answer_progression}%`;
572
573  colibri.style.left = `${answer_progression}%`;
574  colibri.style.transform = `translateX(${answer_progression * -1}%)`;
575
576  const bubbles = colibri.parentElement.nextElementSibling.querySelectorAll('.completion-bubbles > div');
577  bubbles.forEach((bubble, i) => {
578    bubble.classList.remove('bg-white');
579    bubble.classList.remove('bg-slate-400');
580    bubble.classList.add(i <= step ? 'bg-white' : 'bg-slate-400');
581  });
582}
583
584function setStep(step, stepsEl, nb_steps) {
585  if (currentStep < step)
586    currentStep = Array.isArray(step) ? step[0] : step;
587  setCompletion(currentStep, nb_steps);
588
589  [...stepsEl].filter((e, i) => {
590    return i > currentStep;
591  }
591).forEach((e) => {
592    e.querySelectorAll('input,select').forEach((input) => input.required = false);
593  });
594
595  stepsEl[currentStep].classList.remove('hidden');
596  stepsEl[currentStep].querySelectorAll('input,select').forEach((input) => input.required = input.name.replace(/^(steps\[2\]\[)\d(\]\[)/, "$1?$2") in requiredInput);
597
598  if (Array.isArray(step)) {
599    const subfieldset = stepsEl[step[0]].querySelectorAll('fieldset');
600    subfieldset[step[1]].classList.remove('hidden');
601    setTimeout(() => {
602      subfieldset[step[1]].scrollIntoView(true, { behavior: 'smooth' });
603    }, 1);
604  } else {
605    setTimeout(() => {
606      stepsEl[step].scrollIntoView(true, { behavior: 'smooth' });
607    }, 1);
608  }
609}
610
611
612let step2SubmitCallback = null;
613let step2SubmitTimer = null;
614
615async function timerFn() {
616  if (step2SubmitCallback) {
617    setTimeout(() => {
618      step2SubmitCallback();
619    }, 1);
620  } else {
621    step2SubmitTimer = setTimeout(timerFn, 1000);
622  }
623}
624function initStep2(stepEl, values = null, base_url, json_org_type) {
625  const niveauEl = stepEl.querySelector("#niveau");
626  niveauEl.addEventListener("change", (e) => onStep2ChangeNiveau(e.target.value, e.target.form, base_url, json_org_type));
627  const submitter = stepEl.querySelector('input[type=submit]');
628  submitter.addEventListener('click', (e) => {
629    if (step2SubmitTimer) {
630      clearTimeout(step2SubmitTimer);
631      step2SubmitTimer = null;
632    }
633    timerFn();
634  });
635}
636
637async function onStep2ChangeNiveau(value, form, base_url, json_org_type) {
638  const filieresJson = await fetch(`${base_url}inscription/filieres/${value}`).then(res => res.json());
639  bacList = filieresJson;
640
641  const filiereElement = document.getElementById('filiere');
642  const bac2020 = document.getElementById('bac-2020');
643
644  bac2020.parentNode.classList.add('hidden');
645  bac2020.disabled = true;
646  filiereElement.parentNode.classList.add('hidden');
647  filiereElement.disabled = true;
648
649  if (value === "87") {
650    // Cas spécial pour le BAC général depuis 2020
651    const bac2020List = bac2020.querySelector('ul');
652
653    bac2020List.innerHTML = "";
654    const submitter = form.querySelectorAll('fieldset')[1].querySelector('input[type=submit]');
655    submitter.disabled = true;
656
657    filieresJson.forEach((filiere) => {
658      const bac2020Li = document.createElement('li');
659      const checkbox = document.createElement('input');
660      const id = `bac2020_${filiere.id}`;
661      checkbox.type = 'checkbox';
662
663      checkbox.id = id;
664      checkbox.name = `bac_2020[]`;
665      checkbox.value = filiere.id;
666      checkbox.addEventListener('change', (e) => {
667        const checkedNumber = bac2020List.querySelectorAll('input:checked').length;
668        if (checkedNumber === 2) {
669          submitter.disabled = false;
670          bac2020List.querySelectorAll('input:not(:checked)').forEach((input) => {
671            input.disabled = true;
672          });
673        }
674        else if (checkedNumber < 2) {
675          submitter.disabled = true;
676          bac2020List.querySelectorAll('input').forEach((input) => {
677            input.disabled = false;
678          });
679        }
680        else {
681          e.target.checked = false;
682        }
683      });
684
685      const label = document.createElement('label');
686      label.htmlFor = id;
687      label.innerText = filiere.nom;
688
689      bac2020Li.appendChild(checkbox);
690      bac2020Li.appendChild(label);
691
692      bac2020List.appendChild(bac2020Li);
693    });
694
695    bac2020.parentNode.classList.remove('hidden');
696    bac2020.disabled = false;
697
698    bac2020.querySelectorAll('input').forEach((el) => {
699      el.addEventListener('change', async (e) => {
700        const stepEl = form.querySelectorAll('form>fieldset')[2];
701        const subStepEl = stepEl.querySelector('fieldset#step3-1');
702        let formationsUrl = `${base_url}inscription/formations?niveau_id=${value}`
703        const bac_2020 = Array.from(bac2020List.querySelectorAll('input:checked')).map((el) => el.value);
704        if (bac_2020.length > 0) {
705          formationsUrl += `&bac_2020_id=${bac_2020.join(',')}`
706          await initStep3Choices(formationsUrl, json_org_type);
707          if (currentStep >= 2) {
708            step2SubmitCallback();
709          }
710        }
711      });
712    });
713  } else {
714    // Vu qu'on a toutes les filières, on filtre par le niveau_id
715    filieres = filieresJson.filter((filiere) => {
716      // Certaines valeurs de niveau_id sont sous le format "2,3,4".
717      if (typeof filiere.niveau_id === 'string' || filiere.niveau_id instanceof String) {
718        return filiere.niveau_id.split(',').includes(value);
719      } else { // D'autres sont seulement un entier
720        return filiere.niveau_id;
721      }
722    });
723
724    // On remet à zéro les options pour les filières à chaque changement de niveau
725
726    filiereElement.innerHTML = "";
727    filiereElement.addEventListener('change', async (e) => {
728      const { 'steps[1][niveau]': niveau, 'steps[1][filiere]': filiere } = Object.fromEntries(new FormData(form));
729      let formationsUrl = `${base_url}inscription/formations?niveau_id=${niveau}`
730      formationsUrl += `&bac_id=${filiere}`
731      // const stepEl = form.querySelectorAll('form>fieldset')[2];
732      // const subStepEl = stepEl.querySelector('fieldset#step3-1');
733      await initStep3Choices(formationsUrl, json_org_type);
734      if (currentStep >= 2) {
735        step2SubmitCallback();
736      }
737    });
738
739    const baseOption = document.createElement('option');
740    baseOption.innerText = "Filière...";
741    baseOption.value = '';
742    baseOption.disabled = true;
743    baseOption.selected = true;
744    filiereElement.appendChild(baseOption);
745
746    // On remplit le select pour les filières
747    filieres
748        .sort((a,b)=>a.bac_nom.localeCompare(b.bac_nom, 'fr', { sensitivity: 'base' }))
749        .forEach((filiere) => {
750      const filiereOption = document.createElement('option');
751      filiereOption.innerText = filiere.bac_nom;
752      filiereOption.value = filiere.bac_id;
753
754      filiereElement.appendChild(filiereOption);
755    });
756
757    filiereElement.parentNode.classList.remove('hidden');
758    filiereElement.disabled = false;
759  }
760}
761
762let choiceCount = 1;
763let step3ListenerAttached = false;
764// Main init function
765async function initStep3Choices(formationsUrl, json_org_type) {
766  const form = document.querySelector('form');
767  const step3Container = form.querySelector('#step3');
768  step3Container.innerHTML = '';
769
770  const formations = await fetch(formationsUrl).then(res => res.json());
771  filieres = formations;
772
773  choiceCount = 1;
774
775  step2SubmitCallback = () => {
776    renderChoices(step3Container, choiceCount, formations, json_org_type);
777  }
778}
779
780async function renderChoices(container, count, formations, json_org_type, removeIdx = null) {
781  let choices = Array.from(container.querySelectorAll('fieldset'));
782  if (removeIdx !== null) {
783    choices.splice(removeIdx, 1);
784  }
785  while (choices.length < count) {
786    const fs = await createChoiceFieldset(choices.length, formations, json_org_type);
787    updateChoiceSelects(fs, formations);
788    choices.push(fs);
789  }
790  while (choices.length > count) {
791    choices.pop();
792  }
793  container.innerHTML = '';
794  for (let i = 0; i < choices.length; i++) {
795    const fs = choices[i];
796    if (!fs || !fs.dataset) continue;
797    fs.dataset.idx = i;
798    updateChoiceFieldset(fs, i, count);
799    const selectAllBtn = fs.querySelector('.formationtype-selectall-btn');
800    if (selectAllBtn) {
801      const checkboxes = fs.querySelectorAll('input[type="checkbox"][name*="[formation_type]"]');
802      const allChecked = Array.from(checkboxes).every(cb => cb.checked);
803      selectAllBtn.textContent = allChecked ? 'Tout désélectionner' : 'Tout sélectionner';
804    }
805    container.appendChild(fs);
806    if (removeIdx === null && count > 1 && i === choices.length - 1) {
807      setTimeout(() => {
808        fs.scrollIntoView({ behavior: 'smooth', block: 'start' });
809      }, 100);
810    }
811  }
812
813  document.querySelector('form').dispatchEvent(new Event("change", { bubbles: true }));
814}
815
816async function createChoiceFieldset(idx, formations, json_org_type) {
817  const org_type_display = json_org_type;
818  const org_types = [...new Set(formations.map(formation => formation.org_type).filter(o => org_type_display["_" + o]))];
819
820  formation_types = formations.map(formation => {
821    return { id: formation.formation_type, nom: formation.formation_type_nom };
822  }).filter((formation, index, arr) => arr.findIndex((el) => el['id'] == formation['id']) == index);
823
824  let orgTypeHTML = '';
825  if (org_types.length > 1) {
826    orgTypeHTML += `
827    <label>Vous recherchez</label>
828    <ul class="flex gap-x-3" id="org_types_${idx}">
829    `;
830    for (let i = 0; i < org_types.length; i++) {
831      const orgTypeChild = `
832        <li>
833        <input type="radio" name="steps[2][${idx}][org_type]" id="orgtype-${idx}-${i}" value="${org_types[i]}" class="peer hidden" required>
834          <label for="orgtype-${idx}-${i}"
835            class="labelRadio peer-checked:text-white peer-checked:bg-primary hover:bg-primary hover:text-white">
836            ${org_type_display["_" + org_types[i]]}
837          </label>
838        </li>
839      `;
840      orgTypeHTML += orgTypeChild;
841    }
842    orgTypeHTML += `
843      </ul>
844    `;
845  } else {
846    const org_type_display = Object.fromEntries(
847      Object.entries(json_org_type)
848        .flatMap(function ([k, v]) {
849          if (k == '_7') {
850            return [['_1', '1'], ['_2', '2'], ['_3', '3']];
851          } else {
852            return [[k, v]];
853          }
854        })
855    );
856    const org_types = [...new Set(formations.map(formation => formation.org_type).filter(o => org_type_display["_" + o]))];
857
858    orgTypeHTML = org_types.map((org, oi) => `<input type="hidden" name="steps[2][${idx}][org_type][]" id="orgtype-${idx}-0" value="${org}" />`).join('');
859  }
860
861  let formationTypeHTML = '';
862  for (let i = 0; i < formation_types.length; i++) {
863    const formationTypeChild = `
864    <li>
865      <input type="checkbox" name="steps[2][${idx}][formation_type][]" id="formationtype-${idx}-${i}" value="${formation_types[i]['id']}" class="peer hidden" required>
866      <label for="formationtype-${idx}-${i}"
867        class="labelRadio peer-checked:text-white peer-checked:bg-primary hover:bg-primary hover:text-white">
868        ${formation_types[i]['nom']}
869      </label>
870    </li>
871  `;
872    formationTypeHTML += formationTypeChild;
873  }
874
875  const selectAllBtnId = `formationtype-selectall-${idx}`;
876  const formationTypeLabel = `<label for="formation_types_${idx}">Rythme de formation <button type="button" class="formationtype-selectall-btn" id="${selectAllBtnId}" tabindex="-1" style="font-size:0.8em;margin-left:0.5em;">Tout sélectionner</button></label>
877  <p>Vous pouvez choisir les deux options si vous hésitez</p>`;
878
879  const fs = document.createElement('fieldset');
880  fs.className = 'choice-fieldset';
881  fs.dataset.idx = idx;
882  fs.innerHTML = `
883    <div class="flex justify-between items-center w-full">
884      <legend>Choix ${idx + 1}</legend>
885      <button id="remove-choice" class="close-btn"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg></button>
886    </div>
887
888    ${orgTypeHTML}
889    
890    ${formationTypeLabel}
891    <ul class="flex gap-x-3" id="formation_types_${idx}">
892    ${formationTypeHTML}
893    </ul>
894    
895    <label>Quelle(s) formation(s) recherchez-vous ?</label>
896    <ul class="flex flex-col gap-3">
897      <li>
898        <select name="steps[2][${idx}][formation]" required>
899          <option value="" disabled>CHOISIR UNE FILIERE DE FORMATION...</option>
900        </select>
901      </li>
902      <li>
903        <select name="steps[2][${idx}][filiere]" required disabled>
904          <option value="" disabled>CHOISIR UNE SPECIALITE...</option>
905        </select>
906      </li>
907    </ul>
908    <label>Où souhaitez-vous étudier ?</label>
909    <ul class="flex flex-col gap-3">
910      <li>
911        <select name="steps[2][${idx}][region][]" multiple required disabled>
912          <option value="" disabled>CHOISIR UNE REGION...</option>
913        </select>
914      </li>
915      <li>
916        <select name="steps[2][${idx}][departement][]" multiple required disabled>
917          <option value="" disabled>CHOISIR UN OU PLUSIEURS DEPARTEMENTS...</option>
918        </select>
919      </li>
920    </ul>
921    <ul class="nav flex items-center gap-4">
922      <li class="w-full">
923        <input type="submit" id="add-choice" class="w-full" value="Choix ${idx + 2}"/>
924      </li>
925      <li class="font-bold pt-2"><span>OU</span></li>
926      <li class="w-full">
927        <input type="submit" id="submit-choice" class="w-full" value="C’est parti !" />
928      </li>
929    </ul>
930  `;
931  console.log('Step', currentStep);
932  return fs;
933}
934
935const ERROR_MESSAGE_EMPTY_SELECT = 'Veuillez sélectionner un élément de la liste.';
936function _setSelectMultipleEmptyErrorMessage(el) {
937  if (el.value == undefined || el.value == '') {
938    el.setCustomValidity(ERROR_MESSAGE_EMPTY_SELECT)
939  } else {
940    el.setCustomValidity('')
941  }
942}
943
944function updateChoiceFieldset(fs, idx, total) {
945  const legend = fs.querySelector('legend');
946  if (legend) legend.textContent = `Choix ${idx + 1}`;
947  fs.querySelectorAll('input, select, label').forEach(el => {
948    if (el.name) {
949      el.name = el.name.replace(/steps\[2\]\[\d+\]/, `steps[2][${idx}]`);
950    }
951    if (el.id) {
952      el.id = el.id.replace(/_(\d+)$/, `_${idx}`);
953      el.id = el.id.replace(/-(\d+)-/, `-${idx}-`);
954      el.id = el.id.replace(/orgtype-\d+-/, `orgtype-${idx}-`);
955      el.id = el.id.replace(/formationtype-\d+-/, `formationtype-${idx}-`);
956    }
957    if (el.htmlFor) {
958      el.htmlFor = el.htmlFor.replace(/_(\d+)$/, `_${idx}`);
959      el.htmlFor = el.htmlFor.replace(/-(\d+)-/, `-${idx}-`);
960      el.htmlFor = el.htmlFor.replace(/orgtype-\d+-/, `orgtype-${idx}-`);
961      el.htmlFor = el.htmlFor.replace(/formationtype-\d+-/, `formationtype-${idx}-`);
962    }
963  });
964  fs.querySelectorAll('select[multiple]').forEach(el => {
965    _setSelectMultipleEmptyErrorMessage(el);
966    el.oninput = (e) => _setSelectMultipleEmptyErrorMessage(e.target);
967  });
968  const addBtn = fs.querySelector('#add-choice');
969  if (addBtn) {
970    const display = (idx === total - 1 && total < 3) ? '' : 'none';
971    addBtn.style.display = display;
972    addBtn.value = `Choix ${idx + 2}`;
973  }
974  const submitBtn = fs.querySelector('#submit-choice');
975  if (submitBtn) {
976    const display = (currentStep == 2 && idx === total - 1) ? '' : 'none';
977    submitBtn.style.display = display;
978    // "OU" entre les boutons
979    const ouDisplay = (currentStep == 2 && idx === total - 1) && (idx === total - 1 && total < 3) ? '' : 'none';
980    submitBtn.parentElement.previousElementSibling.childNodes.forEac
980h(e => {
981      e.style.display = ouDisplay;
982    });
983  }
984  const removeBtn = fs.querySelector('#remove-choice');
985  if (removeBtn) {
986    const display = (total > 1) ? '' : 'none';
987    removeBtn.style.display = display;
988  }
989}
990
991function _isAfterInDom(me, other) {
992  if (!(me instanceof Object && other instanceof Object)) {
993    return false;
994  }
995
996  return me.compareDocumentPosition(other) == Node.DOCUMENT_POSITION_PRECEDING;
997}
998
999function updateChoiceSelects(fieldset, formations, clearAfter) {
1000  const orgTypes = Array.from(fieldset.querySelectorAll('input[name*="[org_type]"]:checked,input[type="hidden"][name*="[org_type]"]')).map(orgtypeEl => orgtypeEl.value);
1001  const formationTypes = Array.from(fieldset.querySelectorAll('input[name*="[formation_type]"]:checked')).map(formationTypesEl => formationTypesEl.value);
1002  const formationEl = fieldset.querySelector('select[name*="[formation]"]');
1003  const filiereEl = fieldset.querySelector('select[name*="[filiere]"]');
1004  const regionEl = fieldset.querySelector('select[name*="[region]"]');
1005  const departementEl = fieldset.querySelector('select[name*="[departement]"]');
1006
1007  const prevFormation = formationEl && !_isAfterInDom(formationEl, clearAfter) ? formationEl.value : '';
1008  const prevFiliere = filiereEl && !_isAfterInDom(filiereEl, clearAfter) ? filiereEl.value : '';
1009  const prevRegions = regionEl && !_isAfterInDom(regionEl, clearAfter) ? Array.from(regionEl.selectedOptions).map(opt => opt.value) : [];
1010  const prevDepartements = departementEl && !_isAfterInDom(departementEl, clearAfter) ? Array.from(departementEl.selectedOptions).map(opt => opt.value) : [];
1011
1012  if (formationEl) formationEl.innerHTML = '<option value="" disabled>CHOISIR UNE FILIERE DE FORMATION...</option>';
1013  if (filiereEl) filiereEl.innerHTML = '<option value="" disabled>CHOISIR UNE SPECIALITE...</option>';
1014  if (regionEl) regionEl.innerHTML = '<option value="" disabled>CHOISIR UNE REGION...</option>';
1015  if (departementEl) departementEl.innerHTML = '<option value="" disabled>CHOISIR UN OU PLUSIEURS DEPARTEMENTS...</option>';
1016
1017  const filtered_all = formations
1018    .filter(formation =>
1019      (orgTypes.includes(formation.org_type)) &&
1020      (formationTypes.includes(formation.formation_type))
1021    );
1022
1023  const formationOptions = filtered_all.map(formation => {
1024    const option = document.createElement('option');
1025    option.innerText = formation.categorie_nom;
1026    option.value = formation.categorie_id;
1027    return option;
1028  }).filter((option, index, arr) =>
1029    arr.findIndex(other => other.value == option.value) == index
1030  );
1031  formationOptions.forEach(option => formationEl && formationEl.appendChild(option));
1032  if (formationEl && [...formationEl.options].some(opt => opt.value === prevFormation)) {
1033    formationEl.value = prevFormation;
1034  }
1035
1036  const selectedFormation = formationEl?.value;
1037  if (filiereEl) filiereEl.disabled = !selectedFormation;
1038  const filtered_formations = filtered_all.filter(formation => formation.categorie_id == selectedFormation);
1039  const filiereOptions = filtered_formations.map(formation => {
1040    const option = document.createElement('option');
1041    option.innerText = formation.formation_nom;
1042    option.value = formation.formation_id;
1043    return option;
1044  }).filter((option, index, arr) =>
1045    arr.findIndex(other => other.value == option.value) == index
1046  );
1047
1048  filiereOptions.forEach(option => filiereEl && filiereEl.appendChild(option));
1049  if (filiereEl && [...filiereEl.options].some(opt => opt.value === prevFiliere)) {
1050    filiereEl.value = prevFiliere;
1051  }
1052
1053  const filtered_filiere = filtered_formations.filter(formation => formation.formation_id == filiereEl?.value);
1054  if (regionEl) regionEl.disabled = !filiereEl?.value;
1055  const regionOptions = filtered_filiere.map(formation => {
1056    const option = document.createElement('option');
1057    option.innerText = formation.region_nom;
1058    option.value = formation.region_id;
1059    return option;
1060  }).filter((option, index, arr) =>
1061    arr.findIndex(other => other.value == option.value) == index
1062  );
1063  regionOptions.forEach(option => regionEl && regionEl.appendChild(option));
1064  if (regionEl) {
1065    Array.from(regionEl.options).forEach(opt => {
1066      if (prevRegions.includes(opt.value)) opt.selected = true;
1067    });
1068  }
1069
1070  const filtered_region = filtered_filiere.filter(formation => regionEl && Array.from(regionEl.selectedOptions).map(opt => opt.value).includes(formation.region_id));
1071  if (departementEl) departementEl.disabled = filtered_region.length === 0;
1072  const departementOptions = filtered_region.map(formation => {
1073    const option = document.createElement('option');
1074    option.innerText = formation.departement_nom;
1075    option.value = formation.departement_id;
1076    return option;
1077  }).filter((option, index, arr) =>
1078    arr.findIndex(other => other.value == option.value) == index
1079  );
1080  departementOptions.forEach(option => departementEl && departementEl.appendChild(option));
1081  if (departementEl) {
1082    Array.from(departementEl.options).forEach(opt => {
1083      if (prevDepartements.includes(opt.value)) opt.selected = true;
1084    });
1085  }
1086}
1087
1088async function initStep5(dest, values, nb_steps, base_url) {
1089  const formEl = document.querySelector('form');
1090  const data = jsonToFormData(values, new FormData());
1091  console.log(data);
1092
1093  bacList = await fetch(`${base_url}inscription/filieres/${data.get('steps[1][niveau]')}`).then(res => res.json());
1094  filieres = await fetch(`${base_url}inscription/formations?niveau_id=${data.get('steps[1][niveau]')}&${data.get('bac_2020[]') != null ? `bac_2020_id=${data.getAll('bac_2020[]').join(',')}` : `bac_id=${data.get('steps[1][filiere]')}`}`)
1095    .then((res) => res.json());
1096
1097  formation_types = filieres.map(formation => {
1098    return { id: formation.formation_type, nom: formation.formation_type_nom };
1099  }).filter((formation, index, arr) => arr.findIndex((el) => el['id'] == formation['id']) == index);
1100
1101  setFormResume(document.querySelector('.resume'), data);
1102
1103  const stepsEl = document.querySelectorAll('form>fieldset');
1104  setStep(4, stepsEl, nb_steps);
1105  stepsEl.forEach((el) => {
1106    if (el.id != dest.id) {
1107      el.classList.add('hidden');
1108    }
1109  })
1110}
1111
1112function buildFormData(formData, data, parentKey) {
1113  if (data && typeof data === 'object' && !(data instanceof Date) && !(data instanceof File) && !(data instanceof Blob) && !(Array.isArray(data) && !data.some((k) => k instanceof Object || Array.isArray(k)))) {
1114    Object.entries(data).forEach(([key, value]) => {
1115      buildFormData(formData, data[key], parentKey ? `${parentKey}[${key}]` : key);
1116    });
1117  } else {
1118    const value = data == null ? '' : data;
1119    if (Array.isArray(data)) {
1120      data.forEach((v) => {
1121        formData.append(parentKey + '[]', v);
1122      });
1123    } else {
1124      formData.append(parentKey, value);
1125    }
1126  }
1127}
1128
1129function jsonToFormData(data, formData) {
1130
1131  buildFormData(formData, data);
1132
1133  return formData;
1134}
1135
1136function setFormResume(dest, values) {
1137  const ul1 = document.createElement('ul');
1138
1139  const step1Title = document.createElement('p');
1140  step1Title.classList.add('bold');
1141  step1Title.innerText = `Vous êtes :`;
1142  ul1.appendChild(step1Title);
1143
1144  const titre = _stepValueToLi(values, 0, 'titre', ([titre]) => titre == 'monsieur' ? 'M. ' : 'Mme ');
1145  if (titre)
1146    ul1.appendChild(titre);
1147
1148  const prenom = _stepValueToLi(values, 0, 'prenom', ([prenom]) => prenom.substr(0, 1).toUpperCase() + prenom.substr(1).toLowerCase() + ' ');
1149  if (prenom)
1150    ul1.appendChild(prenom);
1151
1152  const nom = _stepValueToLi(values, 0, 'nom', ([nom]) => nom.toUpperCase());
1153  if (nom)
1154    ul1.appendChild(nom);
1155
1156  const naissance = _stepValueToLi(values, 0, 'naissance', ([date]) => '<strong>Votre date de naissance :</strong> ' + new Date(date).toLocaleDateString(undefined, DATE_FORMAT_SETTINGS));
1157  if (naissance)
1158    ul1.appendChild(naissance);
1159
1160  const demarche = _stepValueToLi(values, 0, 'demarche', ([value]) => {
1161    const formatedValue = value.replace(/([A-Z])/g, ' $1').trim().toLowerCase();
1162    return `<strong>Cette démarche est pour :</strong> ${formatedValue}`;
1163  });
1164  if (demarche) {
1165    ul1.appendChild(demarche);
1166  }
1167  dest.appendChild(ul1);
1168
1169  const ul2 = document.createElement('ul');
1170
1171  const step2Title = document.createElement('p');
1172  step2Title.classList.add('bold');
1173  step2Title.innerText = `Niveau d'études :`;
1174  ul2.appendChild(step2Title);
1175
1176  const filiere = _stepValueToLi(values, 1, 'filiere', ([id]) => bacList.filter((f) => f['bac_id'] == id)[0]['bac_nom']);
1177  if (filiere)
1178    ul2.appendChild(filiere);
1179
1180  const bac_2020 = _stepValueToLi(values, null, 'bac_2020', (ids) => "Bac général (" + ids.map((id) => bacList.filter((f) => f['id'] == id)[0]['nom']).join(' et ') + ')', '', '[]');
1181  if (bac_2020)
1182    ul2.appendChild(bac_2020);
1183
1184
1185  dest.appendChild(ul2);
1186
1187  const ul3 = document.createElement('ul');
1188
1189  for (let i = 0; i < 3; i++) {
1190    const choicekey = `steps[2][${i}]`;
1191    if (!values.has(`${choicekey}[org_type][]`)) {
1192      break;
1193    }
1194
1195    const topLi = document.createElement('li');
1196
1197    const choiceTitle = document.createElement('p');
1198    choiceTitle.style = 'font-weight: bold;';
1199    choiceTitle.innerText = `Choix ${i + 1}`;
1200    topLi.appendChild(choiceTitle);
1201
1202    const choiceUl = document.createElement('ul');
1203
1204    const formationTypeEl = _stepValueToLi(values, [2, i], 'formation_type', (val) => {
1205      if (Array.isArray(val)) {
1206        sep = ', ';
1207        formationTypeLabel = val.map((f) => formation_types.find((x)=>x.id == f)['nom']).join(sep);
1208        offset = formationTypeLabel.lastIndexOf(sep);
1209        if (offset !== -1) {
1210          formationTypeLabel = formationTypeLabel.substr(0, offset) + ' et ' + formationTypeLabel.substr(offset + sep.length);
1211        }
1212        return formationTypeLabel;
1213      }
1214      return formation_types[val]['nom'];
1215    }, undefined, '[]');
1216
1217    if (formationTypeEl)
1218      choiceUl.appendChild(formationTypeEl);
1219
1220    const filiereNom = _stepValueToLi(values, [2, i], 'filiere', ([id]) => filieres.find((f) => f['formation_id'] == id)['formation_nom']);
1221    if (filiereNom)
1222      choiceUl.appendChild(filiereNom);
1223
1224    // const region = _stepValueToLi(values, [2, i], 'region', ([id])=>filieres.find((f)=>f['region_id'] == id)['region_nom']);
1225    // if(region)
1226    //   choiceUl.appendChild(region);
1227
1228    const departement = _stepValueToLi(values, [2, i], 'departement', (ids) =>
1229      filieres
1230        .filter((f, i, a) => ids.includes(f['departement_id']) && a.findIndex(f2 => f2['departement_id'] == f['departement_id']) == i)
1231        .map((f) => f['departement_nom']).join(', ')
1232      , undefined, '[]');
1233    if (departement)
1234      choiceUl.appendChild(departement);
1235
1236    topLi.appendChild(choiceUl);
1237
1238    ul3.appendChild(topLi);
1239  }
1240  dest.appendChild(ul3);
1241
1242  const ul4 = document.createElement('ul');
1243
1244  const email = _stepValueToLi(values, 3, 'email');
1245  if (email) {
1246
1247    ul4.innerText = 'Communication via ';
1248    ul4.appendChild(email);
1249  }
1250  const adresse = _stepValueToLi(values, 3, 'adresse');
1251
1252  const tel = _stepValueToLi(values, 3, 'tel');
1253  if ([email, adresse, tel].filter((e) => e).length >= 2)
1254    ul4.innerHTML += adresse ? ', ' : ' et ';
1255
1256  if (tel) {
1257    ul4.appendChild(tel);
1258    if (adresse) {
1259      ul4.innerHTML += ' et par courrier : '
1260      ul4.appendChild(adresse);
1261    }
1262  }
1263
1264  dest.appendChild(ul4);
1265}
1266
1267function _stepValueToLi(values, stepNumber, key, genTextFn, prefix = 'steps', suffix = '') {
1268  let stepKey;
1269  if (Array.isArray(stepNumber))
1270    stepKey = `[${stepNumber.join('][')}][${key}]`;
1271  else stepKey = stepNumber != null ? `[${stepNumber}][${key}]` : key;
1272
1273  const value = values.getAll(`${prefix}${stepKey}${suffix}`);
1274
1275  if (value.length <= 0 || value.every((v) => v == ''))
1276    return null;
1277
1278  const li = document.createElement('li');
1279  li.classList.add(key.replace('[]', ''));
1280
1281  li.innerHTML = genTextFn ? genTextFn(value) : value;
1282  return li;
1283}
1284

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.