1(function () { 2 'use strict'; 3 4 function qs(root, selector) { 5 return root ? root.querySelector(selector) : null; 6 } 7 8 function qsa(root, selector) { 9 return Array.prototype.slice.call(root ? root.querySelectorAll(selector) : []); 10 } 11 12 13 function initResultsCountrySelect(root, form) { 14 var wraps = qsa(root, '[data-appt-results-countryselect-field]'); 15 var $ = window.jQuery; 16 17 function parseJson(value) { 18 try { 19 var parsed = JSON.parse(value || '[]'); 20 return Array.isArray(parsed) ? parsed : []; 21 } catch (error) { 22 return []; 23 } 24 } 25 26 function normalizeCode(value) { 27 value = String(value == null ? '' : value).trim(); 28 return /^[a-z]{2}$/i.test(value) ? value.toUpperCase() : ''; 29 } 30 31 function fieldName(wrap) { 32 return wrap ? (wrap.getAttribute('data-filter-name') || '') : ''; 33 } 34 35 function visibleInput(wrap) { 36 return qs(wrap, '[data-appt-results-countryselect-input]'); 37 } 38 39 function hiddenInput(wrap) { 40 return qs(wrap, '[data-appt-results-country-code]'); 41 } 42 43 function clearButton(wrap) { 44 return qs(wrap, '[data-appt-results-countryselect-clear]'); 45 } 46 47 function countryList(wrap) { 48 return qs(wrap, '.country-list'); 49 } 50 51 function placeholder(wrap) { 52 return wrap ? (wrap.getAttribute('data-placeholder') || 'All Countries') : 'All Countries'; 53 } 54 55 function flagUrl(wrap, code) { 56 code = normalizeCode(code); 57 var base = wrap ? (wrap.getAttribute('data-flags-base') || '') : ''; 58 return code && base ? base.replace(/\/?$/, '/') + code + '.png' : ''; 59 } 60 61 function paintFlag(flag, wrap, code) { 62 if (!flag) return; 63 code = normalizeCode(code); 64 if (!code) { 65 flag.style.backgroundImage = ''; 66 flag.classList.add('flag--all'); 67 return; 68 } 69 flag.classList.remove('flag--all'); 70 var url = flagUrl(wrap, code); 71 flag.style.backgroundImage = url ? 'url("' + url + '")' : ''; 72 } 73 74 function ensureAllOption(wrap) { 75 var list = countryList(wrap); 76 if (!list) return null; 77 var existing = qs(list, '.appt-rv2-countryselect-all'); 78 if (existing) { 79 var existingName = qs(existing, '.country-name'); 80 if (existingName) existingName.textContent = placeholder(wrap); 81 return existing; 82 } 83 84 var item = document.createElement('li'); 85 item.className = 'appt-rv2-countryselect-all'; 86 item.setAttribute('role', 'option'); 87 item.setAttribute('data-country-code', ''); 88 item.innerHTML = '<span class="flag flag--all" aria-hidden="true"></span><span class="country-name"></span>'; 89 qs(item, '.country-name').textContent = placeholder(wrap); 90 list.insertBefore(item, list.firstChild); 91 return item; 92 } 93 94 function syncAllOption(wrap) { 95 var item = ensureAllOption(wrap); 96 var hidden = hiddenInput(wrap); 97 if (!item) return; 98 var active = !(hidden && normalizeCode(hidden.value)); 99 item.classList.toggle('active', active); 100 item.setAttribute('aria-selected', active ? 'true' : 'false'); 101 } 102 103 function paintFlags(wrap) { 104 ensureAllOption(wrap); 105 qsa(wrap, '.country-list .country').forEach(function (item) { 106 paintFlag(qs(item, '.flag'), wrap, item.getAttribute('data-country-code') || ''); 107 }); 108 syncAllOption(wrap); 109 } 110 111 function close(wrap) { 112 var list = countryList(wrap); 113 if (list) list.classList.add('hide'); 114 var arrow = qs(wrap, '.selected-flag .arrow'); 115 if (arrow) arrow.classList.remove('up'); 116 } 117 118 function open(wrap) { 119 if (!wrap || wrap.classList.contains('is-locked')) return; 120 var input = visibleInput(wrap); 121 var list = countryList(wrap); 122 var flag = qs(wrap, '.selected-flag'); 123 if (input && input.disabled) return; 124 125 if (list && list.classList.contains('hide')) { 126 /* Use the same jQuery trigger as Players & Teams. */ 127 if ($ && flag) { 128 $(flag).trigger('click'); 129 } 130 131 /* Reliable fallback for the custom chevron hit area. */ 132 if (list.classList.contains('hide')) { 133 list.classList.remove('hide'); 134 var arrow = qs(wrap, '.selected-flag .arrow'); 135 if (arrow) arrow.classList.add('up'); 136 } 137 } 138 139 window.setTimeout(function () { 140 ensureAllOption(wrap); 141 paintFlags(wrap); 142 }, 0); 143 } 144 145 function pluginCode(wrap) { 146 var input = visibleInput(wrap); 147 var hidden = hiddenInput(wrap); 148 if (!input) return ''; 149 var visible = String(input.value || '').trim(); 150 var stored = hidden ? normalizeCode(hidden.value) : ''; 151 if (!visible && !stored) return ''; 152 153 /* 154 * When changing directly from one country to another, the APPT hidden 155 * field can still contain the previous ISO code while countrySelect has 156 * already marked the new row as active. Always trust the active row first. 157 */ 158 var active = qs(wrap, '.country-list .country.active'); 159 var activeCode = visible && active 160 ? normalizeCode(active.getAttribute('data-country-code') || '') 161 : ''; 162 if (activeCode) return activeCode; 163 164 var generated = input.id ? document.getElementById(input.id + '_code') : null; 165 var generatedCode = generated ? normalizeCode(generated.value) : ''; 166 if (generatedCode) return generatedCode; 167 168 return stored; 169 } 170 171 function countryItemCode(item) { 172 return item ? normalizeCode(item.getAttribute('data-country-code') || '') : ''; 173 } 174 175 function countryItemLabel(item) { 176 if (!item) return ''; 177 var label = qs(item, '.country-name'); 178 return String(label ? label.textContent : item.textContent || '') 179 .replace(/\s+/g, ' ') 180 .trim(); 181 } 182 183 function selectCountryItem(wrap, item, triggerChange) { 184 var code = countryItemCode(item); 185 var input = visibleInput(wrap); 186 var hidden = hiddenInput(wrap); 187 if (!code || !input || !hidden) return false;
188 189 qsa(wrap, '.country-list .country').forEach(function (row) { 190 row.classList.remove('active', 'highlight'); 191 row.removeAttribute('aria-selected'); 192 }); 193 item.classList.add('active'); 194 item.setAttribute('aria-selected', 'true'); 195 196 input.value = countryItemLabel(item) || code; 197 input.setAttribute('data-country-code', code); 198 199 var generated = input.id ? document.getElementById(input.id + '_code') : null; 200 if (generated) generated.value = code.toLowerCase(); 201 202 hidden.value = code; 203 wrap.classList.add('has-value'); 204 var clear = clearButton(wrap); 205 if (clear) clear.hidden = false; 206 207 paintFlag(qs(wrap, '.selected-flag .flag'), wrap, code); 208 syncAllOption(wrap); 209 close(wrap); 210 211 if (triggerChange) { 212 hidden.dispatchEvent(new Event('change', { bubbles: true })); 213 } 214 215 return true; 216 } 217 218 function clearPlugin(wrap) { 219 var input = visibleInput(wrap); 220 if (!input) return; 221 input.value = ''; 222 var generated = input.id ? document.getElementById(input.id + '_code') : null; 223 if (generated) generated.value = ''; 224 qsa(wrap, '.country-list .country.active').forEach(function (item) { 225 item.classList.remove('active'); 226 item.removeAttribute('aria-selected'); 227 }); 228 paintFlag(qs(wrap, '.selected-flag .flag'), wrap, ''); 229 syncAllOption(wrap); 230 } 231 232 function setValue(name, value, triggerChange) { 233 var code = normalizeCode(value); 234 wraps.forEach(function (wrap) { 235 if (fieldName(wrap) !== name) return; 236 var input = visibleInput(wrap); 237 var hidden = hiddenInput(wrap); 238 var clear = clearButton(wrap); 239 240 if (hidden) hidden.value = code; 241 wrap.classList.toggle('has-value', !!code); 242 if (clear) clear.hidden = !code; 243 244 if ($ && $.fn && typeof $.fn.countrySelect === 'function' && input && input.dataset.apptResultsCountryReady === '1') { 245 try { 246 if (code) $(input).countrySelect('setCountry', code.toLowerCase()); 247 else clearPlugin(wrap); 248 } catch (error) { 249 if (!code) clearPlugin(wrap); 250 } 251 } else if (!code) { 252 clearPlugin(wrap); 253 } 254 255 paintFlag(qs(wrap, '.selected-flag .flag'), wrap, code); 256 syncAllOption(wrap); 257 258 if (triggerChange && hidden) { 259 hidden.dispatchEvent(new Event('change', { bubbles: true })); 260 } 261 }); 262 } 263 264 function sync() { 265 wraps.forEach(function (wrap) { 266 var hidden = hiddenInput(wrap); 267 if (!hidden) return; 268 var code = pluginCode(wrap); 269 if (code || !String(visibleInput(wrap).value || '').trim()) hidden.value = code; 270 wrap.classList.toggle('has-value', !!hidden.value); 271 var clear = clearButton(wrap); 272 if (clear) clear.hidden = !hidden.value; 273 paintFlag(qs(wrap, '.selected-flag .flag'), wrap, hidden.value); 274 syncAllOption(wrap); 275 }); 276 } 277 278 function init() { 279 if (!wraps.length) return true; 280 if (!$ || !$.fn || typeof $.fn.countrySelect !== 'function') return false; 281 282 wraps.forEach(function (wrap) { 283 var input = visibleInput(wrap); 284 var hidden = hiddenInput(wrap); 285 if (!input || !hidden || input.dataset.apptResultsCountryReady === '1') return; 286 input.dataset.apptResultsCountryReady = '1'; 287 288 var onlyCountries = parseJson(wrap.getAttribute('data-only-countries')) 289 .map(function (code) { return String(code || '').toLowerCase(); }) 290 .filter(function (code) { return /^[a-z]{2}$/.test(code); }); 291 292 $(input).countrySelect({ 293 preferredCountries: [], 294 responsiveDropdown: true, 295 onlyCountries: onlyCountries.length ? onlyCountries : undefined 296 }); 297 298 var instance = $.data(input, 'plugin_countrySelect'); 299 if (instance && instance.ns) $(input).off('blur' + instance.ns); 300 301 $(input).on('click.apptResultsCountry', function (event) { 302 event.preventDefault(); 303 event.stopPropagation(); 304 open(wrap); 305 }); 306 307 $(input).on('focus.apptResultsCountry', function () { 308 window.setTimeout(function () { paintFlags(wrap); }, 0); 309 }); 310 311 $(wrap).on('click.apptResultsCountry', function (event) { 312 var target = event.target; 313 if (target && target.closest && target.closest('[data-appt-results-countryselect-clear]')) return; 314 if (target && target.closest && target.closest('.appt-rv2-countryselect-all')) return; 315 // Let the plugin process its selected flag exactly once. 316 if (target && target.closest && target.closest('.selected-flag')) return; 317 if (target && target.closest && target.closest('.country-list')) return; 318 event.preventDefault(); 319 event.stopPropagation(); 320 open(wrap); 321 input.focus(); 322 }); 323 324 $(wrap).on('click.apptResultsCountry', '.selected-flag', function () { 325 window.setTimeout(function () { paintFlags(wrap); }, 0); 326 }); 327 328 /* 329 * Same stable selection flow used by Players & Teams: commit the new 330 * ISO code on mousedown, before countrySelect's blur/click sequence can 331 * expose the previous hidden value to the AJAX form serializer. 332 */ 333 $(wrap).on('mousedown.apptResultsCountry', '.country-list .country', function (event) { 334 event.preventDefault(); 335 event.stopPropagation(); 336 if (typeof event.stopImmediatePropagation === 'function') { 337 event.stopImmediatePropagation(); 338 } 339 selectCountryItem(wrap, this, true); 340 }); 341 342 $(wrap).on('click.apptResultsCountry', '.country-list .country', function (event) { 343 event.preventDefault(); 344 event.stopPropagation(); 345 if (typeof event.stopImmediatePropagation === 'function') { 346 event.stopImmediatePropagation(); 347 } 348 }); 349 350 $(wrap).on('mousedown.apptResultsCountry click.apptResultsCountry', '.appt-rv2-countryselect-all', function (event) { 351 event.preventDefault(); 352 event.stopPropagation(); 353 event.stopImmediatePropagation(); 354 clearPlugin(wrap); 355 setValue(fieldName(wrap), '', true); 356 close(wrap); 357 }); 358 359 $(input).on('change.apptResultsCountry', function () { 360 var code = pluginCode(wrap); 361 hidden.value = code; 362 wrap.classList.toggle('has-value', !!code); 363 var clear = clearButton(wrap); 364 if (clear) clear.hidden = !code; 365 paintFlag(qs(wrap, '.selected-flag .flag'), wrap, code); 366 syncAllOption(wrap); 367 hidden.dispatchEvent(new Event('change', { bubbles: true })); 368 }); 369 370 $(input).on('keydown.apptResultsCountry', function (event) { 371 if (event.key === 'Escape') setValue(fieldName(wrap), '', true); 372 }); 373 374 setValue(fieldName(wrap), hidden.value || wrap.getAttribute('data-selected-code') || '', false); 375 window.setTimeout(function () { paintFlags(wrap); }, 0); 376 }); 377 378 return true; 379 } 380 381 var ready = init(); 382 if (!ready) { 383 window.setTimeout(init, 250); 384 window.addEventListener('load', init, { once: true }); 385 } 386 387 form.addEventListener('click', function (event) { 388 var clear = event.target.closest('[data-appt-results-countryselect-clear]'); 389 if (!clear) return; 390 event.preventDefault(); 391 event.stopPropagation(); 392 var wrap = clear.closest('[data-appt-results-countryselect-field]'); 393 if (wrap) setValue(fieldName(wrap), '', true); 394 }); 395 396 return { 397 setValue: setValue, 398 sync: sync, 399 text: function (name) { 400 var found = ''; 401 wraps.some(function (wrap) { 402 if (fieldName(wrap) !== name) return false;
403 var input = visibleInput(wrap); 404 found = input ? String(input.value || '').trim() : ''; 405 return true; 406 }); 407 return found; 408 } 409 }; 410 } 411 412 function isMobile() { 413 return !window.matchMedia || window.matchMedia('(max-width: 900px)').matches; 414 } 415 416 function showLoader(content) { 417 if (!content) return; 418 content.classList.add('is-loading'); 419 if (!qs(content, '.appt-rv2-loader')) { 420 var loader = document.createElement('div'); 421 loader.className = 'appt-rv2-loader'; 422 loader.setAttribute('role', 'status'); 423 loader.setAttribute('aria-live', 'polite'); 424 loader.innerHTML = '<i aria-hidden="true"></i><strong>' + ((window.apptResultsV2 && window.apptResultsV2.loading) || 'Loading results...') + '</strong><span>Please wait while we refresh the results.</span>'; 425 content.appendChild(loader); 426 } 427 } 428 429 function hideLoader(content) { 430 if (!content) return; 431 content.classList.remove('is-loading'); 432 var loader = qs(content, '.appt-rv2-loader'); 433 if (loader) loader.remove(); 434 } 435 436 437 function setFilterLoading(root, loading) { 438 if (!root) return; 439 var loader = qs(root, '[data-appt-results-filter-loader]'); 440 root.classList.toggle('is-filter-loading', !!loading); 441 root.setAttribute('aria-busy', loading ? 'true' : 'false'); 442 if (loader) { 443 loader.classList.toggle('is-visible', !!loading); 444 loader.setAttribute('aria-hidden', loading ? 'false' : 'true'); 445 } 446 } 447 448 function fieldValue(form, name) { 449 if (!form) return ''; 450 var radio = qs(form, 'input[type="radio"][name="' + name + '"]:checked'); 451 if (radio) return radio.value || ''; 452 var field = form.elements[name]; 453 return field ? String(field.value || '') : ''; 454 } 455 456 function fieldText(form, name) { 457 if (!form) return ''; 458 var radio = qs(form, 'input[type="radio"][name="' + name + '"]:checked'); 459 if (radio) { 460 var label = radio.closest('label'); 461 return label ? String(label.textContent || '').replace(/\s+/g, ' ').trim() : radio.value; 462 } 463 var field = form.elements[name]; 464 if (name === 'country' && field) { 465 var countryWrap = field.closest('[data-appt-results-countryselect-field]'); 466 var countryInput = qs(countryWrap, '[data-appt-results-countryselect-input]'); 467 if (countryInput && String(countryInput.value || '').trim()) { 468 return String(countryInput.value || '').trim(); 469 } 470 } 471 if (field && field.tagName === 'SELECT') { 472 var option = field.options[field.selectedIndex]; 473 return option ? String(option.textContent || '').trim() : ''; 474 } 475 return field ? String(field.value || '').trim() : ''; 476 } 477 478 function updateResultsUrl(root) { 479 if (!root || !window.history || !window.history.replaceState) return; 480 var form = qs(root, '[data-appt-results-form]'); 481 if (!form) return; 482 var url = new URL(window.location.href); 483 ['search','year','level','country','gender','start','end','surface','category','tab','sort'].forEach(function (name) { 484 var value = fieldValue(form, name); 485 if (!value || value === 'all' || (name === 'tab' && value === 'tournaments') || (name === 'sort' && value === 'recent')) { 486 url.searchParams.delete(name); 487 } else { 488 url.searchParams.set(name, value); 489 } 490 }); 491 // 'rpage', not 'page': 'page' is a WordPress-reserved query var that 492 // gets intercepted/stripped by redirect_canonical() before the PHP 493 // shortcode ever sees it (see appt_results_v2_sanitize_filters() in 494 // shortcode_results_v2.php for the full explanation). Keep this in 495 // sync so a direct load of a shared/bookmarked ?rpage=N URL and an 496 // in-page click land on the same state. 497 var pageValue = fieldValue(form, 'rpage'); 498 if (!pageValue || pageValue === '1') { 499 url.searchParams.delete('rpage'); 500 } else { 501 url.searchParams.set('rpage', pageValue); 502 } 503 window.history.replaceState(null, '', url.toString()); 504 } 505 506 function updateActiveFilters(root) { 507 var form = qs(root, '[data-appt-results-form]'); 508 var target = qs(root, '[data-appt-results-active-filters]'); 509 if (!form || !target) return; 510 var definitions = [ 511 ['search', 'Search'], ['year', 'Year'], ['level', 'Level'], ['country', 'Country'], 512 ['gender', 'Gender'], ['start', 'From'], ['end', 'To'], ['surface', 'Surface'], ['category', 'Category'] 513 ]; 514 var active = []; 515 definitions.forEach(function (item) { 516 var value = fieldValue(form, item[0]); 517 if (!value || value === 'all') return; 518 active.push({ name: item[0], label: item[1] + ': ' + fieldText(form, item[0]) }); 519 }); 520 target.innerHTML = ''; 521 var title = document.createElement('span'); 522 title.textContent = 'Active filters'; 523 target.appendChild(title); 524 if (!active.length) { 525 var empty = document.createElement('em'); 526 empty.textContent = 'No active filters'; 527 target.appendChild(empty); 528 return; 529 } 530 active.forEach(function (item) { 531 var button = document.createElement('button'); 532 button.type = 'button'; 533 button.setAttribute('data-appt-results-clear-filter', item.name); 534 button.textContent = item.label + ' Ã'; 535 target.appendChild(button); 536 }); 537 } 538 539 function setPage(root, page) { 540 var input = qs(root, '[data-appt-results-current-page]');
541 if (input) input.value = String(page || 1); 542 } 543 544 function setTab(root, tab) { 545 var input = qs(root, '[data-appt-results-current-tab]'); 546 if (input) input.value = tab || 'tournaments'; 547 } 548 549 function syncSortToForm(root, value) { 550 var form = qs(root, '[data-appt-results-form]'); 551 if (!form) return; 552 var existing = qs(form, 'input[name="sort"]'); 553 if (!existing) { 554 existing = document.createElement('input'); 555 existing.type = 'hidden'; 556 existing.name = 'sort'; 557 form.appendChild(existing); 558 } 559 existing.value = value || 'recent'; 560 } 561 562 function serializeForm(root) { 563 var form = qs(root, '[data-appt-results-form]'); 564 if (root && root._apptResultsCountryApi && typeof root._apptResultsCountryApi.sync === 'function') { 565 root._apptResultsCountryApi.sync(); 566 } 567 var data = new FormData(form || undefined); 568 if (!data.get('action')) data.set('action', 'appt_results_v2_filter'); 569 if (!data.get('nonce') && window.apptResultsV2) data.set('nonce', window.apptResultsV2.nonce || ''); 570 return data; 571 } 572 573 function scrollIntoResults(root) { 574 if (!isMobile()) return; 575 var target = qs(root, '.appt-rv2-tabsbar') || qs(root, '[data-appt-results-content]') || root; 576 window.setTimeout(function () { 577 var rect = target.getBoundingClientRect(); 578 var adminBar = document.getElementById('wpadminbar'); 579 var offset = adminBar && adminBar.offsetHeight ? adminBar.offsetHeight : 0; 580 window.scrollTo({ 581 top: Math.max(0, window.pageYOffset + rect.top - offset - 10), 582 behavior: 'smooth' 583 }); 584 }, 80); 585 } 586 587 588 function isElementActuallyVisible(el) { 589 if (!el) return false; 590 var style = window.getComputedStyle ? window.getComputedStyle(el) : null; 591 if (style && (style.display === 'none' || style.visibility === 'hidden')) return false; 592 var rect = el.getBoundingClientRect(); 593 return rect.width > 0 && rect.height > 0; 594 } 595 596 function getActiveRoots() { 597 var roots = qsa(document, '[data-appt-results-root]'); 598 if (roots.length <= 1) return roots; 599 600 var preferred = null; 601 roots.some(function (root) { 602 if (isElementActuallyVisible(root)) { 603 preferred = root; 604 return true; 605 } 606 return false; 607 }); 608 preferred = preferred || roots[0]; 609 610 roots.forEach(function (root) { 611 if (root === preferred) { 612 root.hidden = false; 613 root.removeAttribute('aria-hidden'); 614 root.classList.remove('is-duplicate-root'); 615 } else { 616 root.hidden = true; 617 root.setAttribute('aria-hidden', 'true'); 618 root.classList.add('is-duplicate-root'); 619 } 620 }); 621 622 return [preferred]; 623 } 624 625 function dedupeRenderedTournaments(root) { 626 if (!root) return; 627 var seen = Object.create(null); 628 qsa(root, '.appt-rv2-card[data-appt-results-tournament-id]').forEach(function (card) { 629 var id = card.getAttribute('data-appt-results-tournament-id') || ''; 630 if (!id) return; 631 if (seen[id]) { 632 card.parentNode && card.parentNode.removeChild(card); 633 return; 634 } 635 seen[id] = true; 636 }); 637 } 638 639 function loadResults(root, opts) { 640 opts = opts || {}; 641 if (!root || !window.fetch || !window.apptResultsV2) return; 642 643 var content = qs(root, '[data-appt-results-content]'); 644 if (!content) return; 645 646 setFilterLoading(root, true); 647 648 if (root._apptResultsAbortController) { 649 root._apptResultsAbortController.abort(); 650 } 651 var controller = window.AbortController ? new AbortController() : null; 652 root._apptResultsAbortController = controller; 653 root._apptResultsRequestId = (root._apptResultsRequestId || 0) + 1; 654 var requestId = root._apptResultsRequestId; 655 656 fetch(window.apptResultsV2.ajaxUrl, { 657 method: 'POST', 658 credentials: 'same-origin', 659 body: serializeForm(root), 660 signal: controller ? controller.signal : undefined 661 }) 662 .then(function (response) { return response.json(); }) 663 .then(function (response) { 664 if (requestId !== root._apptResultsRequestId) return; 665 if (!response || !response.success || !response.data || typeof response.data.html !== 'string') { 666 throw new Error('Invalid results response'); 667 } 668 content.innerHTML = response.data.html; 669 dedupeRenderedTournaments(root); 670 initDynamic(root); 671 updateActiveFilters(root); 672 updateResultsUrl(root); 673 if (opts.scroll) scrollIntoResults(root); 674 }) 675 .catch(function (error) {
676 if (error && error.name === 'AbortError') return; 677 if (requestId !== root._apptResultsRequestId) return; 678 content.innerHTML = '<div class="appt-rv2-empty-state"><strong>Results could not be loaded</strong><span>Please refresh the page and try again.</span></div>'; 679 }) 680 .finally(function () { 681 if (requestId === root._apptResultsRequestId) setFilterLoading(root, false); 682 }); 683 } 684 685 function initCarousel(root) { 686 qsa(root, '[data-appt-results-carousel]').forEach(function (carousel) { 687 if (carousel.dataset.apptCarouselReady === '1') return; 688 carousel.dataset.apptCarouselReady = '1'; 689 690 var track = qs(carousel, '[data-appt-carousel-track]'); 691 var prev = qs(carousel, '[data-appt-carousel-prev]'); 692 var next = qs(carousel, '[data-appt-carousel-next]'); 693 if (!track) return; 694 695 function step() { 696 var card = qs(track, '.appt-rv2-champ-card'); 697 return card ? Math.max(220, card.getBoundingClientRect().width + 16) : 260; 698 } 699 700 if (prev) { 701 prev.addEventListener('click', function () { 702 track.scrollBy({ left: -step(), behavior: 'smooth' }); 703 }); 704 } 705 if (next) { 706 next.addEventListener('click', function () { 707 track.scrollBy({ left: step(), behavior: 'smooth' }); 708 }); 709 } 710 }); 711 } 712 713 714 function centerActiveTab(root, behavior) { 715 if (!root || !isMobile()) return; 716 var tabs = qs(root, '.appt-rv2-tabs'); 717 var active = qs(tabs, 'button.is-active') || qs(tabs, 'button'); 718 if (!tabs || !active) return; 719 window.setTimeout(function () { 720 var left = active.offsetLeft - ((tabs.clientWidth - active.offsetWidth) / 2); 721 left = Math.max(0, left); 722 if (typeof tabs.scrollTo === 'function') { 723 tabs.scrollTo({ left: left, behavior: behavior || 'smooth' }); 724 } else { 725 tabs.scrollLeft = left; 726 } 727 }, 30); 728 } 729 730 function scrollFiltersIntoView(root) { 731 if (!root || !isMobile()) return; 732 var filters = qs(root, '.appt-rv2-filters'); 733 if (!filters) return; 734 window.setTimeout(function () { 735 var rect = filters.getBoundingClientRect(); 736 var adminBar = document.getElementById('wpadminbar'); 737 var offset = adminBar && adminBar.offsetHeight ? adminBar.offsetHeight : 0; 738 if (rect.top < 0 || rect.top > window.innerHeight * .58) { 739 window.scrollTo({ top: Math.max(0, window.pageYOffset + rect.top - offset - 10), behavior: 'smooth' }); 740 } 741 }, 70); 742 } 743 744 function initTabsAndSort(root) { 745 qsa(root, '[data-appt-results-tab]').forEach(function (button) { 746 if (button.dataset.apptTabReady === '1') return; 747 button.dataset.apptTabReady = '1'; 748 button.addEventListener('click', function () { 749 var tab = button.getAttribute('data-appt-results-tab') || 'tournaments'; 750 setTab(root, tab); 751 setPage(root, 1); 752 centerActiveTab(root, 'smooth'); 753 754 window.dataLayer = window.dataLayer || []; 755 window.dataLayer.push({ event: 'results_tab_click', tab: tab }); 756 757 loadResults(root, { scroll: true }); 758 }); 759 }); 760 761 qsa(root, '[data-appt-results-sort]').forEach(function (select) { 762 if (select.dataset.apptSortReady === '1') return; 763 select.dataset.apptSortReady = '1'; 764 select.addEventListener('change', function () { 765 syncSortToForm(root, select.value || 'recent'); 766 setPage(root, 1); 767 loadResults(root, { scroll: false }); 768 }); 769 }); 770 771 qsa(root, '[data-appt-results-page]').forEach(function (link) { 772 if (link.dataset.apptPageReady === '1') return; 773 link.dataset.apptPageReady = '1'; 774 // These are real <a href="?...&rpage=N"> now (crawlable), not plain 775 // buttons â intercept the click to keep the existing no-reload AJAX 776 // UX; the href stays there and fully functional for direct 777 // navigation, middle-click-to-open-in-new-tab, and crawlers. 778 link.addEventListener('click', function (event) { 779 if (link.getAttribute('aria-disabled') === 'true') { event.preventDefault(); return; } 780 event.preventDefault(); 781 var page = link.getAttribute('data-appt-results-page') || 1; 782 setPage(root, page); 783 784 window.dataLayer = window.dataLayer || []; 785 window.dataLayer.push({ event: 'results_pagination_click', page: page }); 786 787 loadResults(root, { scroll: true }); 788 }); 789 }); 790 } 791 792 function initAccordions(root) { 793 qsa(root, '.appt-rv2-card').forEach(function (details) { 794 if (details.dataset.apptDetailsReady === '1') return; 795 details.dataset.apptDetailsReady = '1'; 796 details.addEventListener('toggle', function () {
797 if (!details.open) return; 798 799 window.dataLayer = window.dataLayer || []; 800 window.dataLayer.push({ 801 event: 'results_card_expand', 802 tournament_id: details.getAttribute('data-appt-results-tournament-id') || '' 803 }); 804 805 if (!isMobile()) return; 806 window.setTimeout(function () { 807 var rect = details.getBoundingClientRect(); 808 var adminBar = document.getElementById('wpadminbar'); 809 var offset = adminBar && adminBar.offsetHeight ? adminBar.offsetHeight : 0; 810 if (rect.top < 0 || rect.top > window.innerHeight * 0.75) { 811 window.scrollTo({ top: Math.max(0, window.pageYOffset + rect.top - offset - 12), behavior: 'smooth' }); 812 } 813 }, 60); 814 }); 815 }); 816 } 817 818 function initDynamic(root) { 819 dedupeRenderedTournaments(root); 820 initCarousel(root); 821 initTabsAndSort(root); 822 initAccordions(root); 823 centerActiveTab(root, 'auto'); 824 } 825 826 function initFilters(root) { 827 var form = qs(root, '[data-appt-results-form]'); 828 if (!form || form.dataset.apptFiltersReady === '1') return; 829 form.dataset.apptFiltersReady = '1'; 830 831 var advanced = qs(form, '[data-appt-results-advanced]'); 832 var timer = null; 833 var countryApi = initResultsCountrySelect(root, form); 834 root._apptResultsCountryApi = countryApi; 835 836 function setAdvanced(open) { 837 if (!advanced) return; 838 advanced.classList.toggle('is-open', !!open); 839 qsa(form, '[data-appt-results-toggle-advanced]').forEach(function (button) { 840 button.setAttribute('aria-expanded', open ? 'true' : 'false'); 841 }); 842 } 843 844 function request(scroll) { 845 setPage(root, 1); 846 updateActiveFilters(root); 847 848 var fieldValue = function (name) { 849 var field = form.elements[name]; 850 return field ? String(field.value || '') : ''; 851 }; 852 var start = fieldValue('start'); 853 var end = fieldValue('end'); 854 855 window.dataLayer = window.dataLayer || []; 856 window.dataLayer.push({ 857 event: 'results_filter_applied', 858 search_term: fieldValue('search'), 859 gender: fieldValue('gender'), 860 date_range: (start || end) ? (start + (start && end ? ' - ' : '') + end) : '', 861 country_code: fieldValue('country'), 862 sort: fieldValue('sort') || 'recent' 863 }); 864 865 loadResults(root, { scroll: !!scroll }); 866 } 867 868 function clearFilter(name) { 869 if (name === 'country' && countryApi) { 870 countryApi.setValue('country', '', false); 871 return; 872 } 873 var radios = qsa(form, 'input[type="radio"][name="' + name + '"]'); 874 if (radios.length) { 875 radios.forEach(function (radio) { radio.checked = radio.value === 'all'; }); 876 return; 877 } 878 var field = form.elements[name]; 879 if (!field) return; 880 if (field.tagName === 'SELECT') field.value = 'all'; 881 else field.value = ''; 882 } 883 884 form.addEventListener('submit', function (event) { 885 event.preventDefault(); 886 request(true); 887 }); 888 889 form.addEventListener('change', function (event) { 890 if (!event.target.closest('[data-appt-results-input]')) return; 891 request(false); 892 }); 893 894 form.addEventListener('input', function (event) { 895 var field = event.target.closest('input[type="search"][data-appt-results-input]'); 896 if (!field) return; 897 window.clearTimeout(timer); 898 timer = window.setTimeout(function () { request(false); }, 320); 899 }); 900 901 form.addEventListener('click', function (event) { 902 var toggle = event.target.closest('[data-appt-results-toggle-advanced]'); 903 if (toggle) { 904 event.preventDefault(); 905 setAdvanced(!advanced.classList.contains('is-open')); 906 return; 907 } 908 909 var reset = event.target.closest('[data-appt-results-reset]'); 910 if (reset) { 911 event.preventDefault(); 912 var defaultYear = form.getAttribute('data-appt-results-default-year') || ''; 913 ['search','start','end'].forEach(function (name) { if (form.elements[name]) form.elements[name].value = ''; }); 914 ['level','surface','category'].forEach(function (name) { if (form.elements[name]) form.elements[name].value = 'all'; }); 915 if (countryApi) countryApi.setValue('country', '', false); 916 if (form.elements.year) form.elements.year.value = defaultYear || 'all'; 917 qsa(form, 'input[type="radio"][name="gender"]').forEach(function (radio) { radio.checked = radio.value === 'all'; }); 918 setTab(root, 'tournaments'); 919 setPage(root, 1); 920 syncSortToForm(root, 'recent'); 921 setAdvanced(false); 922 request(true); 923 return; 924 } 925 926 var clear = event.target.closest('[data-appt-results-clear-filter]'); 927 if (clear) { 928 event.preventDefault(); 929 clearFilter(clear.getAttribute('data-appt-results-clear-filter') || ''); 930 request(false); 931 } 932 }); 933 934 updateActiveFilters(root); 935 } 936 937 938 939 function initRoot(root) { 940 if (!root) return; 941 initFilters(root); 942 initDynamic(root); 943 updateActiveFilters(root); 944 } 945 946 function boot() {
947 getActiveRoots().forEach(initRoot); 948 } 949 950 if (document.readyState === 'loading') { 951 document.addEventListener('DOMContentLoaded', boot); 952 } else { 953 boot(); 954 } 955})();
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.