1/************************************************ 2 #Branded Pages 3 ************************************************/ 4var smsVerificationId = null; 5var firstPageLoad = true; 6 7jQuery(function (e) { 8 e.datepicker.regional.nl = 9 { 10 closeText: "Sluiten", 11 prevText: "Vorige", 12 nextText: "Volgende", 13 currentText: "Vandaag", 14 monthNames: ["januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december"], 15 monthNamesShort: ["jan", "feb", "maa", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec"], 16 dayNames: ["zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag"], 17 dayNamesShort: ["zon", "maa", "din", "woe", "don", "vri", "zat"], 18 dayNamesMin: ["zo", "ma", "di", "wo", "do", "vr", "za"], 19 weekHeader: "Wk", 20 dateFormat: "dd/mm/yy", 21 firstDay: 1, 22 isRTL: !1, 23 showMonthAfterYear: !1, yearSuffix: "" 24 }, 25 e.datepicker.regional.en = 26 { 27 closeText: "Done", 28 prevText: "Previous", 29 nextText: "Next", 30 currentText: "Today", 31 monthNames: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "
31November", "December"], 32 monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], 33 dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], 34 dayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], 35 dayNamesMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], 36 weekHeader: "Wk", 37 dateFormat: "dd/mm/yy", 38 firstDay: 1, 39 isRTL: !1, 40 showMonthAfterYear: !1, 41 yearSuffix: "" 42 }, 43 e.datepicker.regional.fr = 44 { 45 closeText: "Fermer", 46 prevText: "Précédent", 47 nextText: "Suivant", 48 currentText: "Aujourd'hui", 49 monthNames: ["janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"], 50 monthNamesShort: ["janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc."], 51 dayNames: ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"], 52 dayNamesShort: ["dim.", "lun.", "mar.", "mer.", "jeu.", "ven.", "sam."], 53 dayNamesMin: ["D", "L", "M", "M", "J", "V", "S"], 54 weekHeader: "Sem.", 55 dateFormat: "dd/mm/yy", 56 firstDay: 1, 57 isRTL: !1, 58 showMonthAfterYear: !1, 59 yearSuffix: "" 60 }, 61 e.datepicker.regional.de = 62 { 63 closeText: "SchlieÃen", 64 prevText: "<Zurück", 65 nextText: "Vor>", 66 currentText: "Heute", 67 monthNames: ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"], 68 monthNamesShort: ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"], 69 dayNames: ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"], 70 dayNamesShort: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"], 71 dayNamesMin: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"], 72 weekHeader: "KW", 73 dateFormat: "dd/mm/yy", 74 firstDay: 1, 75 isRTL: !1, 76 showMonthAfterYear: !1, 77 yearSuffix: "" 78 }, 79 e.datepicker.regional.es = 80 { 81 closeText: "Cerrar", 82 prevText: "<Ant", 83 nextText: "Sig>", 84 currentText: "Hoy", 85 monthNames: ["enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"], 86 monthNamesShort: ["ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sep", "oct", "nov", "dic"], 87 dayNames: ["domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado"], 88 dayNamesShort: ["dom", "lun", "mar", "mié", "jue", "vie", "sáb"], 89 dayNamesMin: ["D", "L", "M", "X", "J", "V", "S"], 90 weekHeader: "Sm", 91 dateFormat: "dd/mm/yy", 92 firstDay: 1, 93 isRTL: false, 94 showMonthAfterYear: false, 95 yearSuffix: "" 96 }, 97 e.datepicker.regional.it = 98 { 99 closeText: "Chiudi", 100 prevText: "<Prec", 101 nextText: "Succ>", 102 currentText: "Oggi", 103 monthNames: ["Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno", "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre"], 104 monthNamesShort: ["Gen", "Feb", "Mar", "Apr", "Mag", "Giu", "Lug", "Ago", "Set", "Ott", "Nov", "Dic"], 105 dayNames: ["Domenica", "Lunedì", "Martedì", "Mercoledì", "Giovedì", "Venerdì", "Sabato"], 106 dayNamesShort: ["Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab"], 107 dayNamesMin: ["Do", "Lu", "Ma", "Me", "Gi", "Ve", "Sa"], 108 weekHeader: "Sm", 109 dateFormat: "dd/mm/yy", 110 firstDay: 1, 111 isRTL: false, 112 showMonthAfterYear: false, 113 yearSuffix: "" 114 }, 115 e.datepicker.regional.ca = 116 { 117 closeText: "Tanca", 118 prevText: "Anterior", 119 nextText: "Següent", 120 currentText: "Avui", 121 monthNames: ["gener", "febrer", "març", "abril", "maig", "juny", "juliol", "agost", "setembre", "octubre", "novembre", "desembre"], 122 monthNamesShort: ["gen", "feb", "març", "abr", "maig", "juny", "jul", "ag", "set", "oct", "nov", "des"], 123 dayNames: ["diumenge", "dilluns", "dimarts", "dimecres", "dijous", "divendres", "dissabte"], 124 dayNamesShort: ["dg", "dl", "dt", "dc", "dj", "dv", "ds"], 125 dayNamesMin: ["dg", "dl", "dt", "dc", "dj", "dv", "ds"], 126 weekHeader: "Set", 127 dateFormat: "dd/mm/yy", 128 firstDay: 1, 129 isRTL: false, 130 showMonthAfterYear: false, 131 yearSuffix: "" 132 }, 133 e.datepicker.regional.ru = 134 { 135 closeText: "ÐакÑÑÑÑ", 136 prevText: "<ÐÑед", 137 nextText: "След>", 138 currentText: "СегоднÑ", 139 monthNames: ["ЯнваÑÑ", "ФевÑалÑ", "ÐаÑÑ", "ÐпÑелÑ", "Ðай", "ÐÑнÑ", "ÐÑлÑ", "ÐвгÑÑÑ", "СенÑÑбÑÑ", "ÐкÑÑбÑÑ", "ÐоÑбÑÑ", "ÐекабÑÑ"], 140 monthNamesShort: ["Янв", "Фев", "ÐаÑ", "ÐпÑ", "Ðай", "ÐÑн", "ÐÑл", "Ðвг", "Сен", "ÐкÑ", "ÐоÑ", "Ðек"], 141 dayNames: ["воÑкÑеÑенÑе", "понеделÑник", "вÑоÑник", "ÑÑеда", "ÑеÑвеÑг", "пÑÑниÑа", "ÑÑббоÑа"], 142 dayNamesShort: ["вÑк", "пнд", "вÑÑ", "ÑÑд", "ÑÑв", "пÑн", "ÑбÑ"], 143 dayNamesMin: ["ÐÑ", "Ðн", "ÐÑ", "СÑ", "ЧÑ", "ÐÑ", "Сб"], 144 weekHeader: "Ðед", 145 dateFormat: "dd.mm.yy", 146 firstDay: 1, 147 isRTL: false, 148 showMonthAfterYear: false, 149 yearSuffix: "" 150 }, 151 e.datepicker.regional.zh = 152 { 153 closeText: "å ³é", 154 prevText: "<䏿", 155 nextText: "䏿>", 156 currentText: "ä»å¤©", 157 monthNames: ["䏿", "äºæ", "䏿", "åæ", "äºæ", "å æ", "䏿", "å «æ", "乿", "åæ", "å䏿", "åäºæ"], 158 monthNamesShort: ["䏿", "äºæ", "䏿", "åæ", "äºæ", "å æ", "䏿", "å «æ", "乿", "åæ", "å䏿", "åäºæ"], 159 dayNames: ["æææ¥", "ææä¸", "ææäº", "ææä¸", "ææå", "ææäº", "ææå "], 160 dayNamesShort: ["卿¥", "å¨ä¸", "å¨äº", "å¨ä¸", "å¨å", "å¨äº", "å¨å "], 161 dayNamesMin: ["æ¥", "ä¸", "äº", "ä¸", "å", "äº", "å "], 162 weekHeader: "å¨", 163 dateFormat: "yy-mm-dd", 164 firstDay: 1, 165 isRTL: false, 166 showMonthAfterYear: true, 167 yearSuffix: "å¹´" 168 }, 169 e.datepicker.regional.pt = { 170 closeText: "Fechar", 171 prevText: "Anterior", 172 nextText: "Seguinte", 173 currentText: "Hoje", 174 monthNames: ["Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", 175 "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"], 176 monthNamesShort: ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"], 177 dayNames: ["Domingo", "Segunda-feira", "Terça-feira", "Quarta-feira", "Quinta-feira", "Sexta-feira", "SÃ
177¡bado"], 178 dayNamesShort: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"], 179 dayNamesMin: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"], 180 weekHeader: "Sem", 181 dateFormat: "dd/mm/yy", 182 firstDay: 1, 183 isRTL: false, 184 showMonthAfterYear: false, 185 yearSuffix: "" 186 }, 187 e.datepicker.regional.pl = { 188 closeText: "Zamknij", 189 prevText: "<Poprzedni", 190 nextText: "NastÄpny>", 191 currentText: "DziÅ", 192 monthNames: ["StyczeÅ", "Luty", "Marzec", "KwiecieÅ", "Maj", "Czerwiec", "Lipiec", "SierpieÅ", "WrzesieÅ", "Październik", "Listopad", "GrudzieÅ"], 193 monthNamesShort: ["Sty", "Lu", "Mar", "Kw", "Maj", "Cze", 194 "Lip", "Sie", "Wrz", "Pa", "Lis", "Gru"], 195 dayNames: ["Niedziela", "PoniedziaÅek", "Wtorek", "Åroda", "Czwartek", "PiÄ tek", "Sobota"], 196 dayNamesShort: ["Nie", "Pn", "Wt", "År", "Czw", "Pt", "So"], 197 dayNamesMin: ["N", "Pn", "Wt", "År", "Cz", "Pt", "So"], 198 weekHeader: "Tydz", 199 dateFormat: "dd.mm.yy", 200 firstDay: 1, 201 isRTL: false, 202 showMonthAfterYear: false, 203 yearSuffix: "" 204 }, 205 e.datepicker.regional.ja = { 206 closeText: "éãã", 207 prevText: "<å", 208 nextText: "次>", 209 currentText: "仿¥", 210 monthNames: ["1æ", "2æ", "3æ", "4æ", "5æ", "6æ", "7æ", "8æ", "9æ", "10æ", "11æ", "12æ"], 211 monthNamesShort: ["1æ", "2æ", "3æ", "4æ", "5æ", "6æ", "7æ", "8æ", "9æ", "10æ", "11æ", "12æ"], 212 dayNames: ["æ¥ææ¥", "æææ¥", "ç«ææ¥", "æ°´ææ¥", "æ¨ææ¥", "éææ¥", "åææ¥"], 213 dayNamesShort: ["æ¥", "æ", "ç«", "æ°´", "æ¨", "é", "å"], 214 dayNamesMin: ["æ¥", "æ", "ç«", "æ°´", "æ¨", "é", "å"], 215 weekHeader: "é±", 216 dateFormat: "yy/mm/dd", 217 firstDay: 0, 218 isRTL: false, 219 showMonthAfterYear: true, 220 yearSuffix: "å¹´" 221 }; 222}); 223 224/************************************************ 225 Convert price 226 ************************************************/ 227function convertPrice(price) { 228 return price.toLocaleString(language, {minimumFractionDigits: 2, style: 'currency', currency: currency}); 229} 230 231/************************************************ 232 Fee breakdown entry helpers - shared by this file's own #cart-total-fee rendering 233 (updateFeeRow, below) and upsell.js's updateFeeBreakdown, so the label/subtotal 234 for a { title, fee, number } breakdown entry stays in one place regardless of 235 which page's DOM structure ends up rendering it. 236 ************************************************/ 237function feeBreakdownEntryLabel(entry) { 238 return entry.title + ' - ' + langTotalFee; 239} 240 241function feeBreakdownEntrySubtotal(entry) { 242 return entry.fee * entry.number; 243} 244 245/************************************************ 246 Personal Details 247 ************************************************/ 248const generalError = document.querySelector('.error-container[role="alertdialog"]'); 249if (generalError) { 250 generalError.setAttribute('tabindex', '0'); 251 generalError.focus(); 252} 253 254document.addEventListener("DOMContentLoaded", function () { 255 const firstError = document.querySelector('.error-input span'); 256 if (firstError) { 257 firstError.focus(); 258 } 259 260 document.querySelectorAll('input, select, textarea').forEach(element => { 261 element.addEventListener('input', function() { 262 const parentLi = this.closest('li'); 263 if (parentLi) { 264 this.classList.remove('error'); 265 266 const errorDiv = parentLi.querySelector('.error-input'); 267 if (errorDiv) errorDiv.style.display = 'none'; 268 } 269 }); 270 }); 271 272 document.querySelectorAll('ul[role="radiogroup"]').forEach(group => { 273 group.querySelectorAll('input[type="radio"], input[type="checkbox"]').forEach(radio => { 274 radio.addEventListener('change', function() { 275 group.querySelectorAll('input').forEach(input => input.classList.remove('error')); 276 const groupError = group.closest('li').querySelector('.error-input');
277 if (groupError) groupError.style.display = 'none'; 278 }); 279 }); 280 }); 281 282 setTimeout(function() { 283 // Get translations and elements 284 const translations = document.getElementById('country-code-translations'); 285 const selectionText = translations?.getAttribute('data-country-code-selection') || 'Select country code'; 286 const selectedText = translations?.getAttribute('data-country-code-selected') || 'Country code is selected'; 287 const countryList = document.querySelector('.iti__country-list'); 288 289 if (!countryList) return; 290 291 // Single function to handle all accessibility enhancements 292 function makeAccessible() { 293 // Make the list accessible 294 countryList.setAttribute('aria-label', selectionText); 295 countryList.setAttribute('title', selectionText); 296 297 // Make the flag accessible 298 const flag = document.querySelector('.iti__selected-flag'); 299 if (flag) { 300 const dialCode = flag.getAttribute('title')?.match(/:\s*(\+\d+)$/)?.[1] || ''; 301 flag.setAttribute('aria-label', selectionText + dialCode); 302 flag.setAttribute('title', selectionText + dialCode); 303 } 304 305 // Make each country option accessible 306 document.querySelectorAll('.iti__country').forEach(country => { 307 const dialCode = country.querySelector('.iti__dial-code')?.textContent || ''; 308 const isSelected = country.classList.contains('iti__highlight') || 309 country.classList.contains('iti__active'); 310 311 // Set appropriate text and attributes 312 country.setAttribute('aria-label', isSelected ? dialCode + selectedText : dialCode); 313 country.setAttribute('title', isSelected ? dialCode + selectedText : dialCode); 314 country.setAttribute('aria-selected', isSelected ? 'true' : 'false'); 315 316 // Hide redundant text from screen readers 317 country.querySelector('.iti__country-name')?.setAttribute('aria-hidden', 'true'); 318 }); 319 } 320 321 // Run initially 322 makeAccessible(); 323 324 // Watch for changes 325 new MutationObserver(makeAccessible).observe(countryList, { 326 attributes: true, attributeFilter: ['class'], subtree: true 327 }); 328 329 // Handle keyboard navigation 330 document.addEventListener('keydown', function(e) { 331 if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && 332 !countryList.classList.contains('iti__hide')) { 333 setTimeout(makeAccessible, 50); 334 } 335 }); 336 }, 500); 337 338}); 339 340/************************************************ 341 checkDateWithoutTimeslots 342 ************************************************/ 343function checkDateWithoutTimeslots(date) { 344 var date = new Date(date), 345 yy = date.getFullYear(), 346 mm = ('0' + (date.getMonth() + 1)).slice(-2), 347 dd = ('0' + date.getDate()).slice(-2), 348 weekday = date.getDay(), 349 dates = dateWithoutTimeslots['dates'] === undefined ? [] : dateWithoutTimeslots['dates'], 350 weekdays = dateWithoutTimeslots['weekdays'] === undefined ? [] : dateWithoutTimeslots['weekdays'], 351 period = dateWithoutTimeslots['period'] === undefined ? [] : dateWithoutTimeslots['period'], 352 validDates = false, 353 validWeekdays = false, 354 validPeriod = false; 355 356 if (dates.indexOf(yy + '-' + mm + '-' + dd) > -1) { 357 validDates = true; 358 } 359 360 if (weekdays.indexOf(weekday) > -1) { 361 validWeekdays = true; 362 } 363 364 if (period.length) { 365 period.forEach(function (period) { 366 var start = new Date(period.start), 367 end = new Date(period.end); 368 369 if (date.getTime() >= start.getTime() && date.getTime() <= end.getTime()) { 370 validPeriod = true; 371 } 372 }); 373 } 374 375 return (validDates || validWeekdays || validPeriod); 376} 377 378$(function () { 379 /************************************************ 380 navigation bar 381 ************************************************/ 382 // disable hyperlinks if it does not have class prev 383 $('.nav-bar a').on('click', function (e) { 384 if (!$(this).parent().hasClass('prev')) { 385 e.preventDefault(); 386 } 387 }); 388 389 /************************************************ 390 disable spacebar on input email 391 ************************************************/ 392 $('input[type="email"]').on('keydown', function (e) { 393 if (e.which === 32) { 394 return false;
395 } 396 }); 397 398 /************************************************ 399 Set product data in html 400 ************************************************/ 401 // number of selected products 402 function selectedVal(container) { 403 var total = 0; 404 var hasSubtotal = 'has-subtotal'; 405 406 container.find('.row').not('.show-all-rows').each(function () { 407 if ($(this).find('select').length > 0) { 408 var number = parseInt($(this).find('select').val()); 409 410 total += number; 411 } 412 }); 413 414 container.attr('data-quantity', total).data('quantity', total); 415 if (total > 0) { 416 container.addClass(hasSubtotal); 417 container.find('.last-row').show(); 418 showDonationSection(); 419 } else { 420 container.removeClass(hasSubtotal); 421 container.find('.last-row').hide(); 422 hideDonationSection(); 423 } 424 } 425 426 // calculate difference price 427 function differencePrice(container) { 428 var difference = 0; 429 430 container.find('.row').each(function () { 431 if ($(this).find('select').length > 0) { 432 var price = parseFloat($(this).find('select').data('difference')); 433 var number = parseFloat($(this).find('select').val()); 434 var rowPrice = price * number; 435 436 $(this).attr('data-row-difference', rowPrice).data('row-difference', rowPrice); 437 438 difference += rowPrice; 439 } 440 }); 441 442 return difference; 443 } 444 445 // calculate price per row 446 function subtotal(container) { 447 var subtotal = 0; 448 449 container.find('.row').not('.soldout, .show-all-rows').each(function () { 450 if ($(this).find('select').length > 0) { 451 var price = parseFloat($(this).find('select').data('price')); 452 var number = parseFloat($(this).find('select').val()); 453 var rowPrice = price * number; 454 455 $(this).attr('data-row-price', rowPrice).data('row-price', rowPrice); 456 $(this).find('.subtotal span').html(convertPrice(rowPrice)); 457 458 // show price if a product is selected 459 number > 0 ? $(this).find('.subtotal span').show() : $(this).find('.subtotal span').hide(); 460 461 subtotal += rowPrice; 462 } 463 }); 464 465 return subtotal; 466 } 467 468 function feeTotal(container) { 469 var totalFee = 0; 470 471 container.find('.row').not('.soldout, .show-all-rows, #cart-total-fee').each(function () { 472 if ($(this).find('select').length > 0) { 473 var fee = parseFloat($(this).find('select').data('fee')) || 0; 474 var number = parseFloat($(this).find('select').val()); 475 476 totalFee += fee * number; 477 } 478 }); 479 480 return totalFee; 481 } 482 483 function feeBreakdown(container) { 484 var breakdown = []; 485 486 container.find('.row').not('.soldout, .show-all-rows, #cart-total-fee').each(function () { 487 var select = $(this).find('select'); 488 489 if (select.length > 0) { 490 var fee = parseFloat(select.data('fee')) || 0; 491 var number = parseFloat(select.val()) || 0; 492 493 if (fee > 0 && number > 0) { 494 breakdown.push({ 495 title: select.data('title') || '', 496 fee: fee, 497 number: number 498 }); 499 } 500 } 501 }); 502 503 return breakdown; 504 } 505 506 function updateFeeRow(container, feeVal) { 507 var feeRow = container.find('#cart-total-fee'); 508 509 feeRow.empty(); 510 511 if (feeVal > 0) { 512 feeBreakdown(container).forEach(function (entry) { 513 $('<div>', {'class': 'wrap fee-row'}).append( 514 $('<span>').text(feeBreakdownEntryLabel(entry)), 515 $('<span>', {'class': 'fee-price'}).text(convertPrice(feeBreakdownEntrySubtotal(entry))) 516 ).appendTo(feeRow); 517 }); 518 519 feeRow.show(); 520 } else { 521 feeRow.hide(); 522 } 523 } 524 525 // calculate discount 526 function discountData(container) { 527 var inputCode = $('#input-code'); 528 529 if (inputCode.length == 0) { 530 return; 531 } 532 533 var discountCode = inputCode.val().trim(); 534 535 var tickets = {}; 536 var ticketEvents = $('html').hasClass('ticketEvents'); 537 var hasDiscount = 'has-discount'; 538 539 container.find('.selectbox select[name^="tickets"], .selectbox select[name^="subscription"]').each(function () { 540 var id = $(this).attr('id'); 541 id = id.replace('tickettype_', ''); 542 id = id.replace('subscriptiontype_', ''); 543 544 tickets[id] = parseInt($(this).val()); 545 }); 546 547 /* Check if the input field has been filled in */ 548 if (discountCode.length > 0) { 549 /* ajax call */ 550 $.ajax({ 551 url: '/script/discount', 552 data: { 553 eventId: eventId, 554 discountCode: discountCode, 555 tickets: tickets, 556 ticketEvents: ticketEvents, 557 streetname: streetname 558 }, 559 cache: false, 560 dataType: 'json', 561 method: 'POST', 562 success: function (output) { 563 var totalFee = feeTotal(container); 564 565 if (output.success) { 566 var discountPrice = output.discountPrice, 567 subTotal = output.subTotal; 568 569 var total = (subTotal - discountPrice) + totalFee; 570 571 // hide error elements 572 $('.code-container, #discount-error').hide(); 573 $('#discount-error').attr('aria-hidden', 'true').hide(); 574 575 // show discount elements 576 $('.discount-container').show(); 577 $('.subtotal-price').show(); 578 $('#discount-success').show(); 579 580 // set values 581 $('.discount-id').html(discountCode); 582 $('.discount-val').html('- ' + convertPrice(discountPrice)); 583 $('#subtotal-price').html(convertPrice(subTotal)); 584 $('.total-price').html(convertPrice(total)); 585 ticketsTotalPrice = total; 586 587 updateFeeRow(container, totalFee); 588 589 var initialPrice = getInitialDonationPrice(); 590 591 calcDonation(initialPrice); 592 593 // has discount 594 container.addClass(hasDiscount); 595 } else { 596 $('.subtotal-price, .discount-container').hide(); 597 $('#discount-error').attr('aria-hidden', 'false').show(); 598 $('#discount-error, .code-container').show(); 599 $('#input-code').addClass('error'); 600 601 updateFeeRow(container, totalFee); 602 603 // no discount 604 container.removeClass(hasDiscount); 605 } 606 } 607 }); 608 } 609 } 610 611 612 /************************************************ 613 Total 614 ************************************************/ 615 // contain every calculating functions 616 function rowContainerFunctions() { 617 var totalPrice = 0; 618 619 $('.row-container').each(function () { 620 selectedVal($(this)); 621 622 var differenceVal = differencePrice($(this)); 623 var subtotalVal = subtotal($(this)); 624 var feeVal = feeTotal($(this)); 625 var discountVal = discountData($(this)); 626 var calcArray = []; 627 628 // check subtotal 629 if ($(this).hasClass('has-subtotal')) { 630 calcArray.push(subtotalVal); 631 } 632 // check discount 633 /* 634 if ($(this).hasClass('has-discount')) 635 { 636 calcArray.push(discountVal); 637 } 638 */ 639 640 // calculate total price 641 $.each(calcArray, function (i, val) { 642 totalPrice += val; 643 }); 644 645 differenceVal > 0 ? $(this).find('.difference-row').fadeIn() : $(this).find('.difference-row').fadeOut(); 646 $(this).find('.total-difference').html(convertPrice(differenceVal)); 647 648 if (!$(this).hasClass('has-discount')) { 649 if (feeVal > 0) { 650 totalPrice += feeVal; 651 } 652 653 $(this).find('.total-price').html(convertPrice(totalPrice)); 654 ticketsTotalPrice = totalPrice; 655 656 updateFeeRow($(this), feeVal); 657 658 var initialPrice = getInitialDonationPrice(); 659 660 calcDonation(initialPrice); 661 } 662 }); 663 } 664 665 // initiate functions 666 // select products 667 $('.row-container select').on('change selectmenuchange', function () { 668 var event = $(this).data('event'), 669 allowOne = $(this).closest('.row-container').hasClass('allow-one'), 670 ignoreEvent = $(this).closest('.row-container').hasClass('ignore-event'); 671 672 if (event != undefined && !ignoreEvent) { 673 var selects = $('.row-container select').not('[data-event="' + event + '"]'); 674 675 selects.val(0); 676 if (selects.hasClass('gt-desktop-select')) { 677 selects.selectmenu('refresh'); 678 } 679 } 680 681 if (allowOne) { 682 var selects = $('.row-container select').not($(this)); 683 684 selects.val(0); 685 686 if (selects.hasClass('gt-desktop-select')) { 687 selects.selectmenu('refresh'); 688 } 689 } 690 691 rowContainerFunctions(); 692 }); 693 694 $('.row-container').each(function () { 695 updateFeeRow($(this), feeTotal($(this))); 696 }); 697 698 //show more option 699 $('.row-container .show-all-rows').on('click', function (event) { 700 event.preventDefault(); 701 $(this).hide().closest('.row-container').find('.extra-row, .event-title').show(); 702 }) 703 $('.row-container .show-ticket-types-in-ticket-group').on('click', function (event) { 704 event.preventDefault(); 705 $(this).hide().prevUntil('.event-title').show(); 706 }) 707 708 // select discount 709 $('#submit-input-code').on('click', function (event) { 710 rowContainerFunctions() 711 event.preventDefault(); 712 713 setTimeout(() => { 714 const $error = $('#discount-error'); 715 if ($error.length > 0) { 716 const $errorSpan = $error.find('span'); 717 if ($errorSpan.length > 0 && $error.is(':visible')) { 718 const html = $errorSpan.html(); 719 if (html) { 720 const cleanText = html.replace(/<br>\n?/gi, ' '); 721 $errorSpan.attr('aria-label', cleanText); 722 $error.attr('aria-hidden', 'false').trigger('focus'); 723 } 724 } 725 } 726 }, 500); 727 }); 728 729 $('#input-code').on('keydown', function (event) { 730 var key = event.keyCode || event.which; 731 732 if (key === 13) { 733 rowContainerFunctions() 734 event.preventDefault(); 735 } 736 }); 737 738 /************************************************ 739 Row-container description 740 ************************************************/ 741 $('.col.type a').on('click', function (e) { 742 e.preventDefault(); 743 744 $(this).closest('.row').find('.description').toggle('fade'); 745 }); 746 747 /************************************************ 748 Discount code 749 ************************************************/ 750 $('#code-button').on('click', function (e) { 751 $(this).closest('.discount').addClass('active').find('input').focus(); 752 e.preventDefault(); 753 }); 754 755 /************************************************ 756 jQuery UI: Dialogs 757 ************************************************/ 758 // Close dialog button 759 $(document).on('click', '.ui-widget-overlay.ui-front, .close-dialog-button, .ui-dialog, .close-dialog-tag', function (e) { 760 e.preventDefault(); 761 $('.ui-dialog-content').not('.disable-close-dialog').dialog('close'); 762 }); 763 764 $(document).on('click', '.dialog-content, .dialog-header, .dialog-footer', function (e) { 765 e.stopPropagation(); 766 }); 767 768 // faq dialog 769 $('#faq-dialog').dialog({ 770 autoOpen: false, 771 modal: true, 772 width: '100%', 773 resizable: false, 774 draggable: false, 775 show: {effect: "puff", duration: 300}, 776 hide: {effect: "puff", duration: 300} 777 }); 778 779 $('#faq-button, .faq-button, [href="#faq"]').not('.external').on('click', function (e) { 780 $('#faq-dialog').dialog('open'); 781 782 e.preventDefault(); 783 }); 784 785 $('#faq-dialog .dialog-wrapper').on('click', function (e) { 786 e.stopPropagation(); 787 }); 788 $('.close-dialog-button').on('click', function (e) { 789 $('#faq-dialog').dialog('close'); 790 e.preventDefault(); 791 }); 792 793 /***** .message-dialog *****/ 794 $('.message-dialog').dialog( 795 { 796 autoOpen: false, 797 modal: true, 798 draggable: false, 799 resizable: false, 800 width: '100%', 801 }); 802 803 /***** [data-dialog] *****/ 804 $('[data-dialog]').on('click', function (e) { 805 e.preventDefault(); 806 807 var dialog = $(this).data('dialog'); 808 809 $(dialog).dialog('open'); 810 }); 811 812 813 /************************************************ 814 Datepicker: Calendar 815 ************************************************/ 816 // datepicker localisation 817 $.datepicker.setDefaults($.datepicker.regional[language]); 818 819 function checkDisabledDates(calendarDate) { 820 calendarDate.setHours(0, 0, 0, 0); 821 822 var calendarWeekDay = calendarDate.getDay(); 823 824 var dateTitle = '', 825 dateClassAttr = ''; 826 827 if (datesTitle != undefined) { 828 var weekDays = datesTitle.weekDays, 829 dates = datesTitle.dates, 830 upcomingDays = datesTitle.upcomingDays; 831 832 dateTitle = datesTitle.default != undefined ? datesTitle.default.title : ''; 833 dateClassAttr = datesTitle.default != undefined ? datesTitle.default.classAttr : ''; 834
835 $.each(weekDays, function (i, data) { 836 var title = data.title != undefined && data.title.length > 0, 837 classAttr = data.classAttr != undefined && data.classAttr.length > 0; 838 839 if (calendarDate.getDay() == i) { 840 dateTitle = title; 841 dateClassAttr = classAttr; 842 } 843 }); 844 845 $.each(dates, function (i, data) { 846 var d = new Date(data.date), 847 title = data.title, 848 classAttr = data.classAttr; 849 850 d.setHours(0, 0, 0, 0); 851 calendarDate.setHours(0, 0, 0, 0); 852 853 if (d.getTime() == calendarDate.getTime()) { 854 dateTitle = title; 855 dateClassAttr = classAttr 856 } 857 }); 858 859 $.each(upcomingDays, function (i, data) { 860 var d = new Date(), 861 title = data.title, 862 classAttr = data.classAttr; 863 864 d.setDate(d.getDate() + i); 865 d.setHours(0, 0, 0, 0); 866 calendarDate.setHours(0, 0, 0, 0); 867 868 if (d.getTime() == calendarDate.getTime()) { 869 dateTitle = title; 870 dateClassAttr = classAttr 871 } 872 }); 873 } 874 875 var month = ("0" + (calendarDate.getMonth() + 1)).slice(-2); 876 877 var d = calendarDate.getFullYear() + '-' + month + '-' + ("0" + calendarDate.getDate()).slice(-2); 878 879 if (disabledDatesTitle === false && (ticketMinDate > calendarDate || ticketMaxDate < calendarDate)) { 880 return [false, 'disabled']; 881 } 882 883 // Disable the date is the date is marked as one of the closing days. 884 if(typeof disabledDates !== 'undefined') { 885 const year = calendarDate.getFullYear();
886 const month = String(calendarDate.getMonth() + 1).padStart(2, '0'); // Months are 0-based, so add 1 887 const day = String(calendarDate.getDate()).padStart(2, '0'); // Ensure day is always 2 digits 888 889 if(disabledDates.includes(`${year}-${month}-${day}`)) { 890 return [false, 'disabled']; 891 } 892 } 893 894 if ($.inArray(calendarDate.getDay(), disabledWeekDays) >= 0 || $.inArray(d, disabledDates) >= 0) { 895 if ($.inArray(d, openDates) < 0) { 896 return [false, 'disabled', (disabledDatesTitle === true ? dateTitle : '')]; 897 } 898 } 899 900 if ($.inArray(d, soldoutDates) >= 0) { 901 return [false, 'disabled soldout', '']; 902 } 903 904 let result = [true, dateClassAttr, dateTitle]; 905 906 if(typeof window.customCheckDisabledDatesEvent != 'undefined') { 907 result = window.customCheckDisabledDatesEvent(calendarDate, result); 908 } 909 910 return result; 911 } 912 913 var currentRequest = null; 914 915 // create datepicker inline (calendar) 916 $('.calendar').datepicker({ 917 altField: '#calendar-date', 918 beforeShowDay: checkDisabledDates, 919 dateFormat: 'yy-mm-dd', 920 firstDay: 1, 921 minDate: ticketMinDate, 922 maxDate: ticketMaxDate, 923 onSelect: function (ticketDate) { 924 $('.ui-datepicker').removeClass('no-default-date'); 925 926 // skip timeslots if the selected date has no timeslots 927 if ( 928 settingsTimeslots === false 929 || (typeof dateWithoutTimeslots !== 'undefined' && checkDateWithoutTimeslots(ticketDate)) 930 ) { 931 $('#timeslot-section .timeslots-container input:checked').prop('checked', false); 932 $('#timeslot-section').removeClass('completed').slide
932Up().find('.error-container').hide(); 933 if (hasTicketsSelected()) { 934 showNextButton(); 935 } 936 } else { 937 ticketDateChosen(ticketDate); 938 } 939 } 940 }); 941 942 // update calendar and get timeslots based on chosenTicketDate 943 function setDatepickerDate() { 944 $('.calendar').datepicker('setDate', null); 945 946 if (!selectDateOnPageLoad && !chosenTicketDate) { 947 $('.ui-datepicker') 948 .addClass('no-default-date') 949 .find('.ui-state-active, .ui-state-highlight, .ui-datepicker-current-day, .ui-state-hover') 950 .removeClass('ui-state-active ui-state-highlight ui-datepicker-current-day ui-state-hover'); 951 952 return; 953 } 954 955 const today = new Date(); 956 const yy = today.getFullYear(); 957 const mm = ('0' + (today.getMonth() + 1)).slice(-2); 958 const dd = ('0' + today.getDate()).slice(-2); 959 const ticketDate = yy + '-' + mm + '-' + dd; 960 961 var setDate = chosenTicketDate ? chosenTicketDate : ticketDate; 962 var dateIsValid = checkDisabledDates(new Date(setDate))[0]; 963 964 if (dateIsValid) { 965 $('.calendar').datepicker('setDate', setDate); 966 } 967 968 if (typeof dateWithoutTimeslots !== 'undefined' && checkDateWithoutTimeslots(chosenTicketDate)) { 969 $('#timeslot-section .timeslots-container input:checked').prop('checked', false); 970 $('#timeslot-section').removeClass('completed').hide(); 971 showNextButton(); 972 } else if (dateIsValid) { 973 ticketDateChosen(setDate); 974 } 975 } 976 977 if ( 978 page === 'date' 979 && !$('#gift-ticket').prop('checked') 980 && ( 981 chosenTicketDate !== false 982 || $('.calendar').datepicker('getDate') 983 ) 984 ) { 985 setDatepickerDate(); 986 } 987 988 // if only one date is available 989 if ( 990 page === 'date' 991 && !$('#gift-ticket').prop('checked') 992 && ticketMinDate.getTime() === ticketMaxDate.getTime() 993 ) { 994 var yy = ticketMinDate.getFullYear(), 995 mm = ('0' + (ticketMinDate.getMonth() + 1)).slice(-2), 996 dd = ('0' + ticketMinDate.getDate()).slice(-2), 997 ticketDate = yy + '-' + mm + '-' + dd; 998 999 $('.calendar').datepicker('setDate', ticketDate); 1000 $('#calendar-date').val(ticketDate); 1001 1002 ticketDateChosen(ticketDate); 1003 } 1004 1005 if (isAccessibleDatepicker) { 1006 const datepickerConfig = { 1007 inline: true, 1008 monthSelectorType: 'static', 1009 yearSelectorType: 'static', 1010 minDate: ticketMinDate, 1011 maxDate: ticketMaxDate, 1012 altFormat: 'l j F Y', 1013 ariaDateFormat: 'l j F Y', 1014 altInput: true, 1015 altInputClass: 'alt-datepicker', 1016 disable: [ 1017 function (calendarDate) { 1018 var calendarWeekDay = calendarDate.getDay(); 1019 var month = ("0" + (calendarDate.getMonth() + 1)).slice(-2); 1020 var d = calendarDate.getFullYear() + '-' + month + '-' + ("0" + calendarDate.getDate()).slice(-2); 1021 1022 if ($.inArray(calendarWeekDay, disabledWeekDays) >= 0 || $.inArray(d, disabledDates) >= 0) { 1023 if ($.inArray(d, openDates) < 0) { 1024 return true; 1025 } 1026 } 1027 1028 return false; 1029 } 1030 ], 1031 onChange: function (selectedDates, dateStr, instance) { 1032 const formattedDate = instance.formatDate(selectedDates[0], 'l j F Y'); 1033 ticketDateChosen(dateStr, formattedDate); 1034 }, 1035 1036 locale: { 1037 weekdays: { 1038 shorthand: ["zo", "ma", "di", "wo", "do", "vr", "za"], 1039 longhand: [ 1040 "zondag", 1041 "maandag", 1042 "dinsdag", 1043 "woensdag", 1044 "donderdag", 1045 "vrijdag", 1046 "zaterdag", 1047 ], 1048 }, 1049 1050 months: { 1051 shorthand: [ 1052 "jan", 1053 "feb", 1054 "mrt", 1055 "apr", 1056 "mei", 1057 "jun", 1058 "jul", 1059 "aug", 1060 "sept", 1061 "okt", 1062 "nov", 1063 "dec", 1064 ], 1065 longhand: [ 1066 "januari", 1067 "februari", 1068 "maart", 1069 "april", 1070 "mei", 1071 "juni", 1072 "juli", 1073 "augustus", 1074 "september", 1075 "oktober", 1076 "november", 1077 "december", 1078 ], 1079 }, 1080 1081 firstDayOfWeek: 1, 1082 weekAbbreviation: "wk", 1083 rangeSeparator: " t/m ", 1084 scrollTitle: "Scroll voor volgende / vorige", 1085 toggleTitle: "Klik om te wisselen", 1086 time_24hr: true, 1087 1088 ordinal: function (nth) { 1089 if (nth === 1 || nth === 8 || nth >= 20) return "ste"; 1090 1091 return "de"; 1092 }, 1093 } 1094 }; 1095
1096 $("#calendar-date").flatpickr(datepickerConfig); 1097 } 1098 1099 /************************************************ 1100 Personal Data page: Confirm email 1101 ************************************************/ 1102 1103 /* 1104 * function to create the email address confirmation placeholder 1105 */ 1106 function createEmailAddressPlaceholder() { 1107 /* variable */ 1108 var email = '#emailAddress'; 1109 var emailConfirm = '#emailAddressConfirm'; 1110 var emailAddress = $(email).val(); 1111 var emailAddressConfirm = $(emailConfirm).val(); 1112 var placeholderText = ''; 1113 1114 /* check for value */ 1115 if (emailAddress.length > 0) { 1116 var error = false; 1117 var emailAddressArray = emailAddress.split(''); 1118 var emailAddressConfirmArray = emailAddressConfirm.split(''); 1119 1120 /* placeholder */ 1121 placeholderText = LANG_REPEAT + ': ' + emailAddress; 1122 1123 /* compare email address length */ 1124 if (emailAddressConfirm.length > emailAddress.length) { 1125 error = true; 1126 } else { 1127 /* read array */ 1128 $.each(emailAddressArray, function (index, value) { 1129 if (emailAddressConfirmArray[index] && value != emailAddressConfirmArray[index]) { 1130 error = true; 1131 } 1132 }); 1133 } 1134 } 1135 1136 /* check if the email addresses do match */ 1137 if (error) { 1138 $(emailConfirm).addClass('error'); 1139 } else { 1140 $(emailConfirm).removeClass('error'); 1141 } 1142 } 1143 1144 /* 1145 * email address handlers 1146 */ 1147 $('#emailAddress, #emailAddressConfirm').on('keyup change', function () { 1148 createEmailAddressPlaceholder(); 1149 }); 1150 1151 /* 1152 * Disable paste 1153 */ 1154 $('#emailAddressConfirm').on('paste', function (e) { 1155 e.preventDefault(); 1156 }); 1157 1158 1159 /************************************************ 1160 Payment page: #more-payment 1161 ************************************************/ 1162 // display payment options 1163 $('#more-payments').on('click', function (e) { 1164 e.preventDefault(); 1165 $(this).fadeOut(); 1166 $('#payment-section li').fadeIn(); 1167 1168 const $paymentMethods = $('#payment-section li input'); 1169 1170 if($paymentMethods.length > 0) { 1171 $paymentMethods.first().focus(); 1172 } 1173 }); 1174 1175 /************************************************ 1176 selected paymentmethod 1177 ************************************************/ 1178 if (typeof CMPayment !== 'undefined' && CMPayment !== false) { 1179 $('#payment-section input').on('change', function () { 1180 var method = $(this).val(), 1181 isCMPayment = CMPayment.indexOf(method) > -1; 1182 1183 $('#payment-forms > div').removeClass('active'); 1184 1185 if ( 1186 isCMPayment === false 1187 || $('#' + method + '-form').length === 0 1188 ) { 1189 $('#payment-forms').removeClass('active required'); 1190 return; 1191 } 1192 1193 $('#payment-forms').addClass('active required'); 1194 $('#' + method + '-form').addClass('active'); 1195 }); 1196 1197 /***** #ideal-form *****/ 1198 $('#ideal-buttons input').on('change', function () { 1199 var value = $(this).attr('id'); 1200 1201 $('#ideal-banks').val(value); 1202 1203 if ($('#ideal-banks').hasClass('gt-desktop-select')) { 1204 $('#ideal-banks').selectmenu('refresh'); 1205 } 1206 1207 $('#payment-forms').addClass('completed'); 1208 displayNextButton(); 1209 }); 1210 1211 $('#ideal-banks').on('change selectmenuchange', function () { 1212 var value = $(this).val(), 1213 bank = $('#' + value); 1214 1215 if (bank.length === 0) { 1216 $('#ideal-buttons input:checked').prop('checked', false); 1217 } else { 1218 bank.prop('checked', true); 1219 $('#payment-forms').addClass('completed'); 1220 displayNextButton(); 1221 } 1222 }); 1223 } 1224 1225 displayNextButton(); 1226 1227 /************************************************ 1228 Ticket page: next button 1229 ************************************************/
1230 // return true or false if a ticket is selected 1231 function ticketCondition() { 1232 var section = $('#tickets-section'); 1233 var tickets = section.find('.row-container select').filter(function () { 1234 return $(this).val() > 0; 1235 }).length > 0; 1236 var condition = false; 1237 1238 condition = tickets; 1239 1240 // add class 'completed' for single-page. If section has class 'completed', display next section 1241 sectionCompleted(section, condition); 1242 } 1243 1244 // initiate functions 1245 $('#tickets-section .row-container select').on('change selectmenuselect', function () { 1246 ticketCondition(); 1247 displayNextButton(); 1248 }); 1249 1250 // initiate functions 1251 $('#timeslot-section').on('change', '.timeslot input', function () { 1252 timeslotCondition(); 1253 displayNextButton(); 1254 }); 1255 1256 1257 /************************************************ 1258 Personal Data page: next button 1259 ************************************************/ 1260 // check fields with class '.required' 1261 function dataFieldsRequired(section) { 1262 var fields = section.find('.data-fields .required:last'); 1263 1264 // check if one of the radio buttons is checked 1265 var radio = fields.find('[type="radio"]').length ? fields.find('[type="radio"]').is(':checked') : true; 1266 1267 // check if every text field's length is not 0 1268 var text = fields.find('input').not('[type="radio"]').filter(function () { 1269 var val = $(this).val(); 1270 return val.trim().length === 0; 1271 }).length === 0; 1272 1273 // check if select is not default 1274 var select = fields.find('select').filter(function () { 1275 return $(this).val() == 'default'; 1276 }).length === 0; 1277 1278 // check if orderLimit has been reached 1279 var orderLimitReached = section.find('.error-container.orderLimitReached').length > 0; 1280 1281 return (radio && text && select && !orderLimitReached); 1282 } 1283 1284 // return true or false if select, radio buttons and input fields have values. 1285 function personalDataCondition() { 1286 var section = $('#personal-data-section, #subscription-data-section'); 1287 var condition = dataFieldsRequired(section); 1288 1289 sectionCompleted(section, condition); 1290 } 1291 1292 // initiate functions 1293 if ($('#personal-data-section').length) { 1294 personalDataCondition(); 1295 displayNextButton(); 1296 } 1297 1298 $('#subscription-data-section input, #personal-data-section input, #personal-data-section select, #personal-data-section textarea').on('keyup change selectmenuselect', function () { 1299 personalDataCondition(); 1300 displayNextButton(); 1301 }); 1302 1303 if ($('#subscription-data-section').length > 0) { 1304 showNextButton(); 1305 } 1306 1307 /************************************************ 1308 Payment page: next button 1309 ************************************************/ 1310 // return true or false if "payment option" and "terms and conditions" are selected 1311 function paymentCondition() { 1312 var section = $('#payment-section'); 1313 var condition = section.find('input').is(':checked'); 1314 1315 sectionCompleted(section, condition); 1316 } 1317 1318 function termsCondition() { 1319 var section = $('#terms-and-conditions-section'); 1320 var condition = section.find('#terms-and-conditions').is(':checked'); 1321 1322 sectionCompleted(section, condition); 1323 } 1324 1325 // initiate functions 1326 $('#terms-and-conditions, #payment-section input').on('change', function () { 1327 paymentCondition(); 1328 termsCondition(); 1329 displayNextButton(); 1330 }); 1331 1332 if ($('#terms-and-conditions').length) { 1333 paymentCondition(); 1334 termsCondition(); 1335 displayNextButton(); 1336 } 1337 1338 /************************************************ 1339 jQuery Selectmenu 1340 ************************************************/ 1341 // if window is smaller than 768px and select has class 'gt-desktop-select', destroy "selectmenu" else create it 1342 function selectMenuWidth() { 1343 var element = $('.selectbox select'), 1344 selectClass = 'gt-desktop-select'; 1345 1346 if ($(window).width() <= 768 && element.hasClass(selectClass)) { 1347 element.selectmenu('destroy'); 1348 element.removeClass(selectClass); 1349 } else if ($(window).width() >= 768 && !element.hasClass(selectClass)) { 1350 element.selectmenu( 1351 { 1352 width: '100%', 1353 position: {my: "left top", at: "left bottom", collision: "fit"}, 1354 select: function () { 1355 displayNextButton(); 1356 }, 1357 open: function (event, ui) { 1358 selectMenuKeyUp(); 1359 } 1360 }); 1361 1362 element.addClass(selectClass); 1363 } 1364 } 1365
1366 selectMenuWidth(); 1367 1368 $(window).resize(function () { 1369 selectMenuWidth(); 1370 }); 1371 1372 /************************************************ 1373 jQuery Selectmenu: keyup 1374 ************************************************/ 1375 function selectMenuKeyUp() { 1376 $('.ui-selectmenu-button').on('keyup', function () { 1377 var element = $('.ui-selectmenu-open'), 1378 active = $('.ui-menu-item-wrapper.ui-state-active'), 1379 position = active.length ? active.parent().position().top - active.closest('.ui-menu').position().top : 0, 1380 height = element.height() / 2; 1381 1382 element.scrollTop(position - height); 1383 }); 1384 } 1385 1386 /************************************ 1387 Donation 1388 *************************************/ 1389 function getInitialDonationPrice() { 1390 var initialPrice = 0; 1391 var checkedPrice = +$('[name="donation-input"]:checked').val(); 1392 var otherPrice = +$('#donation').val(); 1393 var inputPrice = +$('[name="donation-input"]').val(); 1394 1395 if (!isNaN(checkedPrice)) { 1396 initialPrice = checkedPrice; 1397 } else if (!isNaN(otherPrice)) { 1398 initialPrice = otherPrice; 1399 } else if (!isNaN(inputPrice)) { 1400 initialPrice = inputPrice; 1401 } 1402 1403 return initialPrice; 1404 } 1405 1406 function calcDonation(donationPrice = 0) { 1407 if (isNaN(donationPrice) || donationPrice <= 0) { 1408 $('#donation-section .total-price').html(convertPrice(ticketsTotalPrice)); 1409 $('#donation-section .donation-price').fadeOut(); 1410 } else { 1411 $('#donation-section .total-price').html(convertPrice(ticketsTotalPrice + donationPrice)); 1412 $('#donation-section .donation-price').fadeIn(); 1413 } 1414 } 1415 1416 if ($('#donation-section').length) { 1417 var initialPrice = getInitialDonationPrice(); 1418 1419 calcDonation(initialPrice); 1420 1421 $('#donation-check').on('change', function () { 1422 var checked = $(this).is(':checked'); 1423 1424 $('#donation-section .hidden-container').fadeIn(checked); 1425 1426 const focusableElements = $('#donations-section, #donations-section li, #donations-section input, #donation'); 1427 if (checked) { 1428 focusableElements.attr('tabindex', '0'); 1429 $('#donation').removeAttr('tabindex'); 1430 } else { 1431 focusableElements.attr('tabindex', '-1'); 1432 } 1433 }); 1434 1435 $('[name="donation-input"]').on('keyup change selectmenuchange', function () { 1436 var value = +$(this).val(); 1437 var otherDonation = +$('#donation').val(); 1438 var donationPrice = value ?? (otherDonation ?? 0); 1439 1440 calcDonation(donationPrice); 1441 }); 1442 } 1443 1444 if ($('#donation-section, #donations-section').length) { 1445 $('.donation-amount').on('change', function () { 1446 var val = $(this).val(); 1447 1448 if (val === 'other') { 1449 $('.donationOther').show(); 1450 $('.donationOther').find('input').prop('disabled', false); 1451 } else { 1452 $('.donationOther').hide(); 1453 $('.donationOther').find('input').prop('disabled', true); 1454 } 1455 }); 1456 } 1457 1458 function showDonationSection() { 1459 $('#donation-text-section, #donation-section').show(); 1460 } 1461 1462 function hideDonationSection() { 1463 $('#donation-text-section, #donation-section').hide(); 1464 } 1465 1466 setTimeout(function() { 1467 const donationItems = $('#donation-buttons li'); 1468 if (donationItems.length === 0) { 1469 return; 1470 } 1471 1472 donationItems.each(function() { 1473 $(this).on('focus', function() { 1474 const radioInput = $(this).find('input[type="radio"]'); 1475 if (radioInput.length) { 1476 radioInput.prop('checked', true).trigger('change'); 1477 1478 if (radioInput.attr('id') === 'donation_other' || radioInput.val() === 'other') { 1479 $('.donationOther').show(); 1480 $('#donation').prop('disabled', false); 1481 } else { 1482 $('.donationOther').hide(); 1483 $('#donation').prop('disabled', true); 1484 } 1485 } 1486 1487 const activeLabel = $(this).find('.donation-button.active'); 1488 if (activeLabel.length) { 1489 activeLabel.click(); 1490 } 1491 }); 1492 1493 $(this).on('keydown', function(e) { 1494 if ((e.key === 'Enter' || e.key === ' ') && 1495 $(this).find('input#donation_other').length) { 1496 e.preventDefault(); 1497 $('#donation').trigger('focus'); 1498 } 1499 }); 1500 }); 1501 }, 1000); 1502 1503 /************************************ 1504 Gift tickets 1505 *************************************/ 1506 $('.gift-container input').on('change', function () { 1507 let dateSection = $('#calendar-section, #timeslot-section'), 1508 upsellSection = $('#custom-upsell'), 1509 upsellExists = typeof resetUpsell === 'function'; 1510 isGiftTicketChecked = $('#gift-ticket').is(':checked'); 1511 let selectedDateText = $('#selected-date-text'); 1512 1513 if(isGiftTicketChecked) {
1514 dateSection.slideUp(); 1515 1516 if (upsellExists) { 1517 resetUpsell(); 1518 upsellSection.slideUp(); 1519 selectedDateText.hide(); 1520 } 1521 } 1522 else { 1523 $('#calendar-section').slideDown(function () { 1524 timeslotCondition(); 1525 setDatepickerDate(); 1526 }); 1527 if (upsellExists) { 1528 upsellSection.slideDown(); 1529 selectedDateText.show(); 1530 } 1531 } 1532 1533 var ticketDate = $('#calendar-date').val(); 1534 1535 if (isGiftTicketChecked === false && ticketDate.length) { 1536 ticketDateChosen(ticketDate); 1537 } 1538 if(upsellExists) { 1539 updateCart(ticketDate); 1540 } 1541 1542 sectionCompleted(dateSection, isGiftTicketChecked); 1543 displayNextButton(); 1544 }); 1545 $('.gift-container input').trigger('change'); 1546 /************************************************ 1547 allow input with only numbers 1548 ************************************************/ 1549 $('input.day, input.month, input.year').on('click', function () { 1550 $(this).select(); 1551 }); 1552 $('input.day, input.month, input.year').on('keydown', function (e) { 1553 var key = e.charCode || e.keyCode || 0; 1554 // allow backspace, numbers and keypad numbers ONLY 1555 // tabs, home, end, period, and numpad decimal 1556 // arrow keys 1557 return ( 1558 key == 8 || 1559 key == 9 || 1560 (key >= 37 && key <= 40) || 1561 key == 110 || 1562 key == 190 || 1563 (key >= 48 && key <= 57) || 1564 (key >= 96 && key <= 105) 1565 ); 1566 }); 1567 1568 $('input.day, input.month, input.year').on('keyup', function (e) { 1569 var tab = parseInt($(this).attr('tabindex')) + 1, 1570 min = parseInt($(this).data('min')), 1571 max = parseInt($(this).data('max')), 1572 length = parseInt($(this).data('length')), 1573 key = e.charCode || e.keyCode || 0; 1574 1575 var value = parseInt($(this).val()); 1576 1577 // +1 or -1 with arrow keys 1578 if (key == 38 && value < max) { 1579 $(this).val(value + 1); 1580 } 1581 if (key == 40 && value > min) { 1582 $(this).val(value - 1); 1583 } 1584 1585 // if this value length is same as max length, and it's not up or down key 1586 if ($(this).val().length == length && (key < 37 || key > 40) && key != 9) { 1587 // if length is same as max length, go to next field 1588 $('[tabindex=' + tab + ']').focus().select(); 1589 } 1590 }); 1591 1592 function birthdateBlur(input) { 1593 var min = parseInt(input.data('min')), 1594 max = parseInt(input.data('max')), 1595 value = input.val(); 1596 1597 // if value is smaller than min, set min 1598 if (value < min) { 1599 input.val(min); 1600 } 1601 // if value is greater than max, set max 1602 if (value > max) { 1603 input.val(max); 1604 } 1605 1606 if (parseInt(input.val()) < 10) { 1607 input.val(('0' + input.val()).slice(-2)); 1608 } 1609 } 1610 1611 $('input.day, input.month, input.year').on('blur', function (e) { 1612 if ($('.birthdate').hasClass('required')) { 1613 birthdateBlur($(this)) 1614 } else { 1615 if ($(this).val().length) { 1616 birthdateBlur($(this)); 1617 } 1618 } 1619 }); 1620 1621 /************************************************ 1622 faq-list 1623 ************************************************/ 1624 $('.faq-list .toggle-question').on('click', function (e) { 1625 $(this).parent().toggleClass('active'); 1626 1627 e.preventDefault(); 1628 }); 1629 1630 /************************************************ 1631 language selection 1632 ************************************************/ 1633 function updateLanguageIcon(languageCode) { 1634 var selectButton = $('#language-selection .ui-selectmenu-button'); 1635 var flagImg = $('<span class="language-flag"><span class="flag-icon ' + languageCode.toLowerCase() + '"></span></span>'); 1636 selectButton.find(".ui-selectmenu-text").prepend(flagImg); 1637 1638 $('#language-selection .language-flag-mobile .flag-icon') 1639 .attr('class', 'flag-icon ' + languageCode.toLowerCase()); 1640 } 1641 1642 if ($('#language-selection').hasClass('language-flags')) { 1643 updateLanguageIcon($("#language-selection select").val()); 1644 } 1645 1646 $('#language-selection select').on('change selectmenuchange', function () { 1647 window.location.href = $(this).find('option:selected').data('url'); 1648 }); 1649 1650 /************************************************ 1651 Subscriptions: Profile picture 1652 ************************************************/ 1653 if ($('.upload-image-container').length) { 1654 Dropzone.autoDiscover = false;
1655 1656 var uploadProfile = new Dropzone('.upload-image-container', 1657 { 1658 autoProcessQueue: true, 1659 uploadMultiple: false, 1660 maxFiles: 1, 1661 url: '/script/profile-picture', 1662 hiddenInputContainer: '#profile-picture-container', 1663 previewsContainer: '#preview-profile-picture', 1664 acceptedFiles: 'image/jpeg,image/png,image/gif', 1665 thumbnailWidth: 80, 1666 thumbnailHeight: 100 1667 }); 1668 1669 uploadProfile.on('addedfile', function (file) { 1670 // if another file is added, remove the previous file 1671 if (this.files.length > 1) { 1672 this.removeFile(this.files[0]); 1673 } 1674 }); 1675 1676 uploadProfile.on('thumbnail', function (file) { 1677 var minWidth = 150, 1678 minHeight = 150; 1679 1680 var element = this.element; 1681 1682 // if the added image does not meet the required dimensions remove it. 1683 if (file.width < minWidth || file.height < minHeight) { 1684 this.removeFile(this.files[0]); 1685 $(element).siblings('.error-input').find('span').html('Afbeeldingen dienen minimaal ' + minWidth + ' x ' + minHeight + ' px groot te zijn en kan alleen geüpload worden in de volgende formaten: .jpg, .png, .gif').end().fadeIn(); 1686 } else { 1687 $(element).siblings('.error-input').hide(); 1688 } 1689 }); 1690 1691 uploadProfile.on('sending', function (file, output) { 1692 $('#profile-picture').val(''); 1693 $('.upload-image-container .progress-bar-container').show(); 1694 }); 1695 1696 uploadProfile.on('uploadprogress', function (file, output) { 1697 1698 $('.upload-image-container .progress-bar-container .fill').width(output + '%'); 1699 $('.upload-image-container .progress-bar-container .number').text(parseInt(output) + '%'); 1700 }); 1701 1702 uploadProfile.on('complete', function (file, output) { 1703 1704 $('.upload-image-container .progress-bar-container').hide(); 1705 $('.upload-image-container .progress-bar-container .fill').width(0); 1706 $('.upload-image-container .progress-bar-container .number').text('0%'); 1707 }); 1708 1709 uploadProfile.on('success', function (file, output) { 1710 if (output.success) { 1711 $('#profile-picture').val(output.imagePath); 1712 } 1713 }); 1714 1715 $('form').on('submit', function (e) { 1716 uploadProfile.processQueue(); 1717 }); 1718 } 1719 1720 /************************************************ 1721 .bgl-ticket 1722 ************************************************/ 1723 $('.bgl-ticket').on('change selectmenuchange selectmenuselect', function () { 1724 bglTicket(); 1725 }); 1726 1727 function bglTicket() { 1728 if ($('.bgl-ticket').length === 0) { 1729 return; 1730 } 1731 1732 var number = parseInt($('.bgl-ticket').val()), 1733 container = $('#bgl-section'), 1734 inputs = $('.bgl-container input'); 1735 1736 number ? container.fadeIn() : container.fadeOut(); 1737 1738 inputs.slice(number, inputs.length).hide(); 1739 inputs.slice(0, number).show(); 1740 } 1741 1742 bglTicket(); 1743 1744 $('.bgl-container input').on( 1745 { 1746 keydown: function (e) { 1747 if (e.which === 32) { 1748 return false; 1749 } 1750 }, 1751 change: function () { 1752 this.value = this.value.replace(/\s/g, ""); 1753 } 1754 }); 1755 1756 /************************************************ 1757 OK Wallet 1758 ************************************************/ 1759 var okWalletSend = false; 1760 1761 function okWallet() { 1762 var uuid = $('#ok-wallet-qr').data('uuid'); 1763 var errorContainer = $('#ok-wallet-error-container'); 1764 var loadingScreen = $('#ok-wallet-screen'); 1765 var okWalletContainer = $('#ok-wallet-section'); 1766 1767 $.get('/api/wallet/ok/auth/status/' + uuid) 1768 .done(function (output) { 1769 var result = output.result; 1770 1771 if (result == 'OPEN') { 1772 if (okWalletSend === false) { 1773 setTimeout(okWallet, 1500); 1774 } 1775 1776 return; 1777 } 1778 1779 if (result == 'OK') { 1780 loadingScreen.fadeIn().css('display', 'table'); 1781 1782 $.get('/api/wallet/ok/auth/update/' + uuid) 1783 .done(function (output) { 1784 if (output.success && okWalletSend === false) { 1785 okWalletSend = true; 1786
1787 $.get('/api/wallet/ok/send/' + uuid) 1788 .done(function (output) { 1789 if (output.success) { 1790 $('#ok-wallet-loading').hide(); 1791 $('#ok-wallet-success').show(); 1792 loadingScreen.delay('2000').fadeOut(); 1793 1794 okWalletContainer.find('.col.tickets').hide(); 1795 okWalletContainer.find('.col.text .mobile').hide(); 1796 okWalletContainer.find('.col.text .desktop').hide(); 1797 okWalletContainer.find('.col.text .success').show(); 1798 return; 1799 } 1800 1801 loadingScreen.delay('2000').fadeOut(); 1802 okWalletContainer.find('.message-container').hide(); 1803 errorContainer.find('p').html(output.message); 1804 errorContainer.show(); 1805 return; 1806 }) 1807 .fail(function (output) { 1808 loadingScreen.delay('2000').fadeOut(); 1809 okWalletContainer.find('.message-container').hide(); 1810 errorContainer.find('p').html(output.responseJSON.message); 1811 errorContainer.show(); 1812 }); 1813 1814 return; 1815 } 1816 1817 loadingScreen.delay('2000').fadeOut(); 1818 okWalletContainer.find('.message-container').hide(); 1819 errorContainer.find('p').html(output.message); 1820 errorContainer.show(); 1821 return; 1822 }); 1823 1824 return; 1825 } 1826 1827 loadingScreen.delay('2000').fadeOut(); 1828 okWalletContainer.find('.message-container').hide(); 1829 errorContainer.find('p').html(output.message); 1830 errorContainer.show(); 1831 return; 1832 }); 1833 } 1834 1835 if ($('#ok-wallet-section').length && $('#ok-wallet-qr').data('uuid')) { 1836 okWallet(); 1837 } 1838 1839 /************************************************ 1840 tooltip image 1841 ************************************************/ 1842 $(document).tooltip( 1843 { 1844 items: '.tooltip-image', 1845 position: {my: "right center", at: "right top", collision: "flipfit"}, 1846 content: function () { 1847 var src = $(this).data('tooltip'), 1848 alt = $(this).attr('alt'); 1849 1850 var img = '<img src="#" alt="#" />' 1851 1852 return $(img).attr( 1853 { 1854 src: src, 1855 alt: alt 1856 }); 1857 } 1858 }); 1859 1860 /************************************************ 1861 #voucher-section 1862 ************************************************/ 1863 // display the number of voucher inputs 1864 function numberOfVouchers() { 1865 var number = ($('#number-of-vouchers').val() ? $('#number-of-vouchers').val() : 1); 1866 1867 $('#voucher-section .input-container').removeClass('active'); 1868 $('#voucher-section .input-container:lt(' + number + ')').addClass('active'); 1869 } 1870 1871 numberOfVouchers(); 1872 1873 $('#number-of-vouchers').on('change selectmenuchange', function () { 1874 numberOfVouchers(); 1875 }); 1876 1877 // checks if the voucher inputs have any value filled in 1878 function emptyVoucherCheck() { 1879 if ($('#voucher-section').length) { 1880 var empty = $('#voucher-section input').filter(function () { 1881 var val = $(this).val(); 1882 var value = val.trim().length; 1883 1884 return value > 0; 1885 }).length === 0; 1886 1887 empty ? $('#button-steps .next').fadeOut().prop('disabled', true) : $('#button-steps .next').fadeIn().prop('disabled', false); 1888 } 1889 } 1890 1891 emptyVoucherCheck(); 1892 1893 $('#voucher-section input').on('input keyup change', function () { 1894 emptyVoucherCheck(); 1895 }); 1896 1897 /************************************************ 1898 .toggle-container 1899 ************************************************/ 1900 $('.toggle-container .toggle-handle').on('click', function () { 1901 $(this).closest('.item').toggleClass('active'); 1902 }); 1903 1904 /************************************************ 1905 #refund-form 1906 ************************************************/ 1907 $('#refund-form form').on('submit', function (e) { 1908 e.preventDefault(); 1909 1910 $('#refund-request-confirmation-dialog').dialog('open'); 1911 }); 1912 1913 /************************************************ 1914 submit selfservice date 1915 ************************************************/ 1916 $('#submit-refund-request').not('.submit').on('click', function (e) { 1917 e.preventDefault(); 1918 var button = $(this); 1919 button.addClass('submit'); 1920 $('#refund-request-confirmation-dialog').addClass('disable-close-dialog'); 1921 1922 var name = $('#refund-name').val().trim(), 1923 email = $('#refund-email').val().trim(), 1924 ticket = $('#refund-ticket').val().trim(), 1925 comments = $('#refund-comments').val().trim(); 1926 1927 $.ajax({ 1928 url: '/script/refund-request', 1929 data: { 1930 name: name, 1931 email: email, 1932 ticket: ticket, 1933 comments: comments, 1934 language: language 1935 }, 1936 cache: false, 1937 dataType: 'json', 1938 method: 'POST', 1939 }).done(function (output) { 1940 $('#refund-request-confirmation-dialog').dialog('close'); 1941 1942 if (output.success) { 1943 $('#refund-form').hide(); 1944 $('#refund-send-success-container').show(); 1945 $('#refund-send-error-container').hide(); 1946 } else { 1947 $('#refund-form').show(); 1948 $('#refund-send-success-container').hide(); 1949 $('#refund-send-error-container').show(); 1950 } 1951 1952 button.removeClass('submit'); 1953 }); 1954 }); 1955 1956 /************************************************ 1957 disable submit after submitting form 1958 ************************************************/ 1959 var hasPerformedCheckout = false; // push checkout data to GTM before submitting form 1960 var hasPerformedSubmit = false; // submit checkout form only once 1961 1962 $('form').on('submit', function (e) { 1963 var paymentMethod = $('#payment-section input:checked').val(); 1964 1965 if (hasPerformedCheckout === false) { 1966 e.preventDefault(); 1967 1968 hasPerformedCheckout = typeof onCheckout === 'function' ? onCheckout(paymentMethod) : true; 1969 setTimeout(function () { 1970 $('.page-body form').submit() 1971 }, 500); 1972 } else if (hasPerformedSubmit === false) { 1973 hasPerformedSubmit = true; 1974 } else { 1975 e.preventDefault(); 1976 } 1977 }); 1978 1979 /************************************************ 1980 creditcard inputs 1981 ************************************************/ 1982 $('.creditcard-number').on('keydown', function (e) { 1983 var key = e.charCode || e.keyCode || 0; 1984
1985 // allow backspace, space, numbers and keypad numbers ONLY 1986 // tabs, home, end, period, and numpad decimal 1987 // arrow keys 1988 return ( 1989 key == 8 || 1990 key == 9 || 1991 key == 32 || 1992 (key >= 37 && key <= 40) || 1993 key == 110 || 1994 key == 190 || 1995 (key >= 48 && key <= 57) || 1996 (key >= 96 && key <= 105) 1997 ); 1998 }); 1999 2000 $('.creditcard-number').on('keyup', function (e) { 2001 var charLength = $(this).val().length; 2002 2003 var val = $(this).val().replace(/[^0-9]/g, ''), 2004 charLength = $(this).val().length, 2005 caretPos = e.target.selectionStart; 2006 2007 if (charLength === caretPos && charLength >= 4) { 2008 var newVal = val.match(/.{1,4}/g).join(' '); 2009 2010 $(this).val(newVal); 2011 } 2012 }); 2013 2014 $('.creditcard-name').on('keydown', function (e) { 2015 var key = e.keyCode; 2016 2017 if (key >= 48 && key <= 57) { 2018 return false; 2019 } 2020 }); 2021 2022 $('.creditcard-number, .creditcard-name').on('paste', function (e) { 2023 e.preventDefault(); 2024 }); 2025 2026 /************************************************ 2027 international phone numbers 2028 ************************************************/ 2029 var prefCountries = ["gb", "nl", "de", "fr", "pl", "es", "it", "pt", "ru", "cn", "jp", "ko"]; 2030 2031 if (language == 'nl') { 2032 prefCountries = ["nl", "gb", "de", "fr", "pl", "es", "it", "pt", "ru", "cn", "jp", "ko"]; 2033 } else if (language == 'de') { 2034 prefCountries = ["de", "nl", "gb", "fr", "pl", "es", "it", "pt", "ru", "cn", "jp", "ko"]; 2035 } else if (language == 'fr') { 2036 prefCountries = ["fr", "nl", "gb", "de", "pl", "es", "it", "pt", "ru", "cn", "jp", "ko"]; 2037 } else if (['es', 'ca'].indexOf(language) > -1) { 2038 prefCountries = ["es", "nl", "gb", "de", "fr", "pl", "it", "pt", "ru", "cn", "jp", "ko"]; 2039 } else if (language == 'it') { 2040 prefCountries = ["it", "nl", "gb", "de", "fr", "pl", "es", "pt", "ru", "cn", "jp", "ko"]; 2041 } else if (language == 'pt') { 2042 prefCountries = ["pt", "nl", "gb", "de", "fr", "pl", "es", "it", "ru", "cn", "jp", "ko"]; 2043 } else if (language == 'zh') { 2044 prefCountries = ["cn", "nl", "gb", "de", "fr", "pl", "es", "it", "pt", "ru", "jp", "ko"]; 2045 } else if (language == 'jp') { 2046 prefCountries = ["jp", "nl", "gb", "de", "fr", "pl", "es", "it", "pt", "ru", "cn", "ko"]; 2047 } else if (language == 'ru') { 2048 prefCountries = ["ru", "nl", "gb", "de", "fr", "pl", "es", "it", "pt", "cn", "jp", "ko"]; 2049 } 2050 2051 var $phoneNumber = $('#phone-number'); 2052 2053 if ($phoneNumber.length > 0) { 2054 if (defaultPhoneCountry) { 2055 prefCountries.unshift(defaultPhoneCountry); 2056 } 2057 2058 if ((smsVerificationEnabled === 'true' || smsVerificationEnabled === true) && typeof phoneOnlyCountries != 'undefined') { 2059 $phoneNumber.intlTelInput({ 2060 autoPlaceholder: "polite", 2061 formatOnDisplay: true, 2062 hiddenInput: "phone-number", 2063 onlyCountries: phoneOnlyCountries, 2064 }); 2065 } 2066 else if (smsVerificationEnabled === 'true' || smsVerificationEnabled === true) { 2067 $phoneNumber.intlTelInput({ 2068 autoPlaceholder: "polite", 2069 formatOnDisplay: true, 2070 hiddenInput: "phone-number", 2071 nationalMode: true, 2072 separateDialCode: true, 2073 preferredCountries: prefCountries, 2074 utilsScript: "/static/external/intlTelInput/utils.js" 2075 }); 2076 } 2077 else { 2078 $phoneNumber.intlTelInput({ 2079 autoPlaceholder: "polite", 2080 formatOnDisplay: true, 2081 hiddenInput: "phone-number", 2082 preferredCountries: prefCountries, 2083 }); 2084 } 2085 } 2086 2087 if (smsVerificationEnabled === 'true' || smsVerificationEnabled === true) { 2088 var $verifyCodeButton = $('#verify-code-button'); 2089 var $verificationCode = $('#verification_code'); 2090 var $smsVerificationContainer = $('#sms-verification-container'); 2091 var $verificationCodeError = $('#verification-code-error'); 2092 var $inputVerificationCodeContainer = $('#input-verification-code-container'); 2093 2094 $('.cm-sms-verification #telephone-info').on('click', function(e) { 2095 e.preventDefault(); 2096 if ($('#telephone-info-description').is(':hidden')) { 2097 $('#telephone-info-description').show(); 2098 } 2099 else { 2100 $('#telephone-info-description').hide(); 2101 } 2102 }); 2103 2104 $('.cm-sms-verification #sms-verification-container-show').on('click', function(e) { 2105 e.preventDefault(); 2106 $('#phone-number-contact-container').hide(); 2107 $('#sms-verification-container').show(); 2108 $('.adyen-web-component-wrapper').removeClass('hidden').show(); 2109 }); 2110 2111 $('.cm-sms-verification #phone-number-verify').not('.submit').on('click', function(e) { 2112 e.preventDefault(); 2113 var button = $(this); 2114 button.addClass('submit'); 2115 2116 if ($('#phone-number').val() == '') { 2117 $('#unverified-phone-number-error').show(); 2118 return; 2119 } 2120 2121 // Make ajax call to send sms 2122 $.ajax({ 2123 url: '/script/generate-sms', 2124 cache: false, 2125 dataType: 'json', 2126 data: {phoneNumber: $phoneNumber.intlTelInput('getNumber')}, 2127 method: 'POST', 2128 success: function (output) { 2129 /* clear loading */ 2130 loaded = true; 2131 2132 if (output.success === true) { 2133 $('#phone-number-contact-container').hide(); 2134 $('#sms-verification-container').show(); 2135 $('.adyen-web-component-wrapper').removeClass('hidden').show(); 2136 } 2137 else { 2138 $('#unverified-phone-number-error').show(); 2139 } 2140 } 2141 }); 2142 }); 2143 2144 $('input[name="payment"]').on('change', function(){ 2145 $('#sms-verification-container').hide(); 2146 if ($.inArray($(this).val(), ['visa', 'mastercard', 'american-express', 'vpay', 'bcmc', 'mc']) !== -1) { 2147 $('#phone-number-contact-container').show(); 2148 $('.adyen-web-component-wrapper').addClass('hidden').hide(); 2149 } 2150 else { 2151 $('.adyen-web-component-wrapper').removeClass('hidden').show(); 2152 $('#phone-number-contact-container').hide(); 2153 } 2154 }); 2155 2156 $verifyCodeButton.on('click', function (e) { 2157 e.preventDefault(); 2158 2159 if (!$verificationCode.val()) { 2160 $verificationCodeError.find('.message').text('Vul uw verificatiecode in'); 2161 $verificationCodeError.show(); 2162 return; 2163 } 2164 2165 $smsVerificationContainer.addClass('loading'); 2166 2167 $('#verification_code').val($verificationCode.val()); 2168 $('#summary-form').submit(); 2169 }); 2170 2171 $inputVerificationCodeContainer.on('input', function () { 2172 var verificationCode = ''; 2173 var codes = $(this).find('.code'); 2174 2175 codes.each(function () { 2176 verificationCode += $(this).val(); 2177 }); 2178 2179 $verificationCode.val(verificationCode); 2180 }); 2181 2182 $inputVerificationCodeContainer.find('.code').on('keyup', function (e) { 2183 var keycode = e.keyCode; 2184 var code = $(this); 2185 2186 $verificationCodeError.hide(); 2187 2188 if (keycode === 8) { 2189 code.prev('.code').focus().select(); 2190 return; 2191 } 2192 2193 if (code.val().length === 1) { 2194 code.next('.code').focus().select(); 2195 } 2196 }); 2197 } 2198 2199 2200 /************************************************ 2201 create QR code 2202 ************************************************/ 2203 if ($('#qrcode-vaccine').length) { 2204 var qrcode = new QRCode(document.getElementById("qrcode-vaccine"), { 2205 width: 160, 2206 height: 160 2207 }); 2208 2209 function makeCode() { 2210 qrcode.makeCode('thisIsAVaccineCode'); 2211 } 2212 2213 makeCode(); 2214 2215 $('#print-page').on('click', function (e) { 2216 e.preventDefault(); 2217 2218 window.print(); 2219 }); 2220 } 2221 2222 /************************************************ 2223 Chosen date and time display 2224 ************************************************/ 2225 function chosenDateAndTimeText() { 2226 $('#chosen-date-time-section').hide(); 2227
2228 if ($('#calendar-date').val() && $('#timeslot-section .timeslot input:checked').length) { 2229 var date = new Date($('#calendar-date').val()), 2230 dateText = $.datepicker.formatDate('DD d MM yy', date), 2231 time = $('#timeslot-section .timeslot input:checked').val(); 2232 2233 $('#chosen-date-text').text(dateText); 2234 $('#chosen-time-text').text(time); 2235 $('#chosen-date-time-section').show(); 2236 } 2237 } 2238 2239 $('#calendar-date').on('change', function () { 2240 chosenDateAndTimeText(); 2241 }); 2242 2243 $('#timeslot-section .daypart-container > input').on('change', function () { 2244 $('#timeslot-section .timeslot input:checked').prop('checked', false); 2245 }); 2246 2247 $('#timeslot-section').on('change', '.timeslot input, .daypart-container', function () { 2248 chosenDateAndTimeText(); 2249 }); 2250 2251 /************************************************ 2252 sms verification 2253 ************************************************/ 2254 if (typeof phonenumber !== 'undefined' && typeof email !== 'undefined') { 2255 var sendSms = false; 2256 $('#summary-form').submit(function (e) { 2257 if ($('#verification_reference').val().length && $('#verification_code').val().length) { 2258 return; 2259 } else if (sendSms === true) { 2260 e.preventDefault(); 2261 return; 2262 } 2263 e.preventDefault(); 2264 2265 sendSms = true; 2266 postData('https://api-cm.globalticket.com/v1/public/sms/generate?phone_number=' + phonenumber) 2267 .then(function (data) { 2268 $('#verification_reference').val(data.verification_reference); 2269 }); 2270 2271 $('#vaccination-reservation-data-overview, #button-steps').hide(); 2272 $('#vaccination-verification-overview, #sms-verification-container').show(); 2273 }); 2274 } 2275 2276 // Dynamic-pricing calendar labels live in static/js/calendar-pricing.js 2277}); 2278 2279// display next button or get timeslots 2280function ticketDateChosen(ticketDate, formattedDate) { 2281 $('#calendar-date').change(); 2282 dateCondition(); 2283 2284 if (settingsTimeslots) { 2285 getTimeslots(ticketDate, formattedDate); 2286 } else { 2287 if (hasTicketsSelected()) { 2288 showNextButton(); 2289 } 2290 } 2291 2292 if(typeof window.customCalendarDateSelectEvent != 'undefined') { 2293 window.customCalendarDateSelectEvent(ticketDate, formattedDate); 2294 } 2295} 2296 2297function hasTicketsSelected() { 2298 return ticketsTotalNumber > 0; 2299} 2300 2301function hideNextButton() { 2302 $('#button-steps .next').hide().prop('disabled', true); 2303} 2304 2305function showNextButton() { 2306 $('#button-steps .next').show().prop('disabled', false); 2307} 2308 2309// Get Timeslots Ajax call 2310function getTimeslots(ticketDate, formattedDate) { 2311 if (typeof disabledDates != 'undefined' && $.inArray(ticketDate, disabledDates) >= 0) { 2312 // Do not get timeslots if the date is disabled 2313 return; 2314 } 2315 2316 // Do not get timeslots if gift ticket is checked 2317 if ($('#gift-ticket').prop('checked')) { 2318 return; 2319 } 2320 2321 /* initialize timeslots */ 2322 var $timeslotSection = $('#timeslot-section'); 2323 var $timeslotsLoading = $timeslotSection.find('.timeslots-loading'); 2324 var $timeslotsContainer = $('.timeslots-container'); 2325 var $daypartContainer = $('.daypart-container'); 2326 var $chosenDateTimeSection = $('#chosen-date-time-section'); 2327 2328 var $timeslotsSoldout = $('#timeslots-soldout'); 2329 var $timeslotsOverlimit = $('#timeslots-overlimit'); 2330 var $timeslotsTexts = $('.timeslots-container-top, .timeslots-container-bottom'); 2331 var $timeslotErrorContainer = $timeslotSection.find('.error-container'); 2332 var $buttonStepsNext = $('#button-steps .next'); 2333 2334 /* ajax call */ 2335 if (currentRequest != null) { 2336 currentRequest.abort(); 2337 } 2338 currentRequest = $.ajax({ 2339 url: '/script/timeslots', 2340 cache: false, 2341 dataType: 'json', 2342 data: { 2343 tag: $('html').attr('data-tag'), 2344 eventId: (eventId ? eventId : null), 2345 productEventId: (productEventId ? productEventId : ''), 2346 ticketDate: ticketDate, 2347 ticketNumber: ticketsTotalNumber, 2348 ticketNumbers: ticketNumbers, 2349 timeslotsGroup: timeslotsGroup, 2350 streetname: streetname 2351 }, 2352 method: 'POST', 2353 beforeSend: function () { 2354 $timeslotsLoading.show(); 2355 $timeslotsContainer.empty(); 2356 $daypartContainer.hide().find('.timeslot').remove(); 2357 $chosenDateTimeSection.hide(); 2358 $timeslotsSoldout.hide(); 2359 $timeslotsOverlimit.hide(); 2360 $timeslotsTexts.hide(); 2361 $buttonStepsNext.hide().prop('disabled', true); 2362 $timeslotSection.slideDown(); 2363 $timeslotsContainer.removeClass('has-expirations has-soldouts'); 2364 } 2365 }).done(function (output) { 2366 // Don't render timeslots if gift ticket is now checked 2367 if ($('#gift-ticket').prop('checked')) { 2368 return; 2369 } 2370 2371 $timeslotsLoading.hide(); 2372 2373 if (output.success) { 2374 var timeslotsSoldout = true; 2375 var hasRemaining = 0; 2376 2377 /* Flatpickr */ 2378 if (timeslotsByDayParting) { 2379 var morningTimeslots = []; 2380 var afternoonTimeslots = []; 2381 var eveningTimeslots = []; 2382 2383 $.each(output.timeslots, function (index, timeslot) { 2384 if (settingsHideExpiredTimeslots === true && timeslot.expired) { 2385 return; 2386 } 2387 2388 if (timeslot.active || settingsDisplayInactiveTimeslots) { 2389 var timeslotElement = $('<div class="timeslot"></div>'); 2390 var timeslotInput = $('<input type="radio" name="ticketTime">'); 2391 var timeslotLabel = $('<label></label>'); 2392 var id = timeslot.time.replace(':', ''); 2393 var timeslotTitle = settingsDisableTimeslotsTitle ? timeslot.time : timeslot.title; 2394 2395 timeslotElement.data('timeslot', timeslot.time).addClass(timeslot.classAttr); 2396 timeslotLabel.attr('title', timeslotTitle + ' uur').text(timeslotTitle); 2397 2398 if (settingsDisplayInactiveTimeslots === true && timeslot.active === false) { 2399 timeslotLabel.addClass('inactive'); 2400 2401 if (timeslot.expired === true) { 2402 $timeslotsContainer.addClass('has-expirations'); 2403 } 2404 if (timeslot.soldOut === true) { 2405 timeslotLabel.addClass('soldout'); 2406 $timeslotsContainer.addClass('has-soldouts'); 2407 } 2408 } else { 2409 var chosenTicketDateTime = chosenTicketDate === ticketDate && chosenTicketTime === timeslot.time; 2410 2411 timeslotInput.attr({ 2412 id: 'timeslot-' + id, 2413 title: timeslotTitle, 2414 value: timeslot.time, 2415 'aria-label': formattedDate ? (formattedDate + ', ' + timeslot.time) : timeslot.time, 2416 }).prop('checked', chosenTicketDateTime); 2417 2418 timeslotLabel.attr({ 2419 for: 'timeslot-' + id, 2420 title: timeslot.time + ' uur' 2421 }); 2422 2423 timeslotElement.append(timeslotInput); 2424 } 2425 2426 if (output.showAvailableTickets && output.showAvailableTickets === true && timeslot.availableTickets !== null) { 2427 timeslotLabel.append('<span class="available-tickets">' + timeslot.availableTickets + ' ' + LANG_AVAILABLE + '</span>'); 2428 } 2429 2430 timeslotElement.append(timeslotLabel); 2431 2432 if (parseInt(id) >= 0 && parseInt(id) < 1200) { 2433 morningTimeslots.push(timeslotElement); 2434 } else if (parseInt(id) >= 1200 && parseInt(id) < 1800) { 2435 afternoonTimeslots.push(timeslotElement); 2436 } else { 2437 eveningTimeslots.push(timeslotElement); 2438 } 2439 2440 timeslotsSoldout = false;
2441 } else { 2442 if (timeslot.expired === false) { 2443 hasRemaining += timeslot.totalAvailable; 2444 } 2445 } 2446 }); 2447 2448 if (morningTimeslots.length) { 2449 $('#morning').prop('disabled', false); 2450 $('#morning-timeslots').html(morningTimeslots); 2451 } 2452 if (afternoonTimeslots.length) { 2453 $('#afternoon').prop('disabled', false); 2454 $('#afternoon-timeslots').html(afternoonTimeslots); 2455 } 2456 if (eveningTimeslots.length) { 2457 $('#evening').prop('disabled', false); 2458 $('#evening-timeslots').html(eveningTimeslots); 2459 } 2460 2461 /* show available tickets */ 2462 if (output.showAvailableTickets && output.showAvailableTickets === true) { 2463 $daypartContainer.addClass('show-available-tickets'); 2464 } 2465 } else { /* Jquery UI Datepicker */ 2466 var html = ''; 2467 2468 $.each(output.timeslots, function (index, timeslot) { 2469 if (settingsHideExpiredTimeslots === true && timeslot.expired) { 2470 return; 2471 } 2472 var timeslotTitle = settingsDisableTimeslotsTitle ? timeslot.time : timeslot.title; 2473 2474 if (timeslot.active || settingsDisplayInactiveTimeslots) { 2475 /* id */ 2476 var id = timeslot.time.replace(':', ''); 2477 var classAttr = 'gt-background-second timeslot'; 2478 2479 if (timeslot.classAttr) { 2480 classAttr += ' ' + timeslot.classAttr; 2481 } 2482 2483 /* html */ 2484 html += '<li class="' + classAttr + '" data-timeslot="' + timeslot.time + '">'; 2485 2486 if (settingsDisplayInactiveTimeslots === true && timeslot.active === false && timeslot.expired === true) { 2487 html += '<label class="inactive">' + timeslotTitle; 2488 2489 $timeslotsContainer.addClass('has-expirations'); 2490 } else if (settingsDisplayInactiveTimeslots === true && timeslot.active === false && timeslot.soldOut === true) { 2491 html += '<label class="inactive soldout">' + timeslotTitle; 2492 2493 $timeslotsContainer.addClass('has-soldouts'); 2494 } else if (timeslot.classAttr === 'timeslotAlmostFull') { 2495 var chosenTicketDateTime = chosenTicketDate === ticketDate && chosenTicketTime === timeslot.time; 2496 html += '<input type="radio" id="timeslot-' + id + '" name="ticketTime" value="' + timeslot.time + '"' + (chosenTicketDateTime ? ' checked' : '') + '>'; 2497 html += '<label for="timeslot-' + id + '" class="almost-gone gt-background-main gt-border-color-third">' + timeslotTitle; 2498 2499 $timeslotsContainer.addClass('almost-full'); 2500 } else { 2501 var chosenTicketDateTime = chosenTicketDate === ticketDate && chosenTicketTime === timeslot.time; 2502 2503 html += '<input type="radio" id="timeslot-' + id + '" name="ticketTime" value="' + timeslot.time + '"' + (chosenTicketDateTime ? ' checked' : '') + '>'; 2504 html += '<label for="timeslot-' + id + '" class="gt-background-main gt-border-color-third">' + timeslotTitle; 2505 } 2506 2507 if (output.showAvailableTickets && output.showAvailableTickets === true && timeslot.availableTickets !== null) { 2508 html += '<span class="available-tickets">' + timeslot.availableTickets + ' ' + LANG_AVAILABLE + '</span>'; 2509 } 2510 2511 html += '</label>'; 2512 html += '</li>'; 2513 2514 timeslotsSoldout = false;
2515 } else { 2516 if (timeslot.expired === false) { 2517 hasRemaining += timeslot.totalAvailable; 2518 } 2519 } 2520 }); 2521 2522 /* show available tickets */ 2523 if (output.showAvailableTickets && output.showAvailableTickets === true) { 2524 $timeslotsContainer.addClass('show-available-tickets'); 2525 } 2526 2527 $timeslotsContainer.html(html); 2528 2529 // Ensure the first timeslot is focused when a date is chosen, streamlining the process for users navigating solely with a keyboard to select a slot more efficiently. 2530 const $timeslots = $timeslotsContainer.find('input[name="ticketTime"]'); 2531 if($timeslots.length > 0 && firstPageLoad === false) { 2532 $timeslots.first().focus(); 2533 } 2534 } 2535 2536 document.dispatchEvent(new CustomEvent('timeslots:rendered', { detail: { date: ticketDate } })); 2537 2538 /* show custom timeslot styling */ 2539 if (settingsCustomTimeslotStyling === true) { 2540 $timeslotsContainer.find('li label').removeClass('gt-background-main gt-border-color-third'); 2541 $timeslotsContainer.find('li').removeClass('gt-background-second'); 2542 } 2543 2544 if (hasRemaining > 0 && timeslotsSoldout) { 2545 $timeslotsSoldout.hide(); 2546 $timeslotsOverlimit.show(); 2547 $timeslotsTexts.hide(); 2548 $timeslotSection.removeClass('completed'); 2549 $buttonStepsNext.hide().prop('disabled', true); 2550 } else if (timeslotsSoldout) { 2551 $timeslotsSoldout.show(); 2552 $timeslotsOverlimit.hide(); 2553 $timeslotsTexts.hide(); 2554 $timeslotSection.removeClass('completed'); 2555 $buttonStepsNext.hide().prop('disabled', true); 2556 } else { 2557 $daypartContainer.slideDown(); 2558 $timeslotsSoldout.hide(); 2559 $timeslotsOverlimit.hide() 2560 $timeslotsTexts.show(); 2561 } 2562 2563 let autoselectFirstTimeslotIfOne = true; 2564 if (typeof(window.settingAutoselectFirstTimeslotIfOne) != 'undefined') { 2565 autoselectFirstTimeslotIfOne = window.settingAutoselectFirstTimeslotIfOne; 2566 } 2567 if (autoselectFirstTimeslotIfOne) { 2568 if ($timeslotsContainer.find('.timeslot input').length === 1) { 2569 $timeslotsContainer.find('.timeslot input').prop('checked', true).change(); 2570 } else if ($timeslotsContainer.find('input:checked').length === 1) { 2571 $timeslotsContainer.find('.timeslot input').change(); 2572 } 2573 } 2574 2575 $timeslotSection.slideDown().change(); 2576 } 2577 2578 if (typeof homeUrl == 'undefined' && (scrollToTimeslotsOnPageLoad === true || firstPageLoad === false)) { 2579 window.scrollTo({ 2580 top: $timeslotSection.offset().top - 50, 2581 behavior: 'smooth' 2582 }); 2583 } 2584 2585 firstPageLoad = false; 2586 timeslotCondition(); 2587 displayNextButton(); 2588 }); 2589 2590 if(typeof window.customTimeslotLoadedEvent != 'undefined') { 2591 window.customTimeslotLoadedEvent(); 2592 } 2593 2594 dateCondition(); 2595 hideNextButton(); 2596} 2597 2598function getMainSelectedTimeslot() { 2599 return $('#timeslot-section .timeslots-container input:checked').prop('checked', true).val(); 2600} 2601 2602/************************************************ 2603 Date page: next button 2604 ************************************************/ 2605// return true or false if date is selected 2606// and if timeslots exist, return true or false if date and timeslot are selected 2607function dateCondition() { 2608 var section = $('#calendar-section'); 2609 var condition = section.find('#calendar-date').val() != ''; 2610 2611 sectionCompleted(section, condition); 2612} 2613 2614function timeslotCondition() { 2615 var section = $('#timeslot-section'); 2616 var condition = section.find('input').is(':checked'); 2617 2618 condition ? $('#timeslot-availability-section').slideDown() : $('#timeslot-availability-section').slide
2618Up(); 2619 2620 sectionCompleted(section, condition); 2621} 2622 2623// display next button if all required section has class "completed" 2624function displayNextButton() { 2625 var nextButton = $('#button-steps .button.next'); 2626 var completed = $('.section-container .section.required:not(.completed)').length === 0; 2627 2628 completed ? showNextButton() : hideNextButton(); 2629} 2630 2631/************************************************ 2632 Section is completed 2633 ************************************************/ 2634// if section is completed, add class 'completed' 2635function sectionCompleted(element, condition) { 2636 if (condition) { 2637 element.addClass('completed'); 2638 } else { 2639 element.removeClass('completed'); 2640 } 2641} 2642 2643function getUpsellTickets() 2644{ 2645 var data = [], 2646 upsell = $('#custom-upsell'), 2647 items = upsell.find('.item'); 2648 2649 $.each(items, function (i, item) { 2650 var item = $(this); 2651 var selectMenuItems = item.find('.quantity select'); 2652 2653 $.each(selectMenuItems, function (j, select) { 2654 let title = $(select).data('title'), 2655 price = $(select).data('price'), 2656 fromPrice = $(select).data('from-price'), 2657 quantity = $(select).val(), 2658 timeslot = item.find('.timeslot select').val(), 2659 ticketGroupId = $(select).data('ticket-group-id'); 2660 2661 //no item selected: don't push to cart 2662 if(parseInt(quantity) === 0) { 2663 return; 2664 } 2665 2666 fromPrice = parseFloat(fromPrice); 2667 price = parseFloat(price); 2668 difference = fromPrice < price ? 0 : (fromPrice - price); 2669 2670 //if the item has no timeslots: push to cart 2671 if (item.hasClass('no-timeslots')) { 2672 data.push({ 2673 description: title, 2674 fromPrice: fromPrice, 2675 price: price, 2676 number: parseInt(quantity), 2677 subtotal: parseInt(quantity) * parseFloat(price), 2678 difference: difference 2679 }); 2680 2681 return; 2682 } 2683 2684 // if item has timeslots but there is no timeslot selected: don't push to cart 2685 if(timeslot === 'false' || timeslot === false || timeslot === null) { 2686 return; 2687 } 2688 2689 //quantity and timeslot selected: push to cart 2690 data.push({ 2691 description: title, 2692 fromPrice: fromPrice, 2693 price: price, 2694 number: parseInt(quantity), 2695 subtotal: parseInt(quantity) * parseFloat(price), 2696 difference: difference 2697 }); 2698 }); 2699 }); 2700 2701 return data; 2702} 2703 2704var currentRequest = null; 2705 2706function postData(url, data) { 2707 currentRequest = $.ajax({ 2708 url: url, 2709 data: data, 2710 cache: false, 2711 dataType: 'json', 2712 method: 'POST', 2713 beforeSend: function () { 2714 if (currentRequest != null) { 2715 currentRequest.abort(); 2716 } 2717 }, 2718 success: function (output) { 2719 return output; 2720 } 2721 }); 2722 2723 return currentRequest; 2724} 2725 2726// Prevent resubmission of the form when user hit Control/Cmd + R or browser refresh button 2727if (window.history.replaceState) { 2728 window.history.replaceState(null, null, window.location.href); 2729} 2730 2731/************************************************ 2732Accessibility menu 2733************************************************/ 2734$('.accessibility-menu-toggle').on('click', () => { 2735 $('.accessibility-menu-container').toggleClass('active'); 2736}); 2737 2738$('.accessibility-menu .toggle-increase-contrast').on('click', (e) => { 2739 $('.page-body').toggleClass('increase-contrast'); 2740 $(e.currentTarget).toggleClass('active'); 2741 document.cookie = "_wcag_increase_contrast=" + $('.page-body').hasClass('increase-contrast') + "; path=/"; 2742}); 2743 2744$('.accessibility-menu .toggle-increase-font-size').on('click', (e) => { 2745 $('.page-body').toggleClass('increase-font-size'); 2746 $(e.currentTarget).toggleClass('active'); 2747 document.cookie = "_wcag_increase_fontsize=" + $('.page-body').hasClass('increase-font-size') + "; path=/"; 2748}); 2749 2750/************************************************ 2751 Availability page accessibility 2752 ************************************************/ 2753const stepEl = document.getElementById('step-name'); 2754if (stepEl && stepEl.dataset.stepName === 'date') { 2755 (function () { 2756 /* ----------------------------------------------------------- 2757 0. Lazy helpers to read serverâside translations once 2758 ----------------------------------------------------------- */ 2759 let _timeslotTranslations = null; 2760 const getTimeslotTranslations = () => { 2761 if (_timeslotTranslations) return _timeslotTranslations; 2762 const el = document.getElementById('timeslot-translations'); 2763 _timeslotTranslations = el 2764 ? JSON.parse(el.dataset.translations)
2765 : { timeslot: '', almostFull: '', busy: '', closed: '' }; 2766 return _timeslotTranslations; 2767 }; 2768 2769 let _calendarTranslations = null; 2770 const getCalendarTranslations = () => { 2771 if (_calendarTranslations) return _calendarTranslations; 2772 const el = document.getElementById('calendar-month-translations'); 2773 _calendarTranslations = el 2774 ? JSON.parse(el.dataset.calendarMonthTranslations) 2775 : { currentMonth: '', previousMonth: '', nextMonth: '' }; 2776 return _calendarTranslations; 2777 }; 2778 2779 /* ----------------------------------------------------------- 2780 1. Timeslot list (<li><label>) accessibility 2781 ----------------------------------------------------------- */ 2782 const makeLabelsFocusable = () => { 2783 const t = getTimeslotTranslations(); 2784 document.querySelectorAll('.timeslots-container li label').forEach(label => { 2785 label.tabIndex = 0; 2786 label.setAttribute('role', 'button'); 2787 2788 const timeText = (label.firstChild?.nodeType === Node.TEXT_NODE) 2789 ? label.firstChild.textContent.trim() 2790 : ''; 2791 2792 const availabilitySpan = label.querySelector('.available-tickets'); 2793 const availabilityText = availabilitySpan ? availabilitySpan.textContent.trim() : ''; 2794 2795 let labelText = t.timeslot + timeText; 2796 if (availabilityText) labelText += ', ' + availabilityText; 2797 2798 const li = label.closest('li'); 2799 if (li) { 2800 if (li.classList.contains('timeslotAlmostFull')) labelText += ', ' + t.almostFull; 2801 if (li.classList.contains('timeslotBusy')) labelText += ', ' + t.busy; 2802 if (li.classList.contains('timeslotClosed')) labelText += ', ' + t.closed; 2803 } 2804 2805 label.setAttribute('title', labelText); 2806 label.setAttribute('aria-label', labelText); 2807 }); 2808 }; 2809 2810 /* ----------------------------------------------------------- 2811 2. Calendarâday links accessibility 2812 ----------------------------------------------------------- */ 2813 const makeCalendarDaysAccessible = () => { 2814 const datepicker = document.querySelector('.ui-datepicker'); 2815 if (!datepicker) return; 2816 2817 const dayNames = Array.from( 2818 datepicker.querySelectorAll('.ui-datepicker-calendar thead th span') 2819 ).map(span => span.getAttribute('title') || ''); 2820 2821 datepicker.querySelectorAll('.ui-datepicker-calendar a').forEach(link => { 2822 const dayNumber = link.textContent.trim(); 2823 const cell = link.closest('td'); 2824 if (!cell) return; 2825 2826 const cellIndex = Array.from(cell.parentNode.children).indexOf(cell); 2827 const dayName = dayNames[cellIndex] || ''; 2828 2829 const labelText = `${dayName} ${dayNumber}`; 2830 link.setAttribute('title', labelText); 2831 link.setAttribute('aria-label', labelText); 2832 }); 2833 }; 2834 2835 /* ----------------------------------------------------------- 2836 3. Month header & nav: reorder nodes + aria 2837 ----------------------------------------------------------- */ 2838 const attachMonthNav = (datepicker = document.querySelector('.ui-datepicker')) => { 2839 if (!datepicker) return; 2840 2841 const header = datepicker.querySelector('.ui-datepicker-header'); 2842 if (!header) return; 2843 2844 const title = header.querySelector('.ui-datepicker-title'); 2845 const prevButton = header.querySelector('.ui-datepicker-prev'); 2846 const nextButton = header.querySelector('.ui-datepicker-next'); 2847 2848 /* 3a. Put the title first in the DOM so natural tabâorder is ok */ 2849 if (title && header.firstChild !== title) { 2850 header.insertBefore(title, header.firstChild); // title, prev, next 2851 } 2852 2853 /* 3b. Normal tabindex (0) so every browser sees them */
2854 [title, prevButton, nextButton].forEach(el => { 2855 if (!el) return; 2856 el.removeAttribute('tabindex'); 2857 el.tabIndex = 0; 2858 }); 2859 if (prevButton?.classList.contains('ui-state-disabled')) prevButton.removeAttribute('tabindex'); 2860 2861 /* 3c. Accessible names */ 2862 const t = getCalendarTranslations(); 2863 2864 if (title) { 2865 const month = title.querySelector('.ui-datepicker-month')?.textContent ?? ''; 2866 const year = title.querySelector('.ui-datepicker-year')?.textContent ?? ''; 2867 title.setAttribute('role', 'heading'); 2868 title.setAttribute('aria-level', '2'); 2869 title.setAttribute('aria-label', (t.currentMonth ?? '') + `${month} ${year}`.trim()); 2870 } 2871 if (prevButton && !prevButton.classList.contains('ui-state-disabled')) { 2872 prevButton.setAttribute('role', 'button'); 2873 prevButton.setAttribute('aria-label', t.previousMonth ?? 'Previous month'); 2874 } 2875 if (nextButton) { 2876 nextButton.setAttribute('role', 'button'); 2877 nextButton.setAttribute('aria-label', t.nextMonth ?? 'Next month'); 2878 } 2879 }; 2880 2881 /* ----------------------------------------------------------- 2882 4. Focus handling for âsoldâoutâ message (unchanged) 2883 ----------------------------------------------------------- */ 2884 const setupSoldOutFocus = () => { 2885 const soldOutMessage = document.getElementById('timeslots-soldout'); 2886 let lastActiveDate = null; 2887 2888 document.addEventListener('keydown', e => { 2889 if (e.key === 'Enter' || e.key === ' ') { 2890 const calendarDate = e.target.closest('.ui-datepicker-calendar a'); 2891 if (calendarDate) lastActiveDate = calendarDate; 2892 } 2893 }, true); 2894 2895 if (!soldOutMessage) return; 2896 2897 document.addEventListener('keydown', e => { 2898 if (e.key === 'Tab' && !e.shiftKey && 2899 soldOutMessage.style.display === 'block' && 2900 document.activeElement === soldOutMessage) { 2901 2902 e.preventDefault(); 2903 (lastActiveDate && document.body.contains(lastActiveDate) 2904 ? lastActiveDate 2905 : document.querySelector('.ui-datepicker-calendar a[href]')) 2906 ?.focus(); 2907 } 2908 }, true); 2909 2910 new MutationObserver(muts => { 2911 muts.forEach(mut => { 2912 if (mut.attributeName === 'style' && soldOutMessage.style.display === 'block') { 2913 setTimeout(() => soldOutMessage.focus(), 0); 2914 } 2915 }); 2916 }).observe(soldOutMessage, { attributes: true, attributeFilter: ['style'] }); 2917 }; 2918 2919 /* ----------------------------------------------------------- 2920 5. Generic errorâcontainer focus trap (unchanged) 2921 ----------------------------------------------------------- */ 2922 const trapErrorFocus = el => { 2923 let trap = true; 2924 const releaseTrap = () => { trap = false; }; 2925 2926 el.addEventListener('blur', () => { 2927 setTimeout(() => { 2928 if (document.activeElement !== el && trap) el.focus(); 2929 }, 10); 2930 }); 2931 el.addEventListener('keydown', e => { 2932 if (e.key === 'Tab') releaseTrap(); 2933 }); 2934 // Release trap on click/touch anywhere 2935 document.addEventListener('mousedown', releaseTrap, { once: true }); 2936 document.addEventListener('touchstart', releaseTrap, { once: true }); 2937 }; 2938 2939 const focusErrorContainer = () => { 2940 const error = document.querySelector('.error-container[role="alert"]'); 2941 if (!error) return; 2942 if (!error.hasAttribute('tabindex')) error.tabIndex = 0; 2943 setTimeout(() => { 2944 error.focus(); 2945 trapErrorFocus(error); 2946 error.scrollIntoView({ behavior: 'smooth', block: 'center' }); 2947 }, 0); 2948 }; 2949 2950 /* ----------------------------------------------------------- 2951 6. Oneâtime setup on initial load 2952 ----------------------------------------------------------- */ 2953 document.addEventListener('DOMContentLoaded', () => { 2954 makeLabelsFocusable(); 2955 attachMonthNav(); 2956 makeCalendarDaysAccessible(); 2957 setupSoldOutFocus(); 2958 focusErrorContainer(); 2959 }); 2960 2961 /* ----------------------------------------------------------- 2962 7. Observe calendar container for every redraw 2963 ----------------------------------------------------------- */ 2964 const containers = document.querySelectorAll( 2965 '.calendar-container, #timeslot-section' 2966 ); 2967 if (containers.length) { 2968 const observer = new MutationObserver(() => { 2969 makeLabelsFocusable(); // when timeslots appear/refresh 2970 attachMonthNav(); // when calendar header redraws 2971 makeCalendarDaysAccessible(); // when days change 2972 setupSoldOutFocus(); // keep soldâout focus behavior 2973 });
2974 containers.forEach(c => 2975 observer.observe(c, { childList: true, subtree: true }) 2976 ); 2977 } 2978 2979 /* ----------------------------------------------------------- 2980 8. Enter / Space triggers for pseudoâbuttons & links 2981 ----------------------------------------------------------- */ 2982 document.addEventListener('keydown', e => { 2983 if (e.key !== 'Enter' && e.key !== ' ') return; 2984 2985 const clickable = e.target.closest( 2986 '.ui-datepicker-calendar a, .timeslots-container li label, .ui-datepicker-prev, .ui-datepicker-next' 2987 ); 2988 if (!clickable) return; 2989 2990 if (clickable.matches('.ui-datepicker-prev, .ui-datepicker-next') && 2991 clickable.getAttribute('tabindex') === '0') { 2992 e.preventDefault(); 2993 clickable.click(); 2994 } else if (!clickable.matches('.ui-datepicker-prev.ui-state-disabled')) { 2995 e.preventDefault(); 2996 clickable.click(); 2997 } 2998 }); 2999 })(); 3000} 3001 3002/************************************************ 3003 Donation flow accessibility 3004 ************************************************/ 3005document.addEventListener('DOMContentLoaded', () => { 3006 // --- Part 1: Listeners for donation price options 3007 const optionsContainer = document.querySelector('#donation-buttons'); 3008 if (!optionsContainer) return; 3009 3010 const options = Array.from(optionsContainer.querySelectorAll('[role="radio"]')); 3011 3012 options.forEach((opt, i) => 3013 opt.setAttribute('tabindex', i === 0 ? '0' : '-1') 3014 ); 3015 3016 options.forEach((option, currentIndex) => { 3017 option.addEventListener('keydown', event => { 3018 let newIndex; 3019 3020 switch (event.key) { 3021 case 'ArrowRight': 3022 case 'ArrowDown': 3023 newIndex = (currentIndex + 1) % options.length; 3024 break; 3025 3026 case 'ArrowLeft': 3027 case 'ArrowUp': 3028 newIndex = (currentIndex - 1 + options.length) % options.length; 3029 break; 3030 3031 case 'Enter': 3032 case ' ': 3033 event.preventDefault(); 3034 const radio = option.querySelector('input[type="radio"]'); 3035 if (radio) radio.click(); 3036 return; 3037 3038 default: 3039 return; 3040 } 3041 3042 event.preventDefault(); 3043 moveFocus(options, currentIndex, newIndex); 3044 }); 3045 }); 3046 3047 function moveFocus(items, oldIndex, newIndex) { 3048 items[oldIndex].setAttribute('tabindex', '-1'); 3049 items[newIndex].setAttribute('tabindex', '0'); 3050 items[newIndex].focus(); 3051 } 3052 3053 // --- Part 2: Listeners for other elements of payment and thank you page 3054 const otherClickables = [ 3055 ...document.querySelectorAll('.js-donation-retry'), 3056 ...document.querySelectorAll('.js-payment-method'), 3057 ...document.querySelectorAll('.js-more-payments'), 3058 ...document.querySelectorAll('.js-faq') 3059 ]; 3060
3061 otherClickables.forEach(el => { 3062 el.addEventListener('keydown', event => { 3063 if (event.key === 'Enter' || event.key === ' ') { 3064 event.preventDefault(); 3065 if (el.classList.contains('js-payment-method')) { 3066 const radio = el.querySelector('input[type="radio"]'); 3067 if (radio) radio.click(); 3068 } else if (el.classList.contains('js-faq')) { 3069 const link = el.querySelector('a'); 3070 if (link) link.click(); 3071 } else { 3072 el.click(); 3073 } 3074 } 3075 }); 3076 }); 3077}); 3078 3079
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.