https://www.selbstverstaendlich-vielfalt.de/wp-content/plugins/events-made-easy/js/eme.js?ver=3.2.15
1// namespace EME variables 2if (typeof window.EME === 'undefined') { 3 window.EME = { 4 $: (selector, context = document) => context.querySelector(selector), 5 $$: (selector, context = document) => Array.from(context.querySelectorAll(selector)) 6 }; 7} 8 9function eme_debounce(func, wait = 300) { 10 let timeout; 11 return function(...args) { 12 const context = this; 13 clearTimeout(timeout); 14 timeout = setTimeout(() => func.apply(context, args), wait); 15 }; 16} 17 18function eme_htmlDecode(inputStr) { 19 const textarea = document.createElement('textarea'); 20 textarea.innerHTML = inputStr; 21 return textarea.value; 22} 23 24function eme_getValue(element) { 25 if (!element) return null; 26 if (!element.multiple) return element.value; 27 return Array.from(element.selectedOptions).map(option => option.value); 28} 29 30function eme_getQueryParams(qs) { 31 qs = qs.split('+').join(' '); 32 let params = {}, tokens, re = /[?&]?([^=]+)=([^&]*)/g; 33 while ((tokens = re.exec(qs))) { 34 params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]); 35 } 36 return params; 37} 38const $_GET = eme_getQueryParams(document.location.search); 39 40function eme_isFalsey(paramValue) { 41 return paramValue === undefined || paramValue === null || paramValue === '0' || paramValue === ''; 42} 43 44function eme_toggle(el, show) { 45 if (el) el.classList.toggle('eme-hidden', !show); 46 //if (el) el.style.display = show ? '' : 'none'; 47} 48 49function initSnapSelect(selector, options = {}) { 50 // Convert selector to elements array 51 const elements = typeof selector === 'string' 52 ? EME.$$(selector) 53 : [selector]; 54 55 if (!elements.length) return []; 56 57 return Array.from(elements).map(el => { 58 // Guard: skip if already initialised 59 if (el.snapselectInstance) return el.snapselectInstance; 60 61 if (!el.id) { 62 el.id = 'snap-select-' + Math.random().toString(36).slice(2, 11); 63 } 64 65 const config = { 66 placeholder: options.placeholder || undefined, 67 defaultText: options.defaultText || emebasic.translate_optionselect, 68 liveSearch: options.liveSearch !== undefined ? options.liveSearch : el.multiple ? true : undefined, 69 closeOnSelect: options.closeOnSelect !== undefined ? options.closeOnSelect : undefined, 70 showClearButton: options.showClearButton !== undefined ? options.showClearButton : undefined, 71 }; 72 73 const instance = new SnapSelectClass(el, config); 74 el.snapselectInstance = instance; 75 return instance; 76 }); 77} 78 79function initSnapSelectRemote(selector, options = {}) { 80 // Convert selector to elements array 81 const elements = typeof selector === 'string' 82 ? EME.$$(selector) 83 : [selector]; 84 85 if (!elements.length) return []; 86 87 return Array.from(elements).map(el => { 88 // Guard: skip if already initialised 89 if (el.snapselectInstance) return el.snapselectInstance; 90 91 if (!el.id) { 92 el.id = 'snap-select-' + Math.random().toString(36).slice(2, 11); 93 } 94 95 const pagesize = options.pagesize || 30; 96 // Build the ajax.url function, incorporating any extra data 97 const config = { 98 placeholder: options.placeholder || undefined, 99 defaultText: options.defaultText || emebasic.translate_optionselect, 100 closeOnSelect: !el.multiple, 101 showClearButton: options.showClearButton !== undefined ? options.showClearButton : undefined, 102 onItemAdd: typeof options.onItemAdd === 'function' ? options.onItemAdd : undefined, 103 onItemDelete: typeof options.onItemDelete === 'function' ? options.onItemDelete : undefined, 104 ajax: { 105 // Allow caller to pass a custom url function (e.g. for stateâcountry cascade) 106 url: options.url || emebasic.translate_ajax_url, 107 cache: options.cache !== undefined ? options.cache : undefined, 108 pagesize: pagesize, 109 data: options.data || {}, 110 processResults: function(data, search, page) { 111 // EME backend returns { TotalRecordCount, Records:[{id,text},...] } 112 // or a plain array 113 const records = Array.isArray(data) 114 ? data 115 : (data.Records || []); 116 let hasMore; 117 if (data.hasMore !== undefined) { 118 hasMore = data.hasMore; 119 } else {
120 const total = data.TotalRecordCount !== undefined 121 ? data.TotalRecordCount 122 : records.length; 123 hasMore = total > page * pagesize; 124 // the next if covers the case where all rows are returned in 1 go, not respecting paging 125 if (data.TotalRecordCount !== undefined && records.length >= data.TotalRecordCount) 126 hasMore = false; 127 } 128 return { results: records, hasMore }; 129 }, 130 } 131 }; 132 const instance = new SnapSelectClass(el, config); 133 134 // Expose a .snapselect property on the element 135 el.snapselectInstance = instance; 136 137 return instance; 138 }); 139} 140 141// Helper functions for class manipulation 142function eme_addClass(el, className) { 143 if (el.classList) { 144 el.classList.add(className); 145 } else { 146 el.className += ' ' + className; 147 } 148} 149 150function eme_removeClass(el, className) { 151 if (el.classList) { 152 el.classList.remove(className); 153 } else { 154 el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); 155 } 156} 157 158function eme_hasClass(el, className) { 159 return el.classList ? el.classList.contains(className) : new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className); 160} 161 162function eme_lastname_clearable() { 163 const ln = EME.$('input[name=lastname]'); 164 if (!ln) return; 165 166 const fields = ['firstname', 'address1', 'address2', 'city', 'state', 'zip', 'country', 'email', 'phone','birthdate']; 167 if (ln.value == '') { 168 ln.readOnly = false; 169 eme_removeClass(ln, 'clearable'); 170 eme_removeClass(ln, 'x'); 171 fields.forEach(f => { 172 const field = EME.$(`input[name=${f}]`); 173 if (field) { 174 field.value = ''; 175 field.readOnly = false; 176 } 177 }); 178 const birthfield = EME.$('input#birthdate'); 179 if (birthfield) { 180 birthfield.value = ''; 181 } 182 const wpIdField = EME.$('input[name=wp_id]'); 183 const personIdField = EME.$('input[name=person_id]'); 184 if (wpIdField) wpIdField.value = ''; 185 if (personIdField) personIdField.value = ''; 186 } 187 if (ln.value != '') { 188 eme_addClass(ln, 'clearable'); 189 eme_addClass(ln, 'x'); 190 } 191} 192 193// --- Widget Initialization --- 194function eme_init_widgets(dynamicOnly = false) { 195 const dynamicSelector = dynamicOnly ? '.dynamicfield' : ''; 196 197 // Initialize fdatepicker for datetime fields 198 EME.$$('.eme_formfield_fdatetime' + dynamicSelector).forEach(el => { 199 if (typeof FDatepicker !== 'undefined') { 200 new FDatepicker(el, { 201 todayButton: true, 202 clearButton: true, 203 closeButton: true, 204 timepicker: true, 205 minutesStep: parseInt(emebasic.translate_minutesStep), 206 firstDayOfWeek: parseInt(emebasic.translate_firstDayOfWeek), 207 altFormat: 'Y-m-d H:i:00', 208 multipleSeparator: ", ", 209 format: emebasic.translate_fdatetimeformat, 210 }); 211 } else { 212 el.removeAttribute('readonly'); 213 el.type = 'datetime-local'; 214 } 215 }); 216 217 // Initialize fdatepicker for date fields 218 EME.$$('.eme_formfield_fdate' + dynamicSelector).forEach(el => { 219 if (typeof FDatepicker !== 'undefined') { 220 new FDatepicker(el, { 221 todayButton: true, 222 clearButton: true, 223 closeButton: true, 224 autoClose: true, 225 firstDayOfWeek: parseInt(emebasic.translate_firstDayOfWeek), 226 altFormat: 'Y-m-d', 227 multipleSeparator: ", ", 228 format: emebasic.translate_fdateformat 229 }); 230 } else { 231 el.removeAttribute('readonly'); 232 el.type = 'date'; 233 } 234 }); 235 236 // Initialize fdatepicker for time fields 237 EME.$$('.eme_formfield_ftime' + dynamicSelector).forEach(el => { 238 if (typeof FDatepicker !== 'undefined') { 239 new FDatepicker(el, { 240 timepicker: true, 241 timeOnly: true, 242 clearButton: true, 243 closeButton: true, 244 minutesStep: parseInt(emebasic.translate_minutesStep), 245 altFormat: 'H:i:00', 246 format: emebasic.translate_ftimeformat 247 }); 248 } else { 249 el.removeAttribute('readonly'); 250 el.type = 'time'; 251 } 252 }); 253 254 initSnapSelect('select.eme_snapselect' + dynamicSelector); 255 initSnapSelect('select.eme_snapselect_allow_empty' + dynamicSelector, { 256 showClearButton: true 257 }); 258 initSnapSelectRemote('select.eme_snapselect_country_class' + dynamicSelector, { 259 placeholder: emebasic.translate_selectcountry, 260 data: { 261 action: 'eme_select_country', 262 eme_frontend_nonce: emebasic.translate_frontendnonce 263 }, 264 cache: true, 265 showClearButton: true, 266 // When country changes, reset the state field in the same form 267 onItemAdd: function(value, text) { 268 const form = this.closest('form'); 269 const stateField = form?.querySelector('.eme_snapselect_state_class'); 270 if (stateField && stateField.snapselectInstance) { 271 stateField.snapselectInstance.clear(); 272 stateField.snapselectInstance.clearCache(); 273 } 274 }, 275 onItemDelete: function(value, text) { 276 const form = this.closest('form'); 277 const stateField = form?.querySelector('.eme_snapselect_state_class'); 278 if (stateField && stateField.snapselectInstance) { 279 stateField.snapselectInstance.clear(); 280 stateField.snapselectInstance.clearCache(); 281 } 282 } 283 }); 284 285 initSnapSelectRemote('select.eme_snapselect_state_class' + dynamicSelector, { 286 placeholder: emebasic.translate_selectstate, 287 showClearButton: true, 288 // Dynamically include the currently selected country_code in every request 289 data: function(search, page) { // since this is a function, no caching happens 290 const stateEl = document.querySelector('select.eme_snapselect_state_class'); 291 const form = stateEl?.closest('form'); 292 const countryCode = form?.querySelector('[name=country_code]')?.value || ''; 293 return { 294 action: 'eme_select_state', 295 eme_frontend_nonce: emebasic.translate_frontendnonce, 296 country_code: countryCode 297 }; 298 }, 299 cache: true // we set the cache true and clear it in the country select if needed 300 }); 301} 302 303// function to execute the injected javascript via ajax to force payment button click 304function eme_executeScriptsInElement(element) { 305 const scripts = element.querySelectorAll('script');
306 scripts.forEach(oldScript => { 307 const newScript = document.createElement('script'); 308 309 // Copy all attributes from old script to new script 310 Array.from(oldScript.attributes).forEach(attr => { 311 newScript.setAttribute(attr.name, attr.value); 312 }); 313 314 // Copy the script content 315 newScript.textContent = oldScript.textContent; 316 317 // Replace the old script with the new one to trigger execution 318 oldScript.parentNode.replaceChild(newScript, oldScript); 319 }); 320} 321 322// --- Unified AJAX Handler for Booking/Member/Generic forms --- 323function eme_ajax_form(form_id, action, okSel, errSel, submitBtn, extraParams = {}) { 324 const form = document.getElementById(form_id); 325 if (!form) return; 326 const okEl = EME.$(okSel); 327 const errEl = EME.$(errSel); 328 329 const btn = submitBtn || form.querySelector('[type="submit"]'); 330 const spinner = document.createElement('span'); 331 spinner.className = 'spinner'; 332 spinner.innerHTML = '⟷'; 333 if (btn) { 334 btn.dataset.emeOrigText = btn.value || btn.textContent; 335 btn.disabled = true; 336 if (btn.tagName === 'INPUT') { 337 btn.value = emebasic.translate_pleasewait; 338 } else { 339 btn.textContent = emebasic.translate_pleasewait; 340 } 341 btn.parentNode.insertBefore(spinner, btn.nextSibling); 342 } 343 const restoreBtn = () => { 344 if (btn) { 345 btn.disabled = false; 346 if (btn.tagName === 'INPUT') { 347 btn.value = btn.dataset.emeOrigText; 348 } else { 349 btn.textContent = btn.dataset.emeOrigText; 350 } 351 } 352 if (spinner && spinner.parentNode) spinner.parentNode.removeChild(spinner); 353 }; 354 355 let alldata = new FormData(form); 356 alldata.append('action', action); 357 Object.entries(extraParams).forEach(([k, v]) => alldata.append(k, v)); 358 359 fetch(emebasic.translate_ajax_url, { 360 method: 'POST', 361 body: alldata 362 }) 363 .then(response => response.json()) 364 .then(data => { 365 restoreBtn(); 366 367 if (data.Result === "OK") { 368 if (errSel) { 369 if (errEl) eme_toggle(errEl, false); 370 } 371 if (okSel) { 372 if (okEl && data.htmlmessage && (!data.redirect || ( data.redirect && parseInt(data.waitperiod)>0 ) ) ) { 373 okEl.innerHTML = data.htmlmessage; 374 eme_toggle(okEl, true); 375 } 376 } 377 if (parseInt(data.keep_form) == 1) { 378 form.reset(); 379 eme_refresh_captcha(form_id); 380 } else { 381 const parentDiv = form.closest('[id^=div_eme-]'); 382 if (parentDiv) eme_toggle(parentDiv, false); 383 } 384 if (data.paymentform) { 385 const paymentDiv = EME.$(`#div_eme-payment-form-${form_id}`); 386 if (paymentDiv && data.paymentform) { 387 paymentDiv.innerHTML = data.paymentform; 388 eme_toggle(paymentDiv, true); 389 eme_executeScriptsInElement(paymentDiv); 390 } 391 } 392 if (data.redirect) { 393 setTimeout(() => window.location.href = data.redirect, parseInt(data.waitperiod)); 394 } 395 eme_scrollToEl(okEl); 396 } else { 397 if (errSel) { 398 if (errEl && data.htmlmessage) { 399 errEl.innerHTML = data.htmlmessage; 400 eme_toggle(errEl, true); 401 } 402 } 403 if (okSel) { 404 if (okEl) eme_toggle(okEl, false); 405 } 406 eme_scrollToEl(errEl); 407 } 408 }) 409 .catch(error => { 410 restoreBtn(); 411 if (errSel) { 412 if (errEl) { 413 errEl.innerHTML = emebasic.translate_error + (error?.message ? '<br>' + error.message : ''); 414 eme_toggle(errEl, true); 415 } 416 } 417 if (okSel) { 418 if (okEl) eme_toggle(okEl, false); 419 } 420 eme_scrollToEl(errEl); 421 }); 422} 423 424function eme_refresh_captcha(form_id) { 425 const form = document.getElementById(form_id); 426 if (!form) return; 427 428 const captcha = form.querySelector('.eme-captcha-img'); 429 if (captcha) { 430 const src = captcha.src.replace(/&ts=[^&]*/, ''); 431 captcha.src = `${src}&ts=${Date.now()}`; 432 } 433} 434 435function eme_scrollToEl(sel) { 436 if (sel) { 437 sel.scrollIntoView({ behavior: 'smooth', block: 'center' }); 438 //const rect = sel.getBoundingClientRect(); 439 //const scrollTop = window.scrollY + rect.top - (window.innerHeight / 2) + (rect.height / 2); 440 //window.scrollTo({ top: scrollTop, behavior: 'smooth' }); 441 } 442} 443 444// --- Unified Dynamic Data/Price AJAX --- 445function eme_dynamic_price_json(form_id, isBooking = true) { 446 const form = document.getElementById(form_id); 447 if (!form) return; 448 449 form.querySelectorAll('[type="submit"]').forEach(btn => eme_toggle(btn, false)); 450 let alldata = new FormData(form); 451 452 const priceSpans = isBooking
453 ? [{ sel: 'span#eme_calc_bookingprice', action: 'eme_calc_bookingprice' }, { sel: 'span#eme_calc_bookingprice_detail', action: 'eme_calc_bookingprice_detail' }] 454 : [{ sel: 'span#eme_calc_memberprice', action: 'eme_calc_memberprice' }, { sel: 'span#eme_calc_memberprice_detail', action: 'eme_calc_memberprice_detail' }]; 455 456 let found = false; 457 priceSpans.forEach(({ sel, action }) => { 458 const span = form.querySelector(sel); 459 if (span) { 460 found = true; 461 span.innerHTML = '<span class="spinner">â³</span>'; 462 alldata.set('action', action); 463 alldata.set('eme_frontend_nonce', emebasic.translate_frontendnonce); 464 465 fetch(emebasic.translate_ajax_url, { 466 method: 'POST', 467 body: alldata 468 }) 469 .then(response => response.json()) 470 .then(data => { 471 form.querySelectorAll('[type="submit"]').forEach(btn => eme_toggle(btn, true)); 472 span.innerHTML = data.total; 473 }) 474 .catch(() => { 475 form.querySelectorAll('[type="submit"]').forEach(btn => eme_toggle(btn, true)); 476 span.innerHTML = 'Invalid reply'; 477 }); 478 } 479 }); 480 481 if (!found) { 482 form.querySelectorAll('[type="submit"]').forEach(btn => eme_toggle(btn, true)); 483 } 484} 485 486function eme_dynamic_data_json(form_id, isBooking = true) { 487 const form = document.getElementById(form_id); 488 if (!form) return; 489 490 form.querySelectorAll('input[type="submit"], button[type="submit"]').forEach(btn => eme_toggle(btn, false)); 491 let alldata = new FormData(form); 492 493 const dataDivSel = 'div#eme_dyndata'; 494 const action = isBooking ? 'eme_dyndata_rsvp' : 'eme_dyndata_member'; 495 const dataDiv = form.querySelector(dataDivSel); 496 497 if (dataDiv) { 498 dataDiv.innerHTML = '<span class="spinner">â³</span>'; 499 alldata.set('action', action); 500 alldata.set('eme_frontend_nonce', emebasic.translate_frontendnonce); 501 502 fetch(emebasic.translate_ajax_url, { 503 method: 'POST', 504 body: alldata 505 }) 506 .then(response => response.json()) 507 .then(data => { 508 form.querySelectorAll('[type="submit"]').forEach(btn => eme_toggle(btn, true)); 509 dataDiv.innerHTML = data.Result; 510 eme_init_widgets(true); 511 eme_dynamic_price_json(form_id, isBooking); 512 }) 513 .catch(() => { 514 form.querySelectorAll('[type="submit"]').forEach(btn => eme_toggle(btn, true)); 515 }); 516 } else { 517 eme_dynamic_price_json(form_id, isBooking); 518 } 519} 520 521function eme_dynamic_familymemberdata_json(form_id) { 522 const form = document.getElementById(form_id); 523 if (!form) return; 524 525 form.querySelectorAll('[type="submit"]').forEach(btn => eme_toggle(btn, false)); 526 let alldata = new FormData(form); 527 const dataDiv = form.querySelector('div#eme_dyndata_family'); 528 529 if (dataDiv) { 530 dataDiv.innerHTML = '<span class="spinner">â³</span>'; 531 alldata.set('action', 'eme_dyndata_familymember'); 532 alldata.set('eme_frontend_nonce', emebasic.translate_frontendnonce); 533 534 fetch(emebasic.translate_ajax_url, { 535 method: 'POST', 536 body: alldata 537 }) 538 .then(response => response.json()) 539 .then(data => { 540 form.querySelectorAll('[type="submit"]').forEach(btn => eme_toggle(btn, true)); 541 dataDiv.innerHTML = data.Result; 542 eme_init_widgets(true); 543 }) 544 .catch(() => { 545 form.querySelectorAll('[type="submit"]').forEach(btn => eme_toggle(btn, true)); 546 }); 547 } else { 548 form.querySelectorAll('[type="submit"]').forEach(btn => eme_toggle(btn, true)); 549 } 550} 551 552// Calendar navigation 553function loadCalendar( 554 tableDiv, fullcalendar = 0, htmltable, htmldiv, showlong_events = 0, month = 0, 555 year = 0, cat_chosen = '', author_chosen = '', contact_person_chosen = '', 556 location_chosen = '', not_cat_chosen = '', template_chosen = 0, holiday_chosen = 0, 557 weekdays = '' 558) { 559 const formData = new FormData(); 560 formData.append('eme_frontend_nonce', emebasic.translate_frontendnonce); 561 formData.append('action', 'eme_calendar'); 562 formData.append('calmonth', parseInt(month, 10)); 563 formData.append('calyear', parseInt(year, 10)); 564 formData.append('full', fullcalendar); 565 formData.append('long_events', showlong_events); 566 formData.append('htmltable', htmltable); 567 formData.append('htmldiv', htmldiv); 568 formData.append('category', cat_chosen); 569 formData.append('notcategory', not_cat_chosen); 570 formData.append('author', author_chosen); 571 formData.append('contact_person', contact_person_chosen); 572 formData.append('location_id', location_chosen); 573 formData.append('template_id', template_chosen); 574 formData.append('holiday_id', holiday_chosen); 575 formData.append('weekdays', weekdays); 576 577 fetch(emebasic.translate_ajax_url, { 578 method: 'POST', 579 body: formData 580 }) 581 .then(response => response.text()) 582 .then(data => { 583 const tableEl = document.getElementById(tableDiv); 584 if (tableEl) { 585 tableEl.outerHTML = data; 586 // Re-attach event handlers after DOM replacement 587 attachCalendarHandlers(); 588 } 589 }); 590} 591 592function attachCalendarHandlers() { 593 EME.$$('a.eme-cal-prev-month, a.eme-cal-next-month').forEach(link => { 594 link.addEventListener('click', function(e) { 595 e.preventDefault(); 596 this.innerHTML = '<span class="spinner">â³</span>'; 597 loadCalendar( 598 this.dataset.calendar_divid, 599 this.dataset.full, 600 this.dataset.htmltable, 601 this.dataset.htmldiv, 602 this.dataset.long_events, 603 this.dataset.month, 604 this.dataset.year, 605 this.dataset.category, 606 this.dataset.author, 607 this.dataset.contact_person, 608 this.dataset.location_id, 609 this.dataset.notcategory, 610 this.dataset.template_id, 611 this.dataset.holiday_id, 612 this.dataset.weekdays 613 ); 614 }); 615 }); 616} 617 618// --- Booking/Member form handlers with confirmation --- 619function eme_handle_massmail(form_id, callback) { 620 const form = document.getElementById(form_id); 621 const massmailField = form.querySelector('#massmail'); 622 const massmailDialog = EME.$('#MassMailDialog'); 623 624 if (massmailField && massmailField.value != 1 && massmailDialog) { 625 massmailDialog.showModal(); 626 627 const confirmBtn = EME.$('#dialog-confirm'); 628 const cancelBtn = EME.$('#dialog-cancel'); 629 630 if (confirmBtn) { 631 confirmBtn.addEventListener('click', function(e) { 632 e.preventDefault(); 633 massmailDialog.close(); 634 callback(form_id); 635 }, { once: true }); 636 } 637 638 if (cancelBtn) { 639 cancelBtn.addEventListener('click', function(e) { 640 e.preventDefault(); 641 massmailDialog.close(); 642 }, { once: true }); 643 } 644 } else { 645 callback(form_id); 646 } 647} 648 649// --- Dynamic fields AJAX debounce handlers --- 650function eme_attach_dynamic_handlers(selector, isBooking) { 651 EME.$$(selector).forEach(form => { 652 const form_id = form.id; 653 const debounced_data = eme_debounce(() => eme_dynamic_data_json(form_id, isBooking), 500); 654 const debounced_price = eme_debounce(() => eme_dynamic_price_json(form_id, isBooking), 500); 655 const debounced_family = !isBooking ? eme_debounce(() => eme_dynamic_familymemberdata_json(form_id), 500) : null; 656 657 form.addEventListener('input', function(event) { 658 // Ignore events from hidden fields (e.g. FDatepicker auto-created altField); 659 // those are internal implementation details and should not trigger a dynamic refresh. 660 if (event.target.type === 'hidden') { 661 return; 662 } 663 if (debounced_family && event.target.id === 'familycount') { 664 debounced_family(); 665 } 666 if (eme_hasClass(event.target, 'nodynamicupdates') || event.target.closest('fieldset.nodynamicupdates')) { 667 if (eme_hasClass(event.target, 'dynamicprice')) { 668 form.querySelectorAll('[type="submit"]').forEach(btn => eme_toggle(btn, false)); 669 debounced_price(); 670 } 671 return; 672 } 673 form.querySelectorAll('[type="submit"]').forEach(btn => eme_toggle(btn, false)); 674 debounced_data(); 675 }); 676 677 if (debounced_family) debounced_family(); 678 debounced_data(); 679 }); 680} 681 682function eme_scrollToInvalidInput(el) { 683 // open details if closed 684 const detailsParent = el.closest('.eme_accordion'); 685 if (detailsParent && !detailsParent.hasAttribute('open')) { 686 // Open it 687 detailsParent.setAttribute('open', 'true'); 688 // Find the content div after summary 689 const summary = detailsParent.querySelector('summary'); 690 if (summary) { 691 const contentDiv = detailsParent.querySelector('summary + div'); 692 if (contentDiv) { 693 eme_toggle(contentDiv, true); 694 contentDiv.style.height = ''; // reset height 695 } 696 } 697 } 698 699 // First check if field is in a tab 700 const tabPane = el.closest('.eme-tab-content'); 701 if (tabPane) { 702 const tabId = tabPane.id; 703 const isVisible = tabPane.classList.contains('active') || 704 window.getComputedStyle(tabPane).display !== 'none'; 705 706 if (!isVisible && typeof eme_activateTab === 'function') { 707 // Activate the tab containing the invalid field 708 eme_activateTab(tabId); 709 // Small delay to ensure tab is visible before scrolling 710 setTimeout(() => { 711 if (typeof el.reportValidity === 'function') { 712 el.reportValidity(); 713 } else { 714 eme_addClass(el, 'eme_required'); 715 eme_scrollToEl(el); 716 } 717 }, 300); 718 return; // Exit, scrolling happens after timeout 719 } 720 } 721 722 // If not in tab or tab is already active, scroll normally 723 if (typeof el.reportValidity === 'function') { 724 el.reportValidity(); 725 } else { 726 eme_addClass(el, 'eme_required'); 727 eme_scrollToEl(el); 728 } 729} 730 731document.addEventListener('DOMContentLoaded', function() { 732 // Show elements that should be visible with JS 733 EME.$$('.eme-showifjs').forEach(el => { 734 eme_toggle(el, true); 735 }); 736 737 738 // Initial calendar handler attachment 739 attachCalendarHandlers(); 740 741 // Clearable input with 'x' 742 document.addEventListener('input', function(e) { 743 if (eme_hasClass(e.target, 'clearable')) { 744 if (e.target.value) { 745 eme_addClass(e.target, 'x'); 746 } else { 747 eme_removeClass(e.target, 'x'); 748 } 749 } 750 }); 751 752 document.addEventListener('mousemove', function(e) { 753 if (eme_hasClass(e.target, 'x')) { 754 const rect = e.target.getBoundingClientRect(); 755 if (e.target.offsetWidth - 18 < e.clientX - rect.left) { 756 eme_addClass(e.target, 'onX'); 757 } else { 758 eme_removeClass(e.target, 'onX'); 759 } 760 } 761 }); 762 763 document.addEventListener('click', function(e) { 764 if (eme_hasClass(e.target, 'onX')) { 765 e.preventDefault(); 766 eme_removeClass(e.target, 'x'); 767 eme_removeClass(e.target, 'onX'); 768 e.target.value = ''; 769 e.target.dispatchEvent(new Event('change')); 770 } 771 }); 772 773 document.addEventListener('touchstart', function(e) { 774 if (eme_hasClass(e.target, 'onX')) { 775 e.preventDefault(); 776 eme_removeClass(e.target, 'x'); 777 eme_removeClass(e.target, 'onX'); 778 e.target.value = ''; 779 e.target.dispatchEvent(new Event('change')); 780 } 781 }); 782 783 // Lastname clearable 784 const lastnameField = EME.$("input[name=lastname]"); 785 if (lastnameField && lastnameField.dataset.clearable) { 786 lastnameField.addEventListener('change', eme_lastname_clearable); 787 eme_lastname_clearable(); 788 } 789 790 // --- Generic forms --- 791 const genericForms = [
792 { sel: '[name=eme-cancel-payment-form]', action: 'eme_cancel_payment', ok: 'div#eme-cancel-payment-message-ok-', err: 'div#eme-cancel-payment-message-error-' }, 793 { sel: '[name=eme-cancel-bookings-form]', action: 'eme_cancel_bookings', ok: 'div#eme-cancel-bookings-message-ok-', err: 'div#eme-cancel-bookings-message-error-' }, 794 { sel: '[name=eme-subscribe-form]', action: 'eme_subscribe', ok: 'div#eme-subscribe-message-ok-', err: 'div#eme-subscribe-message-error-' }, 795 { sel: '[name=eme-unsubscribe-form]', action: 'eme_unsubscribe', ok: 'div#eme-unsubscribe-message-ok-', err: 'div#eme-unsubscribe-message-error-' }, 796 { sel: '[name=eme-rpi-form]', action: 'eme_rpi', ok: 'div#eme-rpi-message-ok-', err: 'div#eme-rpi-message-error-' }, 797 { sel: '[name=eme-gdpr-approve-form]', action: 'eme_gdpr_approve', ok: 'div#eme-gdpr-approve-message-ok-', err: 'div#eme-gdpr-approve-message-error-' }, 798 { sel: '[name=eme-cpi-request-form]', action: 'eme_cpi_request', ok: 'div#eme-cpi-request-message-ok-', err: 'div#eme-cpi-request-message-error-' }, 799 { sel: '[name=eme-cpi-form]', action: 'eme_cpi', ok: 'div#eme-cpi-message-ok-', err: 'div#eme-cpi-message-error-' }, 800 { sel: '[name=eme-tasks-form]', action: 'eme_tasks', ok: 'div#eme-tasks-message-ok-', err: 'div#eme-tasks-message-error-' }, 801 { sel: '[name=eme-fs-form]', action: 'eme_frontend_submit', ok: 'div#eme-fs-message-ok-', err: 'div#eme-fs-message-error-', isFS: true } 802 ]; 803 804 genericForms.forEach(({ sel, action, ok, err, isFS }) => { 805 EME.$$(sel).forEach(form => { 806 form.addEventListener('submit', function(event) { 807 event.preventDefault(); 808 const form_id = this.id; 809 if (isFS && emebasic.translate_htmleditor === "tinemce" && emebasic.translate_fs_wysiwyg === "true") { 810 if (typeof tinymce !== "undefined") { 811 const eventNotesEditor = tinymce.get('event_notes'); 812 const locationDescEditor = tinymce.get('location_description'); 813 if (eventNotesEditor) eventNotesEditor.save(); 814 if (locationDescEditor) locationDescEditor.save(); 815 } 816 } 817 eme_ajax_form(form_id, action, ok + form_id, err + form_id, event.submitter); 818 }); 819 }); 820 }); 821 822 EME.$$('[name=eme-rsvp-form]').forEach(form => { 823 form.addEventListener('submit', function(event) { 824 event.preventDefault(); 825 eme_handle_massmail(this.id, function(form_id) { 826 let extra = {}; 827 ['eme_invite', 'eme_email', 'eme_ln', 'eme_fn'].forEach(k => { 828 if ($_GET[k]) extra[k] = $_GET[k]; 829 }); 830 eme_ajax_form(form_id, 'eme_add_bookings', 'div#eme-rsvp-addmessage-ok-' + form_id, 'div#eme-rsvp-addmessage-error-' + form_id, event.submitter, extra); 831 eme_dynamic_data_json(form_id, true); 832 }); 833 }); 834 }); 835 836 EME.$$('[name=eme-member-form]').forEach(form => { 837 form.addEventListener('submit', function(event) { 838 event.preventDefault(); 839 eme_handle_massmail(this.id, function(form_id) { 840 eme_ajax_form(form_id, 'eme_add_member', 'div#eme-member-addmessage-ok-' + form_id, 'div#eme-member-addmessage-error-' + form_id, event.submitter); 841 eme_dynamic_data_json(form_id, false); 842 }); 843 }); 844 }); 845 846 // --- Validation for submit buttons --- 847 EME.$$('.eme_submit_button').forEach(btn => { 848 btn.addEventListener('click', function(event) { 849 let valid = true; 850 const parent_form_id = this.form.id; 851 852 // Check required text inputs and date fields 853 EME.$$('input[type="text"]:not(:disabled)[required], input:not([type]):not(:disabled)[required], .eme_formfield_fdatetime:not(:disabled)[required], .eme_formfield_fdate:not(:disabled)[required]').forEach(input => { 854 if (valid && input.closest("form").id === parent_form_id) { 855 const val = input.value; 856 if (val.match(/^\s*$/)) { 857 eme_scrollToInvalidInput(input); 858 valid = false;
859 } else { 860 eme_removeClass(input, 'eme_required'); 861 } 862 } 863 }); 864 865 // Check required checkbox groups 866 EME.$$('.eme-checkbox-group-required').forEach(group => { 867 if (valid && group.closest("form").id === parent_form_id) { 868 const checked = group.querySelectorAll('input[type="checkbox"]:checked').length; 869 if (!checked) { 870 eme_scrollToInvalidInput(group); 871 valid = false; 872 } else { 873 eme_removeClass(group, 'eme_required'); 874 } 875 } 876 }); 877 878 if (!valid) { 879 event.preventDefault(); 880 event.stopPropagation(); // optional, prevent fire of other click-listeners 881 } 882 883 }); 884 }); 885 886 eme_attach_dynamic_handlers('[name=eme-rsvp-form]', true); 887 eme_attach_dynamic_handlers('#eme-rsvp-adminform', true); 888 eme_attach_dynamic_handlers('[name=eme-member-form]', false); 889 eme_attach_dynamic_handlers('#eme-member-adminform', false); 890 891 // Remember-me functionality: only active if a form contains the #_REMEMBERME checkbox 892 const rememberMeForms = EME.$$('form').filter(form => form.querySelector('input#eme_rememberme')); 893 if (rememberMeForms.length) { 894 rememberMeForms.forEach(form => { 895 if (localStorage.getItem('eme_rememberme') == 1) { 896 const checkbox = form.querySelector('input#eme_rememberme'); 897 if (checkbox) { 898 checkbox.checked = true; 899 } 900 } 901 ['lastname', 'firstname', 'email', 'phone', 'task_lastname', 'task_firstname', 'task_email', 'task_phone'].forEach(fieldName => { 902 const field = form.querySelector(`input[name=${fieldName}]`); 903 if (field && field.value === '') { 904 field.value = localStorage.getItem('eme_' + fieldName.replace('task_', '')) || ''; 905 } 906 }); 907 form.addEventListener('submit', function() { 908 const rememberCheckbox = this.querySelector('input#eme_rememberme'); 909 if (rememberCheckbox && rememberCheckbox.checked) { 910 localStorage.setItem('eme_rememberme', 1); 911 ['lastname', 'firstname', 'email', 'phone', 'task_lastname', 'task_firstname', 'task_email', 'task_phone'].forEach(fieldName => { 912 const field = this.querySelector(`input[name=${fieldName}]`); 913 if (field) { 914 localStorage.setItem('eme_' + fieldName.replace('task_', ''), field.value); 915 } 916 }); 917 } else { 918 ['eme_lastname', 'eme_firstname', 'eme_email', 'eme_phone', 'eme_rememberme'].forEach(storageKey => { 919 localStorage.removeItem(storageKey); 920 }); 921 } 922 }); 923 }); 924 } 925 926 // Person image upload widget 927 const personImageButton = document.getElementById('eme_person_image_button'); 928 if (personImageButton) { 929 const removeButton = document.getElementById('eme_person_remove_old_image'); 930 const imageIdField = document.getElementById('eme_person_image_id'); 931 const imageExample = document.getElementById('eme_person_image_example'); 932 const currentImageDiv = document.getElementById('eme_person_current_image'); 933 const noImageDiv = document.getElementById('eme_person_no_image'); 934 935 if (removeButton) { 936 removeButton.addEventListener('click', function() { 937 imageIdField.value = ''; 938 imageExample.src = ''; 939 eme_toggle(currentImageDiv, false); 940 eme_toggle(noImageDiv, true); 941 eme_toggle(removeButton, false); 942 personImageButton.value = emebasic.translate_chooseimg; 943 }); 944 } 945 946 personImageButton.addEventListener('click', function(e) { 947 e.preventDefault(); 948 if (typeof wp !== 'undefined' && wp.media) { 949 const custom_uploader = wp.media({ 950 title: emebasic.translate_selectimg, 951 button: { text: emebasic.translate_setimg }, 952 library: { type: 'image' }, 953 multiple: false 954 }).on('select', function() { 955 const attachment = custom_uploader.state().get('selection').first().toJSON(); 956 imageIdField.value = attachment.id; 957 imageExample.src = attachment.url; 958 eme_toggle(currentImageDiv, true); 959 eme_toggle(noImageDiv, false); 960 eme_toggle(removeButton, true); 961 personImageButton.value = emebasic.translate_replaceimg; 962 }).open(); 963 } 964 }); 965 966 // Initialize display state 967 if (parseInt(imageIdField.value) > 0) { 968 eme_toggle(noImageDiv, false); 969 eme_toggle(currentImageDiv, true); 970 eme_toggle(removeButton, true); 971 personImageButton.value = emebasic.translate_replaceimg; 972 } else { 973 eme_toggle(noImageDiv, true); 974 eme_toggle(currentImageDiv, false); 975 eme_toggle(removeButton, false); 976 personImageButton.value = emebasic.translate_chooseimg; 977 } 978 } 979 980 // Scroll to payment form if present 981 const paymentForm = document.getElementById('eme-payment-form'); 982 if (paymentForm) { 983 eme_scrollToEl(paymentForm); 984 } 985 986 // Initialize widgets 987 eme_init_widgets(); 988});
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.