1/** 2 * Form Richiesta Info - Dynamic Filtering 3 * Gestisce il filtering dinamico tra sede e corsi 4 */ 5 6document.addEventListener('DOMContentLoaded', function() { 7 const sedeSelect = document.getElementById('sede'); 8 const corsoSelect = document.getElementById('corso'); 9 const tipoRichiestaSelect = document.getElementById('tipo_richiesta'); 10 const dataOrarioSelect = document.getElementById('data_orario'); 11 12 if (!sedeSelect || !corsoSelect || !tipoRichiestaSelect || !dataOrarioSelect) { 13 return; 14 } 15 16 // Snapshot delle option (clone) per poter ricostruire i select senza perdere data-* 17 const allCorsiOptions = Array.from(corsoSelect.options).map(function(opt) { 18 return opt.cloneNode(true); 19 }); 20 const allSediOptions = Array.from(sedeSelect.options).map(function(opt) { 21 return opt.cloneNode(true); 22 }); 23 24 // Salva le opzioni del tipo richiesta 25 const openDayOption = tipoRichiestaSelect.querySelector('option[value="open_day"]'); 26 const orientamentoOption = tipoRichiestaSelect.querySelector('option[value="orientamento"]'); 27 28 function parseIdList(attr) { 29 if (!attr) { 30 return []; 31 } 32 try { 33 const parsed = JSON.parse(attr); 34 if (!Array.isArray(parsed)) { 35 return []; 36 } 37 return parsed 38 .map(function(id) { return parseInt(id, 10); }) 39 .filter(function(id) { return !Number.isNaN(id); }); 40 } catch (e) { 41 console.error('Errore parsing ID list:', e); 42 return []; 43 } 44 } 45 46 function findOptionByValue(options, value) { 47 const valueStr = String(value || ''); 48 return options.find(function(opt) { return String(opt.value) === valueStr; }) || null; 49 } 50 51 // Helpers per formattare data/ora come in open-day-form.js 52 const monthsFallback = [ 53 'Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 54 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre' 55 ]; 56 57 function getMonths() { 58 const i18nMonths = (typeof iuadRichiestaInfo !== 'undefined' && iuadRichiestaInfo.i18n && iuadRichiestaInfo.i18n.months) ? iuadRichiestaInfo.i18n.months : null; 59 return (Array.isArray(i18nMonths) && i18nMonths.length === 12) ? i18nMonths : monthsFallback; 60 } 61 62 function formatDate(dateString) { 63 if (!dateString) return ''; 64 const date = new Date(dateString); 65 const day = date.getDate(); 66 const months = getMonths();
67 const month = months[date.getMonth()]; 68 const year = date.getFullYear(); 69 return `${day} ${month} ${year}`; 70 } 71 72 function formatTime(timeString) { 73 if (!timeString) return ''; 74 return timeString; // già in formato HH:MM 75 } 76 77 function resolveOpenDayDateBucket(sedeOption) { 78 if (!sedeOption || !sedeOption.value) { 79 return null; 80 } 81 const fromAttr = sedeOption.getAttribute('data-open-day-dates-key'); 82 if (fromAttr === 'milano' || fromAttr === 'napoli') { 83 return fromAttr; 84 } 85 const label = String(sedeOption.textContent || '').toLowerCase(); 86 if (label.includes('milano') || label.includes('milan')) { 87 return 'milano'; 88 } 89 if (label.includes('napoli') || label.includes('naples')) { 90 return 'napoli'; 91 } 92 return null; 93 } 94 95 function openDayTipoLabelFromCardType(type, isEnglish) { 96 const i18n = (typeof iuadRichiestaInfo !== 'undefined' && iuadRichiestaInfo.i18n) ? iuadRichiestaInfo.i18n : {}; 97 let label; 98 switch (type) { 99 case 'online': 100 label = i18n.openDayTipoOnline || 'Online'; 101 break; 102 case 'in_sede_e_online': 103 label = i18n.openDayTipoInSedeEOnline || 'In sede e Online'; 104 break; 105 case 'in_sede': 106 default: 107 label = i18n.openDayTipoInSede || 'In sede'; 108 break; 109 } 110 if (isEnglish) { 111 const suffix = i18n.openDayTipoInEnglishSuffix || ', in inglese'; 112 label += suffix; 113 } 114 return label; 115 } 116 117 /** 118 * Filtra i corsi in base alla sede selezionata e al tipo richiesta: 119 * - orientamento: corsi per sede (senza vincoli sulle date Open Day) 120 * - open_day: solo corsi con data-has-openday="1" e (se sede Milano/Napoli) almeno 1 data per quella sede 121 */ 122 function filterCorsi(sedeId, tipoRichiesta) { 123 // Rimuovi tutte le opzioni tranne la prima (placeholder) 124 while (corsoSelect.options.length > 1) { 125 corsoSelect.remove(1); 126 } 127 128 const mustHaveOpenDay = (tipoRichiesta === 'open_day'); 129 const shouldCheckDatesForSede = mustHaveOpenDay; // le date contano solo per Open Day 130 131 const sedeOption = sedeId ? sedeSelect.querySelector(`option[value="${sedeId}"]`) : null; 132 const dateBucket = resolveOpenDayDateBucket(sedeOption); 133 const requireMilanoDates = shouldCheckDatesForSede && dateBucket === 'milano'; 134 const requireNapoliDates = shouldCheckDatesForSede && dateBucket === 'napoli'; 135 136 // Filtra e aggiungi corsi 137 allCorsiOptions.forEach((option, index) => { 138 if (index === 0) return; // Skip placeholder 139 140 if (mustHaveOpenDay && option.getAttribute('data-has-openday') !== '1') { 141 return; 142 } 143 144 // Se nessuna sede è selezionata, includi (eventualmente filtrato per Open Day) 145 if (!sedeId) { 146 corsoSelect.add(option.cloneNode(true)); 147 return; 148 } 149 150 const sediArray = parseIdList(option.getAttribute('data-sedi')); 151 if (sediArray.includes(parseInt(sedeId, 10))) { 152 // Se stiamo filtrando Open Day per Milano/Napoli, includi solo corsi con almeno 1 data per quella sede 153 if (requireMilanoDates || requireNapoliDates) { 154 const dateAttr = requireMilanoDates 155 ? option.getAttribute('data-date-milano') 156 : option.getAttribute('data-date-napoli'); 157 158 let dateArray = []; 159 if (dateAttr) { 160 try { 161 dateArray = JSON.parse(dateAttr); 162 } catch (e) { 163 dateArray = []; 164 } 165 } 166 167 if (Array.isArray(dateArray) && dateArray.length > 0) { 168 corsoSelect.add(option.cloneNode(true)); 169 } 170 } else { 171 corsoSelect.add(option.cloneNode(true)); 172 } 173 } 174 }); 175 176 // Se non ci sono corsi per questa sede, mostra un messaggio 177 if (corsoSelect.options.length === 1) { 178 const noCorsiOption = document.createElement('option'); 179 const i18n = (typeof iuadRichiestaInfo !== 'undefined' && iuadRichiestaInfo.i18n) ? iuadRichiestaInfo.i18n : {}; 180 const fallbackNoCourses = 'Nessun corso disponibile per questa sede'; 181 const fallbackNoOpenDayCourses = 'Nessun corso con Open Day disponibile per questa sede'; 182 noCorsiOption.textContent = mustHaveOpenDay
183 ? (i18n.noOpenDayCoursesForSede || fallbackNoOpenDayCourses) 184 : (i18n.noCoursesForSede || fallbackNoCourses); 185 noCorsiOption.disabled = true; 186 corsoSelect.add(noCorsiOption); 187 } 188 } 189 190 /** 191 * Filtra le sedi in base al corso selezionato (data-sedi). 192 * Se nessuna sede corrente è valida, seleziona automaticamente quando possibile. 193 */ 194 function filterSedi(corsoId) { 195 const currentSedeId = sedeSelect.value; 196 197 while (sedeSelect.options.length > 1) { 198 sedeSelect.remove(1); 199 } 200 201 if (!corsoId) { 202 allSediOptions.forEach(function(option, index) { 203 if (index === 0) return; 204 sedeSelect.add(option.cloneNode(true)); 205 }); 206 if (currentSedeId && Array.from(sedeSelect.options).some(function(opt) { 207 return String(opt.value) === String(currentSedeId); 208 })) { 209 sedeSelect.value = currentSedeId; 210 } 211 return; 212 } 213 214 const corsoOption = findOptionByValue(allCorsiOptions, corsoId); 215 if (!corsoOption) { 216 return; 217 } 218 219 const sediArray = parseIdList(corsoOption.getAttribute('data-sedi')); 220 allSediOptions.forEach(function(option, index) { 221 if (index === 0) return; 222 if (sediArray.includes(parseInt(option.value, 10))) { 223 sedeSelect.add(option.cloneNode(true)); 224 } 225 }); 226 227 if (sediArray.length === 1) { 228 sedeSelect.value = String(sediArray[0]); 229 } else if (sediArray.includes(parseInt(currentSedeId, 10))) { 230 sedeSelect.value = currentSedeId; 231 } else if (sediArray.length > 1) { 232 sedeSelect.value = String(sediArray[0]); 233 } else { 234 sedeSelect.value = ''; 235 } 236 } 237 238 /** 239 * @deprecated mantenuto come alias per chiarezza nei call site legacy 240 */ 241 function updateSedeByCorso(corsoId) { 242 filterSedi(corsoId); 243 } 244 245 /** 246 * Aggiorna il campo "Data e orario" in base al corso, alla sede e al tipo di richiesta 247 */ 248 function updateDataOrarioByCorsoAndSede(corsoId, sedeId, tipoRichiesta) { 249 // Rimuovi tutte le opzioni tranne il placeholder 250 while (dataOrarioSelect.options.length > 1) { 251 dataOrarioSelect.remove(1); 252 } 253 254 // Se il tipo richiesta è "orientamento", mostra solo "Orientamento personalizzato" 255 if (tipoRichiesta === 'orientamento') { 256 const orientamentoOpt = document.createElement('option'); 257 orientamentoOpt.value = 'orientamento_personalizzato'; 258 orientamentoOpt.textContent = (typeof iuadRichiestaInfo !== 'undefined' && iuadRichiestaInfo.i18n && iuadRichiestaInfo.i18n.daDefinire) 259 ? iuadRichiestaInfo.i18n.daDefinire 260 : 'Da definire'; 261 dataOrarioSelect.add(orientamentoOpt); 262 dataOrarioSelect.value = 'orientamento_personalizzato'; 263 return; 264 } 265 266 if (!corsoId || !sedeId) { 267 // Se non ci sono corso o sede selezionati, mostra solo orientamento personalizzato 268 const orientamentoOpt = document.createElement('option'); 269 orientamentoOpt.value = 'orientamento_personalizzato'; 270 orientamentoOpt.textContent = (typeof iuadRichiestaInfo !== 'undefined' && iuadRichiestaInfo.i18n && iuadRichiestaInfo.i18n.daDefinire) 271 ? iuadRichiestaInfo.i18n.daDefinire 272 : 'Da definire'; 273 dataOrarioSelect.add(orientamentoOpt); 274 dataOrarioSelect.value = 'orientamento_personalizzato'; 275 return; 276 } 277 278 // Trova l'opzione del corso selezionato 279 const corsoOption = findOptionByValue(allCorsiOptions, corsoId); 280 if (!corsoOption) return; 281 282 const sedeOption = sedeSelect.querySelector(`option[value="${sedeId}"]`); 283 const dateBucket = resolveOpenDayDateBucket(sedeOption); 284 285 let dateData = null; 286 if (dateBucket === 'milano') { 287 dateData = corsoOption.getAttribute('data-date-milano'); 288 } else if (dateBucket === 'napoli') { 289 dateData = corsoOption.getAttribute('data-date-napoli'); 290 } 291 292 let dateArray = []; 293 if (dateData) { 294 try { 295 dateArray = JSON.parse(dateData); 296 } catch (e) { 297 console.error('Errore parsing date corso:', e); 298 } 299 } 300 301 // Se ci sono date disponibili, aggiungile 302 if (dateArray && dateArray.length > 0) { 303 dateArray.forEach(dateObj => { 304 const option = document.createElement('option'); 305 306 // Formatta data/ora come open-day-form.js 307 const dataFormatted = formatDate(dateObj.data);
308 const oraFormatted = formatTime(dateObj.ora); 309 310 const valueParts = [dateObj.tipo, dateObj.data]; 311 if (oraFormatted) valueParts.push(dateObj.ora); 312 313 let text = `${dateObj.tipo} - ${dataFormatted}`; 314 if (oraFormatted) text += ` - ${oraFormatted}`; 315 316 option.value = valueParts.join('_'); 317 option.textContent = text; 318 dataOrarioSelect.add(option); 319 }); 320 } else { 321 // Se non ci sono date, mostra "Orientamento personalizzato" 322 const orientamentoOpt = document.createElement('option'); 323 orientamentoOpt.value = 'orientamento_personalizzato'; 324 orientamentoOpt.textContent = (typeof iuadRichiestaInfo !== 'undefined' && iuadRichiestaInfo.i18n && iuadRichiestaInfo.i18n.daDefinire) 325 ? iuadRichiestaInfo.i18n.daDefinire 326 : 'Da definire'; 327 dataOrarioSelect.add(orientamentoOpt); 328 dataOrarioSelect.value = 'orientamento_personalizzato'; 329 } 330 } 331 332 /** 333 * Aggiorna il campo "Per cosa vuoi iscriverti" in base al corso selezionato 334 */ 335 function updateTipoRichiestaByCorso(corsoId) { 336 // Manteniamo sempre selezionabili Open Day / Orientamento. 337 // Il filtro dei corsi avviene in base a tipo_richiesta (vedi filterCorsi). 338 void corsoId; 339 } 340 341 /** 342 * Event listener per il cambio di sede 343 */ 344 sedeSelect.addEventListener('change', function() { 345 const sedeId = this.value; 346 const currentCorsoId = corsoSelect.value; 347 348 // Filtra i corsi in base alla sede selezionata 349 filterCorsi(sedeId, tipoRichiestaSelect.value); 350 351 // Se il corso corrente non è più disponibile, resettalo e ripristina tutte le sedi 352 const corsoStillAvailable = Array.from(corsoSelect.options).some(function(opt) { 353 return String(opt.value) === String(currentCorsoId); 354 }); 355 if (!corsoStillAvailable) { 356 corsoSelect.value = ''; 357 filterSedi(''); 358 if (sedeId) { 359 sedeSelect.value = sedeId; 360 } 361 } 362 363 // Aggiorna il campo data e orario 364 updateDataOrarioByCorsoAndSede(corsoSelect.value, sedeSelect.value, tipoRichiestaSelect.value); 365 }); 366 367 /** 368 * Event listener per il cambio di corso 369 */ 370 corsoSelect.addEventListener('change', function() { 371 const corsoId = this.value; 372 373 // Filtra le sedi associate al corso (e seleziona se possibile) 374 filterSedi(corsoId); 375 376 const sedeId = sedeSelect.value; 377 updateTipoRichiestaByCorso(corsoId); 378 379 // Allinea anche l'elenco corsi alla sede risultante, mantenendo la selezione corrente 380 filterCorsi(sedeId, tipoRichiestaSelect.value); 381 if (corsoId && Array.from(corsoSelect.options).some(function(opt) { 382 return String(opt.value) === String(corsoId); 383 })) { 384 corsoSelect.value = corsoId; 385 } 386 387 // Se l'utente ha già selezionato "Open Day" ma questo corso non lo supporta, 388 // non forziamo la select tipo_richiesta: deselezioniamo il corso e filtriamo l'elenco. 389 if (tipoRichiestaSelect.value === 'open_day' && corsoId) { 390 const corsoOption = findOptionByValue(allCorsiOptions, corsoId); 391 const hasOpenDay = corsoOption && corsoOption.getAttribute('data-has-openday') === '1'; 392 if (!hasOpenDay) { 393 corsoSelect.value = ''; 394 filterSedi(''); 395 filterCorsi(sedeSelect.value, 'open_day'); 396 } 397 } 398 updateDataOrarioByCorsoAndSede(corsoSelect.value, sedeSelect.value, tipoRichiestaSelect.value); 399 }); 400 401 /** 402 * Event listener per il cambio di tipo richiesta 403 */ 404 tipoRichiestaSelect.addEventListener('change', function() { 405 const tipoRichiesta = this.value; 406 const currentCorsoId = corsoSelect.value; 407 const sedeId = sedeSelect.value; 408 409 // Rifiltra i corsi in base al tipo richiesta (Open Day vs Orientamento) 410 filterCorsi(sedeId, tipoRichiesta); 411 412 const corsoStillAvailable = Array.from(corsoSelect.options).some(function(opt) { 413 return String(opt.value) === String(currentCorsoId); 414 }); 415 if (corsoStillAvailable) { 416 corsoSelect.value = currentCorsoId; 417 filterSedi(currentCorsoId); 418 } else { 419 corsoSelect.value = ''; 420 filterSedi(''); 421 if (sedeId) { 422 sedeSelect.value = sedeId; 423 } 424 } 425 426 const corsoId = corsoSelect.value; 427 updateDataOrarioByCorsoAndSede(corsoId, sedeSelect.value, tipoRichiesta); 428 }); 429 430 // Flag per evitare doppia inizializzazione quando arriviamo con parametri in query string 431 let prefilledFromOpenDayLink = false;
432 433 /** 434 * Pre-compilazione da URL (es. link dal dettaglio corso) 435 * Parametri supportati: 436 * - open_day_course: ID del corso 437 * - open_day_type: tipo richiesta (es. 'open_day') 438 */ 439 try { 440 const urlParams = new URLSearchParams(window.location.search); 441 const prefillCourseId = urlParams.get('open_day_course'); 442 const prefillType = urlParams.get('open_day_type'); 443 444 if (prefillCourseId) { 445 const corsoOptionExists = allCorsiOptions.some(function(opt) { 446 return String(opt.value) === String(prefillCourseId); 447 }); 448 if (corsoOptionExists) { 449 // Imposta il corso senza triggerare il cambio sede automatico 450 corsoSelect.value = prefillCourseId; 451 452 // Aggiorna le opzioni di tipo richiesta in base al corso 453 updateTipoRichiestaByCorso(prefillCourseId); 454 455 // Forza il tipo richiesta a Open Day se consentito 456 if (prefillType === 'open_day' && openDayOption) { 457 tipoRichiestaSelect.value = 'open_day'; 458 } 459 460 filterSedi(prefillCourseId); 461 filterCorsi(sedeSelect.value, tipoRichiestaSelect.value); 462 if (Array.from(corsoSelect.options).some(function(opt) { 463 return String(opt.value) === String(prefillCourseId); 464 })) { 465 corsoSelect.value = prefillCourseId; 466 } 467 468 // Aggiorna il campo Data/Orario in base a corso + sede corrente 469 updateDataOrarioByCorsoAndSede(prefillCourseId, sedeSelect.value, tipoRichiestaSelect.value); 470 471 prefilledFromOpenDayLink = true; 472 } 473 } 474 } catch (e) { 475 console.error('Errore nella lettura dei parametri Open Day dalla URL:', e); 476 } 477 478 // Inizializza il filtro al caricamento della pagina 479 // Se c'è un corso preselezionato (e non siamo arrivati con parametri Open Day), 480 // aggiorna la sede, il tipo richiesta e la data/orario di conseguenza 481 if (corsoSelect.value && !prefilledFromOpenDayLink) { 482 const selectedCorso = corsoSelect.value; 483 filterSedi(selectedCorso); 484 filterCorsi(sedeSelect.value, tipoRichiestaSelect.value); 485 if (Array.from(corsoSelect.options).some(function(opt) { 486 return String(opt.value) === String(selectedCorso); 487 })) { 488 corsoSelect.value = selectedCorso; 489 } 490 updateTipoRichiestaByCorso(selectedCorso); 491 updateDataOrarioByCorsoAndSede(corsoSelect.value, sedeSelect.value, tipoRichiestaSelect.value); 492 } else if (!prefilledFromOpenDayLink) { 493 filterCorsi(sedeSelect.value, tipoRichiestaSelect.value); 494 } 495 496 /** 497 * Pre-compilazione del form quando si clicca su una card dell'Open Day Grid 498 * - Tipo di richiesta: Open Day 499 * - Sede: sede del corso della card 500 * - Corso: corso della card 501 * - Data e orario: se disponibili sulla card, vengono preselezionati 502 * La select "Data e orario" viene comunque popolata dai listener esistenti. 503 */ 504 const openDayButtons = document.querySelectorAll('.open-day-grid__card-button'); 505 if (openDayButtons.length) { 506 openDayButtons.forEach(button => { 507 button.addEventListener('click', function () { 508 const corsoId = this.getAttribute('data-open-day-corso'); 509 const sedeId = this.getAttribute('data-open-day-sede'); 510 const dateRaw = this.getAttribute('data-open-day-date'); 511 const timeRaw = this.getAttribute('data-open-day-time'); 512 const type = this.getAttribute('data-open-day-type'); 513 const isEnglish = this.getAttribute('data-open-day-english') === '1'; 514 515 // Imposta prima la sede (così il filtro corsi si aggiorna correttamente) 516 if (sedeId) { 517 sedeSelect.value = sedeId; 518 sedeSelect.dispatchEvent(new Event('change', { bubbles: true })); 519 } 520 521 // Poi imposta il corso (dopo che le opzioni sono state filtrate per sede) 522 if (corsoId) { 523 // Verifica che l'opzione corso esista nel select corrente 524 const corsoOptionExists = Array.from(corsoSelect.options).some(opt => opt.value === corsoId); 525 if (corsoOptionExists) { 526 corsoSelect.value = corsoId; 527 corsoSelect.dispatchEvent(new Event('change', { bubbles: true })); 528 } else { 529 // Fallback: imposta comunque il valore e lascia che la logica di change gestisca la coerenza 530 corsoSelect.value = corsoId; 531 corsoSelect.dispatchEvent(new Event('change', { bubbles: true })); 532 } 533 } 534 535 // Forza il tipo richiesta su "Open Day" 536 if (openDayOption) { 537 tipoRichiestaSelect.value = 'open_day'; 538 tipoRichiestaSelect.dispatchEvent(new Event('change', { bubbles: true })); 539 } 540 541 // Se abbiamo data/ora dalla card, prova a preselezionare la voce corrispondente 542 if (dateRaw) { 543 const tipoLabel = openDayTipoLabelFromCardType(type, isEnglish); 544 545 const valueParts = [tipoLabel, dateRaw]; 546 if (timeRaw) { 547 valueParts.push(timeRaw); 548 } 549 const targetValue = valueParts.join('_'); 550 551 // Piccolo delay per assicurarsi che le opzioni siano state appena rigenerate 552 setTimeout(() => { 553 const matchingOption = Array.from(dataOrarioSelect.options).find( 554 opt => opt.value === targetValue 555 ); 556 if (matchingOption) { 557 dataOrarioSelect.value = targetValue; 558 } 559 }, 50); 560 } 561 }); 562 }); 563 } 564}); 565 566/** 567 * Form Richiesta Info - AJAX Submit Handler 568 * Gestisce la submit del form in AJAX e mostra il messaggio di thank you 569 */ 570document.addEventListener('DOMContentLoaded', function() { 571 const form = document.getElementById('form-richiesta-info'); 572 if (!form) return; 573 574 const formWrapper = form.closest('.form_richiesta_info_form'); 575 const formContainer = form.closest('.form_richiesta_info_container'); 576 const titleDiv = formContainer ? formContainer.querySelector('.form_richiesta_info_title') : null; 577 const thankyouDiv = formWrapper ? formWrapper.querySelector('.form-richiesta-info-thankyou') : null; 578 const submitButton = form.querySelector('button[type="submit"]'); 579 580 if (!formWrapper || !thankyouDiv || !submitButton) return; 581 582 // Salva l'HTML originale del bottone all'inizializzazione 583 const originalButtonHTML = submitButton.innerHTML; 584 585 /** 586 * Resetta il form ai valori di default 587 */ 588 function resetForm() { 589 form.reset(); 590 591 // Ripristina completamente il bottone al suo stato originale 592 submitButton.disabled = false;
593 submitButton.innerHTML = originalButtonHTML; 594 595 // Se ci sono select custom, potrebbe essere necessario reinizializzarli 596 // Ad esempio se usi Select2 o altre librerie 597 const customSelects = form.querySelectorAll('.select-custom'); 598 customSelects.forEach(select => { 599 select.value = ''; 600 // Se usi Select2, potresti dover chiamare select2('destroy') e reinizializzarlo 601 if (typeof jQuery !== 'undefined' && jQuery(select).hasClass('select2-hidden-accessible')) { 602 jQuery(select).val(null).trigger('change'); 603 } 604 }); 605 606 thankyouDiv.style.display = 'none'; 607 form.style.removeProperty('display'); 608 if (titleDiv) { 609 titleDiv.style.removeProperty('display'); 610 } 611 } 612 613 /** 614 * Mostra il messaggio di thank you e nasconde il form 615 */ 616 function showThankYou() { 617 form.style.display = 'none'; 618 if (titleDiv) { 619 titleDiv.style.display = 'none'; 620 } 621 thankyouDiv.style.display = 'block'; 622 } 623 624 // Event listener per il bottone "Nuova richiesta" 625 const newRequestBtn = thankyouDiv.querySelector('.btn-new-request'); 626 if (newRequestBtn) { 627 newRequestBtn.addEventListener('click', function(e) { 628 e.preventDefault(); 629 resetForm(); 630 }); 631 } 632 633 // Funzione helper per l'invio AJAX del form 634 function submitFormAjax(formData, originalButtonHTML) { 635 const ajaxUrl = (typeof iuadRichiestaInfo !== 'undefined' && iuadRichiestaInfo.ajaxurl) 636 ? iuadRichiestaInfo.ajaxurl 637 : form.action; 638 639 fetch(ajaxUrl, { 640 method: 'POST', 641 body: formData, 642 credentials: 'same-origin' 643 }) 644 .then(function(response) { 645 const contentType = response.headers.get('content-type') || ''; 646 if (!response.ok || contentType.indexOf('application/json') === -1) { 647 const httpError = new Error('HTTP ' + response.status); 648 httpError.status = response.status; 649 throw httpError; 650 } 651 return response.json(); 652 }) 653 .then(data => { 654 if (data.success) { 655 // Google Ads conversione: Invio modulo da sito web 656 if (typeof gtag === 'function') { 657 try { 658 gtag('event', 'conversion', { 659 'send_to': 'AW-1001604186/uFRTCIf4hoMcENqIzd0D', 660 'value': 1.0, 661 'currency': 'EUR' 662 }); 663 } catch (e) { 664 // Silenzia eventuali errori JS legati al tracking 665 } 666 } 667 668 // Mostra il messaggio di thank you 669 showThankYou(); 670 } else { 671 // Mostra errore 672 const fallbackError = (typeof iuadRichiestaInfo !== 'undefined' && iuadRichiestaInfo.i18n && iuadRichiestaInfo.i18n.errorGeneric) 673 ? iuadRichiestaInfo.i18n.errorGeneric 674 : 'Si è verificato un errore. Riprova.'; 675 alert(data.data && data.data.message ? data.data.message : fallbackError); 676 677 // Riabilita il bottone submit 678 submitButton.disabled = false; 679 submitButton.innerHTML = originalButtonHTML; 680 } 681 }) 682 .catch(error => { 683 console.error('Errore AJAX:', error); 684 const fallbackError = (typeof iuadRichiestaInfo !== 'undefined' && iuadRichiestaInfo.i18n && iuadRichiestaInfo.i18n.errorGeneric) 685 ? iuadRichiestaInfo.i18n.errorGeneric 686 : 'Si è verificato un errore. Riprova.'; 687 alert(fallbackError); 688 689 // Riabilita il bottone submit 690 submitButton.disabled = false; 691 submitButton.innerHTML = originalButtonHTML; 692 }); 693 } 694 695 // Gestione submit del form 696 form.addEventListener('submit', function(e) { 697 e.preventDefault(); 698 699 // Salva l'HTML originale del bottone per poterlo ripristinare 700 const originalButtonHTML = submitButton ? submitButton.innerHTML : ''; 701 702 // Disabilita il bottone submit per prevenire doppie submission 703 if (submitButton) { 704 submitButton.disabled = true; 705 const submittingLabel = (typeof iuadRichiestaInfo !== 'undefined' && iuadRichiestaInfo.i18n && iuadRichiestaInfo.i18n.submitting) 706 ? iuadRichiestaInfo.i18n.submitting 707 : 'Invio in corso...'; 708 submitButton.innerHTML = submittingLabel; 709 } 710
711 // Prepara i dati del form 712 const formData = new FormData(form); 713 714 // Aggiungi action per AJAX (fallback, nel caso mancasse l'hidden nel markup) 715 formData.append('action', 'submit_richiesta_info'); 716 717 // Integrazione reCAPTCHA v3 lato client 718 const hasRecaptchaConfig = ( 719 typeof iuadRichiestaInfo !== 'undefined' && 720 iuadRichiestaInfo.recaptchaSiteKey && 721 typeof grecaptcha !== 'undefined' 722 ); 723 724 if (hasRecaptchaConfig) { 725 const siteKey = iuadRichiestaInfo.recaptchaSiteKey; 726 const action = iuadRichiestaInfo.recaptchaAction || 'richiesta_info'; 727 const tokenInput = form.querySelector('#g-recaptcha-response'); 728 729 grecaptcha.ready(function() { 730 grecaptcha.execute(siteKey, { action: action }).then(function(token) { 731 if (tokenInput) { 732 tokenInput.value = token; 733 } 734 formData.set('g-recaptcha-response', token); 735 submitFormAjax(formData, originalButtonHTML); 736 }).catch(function() { 737 // In caso di errore nel recupero del token, procediamo comunque senza bloccare l'invio 738 submitFormAjax(formData, originalButtonHTML); 739 }); 740 }); 741 } else { 742 // Nessuna configurazione reCAPTCHA: procediamo con l'invio normale 743 submitFormAjax(formData, originalButtonHTML); 744 } 745 }); 746}); 747
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.