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.