1// Safe wrapper around decodeURIComponent: some exhibitor fields (e.g. profile 2// text containing a literal "%") are not valid percent-encoded strings and make 3// the native decodeURIComponent throw "URIError: URI malformed", which aborts 4// the whole list render. Fall back to the raw string when decoding fails. 5function safeDecodeURIComponent(str) { 6 if (str == null) return ''; 7 try { 8 return decodeURIComponent(str); 9 } catch (e) { 10 return String(str); 11 } 12} 13 14function escapeHtml(str) { 15 return String(str == null ? '' : str) 16 .replace(/&/g, '&') 17 .replace(/</g, '<') 18 .replace(/>/g, '>') 19 .replace(/"/g, '"') 20 .replace(/'/g, '''); 21} 22 23// Sjednotà hodnotu haly na stabilnà klÃÄ. Data jsou nekonzistentnà - hala je 24// zadaná jako "1" i jako "Hala 1"; obÄ vrátà "1". Pojmenované plochy 25// ("Vstupnà hala I", "Venkovnà plocha") se zachovajà beze zmÄny. 26// Musà odpovÃdat normalizaci v exhibition-plan.php. 27function normalizeHallKey(value) { 28 var s = safeDecodeURIComponent(value).replace(/\+/g, ' ').replace(/\s+/g, ' ').trim(); 29 var m = s.match(/^(?:hala\s*)?(\d+)$/i); 30 return m ? m[1] : s; 31} 32 33var iconCache = {}; 34var oboryFullData = null; 35var pendingExhibitorRestore = null; 36var planHallsData = null; 37var allExhibitorsData = null; 38var mainPlanImageUrl = ''; 39var pendingHallRestore = null; 40var destinationsHierarchy = null; 41var pendingDestinationRestore = null; 42var catalogSources = Array.isArray(customBlockData.catalogSources) && customBlockData.catalogSources.length > 0 43 ? customBlockData.catalogSources 44 : [{ key: 'default', label: 'Aktuálnà veletrh', web_id: 0, web_url: '' }]; 45var defaultCatalogSourceKey = customBlockData.defaultCatalogSource || 'default'; 46 47function isValidCatalogSourceKey(key) { 48 return catalogSources.some(function (source) { 49 return source && source.key === key; 50 }); 51} 52 53function resolveCatalogSourceKeyFromUrl() { 54 var urlParams = new URLSearchParams(window.location.search); 55 var requestedKey = urlParams.get('catalog'); 56 return isValidCatalogSourceKey(requestedKey) ? requestedKey : defaultCatalogSourceKey; 57} 58 59var currentCatalogSourceKey = resolveCatalogSourceKeyFromUrl(); 60 61function ensurePlanMapLightbox($context, imageUrl, title) { 62 if (!$context || $context.length === 0) return; 63 64 var $mapImg = $context.find('.plan-map-image').first(); 65 if ($mapImg.length === 0) return; 66 67 var src = imageUrl || $mapImg.attr('src'); 68 if (!src) return; 69 70 var $link = $mapImg.parent('a.plan-map-lightbox'); 71 if ($link.length === 0) { 72 $mapImg.wrap('<a class="plan-map-lightbox" data-lightbox="catalog-plan-map" href="#"></a>'); 73 $link = $mapImg.parent('a.plan-map-lightbox'); 74 } 75 76 $link.attr({ 77 href: src, 78 'data-lightbox': 'catalog-plan-map', 79 'data-title': title || 'Plánek výstaviÅ¡tÄ', 80 'aria-label': 'OtevÅÃt plánek ve vÄtÅ¡Ãm náhledu' 81 }).css('display', 'block'); 82 83 $mapImg.css('cursor', 'zoom-in'); 84} 85 86function syncPlanScrollablePanels($panel) { 87 if (!$panel || $panel.length === 0) return; 88 89 var $scrollPanels = $panel.find('.plan-halls-grid, .plan-hall-detail'); 90 if ($scrollPanels.length === 0) return; 91 92 var isMobile = window.matchMedia('(max-width: 767px)').matches; 93 94 if (isMobile) { 95 $scrollPanels.css({ 96 'max-height': 'none', 97 'overflow-y': 'visible' 98 }); 99 return; 100 } 101 102 var imgHeight = $panel.find('.plan-map-image').height(); 103 $scrollPanels.css('overflow-y', 'auto'); 104 105 if (imgHeight > 0) { 106 $scrollPanels.css('max-height', imgHeight + 'px'); 107 } else { 108 $scrollPanels.css('max-height', ''); 109 } 110} 111 112function updateCatalogUrl(params) { 113 var url = new URL(window.location); 114 ['catalog', 'tab', 'obor', 'country', 'brand', 'destination', 'continent', 'exhibitor', 'hall'].forEach(function (key) { 115 url.searchParams.delete(key); 116 }); 117 if (isValidCatalogSourceKey(currentCatalogSourceKey) && currentCatalogSourceKey !== defaultCatalogSourceKey) { 118 url.searchParams.set('catalog', currentCatalogSourceKey); 119 }
120 Object.keys(params).forEach(function (key) { 121 if (params[key] != null && params[key] !== '') { 122 url.searchParams.set(key, params[key]); 123 } 124 }); 125 history.replaceState(null, '', url.toString()); 126} 127 128// Sestavà URL na katalog se zadanými filtry (zachová page_id apod., pÅepÃÅ¡e jen filtrovacà parametry). 129function buildCatalogFilterUrl(params) { 130 var url = new URL(window.location); 131 ['catalog', 'tab', 'obor', 'country', 'brand', 'destination', 'continent', 'exhibitor', 'hall'].forEach(function (key) { 132 url.searchParams.delete(key); 133 }); 134 if (isValidCatalogSourceKey(currentCatalogSourceKey) && currentCatalogSourceKey !== defaultCatalogSourceKey) { 135 url.searchParams.set('catalog', currentCatalogSourceKey); 136 } 137 Object.keys(params).forEach(function (key) { 138 if (params[key] != null && params[key] !== '') { 139 url.searchParams.set(key, params[key]); 140 } 141 }); 142 return url.toString(); 143} 144 145function renderCatalogSourceTabs() { 146 if (!Array.isArray(catalogSources) || catalogSources.length <= 1) return; 147 148 $('.content-container--katalog-vystavovatelu .newsletter-card-wrapper').each(function () { 149 var $wrapper = $(this); 150 var $tabsHost = $wrapper.parent(); 151 if ($tabsHost.find('.js-katalog-source-tabs').length > 0) return; 152 153 var html = '<div class="catalog-source-tabs-shell"><div class="catalog-source-tabs js-katalog-source-tabs" role="tablist" aria-label="PÅepnutà katalogu vystavovatelů">'; 154 catalogSources.forEach(function (source) { 155 if (!source || !source.key) return; 156 var activeClass = source.key === currentCatalogSourceKey ? ' is-active' : ''; 157 var isSelected = source.key === currentCatalogSourceKey ? 'true' : 'false'; 158 html += '<button type="button" role="tab" aria-selected="' + isSelected + '" class="catalog-source-tab js-katalog-source-tab' + activeClass + '" data-catalog-source="' + escapeHtml(source.key) + '">' + escapeHtml(source.label || source.key) + '</button>'; 159 }); 160 html += '</div></div>'; 161 162 $tabsHost.prepend(html); 163 }); 164} 165 166// Najde kontinent, pod který destinace patÅà (podle názvu) v naÄtené hierarchii destinacÃ. 167function getContinentForDestination(destName) { 168 if (!destinationsHierarchy) return ''; 169 var found = ''; 170 Object.keys(destinationsHierarchy).forEach(function (continentName) { 171 if (found) return; 172 var dests = destinationsHierarchy[continentName] || []; 173 for (var i = 0; i < dests.length; i++) { 174 if (dests[i].name === destName) { found = continentName; break; } 175 } 176 }); 177 return found; 178} 179 180// PÅepne katalog na daný tab a obnovà zobrazenà seznamu (in-place, bez reloadu). 181// Zrcadlà reset logiku z initKatalogTabs i pÅepnutà is-active z toggle-znacky.js, 182// funguje spolehlivÄ i když je cÃlový tab už aktivnÃ. Vracà $list-with-filter. 183function activateKatalogTab($interactableWrapper, tab) { 184 var $tabsWrap = $interactableWrapper.find('.js-katalog-tabs'); 185 if ($tabsWrap.length === 0) { 186 $tabsWrap = $interactableWrapper.closest('.newsletter-card-wrapper').find('.js-katalog-tabs'); 187 } 188 // Aktivnà tab (jako toggle-znacky.js) 189 $tabsWrap.find('[data-tab]').removeClass('is-active'); 190 $tabsWrap.find('[data-tab="' + tab + '"]').addClass('is-active'); 191 $tabsWrap.find('label[data-tab="' + tab + '"] input[type="radio"]').prop('checked', true); 192 193 var $listFilter = $interactableWrapper.find('.list-with-filter'); 194 195 // Reset: skrýt detaily, zobrazit seznam (jako initKatalogTabs) 196 $interactableWrapper.find('.field-detail-wrapper').remove(); 197 $interactableWrapper.find('.obory-nav').remove(); 198 $interactableWrapper.find('.obory-exhibitors').remove(); 199 $interactableWrapper.find('.plan-hall-topbar').remove(); 200 $interactableWrapper.find('.plan-hall-detail').remove(); 201 $interactableWrapper.find('.company-detail-wrapper-new').hide(); 202 if ($listFilter.is(':hidden')) { 203 $listFilter.css('display', ''); 204 } 205 206 // PÅepnout panely 207 $listFilter.find('.js-katalog-panel').addClass('is-hidden'); 208 $listFilter.find('.js-katalog-panel[data-panel="' + tab + '"]').removeClass('is-hidden'); 209 210 // Skrýt vyhledávánÃ/Åazenà pro ne-companies taby 211 $listFilter.find('.headline-and-search').hide(); 212 $listFilter.children('.sort-buttons-wrapper').hide(); 213 $listFilter.children('.filter-reset').hide(); 214 215 return $listFilter; 216} 217 218function setExhibitorParam(name) { 219 var url = new URL(window.location); 220 if (name) { 221 url.searchParams.set('exhibitor', name); 222 } else { 223 url.searchParams.delete('exhibitor'); 224 } 225 history.replaceState(null, '', url.toString()); 226} 227 228function restoreFromUrl() {
229 var urlParams = new URLSearchParams(window.location.search); 230 var tab = urlParams.get('tab'); 231 var obor = urlParams.get('obor'); 232 var country = urlParams.get('country'); 233 var brand = urlParams.get('brand'); 234 var destination = urlParams.get('destination'); 235 var continent = urlParams.get('continent'); 236 var exhibitor = urlParams.get('exhibitor'); 237 var hall = urlParams.get('hall'); 238 239 if (!tab && !exhibitor) return; 240 241 // Programmatically select the tab (native click so toggle-znacky.js is-active handler fires too) 242 if (tab) { 243 var $tabLabel = $('.js-katalog-tabs label[data-tab="' + tab + '"]'); 244 if ($tabLabel.length > 0) { 245 $tabLabel[0].click(); 246 } 247 } 248 249 // Set pending exhibitor restore (consumed after AJAX-loaded lists render) 250 if (exhibitor) { 251 pendingExhibitorRestore = exhibitor; 252 } 253 254 // Restore filter state 255 if (tab === 'fields' && obor) { 256 var $panel = $('.js-katalog-panel[data-panel="fields"]'); 257 if ($panel.length > 0) { 258 renderFieldsLevel($panel, obor); 259 updateCatalogUrl({ tab: 'fields', obor: obor }); 260 } 261 } else if (tab === 'countries' && country) { 262 $('.catalog-filter-detail--box.country-filter').filter(function () { 263 return $(this).data('name') === country; 264 }).first().trigger('click'); 265 } else if (tab === 'brands' && brand) { 266 $('.catalog-filter-detail--box.brand-filter').filter(function () { 267 return $(this).data('name') === brand; 268 }).first().trigger('click'); 269 } else if (tab === 'destinations' && continent) { 270 // Nastavit pÅed triggerem, aby continent handler promÄnnou vidÄl 271 if (destination) { 272 pendingDestinationRestore = destination; 273 } 274 $('.catalog-filter-detail--box.continent-filter').filter(function () { 275 return $(this).data('name') === continent; 276 }).first().trigger('click'); 277 } else if (tab === 'plan' && hall) { 278 // Plan data loads asynchronously â set pending restore so appendPlanPanel auto-clicks the hall 279 pendingHallRestore = hall; 280 } 281 282 // For companies tab (or no tab), exhibitors are already rendered â open detail directly 283 if (exhibitor && (!tab || tab === 'companies')) { 284 $('.company-detail-katalog-link-new').each(function () { 285 var companyData = $(this).data('company'); 286 if (companyData && companyData.name === exhibitor) { 287 var $el = $(this); 288 var $detail = $el.closest('.newsletter-card-wrapper').find('.company-detail-wrapper-new'); 289 if ($detail.length === 0) { 290 $detail = $('.company-detail-wrapper-new'); 291 } 292 $detail.data('navigation-source', 'list'); 293 $el.closest('.interactable-part-wrapper-catalog').find('.list-with-filter').hide(); 294 $detail.css({ display: 'flex', opacity: 1 }); 295 populateCompanyDetail(companyData, $detail); 296 pendingExhibitorRestore = null; 297 return false; 298 } 299 }); 300 } 301} 302 303$(document).ready(function () { 304 const fieldListWrapper = $('.field-lists-wrapper'); 305 const partnerWrapper = $('.logo-sections-wrapper'); 306 const exhibitorTicket = $('.zbyva-vstupenek'); 307 const mainPlanImage = $('.main-plan-image'); 308 let hasCategoryLoadedFromUrl = false; 309 const isNewLayout = $('.js-katalog-tabs').length > 0; 310 311 newsletterFormSubmit(); 312 exhibitorFormSubmit(); 313 contactFormSubmit(); 314 akreditaceFormSubmit(); 315 exhibitorTicketFormSubmit(); 316 fetchFields(); 317 checkAkreditaceOtevrena(); 318 exhibitorMainPlan(); 319 exhibitorTicketDetail(); 320 loadRSSfeeds(); 321 322 if (isNewLayout) { 323 renderCatalogSourceTabs(); 324 initKatalogTabs(); 325 } 326 327 function exhibitorMainPlan() { 328 if (mainPlanImage.length > 0) { 329 const url = window.location.href; 330 const match = url.match(/historie-veletrhu\/.*?(\d{4}).*?/); 331 if (match) { 332 const year = match[1]; // "2024" 333 const lastTwoDigits = year.slice(-2); // "24" 334 $.ajax({ 335 url: '/wp-json/api/v1/exhibitor-plan-image', 336 method: 'POST', 337 data: { 338 history: lastTwoDigits, 339 catalog: currentCatalogSourceKey 340 }, 341 success: function (response) { 342 const file = JSON.parse(response); 343 mainPlanImage.attr('src', `https://is.abf.cz/file/raster?nl_id=${file.id}&s_format_to=png`) 344 }, 345 error: function () { 346 console.log('Error fetching exhibitors from the API...'); 347 } 348 }) 349 } else { 350 $.ajax({ 351 url: '/wp-json/api/v1/exhibitor-plan-image', 352 method: 'POST', 353 data: { 354 catalog: currentCatalogSourceKey 355 }, 356 success: function (response) { 357 const file = JSON.parse(response); 358 mainPlanImage.attr('src', `https://is.abf.cz/file/raster?nl_id=${file.id}&s_format_to=png`) 359 }, 360 error: function () { 361 console.log('Error fetching exhibitors from the API...'); 362 } 363 }) 364 } 365 } 366 } 367 368 function exhibitorTicketDetail() { 369 if (exhibitorTicket.length > 0) {
370 const urlParams = new URLSearchParams(window.location.search); 371 const vParam = urlParams.get('v'); 372 $.ajax({ 373 url: '/wp-json/api/v1/exhibitor-ticket-detail', 374 data: { 375 id: vParam 376 }, 377 method: 'POST', 378 success: function (response) { 379 let result = JSON.parse(response); 380 if (result.s_x_nazev) { 381 let name = safeDecodeURIComponent(result.s_x_nazev).replace(/\+/g, ' '); 382 $('.h1.secondary').text('VSTUPENKA OD ' + name); 383 } 384 }, 385 error: function () { 386 console.log('Error fetching exhibitors from the API...'); 387 } 388 }) 389 } 390 } 391 392 function fetchFields() { 393 if (fieldListWrapper.length > 0) { 394 const url = window.location.href; 395 const match = url.match(/historie-veletrhu\/.*?(\d{4}).*?/); 396 if (match) { 397 const year = match[1]; // "2024" 398 const lastTwoDigits = year.slice(-2); // "24" 399 $.ajax({ 400 url: '/wp-json/api/v1/oborove-cleneni', 401 data: { 402 oboryLoadNeeded: true, 403 history: lastTwoDigits, 404 catalog: currentCatalogSourceKey 405 }, 406 method: 'POST', 407 beforeSend: function (xhr) { 408 fieldListWrapper.append('<div>NaÄÃtánÃ...</div>'); 409 }, 410 success: function (response) { 411 const obory = response['oborove-cleneni']; 412 partnerWrapper.find('div:contains("NaÄÃtánÃ...")').remove(); // Remove loading indicators 413 414 appendFieldListObory(fieldListWrapper, obory); 415 }, 416 error: function () { 417 console.log('Error fetching partners from the API...'); 418 } 419 }) 420 } else { 421 $.ajax({ 422 url: '/wp-json/api/v1/oborove-cleneni', 423 data: { 424 oboryLoadNeeded: true, 425 catalog: currentCatalogSourceKey 426 }, 427 method: 'POST', 428 beforeSend: function (xhr) { 429 fieldListWrapper.append('<div>NaÄÃtánÃ...</div>'); 430 }, 431 success: function (response) { 432 const obory = response['oborove-cleneni']; 433 partnerWrapper.find('div:contains("NaÄÃtánÃ...")').remove(); // Remove loading indicators 434 435 appendFieldListObory(fieldListWrapper, obory); 436 }, 437 error: function () { 438 console.log('Error fetching partners from the API...'); 439 } 440 }) 441 } 442 } 443 } 444 445 const planListWrapper = $('ul.companies-list.plan'); 446 447 function fetchExhibitorsPlan(searchTerm = '', hallLoadNeeded = true, selectedHall = '') { 448 449 const url = window.location.href; 450 const match = url.match(/historie-veletrhu\/.*?(\d{4}).*?/); 451 452 function isEmptyObject(obj) { 453 for (let key in obj) { 454 if (obj.hasOwnProperty(key)) { 455 if (typeof obj[key] === 'object' && obj[key] !== null) { 456 for (let nestedKey in obj[key]) { 457 if (obj[key][nestedKey] !== "") { 458 return false; 459 } 460 } 461 } else { 462 return false; 463 } 464 } 465 } 466 return true; 467 } 468 if (planListWrapper.length > 0) { 469 if (match) { 470 const year = match[1]; // "2024" 471 const lastTwoDigits = year.slice(-2); // "24" 472 $.ajax({ 473 url: '/wp-json/api/v1/exhibition-plan', 474 method: 'POST', 475 data: { 476 search: searchTerm, // Pass the search term to the server 477 hallLoadNeeded: hallLoadNeeded, // Pass the hall load needed flag to the server 478 selectedHall: selectedHall, // Pass the selected hall to the server 479 history: lastTwoDigits, 480 catalog: currentCatalogSourceKey 481 }, 482 beforeSend: function (xhr) { 483 planListWrapper.append('<div>NaÄÃtánÃ...</div>'); 484 }, 485 success: function (response) { 486 const exhibitors = response['seznam-vystavovatelu']; 487 const haly = response['haly']; 488 489 if (isEmptyObject(exhibitors) && searchTerm === '' && selectedHall === '' && hallLoadNeeded) { 490 $('.interactable-part-wrapper').hide(); 491 } else { 492 $('.interactable-part-wrapper').show(); 493 if (hallLoadNeeded) { 494 appendHallFilter($('.filter-category-wrapper'), haly); 495 } 496 appendExhibitorPlan(planListWrapper, exhibitors, haly); 497 498 if (isEmptyObject(exhibitors)) { 499 planListWrapper.empty(); 500 planListWrapper.append('<div>Nebyl nalezen žádný vystavovatel.</div>'); 501 } 502 } 503 }, 504 }) 505 } else { 506 $.ajax({ 507 url: '/wp-json/api/v1/exhibition-plan', 508 method: 'POST', 509 data: { 510 search: searchTerm, // Pass the search term to the server 511 hallLoadNeeded: hallLoadNeeded, // Pass the hall load needed flag to the server 512 selectedHall: selectedHall, // Pass the selected hall to the server 513 catalog: currentCatalogSourceKey, 514 }, 515 beforeSend: function (xhr) { 516 planListWrapper.append('<div>NaÄÃtánÃ...</div>'); 517 }, 518 success: function (response) { 519 const exhibitors = response['seznam-vystavovatelu']; 520 const haly = response['haly']; 521 522 if (isEmptyObject(exhibitors) && searchTerm === '' && selectedHall === '' && hallLoadNeeded) { 523 $('.interactable-part-wrapper').hide(); 524 } else { 525 $('.interactable-part-wrapper').show(); 526 if (hallLoadNeeded) { 527 appendHallFilter($('.filter-category-wrapper'), haly); 528 } 529 appendExhibitorPlan(planListWrapper, exhibitors, haly); 530 531 if (isEmptyObject(exhibitors)) { 532 planListWrapper.empty(); 533 planListWrapper.append('<div>Nebyl nalezen žádný vystavovatel.</div>'); 534 } 535 } 536 }, 537 }) 538 } 539 } 540 } 541 542 543 const filtersWrapper = $('#obory-filters-list-wrapper'); 544 const znackyfiltersWrapper = $('#znacky-filters-list-wrapper'); 545 const exhibitorListWrapper = $('ul.companies-list.katalog'); 546 let selectedCategories = []; 547 let selectedZnacky = []; 548 let selectedCountries = []; 549 let selectedDestinations = []; 550 let selectedHall = ''; 551 552 function fetchExhibitors(searchTerm = '', categories = [], oboryLoadNeeded = true, znacky = [], countries = [], destinations = []) { 553 if (filtersWrapper.length > 0 || (isNewLayout && exhibitorListWrapper.length > 0)) { 554 555 // Pokud máme cache a nepotÅebujeme naÄÃst panel filtrů, filtrujeme JS-side 556 if (!oboryLoadNeeded && allExhibitorsData !== null) { 557 var filtered = filterExhibitorsFromCache({ 558 search: searchTerm, 559 categories: categories, 560 znacky: znacky, 561 countries: countries, 562 destinations: destinations, 563 }); 564 exhibitorListWrapper.empty(); 565 if (isNewLayout) { 566 appendExhibitorListNew(exhibitorListWrapper, filtered, true, 'name'); 567 } else { 568 appendExhibitorList(exhibitorListWrapper, filtered, true, 'name'); 569 } 570 return; 571 } 572 573 $.ajax({ 574 url: '/wp-json/api/v1/oborove-cleneni', 575 method: 'POST', 576 data: { 577 search: searchTerm, // Pass the search term to the server 578 categories: categories, // Pass the selected categories to the server 579 oboryLoadNeeded: oboryLoadNeeded, 580 znacky: znacky, 581 countries: countries, 582 destinations: destinations, 583 catalog: currentCatalogSourceKey 584 }, 585 beforeSend: function (xhr) { 586 exhibitorListWrapper.empty(); 587 exhibitorListWrapper.append('<div>NaÄÃtánÃ...</div>'); 588 589 if (oboryLoadNeeded && filtersWrapper.length > 0) { 590 filtersWrapper.append('<div>NaÄÃtánÃ...</div>'); 591 znackyfiltersWrapper.append('<div>NaÄÃtánÃ...</div>'); 592 } 593 }, 594 success: function (response) { 595 exhibitorListWrapper.find('div:contains("NaÄÃtánÃ...")').remove(); 596 if (filtersWrapper.length > 0) { 597 filtersWrapper.find('div:contains("NaÄÃtánÃ...")').remove(); 598 znackyfiltersWrapper.find('div:contains("NaÄÃtánÃ...")').remove(); 599 } 600 601 const dataOboroveCleneni = response['oborove-cleneni']; 602 const dataZnacky = response['znacky']; 603 const dataCountries = response['staty']; 604 const dataDestinace = response['destinace_hierarchy']; 605 const dataSeznamVystavovatelu = response['seznam-vystavovatelu']; 606 allExhibitorsData = dataSeznamVystavovatelu; 607 608 let isAscending = true; 609 let sortBy = 'name'; 610 611 // Sort by name (alphabetical) 612 $('.alphabet-sort').on('click', function () { 613 isAscending = !isAscending; 614 sortBy = 'name'; 615 updateSortIcons(isAscending, $(this)); 616 if (isNewLayout) { 617 appendExhibitorListNew(exhibitorListWrapper, dataSeznamVystavovatelu, isA
617scending, sortBy); 618 } else { 619 appendExhibitorList(exhibitorListWrapper, dataSeznamVystavovatelu, isAscending, sortBy); 620 } 621 }); 622 623 // Sort by hall (s_hala) 624 $('.hall-sort').on('click', function () { 625 isAscending = !isAscending; 626 sortBy = 'hala'; 627 updateSortIcons(isAscending, $(this)); 628 if (isNewLayout) { 629 appendExhibitorListNew(exhibitorListWrapper, dataSeznamVystavovatelu, isAscending, sortBy); 630 } else { 631 appendExhibitorList(exhibitorListWrapper, dataSeznamVystavovatelu, isAscending, sortBy); 632 } 633 }); 634 635 // Initial rendering of the exhibitor list (ascending by default) 636 if (isNewLayout) { 637 appendExhibitorListNew(exhibitorListWrapper, dataSeznamVystavovatelu, isAscending, sortBy); 638 if (oboryLoadNeeded) { 639 appendFieldsPanelNew(dataOboroveCleneni); 640 appendCountries(dataCountries); 641 appendBrandsPanel(dataZnacky); 642 destinationsHierarchy = dataDestinace || null; 643 appendDestinationsPanel(dataDestinace); 644 645 // Show tabs that have data (hidden by default) 646 var tabChecks = { 647 'fields': dataOboroveCleneni && Object.keys(dataOboroveCleneni).length > 0, 648 'countries': dataCountries && dataCountries.length > 0, 649 'brands': dataZnacky && Object.keys(dataZnacky).length > 0, 650 'destinations': dataDestinace && Object.keys(dataDestinace).length > 0 651 }; 652 $.each(tabChecks, function (tab, hasData) { 653 if (hasData) { 654 $('.js-katalog-tabs label[data-tab="' + tab + '"]').show(); 655 } 656 }); 657 658 // Fetch plan data (halls + main plan image) for the plan tab 659 fetchPlanData(); 660 } 661 } else { 662 appendExhibitorList(exhibitorListWrapper, dataSeznamVystavovatelu, isAscending, sortBy); 663 if (oboryLoadNeeded) { 664 appendOboroveCleneni(filtersWrapper, dataOboroveCleneni); 665 appendZnacky(znackyfiltersWrapper, dataZnacky); 666 } 667 } 668 669 // Check for URL parameter 'category', but ensure it runs only once 670 if (!hasCategoryLoadedFromUrl) { 671 checkUrlForCategory(); 672 restoreFromUrl(); 673 hasCategoryLoadedFromUrl = true; // Prevent additional calls 674 } 675 }, 676 error: function () { 677 console.log('Error fetching exhibitors from the API...'); 678 } 679 }); 680 } 681 } 682 683 function checkUrlForCategory() { 684 const urlParams = new URLSearchParams(window.location.search); 685 const categoryFromUrl = urlParams.get('category'); 686 687 if (categoryFromUrl) { 688 const matchingCheckbox = $('input[type="checkbox"][data-slug="' + categoryFromUrl + '"]'); 689 690 if (matchingCheckbox.length) { 691 matchingCheckbox.prop('checked', true); 692 matchingCheckbox.closest('.w-checkbox').find('.w-checkbox-input.w-checkbox-input--inputType-custom').addClass('w--redirected-checked'); 693 694 selectedCategories.push(matchingCheckbox.data('name')); 695 696 fetchExhibitors($('.search-input').val().trim(), selectedCategories, false); 697 } 698 } 699 } 700 701 function switchCatalogSource(sourceKey) { 702 if (!isValidCatalogSourceKey(sourceKey) || sourceKey === currentCatalogSourceKey) { 703 return; 704 } 705 706 currentCatalogSourceKey = sourceKey; 707 allExhibitorsData = null; 708 planHallsData = null; 709 mainPlanImageUrl = ''; 710 destinationsHierarchy = null; 711 pendingDestinationRestore = null; 712 pendingHallRestore = null; 713 pendingExhibitorRestore = null; 714 selectedCategories = []; 715 selectedZnacky = []; 716 selectedCountries = []; 717 selectedDestinations = []; 718 selectedHall = ''; 719 720 $('.js-katalog-source-tab').removeClass('is-active').attr('aria-selected', 'false'); 721 $('.js-katalog-source-tab[data-catalog-source="' + sourceKey + '"]').addClass('is-active').attr('aria-selected', 'true'); 722 723 $('.companies-list.katalog input[type="checkbox"], .list-with-filter input[type="checkbox"]').prop('checked', false); 724 $('.w-checkbox-input--inputType-custom').removeClass('w--redirected-checked'); 725 $('.filter-category-wrapper label').removeClass('is-active'); 726 727 $('.interactable-part-wrapper-catalog').each(function () { 728 var $wrapper = $(this); 729 var $listFilter = activateKatalogTab($wrapper, 'companies'); 730 $listFilter.find('.js-katalog-panel[data-panel!="companies"]').addClass('is-hidden'); 731 $listFilter.find('.js-katalog-panel[data-panel="companies"]').removeClass('is-hidden'); 732 $listFilter.find('.field-detail-wrapper, .obory-nav, .obory-exhibitors, .plan-hall-topbar, .plan-hall-detail').remove(); 733 $wrapper.find('.company-detail-wrapper-new').hide(); 734 $wrapper.find('.list-with-filter').show(); 735 $wrapper.find('.js-katalog-tabs label[data-tab!="companies"]').hide(); 736 }); 737 738 var searchValue = $('.search-input').length ? ($('.search-input').val() || '').trim() : ''; 739 var planSearchValue = $('.search-input-plan').length ? ($('.search-input-plan').val() || '').trim() : ''; 740 741 fetchExhibitors(searchValue, [], true, [], [], []); 742 fetchExhibitorsPlan(planSearchValue, true, ''); 743 updateCatalogUrl({}); 744 } 745 746 $(document).on('click', '.js-katalog-source-tab', function (e) { 747 e.preventDefault(); 748 switchCatalogSource($(this).data('catalog-source')); 749 }); 750 751 // Initial fetch without search term 752 fetchExhibitors(); 753 fetchExhibitorsPlan(); 754 755 $(document).on('change', 'input[type="radio"]', function () { 756 // Skip tab radio buttons in the new layout 757 if ($(this).closest('.js-katalog-tabs').length > 0) return; 758 759 selectedHall = $(this).val().trim(); // Get the selected hall from the radio input 760 761 $(this).closest('label').addClass('is-active').siblings().removeClass('is-active'); 762 763 // Re-fetch exhibitors with the selected hall 764 fetchExhibitorsPlan($('.search-input-plan').val().trim(), false, selectedHall); 765 }); 766 767 768 // Trigger the fetch function on search input 769 $('.search-input').on('input', debounce(function () { 770 searchTerm = $(this).val().trim(); // Get the search term from the input 771 fetchExhibitors(searchTerm, selectedCategories, false, selectedZnacky, selectedCountries, selectedDestinations); // Re-fetch the exhibitors with the search term 772 }, 300)); 773 774 $('.search-input-plan').on('input', debounce(function () { 775 searchTerm = $(this).val().trim(); // Get the search term from the input 776 fetchExhibitorsPlan(searchTerm, false, selectedHall); // Re-fetch the exhibitors with the search term 777 }, 300)); 778 779 if (filtersWrapper.length > 0) { 780 $(document).on('change', 'input[type="checkbox"]', function () { 781 selectedCategories = []; 782 selectedZnacky = []; 783 selectedCountries = []; 784 selectedDestinations = []; 785 786 // Gather all checked categories 787 $('input[type="checkbox"]:checked').not('.znacky-input-c
787heckbox').each(function () { 788 selectedCategories.push($(this).data('name')); 789 }); 790 791 792 793 $('input[type="checkbox"].znacky-input-checkbox:checked').each(function () { 794 selectedZnacky.push($(this).data('name')); 795 }); 796 797 // Re-fetch exhibitors with the selected categories 798 fetchExhibitors($('.search-input').val().trim(), selectedCategories, false, selectedZnacky, selectedCountries, selectedDestinations); 799 }); 800 } 801 802 // Handle country filter clicks - show field detail with exhibitor list 803 $(document).on('click', '.catalog-filter-detail--box.country-filter', function () { 804 const countryName = $(this).data('name'); 805 const $interactableWrapper = $(this).closest('.interactable-part-wrapper-catalog'); 806 807 updateCatalogUrl({ tab: 'countries', country: countryName }); 808 809 // Hide the list-with-filter 810 $interactableWrapper.find('.list-with-filter').hide(); 811 $interactableWrapper.find('.company-detail-wrapper-new').hide(); 812 813 // Remove any existing field detail wrapper 814 $interactableWrapper.find('.field-detail-wrapper').remove(); 815 816 // Create field detail wrapper 817 const fieldDetailHTML = ` 818 <div class="field-detail-wrapper" style="display: flex; flex-direction: column; gap: 16px;"> 819 <a href="#" class="back-katalog-button w-inline-block"> 820 <div class="back-icon w-embed"> 821 <svg width="16" height="17" viewbox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg"> 822 <path d="M2 8.5H14M2 8.5L6 12.5M2 8.5L6 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> 823 </svg> 824 </div> 825 <div>ZpÄt</div> 826 </a> 827 <h2 class="fields-headline">${countryName}</h2> 828 <div class="sort-buttons-wrapper desktop"><a href="#" class="alphabet-sort sort-button is-active w-inline-block"><div>Podle abecedy</div><div class="sort-arrows-wrapper"><div class="sort-icon is-active w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a><a href="#" class="hall-sort sort-button w-inline-block"><div>Podle haly</div><div class="sort-arrows-wrapper"><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a></div> 829 <ul role="list" class="companies-list katalog field-detail-exhibitor-list"></ul> 830 </div> 831 `; 832 833 $interactableWrapper.append(fieldDetailHTML); 834 835 const $fieldDetailList = $interactableWrapper.find('.field-detail-exhibitor-list'); 836 renderCachedExhibitors($fieldDetailList, { countries: [countryName] }); 837 }); 838 839 // Handle brand filter clicks - show field detail with exhibitor list 840 $(document).on('click', '.catalog-filter-detail--box.brand-filter', function () { 841 const brandName = $(this).data('name'); 842 const $interactableWrapper = $(this).closest('.interactable-part-wrapper-catalog'); 843 844 updateCatalogUrl({ tab: 'brands', brand: brandName }); 845 846 // Hide the list-with-filter 847 $interactableWrapper.find('.list-with-filter').hide(); 848 $interactableWrapper.find('.company-detail-wrapper-new').hide(); 849 850 // Remove any existing field detail wrapper 851 $interactableWrapper.find('.field-detail-wrapper').remove(); 852 853 // Create field detail wrapper 854 const fieldDetailHTML = ` 855 <div class="field-detail-wrapper" style="display: flex; flex-direction: column; gap: 16px;"> 856 <a href="#" class="back-katalog-button w-inline-block"> 857 <div class="back-icon w-embed"> 858 <svg width="16" height="17" viewbox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg"> 859 <path d="M2 8.5H14M2 8.5L6 12.5M2 8.5L6 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> 860 </svg> 861 </div> 862 <div>ZpÄt</div> 863 </a> 864 <h2 class="fields-headline">${brandName}</h2> 865 <div class="sort-buttons-wrapper desktop"><a href="#" class="alphabet-sort sort-button is-active w-inline-block"><div>Podle abecedy</div><div class="sort-arrows-wrapper"><div class="sort-icon is-active w-embed">
865<svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a><a href="#" class="hall-sort sort-button w-inline-block"><div>Podle haly</div><div class="sort-arrows-wrapper"><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a></div> 866 <ul role="list" class="companies-list katalog field-detail-exhibitor-list"></ul> 867 </div> 868 `; 869 870 $interactableWrapper.append(fieldDetailHTML); 871 872 const $fieldDetailList = $interactableWrapper.find('.field-detail-exhibitor-list'); 873 renderCachedExhibitors($fieldDetailList, { znacky: [brandName] }); 874 }); 875 876 // Helper: returns sub-destinations one level below parentCode within a continent. 877 // parentCode = null â returns all top-level destinations for the continent. 878 // parentCode = "9.1." â returns destinations whose code starts with "9.1." and has one more segment (e.g. "9.1.1."). 879 function getSubDestinations(continentName, parentCode) { 880 if (!destinationsHierarchy || !destinationsHierarchy[continentName]) return []; 881 const allDests = destinationsHierarchy[continentName]; 882 if (!parentCode) return allDests; 883 884 const parentParts = parentCode.replace(/\.$/, '').split('.'); 885 return allDests.filter(function (dest) { 886 const parts = dest.code.replace(/\.$/, '').split('.'); 887 if (parts.length !== parentParts.length + 1) return false; 888 for (var i = 0; i < parentParts.length; i++) { 889 if (parts[i] !== parentParts[i]) return false; 890 } 891 return true; 892 }); 893 } 894 895 // Helper: build sub-destination boxes HTML 896 function buildSubDestsHTML(subDests, continentName) { 897 if (!subDests || subDests.length === 0) return ''; 898 var html = ''; 899 subDests.forEach(function (dest) { 900 html += `<div class="catalog-filter-detail--box destination-filter" data-name="${dest.name}" data-code="${dest.code}" data-continent="${continentName}" style="cursor: pointer;"><div class="img"><img class="img" src="${customBlockData.pluginUrl}images/files.svg" /></div><span class="list-item">${dest.code ? dest.code + ' ' : ''}${dest.name}</span></div>`; 901 }); 902 return `<div class="catalog-filter-detail destination-sub-boxes">${html}</div>`; 903 } 904 905 // âââ Client-side exhibitor cache helpers ââââââââââââââââââââââââââââââââââââ 906 907 // Filtruje allExhibitorsData podle zadaných kritérià (stejná logika jako PHP backend). 908 // filters: { search, categories, znacky, countries, destinations, continents } 909 function filterExhibitorsFromCache(filters) { 910 if (!allExhibitorsData) return null; // cache jeÅ¡tÄ nenà k dispozici 911 912 var search = filters.search ? filters.search.toString().toLowerCase() : ''; 913 var categories = filters.categories || []; 914 var znacky = (filters.znacky || []).map(function (z) { return z.toLowerCase().trim(); }); 915 var countries = filters.countries || []; 916 var destinations = filters.destinations || []; 917 var continents = filters.continents || []; 918 919 var result = {};
920 Object.keys(allExhibitorsData).forEach(function (key) { 921 var ex = allExhibitorsData[key]; 922 923 // Vyhledávánà v názvu, e-mailu, telefonu, adrese a popisu 924 if (search) { 925 var decodeField = function (val) { 926 var s = (val || '').toString().toLowerCase(); 927 try { s = safeDecodeURIComponent(s.replace(/\+/g, ' ')); } catch (e) {} 928 return s; 929 }; 930 var haystack = [ 931 ex.s_nazev, 932 ex.s_email, 933 ex.s_tel1, 934 ex.s_street, 935 ex.s_city, 936 ex.s_zip, 937 ex.s_profil 938 ].map(decodeField).join(' '); 939 if (haystack.indexOf(search) === -1) return; 940 } 941 942 // Filtr podle kategorie (kódy v Äárkou oddÄlené hodnotÄ s_oborove_cleneni) 943 if (categories.length > 0) { 944 var exCats = (ex.s_oborove_cleneni || '').split(',').map(function (c) { return c.trim(); }); 945 if (!categories.some(function (cat) { return exCats.indexOf(cat) !== -1; })) return; 946 } 947 948 // Filtr podle znaÄky 949 if (znacky.length > 0) { 950 var exBrands = (ex.znacky_names || []).map(function (b) { return b.toLowerCase().trim(); }); 951 if (!znacky.some(function (z) { return exBrands.indexOf(z) !== -1; })) return; 952 } 953 954 // Filtr podle státu 955 if (countries.length > 0) { 956 if (countries.indexOf(ex.s_stat) === -1) return; 957 } 958 959 // Filtr podle destinace 960 if (destinations.length > 0) { 961 var exDests = ex.destinace_names || []; 962 if (!destinations.some(function (d) { return exDests.indexOf(d) !== -1; })) return; 963 } 964 965 // Filtr podle kontinentu 966 if (continents.length > 0) { 967 var exConts = ex.destinace_continents || []; 968 if (!continents.some(function (c) { return exConts.indexOf(c) !== -1; })) return; 969 } 970 971 result[key] = ex; 972 }); 973 return result; 974 } 975 976 // Filtruje cache a vykreslà výsledek do $fieldDetailList vÄetnÄ ÅazenÃ. 977 function renderCachedExhibitors($fieldDetailList, filters) { 978 var exhibitors = filterExhibitorsFromCache(filters); 979 $fieldDetailList.empty(); 980 $fieldDetailList.data('exhibitors', exhibitors); 981 $fieldDetailList.data('isAscending', true); 982 $fieldDetailList.data('sortBy', 'name'); 983 appendExhibitorListNew($fieldDetailList, exhibitors, true, 'name'); 984 985 var $wrapper = $fieldDetailList.closest('.field-detail-wrapper'); 986 $wrapper.find('.alphabet-sort').off('click.cached').on('click.cached', function () { 987 var asc = !$fieldDetailList.data('isAscending'); 988 $fieldDetailList.data('isAscending', asc).data('sortBy', 'name'); 989 updateSortIcons(asc, $(this)); 990 appendExhibitorListNew($fieldDetailList, $fieldDetailList.data('exhibitors'), asc, 'name'); 991 }); 992 $wrapper.find('.hall-sort').off('click.cached').on('click.cached', function () { 993 var asc = !$fieldDetailList.data('isAscending'); 994 $fieldDetailList.data('isAscending', asc).data('sortBy', 'hala'); 995 updateSortIcons(asc, $(this)); 996 appendExhibitorListNew($fieldDetailList, $fieldDetailList.data('exhibitors'), asc, 'hala'); 997 }); 998 } 999 1000 // ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1001 1002 // Helper: render destination exhibitor list â použÃvá cache, fallback na AJAX 1003 function fetchAndRenderDestinationExhibitors($fieldDetailList, filterData) { 1004 var cached = filterExhibitorsFromCache(filterData); 1005 if (cached !== null) { 1006 renderCachedExhibitors($fieldDetailList, filterData); 1007 return; 1008 } 1009 1010 // Fallback na AJAX (cache jeÅ¡tÄ nenà k dispozici â mÄlo by nastat jen pÅi prvnÃm naÄtenÃ) 1011 $fieldDetailList.empty(); 1012 $fieldDetailList.append('<div>NaÄÃtánÃ...</div>'); 1013 $.ajax({ 1014 url: '/wp-json/api/v1/oborove-cleneni', 1015 method: 'POST',
1016 data: $.extend({ oboryLoadNeeded: false, catalog: currentCatalogSourceKey }, filterData), 1017 success: function (response) { 1018 var data = response['seznam-vystavovatelu']; 1019 $fieldDetailList.empty(); 1020 $fieldDetailList.data('exhibitors', data).data('isAscending', true).data('sortBy', 'name'); 1021 appendExhibitorListNew($fieldDetailList, data, true, 'name'); 1022 var $w = $fieldDetailList.closest('.field-detail-wrapper'); 1023 $w.find('.alphabet-sort').off('click.dest').on('click.dest', function () { 1024 var asc = !$fieldDetailList.data('isAscending'); 1025 $fieldDetailList.data('isAscending', asc).data('sortBy', 'name'); 1026 updateSortIcons(asc, $(this)); 1027 appendExhibitorListNew($fieldDetailList, $fieldDetailList.data('exhibitors'), asc, 'name'); 1028 }); 1029 $w.find('.hall-sort').off('click.dest').on('click.dest', function () { 1030 var asc = !$fieldDetailList.data('isAscending'); 1031 $fieldDetailList.data('isAscending', asc).data('sortBy', 'hala'); 1032 updateSortIcons(asc, $(this)); 1033 appendExhibitorListNew($fieldDetailList, $fieldDetailList.data('exhibitors'), asc, 'hala'); 1034 }); 1035 }, 1036 error: function () { 1037 $fieldDetailList.empty(); 1038 $fieldDetailList.append('<div>Chyba pÅi naÄÃtánà vystavovatelů.</div>'); 1039 } 1040 }); 1041 } 1042 1043 // Helper: render a destination level detail view (continent level or sub-destination level) 1044 // backTo: 'list' | 'continent' 1045 function renderDestinationLevel($interactableWrapper, opts) { 1046 // opts: { title, continentName, code, filterParams, backTo, backContinent } 1047 $interactableWrapper.find('.field-detail-wrapper').remove(); 1048 1049 const subDests = opts.code 1050 ? getSubDestinations(opts.continentName, opts.code) 1051 : (destinationsHierarchy && destinationsHierarchy[opts.continentName] ? destinationsHierarchy[opts.continentName] : []); 1052 1053 const subDestsHTML = buildSubDestsHTML(subDests, opts.continentName); 1054 1055 const backAttr = opts.backTo === 'continent' 1056 ? `data-back-to="continent" data-back-continent="${opts.backContinent}"` 1057 : ''; 1058 1059 const sortButtonsHTML = `<div class="sort-buttons-wrapper desktop"><a href="#" class="alphabet-sort sort-button is-active w-inline-block"><div>Podle abecedy</div><div class="sort-arrows-wrapper"><div class="sort-icon is-active w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a><a href="#" class="hall-sort sort-button w-inline-block"><div>Podle haly</div><div class="sort-arrows-wrapper"><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a></div>`; 1060 1061 const fieldDetailHTML = ` 1062 <div class="field-detail-wrapper" data-continent="${opts.continentName}" style="display: flex; flex-direction: column; gap: 16px;"> 1063 <a href="#" class="back-katalog-button w-inline-block" ${backAttr}> 1064 <div class="back-icon w-embed"> 1065 <svg width="16" height="17" viewbox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg"> 1066 <path d="M2 8.5H14M2 8.5L6 12.5M2 8.5L6 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> 1067 </svg> 1068 </div> 1069 <div>ZpÄt</div> 1070 </a> 1071 <h2 class="fields-headline">${opts.title}</h2> 1072 ${subDestsHTML} 1073 ${sortButtonsHTML} 1074 <ul role="list" class="companies-list katalog field-detail-exhibitor-list"></ul> 1075 </div> 1076 `; 1077 1078 $interactableWrapper.append(fieldDetailHTML); 1079 fetchAndRenderDestinationExhibitors($interactableWrapper.find('.field-detail-exhibitor-list'), opts.filterParams); 1080 } 1081 1082 // Odkazy v detailu vystavovatele (obory / destinace) â filtrujà katalog in-place bez reloadu. 1083 // (href zůstává funkÄnà pro otevÅenà v novém panelu, kde se filtr obnovà z URL.) 1084 $(document).on('click', '.catalog-filter-link', function (e) { 1085 // Nechat fungovat ctrl/cmd/middle-klik (otevÅenà v nové kartÄ pÅes href) 1086 if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return; 1087 e.preventDefault(); 1088 1089 var $interactableWrapper = $(this).closest('.interactable-part-wrapper-catalog'); 1090 if ($interactableWrapper.length === 0) { 1091 $interactableWrapper = $(this).closest('.newsletter-card-wrapper').find('.interactable-part-wrapper-catalog'); 1092 } 1093 1094 var type = $(this).data('filter-type'); 1095 1096 if (type === 'obor') { 1097 var oborCode = String($(this).data('obor')); 1098 var $listFilter = activateKatalogTab($interactableWrapper, 'fields'); 1099 var $panel = $listFilter.find('.js-katalog-panel[data-panel="fields"]'); 1100 if ($panel.length > 0) { 1101 renderFieldsLevel($panel, oborCode); 1102 updateCatalogUrl({ tab: 'fields', obor: oborCode }); 1103 } 1104 } else if (type === 'destination') { 1105 var destName = $(this).data('destination'); 1106 var continent = $(this).data('continent'); 1107 activateKatalogTab($interactableWrapper, 'destinations'); 1108 // Po kliknutà na kontinent se dÃky pendingDestinationRestore zanoÅà až na destinaci 1109 pendingDestinationRestore = destName; 1110 $interactableWrapper.find('.catalog-filter-detail--box.continent-filter').filter(function () { 1111 return $(this).data('name') === continent; 1112 }).first().trigger('click'); 1113 } 1114 }); 1115 1116 // Handle continent filter clicks 1117 $(document).on('click', '.catalog-filter-detail--box.continent-filter', function () { 1118 const continentName = $(this).data('name'); 1119 const $interactableWrapper = $(this).closest('.interactable-part-wrapper-catalog'); 1120 1121 updateCatalogUrl({ tab: 'destinations', continent: continentName }); 1122 1123 $interactableWrapper.find('.list-with-filter').hide(); 1124 $interactableWrapper.find('.company-detail-wrapper-new').hide(); 1125 1126 renderDestinationLevel($interactableWrapper, { 1127 title: continentName, 1128 continentName: continentName, 1129 code: null, 1130 filterParams: { continents: [continentName] }, 1131 backTo: 'list', 1132 }); 1133 1134 // Pokud Äekáme na obnovenà konkrétnà destinace z URL 1135 if (pendingDestinationRestore) { 1136 const pendingDest = pendingDestinationRestore; 1137 pendingDestinationRestore = null; 1138 setTimeout(function () { 1139 $interactableWrapper.find('.catalog-filter-detail--box.destination-filter').filter(function () { 1140 return $(this).data('name') === pendingDest; 1141 }).first().trigger('click'); 1142 }, 100); 1143 } 1144 }); 1145 1146 // Handle destination filter clicks â in-place update existing field-detail-wrapper 1147 $(document).on('click', '.catalog-filter-detail--box.destination-filter', function () { 1148 const destinationName = $(this).data('name'); 1149 const destinationCode = $(this).data('code') || ''; 1150 const continentName = $(this).data('continent') || ''; 1151 const $fieldDetailWrapper = $(this).closest('.field-detail-wrapper'); 1152 1153 updateCatalogUrl({ tab: 'destinations', continent: continentName, destination: destinationName }); 1154 1155 const displayTitle = destinationCode ? destinationCode + ' ' + destinationName : destinationName; 1156 1157 // Aktualizovat nadpis 1158 $fieldDetailWrapper.find('h2.fields-headline').text(displayTitle); 1159 1160 // Aktualizovat back button â zpÄt na kontinent 1161 const $backBtn = $fieldDetailWrapper.find('.back-katalog-button'); 1162 $backBtn.attr('data-back-to', 'continent'); 1163 $backBtn.attr('data-back-continent', continentName); 1164 1165 // Nahradit sub-nomenklatury (pokud existujà hlubšà úrovnÄ, jinak se nezobrazà nic) 1166 $fieldDetailWrapper.find('.destination-sub-boxes').remove(); 1167 const subDests = getSubDestinations(continentName, destinationCode); 1168 if (subDests.length > 0) { 1169 const subDestsHTML = buildSubDestsHTML(subDests, continentName); 1170 $fieldDetailWrapper.find('h2.fields-headline').after(subDestsHTML); 1171 } 1172 1173 // NaÄÃst vystavovatele pro tuto destinaci 1174 const $fieldDetailList = $fieldDetailWrapper.find('.field-detail-exhibitor-list'); 1175 fetchAndRenderDestinationExhibitors($fieldDetailList, { destinations: [destinationName] }); 1176 }); 1177 1178 // Handle hall filter clicks (plan tab) - show exhibitors for a specific hall 1179 $(document).on('click', '.catalog-filter-detail--box.hall-filter', function () { 1180 var hallKey = $(this).data('hall').toString(); 1181 var hallImage = $(this).data('image'); 1182 var $panel = $(this).closest('.js-katalog-panel[data-panel="plan"]'); 1183 var $planLayout = $panel.find('.plan-layout'); 1184 1185 updateCatalogUrl({ tab: 'plan', hall: hallKey }); 1186 1187 // Swap the map image to the hall plan (keep main plan if no hall image) 1188 if (hallImage) { 1189 $planLayout.find('.plan-map-image').attr('src', hallImage); 1190 ensurePlanMapLightbox($planLayout, hallImage, 'Plánek haly'); 1191 } 1192 1193 // Hide the halls grid 1194 $planLayout.find('.plan-halls-grid').hide(); 1195 $planLayout.find('.plan-hall-detail').remove(); 1196 1197 // Filter exhibitors by hall from allExhibitorsData 1198 var filteredExhibitors = {}; 1199 if (allExhibitorsData) {
1200 Object.keys(allExhibitorsData).forEach(function (key) { 1201 var item = allExhibitorsData[key]; 1202 // PárÑj na celou hodnotu haly ("1", "Vstupnà hala I", "Venkovnà plocha"), 1203 // ne jen na ÄÃslici â jinak by vstupnà haly (ÅÃmské ÄÃslice) vypadly. 1204 // Normalizace sjednotà "1" i "Hala 1" a zachová pojmenované plochy (ÅÃmské ÄÃslice). 1205 if (normalizeHallKey(item.s_hala) === hallKey) { 1206 filteredExhibitors[key] = item; 1207 } 1208 }); 1209 } 1210 1211 // Store filtered data on the panel for search 1212 $panel.data('hall-exhibitors', filteredExhibitors); 1213 1214 // Insert top bar (back + search) BEFORE the plan-layout 1215 var topBarHTML = 1216 '<div class="plan-hall-topbar" style="display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap;">' + 1217 '<a href="#" class="back-katalog-button plan-back-button w-inline-block">' + 1218 '<div class="back-icon w-embed">' + 1219 '<svg width="16" height="17" viewbox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">' + 1220 '<path d="M2 8.5H14M2 8.5L6 12.5M2 8.5L6 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>' + 1221 '</svg>' + 1222 '</div>' + 1223 '<div>ZpÄt</div>' + 1224 '</a>' + 1225 '<form class="search full-width-mobile w-form" onsubmit="return false;" style="margin:0;">' + 1226 '<input class="search-input w-input plan-hall-search-input" maxlength="256" placeholder="Hledatâ¦" type="search" />' + 1227 '<div class="icon-24px search-input-icon w-embed">' + 1228 '<svg width="24" height="24" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">' + 1229 '<path d="M21 21L15 15M3 10C3 10.9193 3.18106 11.8295 3.53284 12.6788C3.88463 13.5281 4.40024 14.2997 5.05025 14.9497C5.70026 15.5998 6.47194 16.1154 7.32122 16.4672C8.17050 16.8189 9.08075 17 10 17C10.9193 17 11.8295 16.8189 12.6788 16.4672C13.5281 16.1154 14.2997 15.5998 14.9497 14.9497C15.5998 14.2997 16.1154 13.5281 16.4672 12.6788C16.8189 11.8295 17 10.9193 17 10C17 9.08075 16.8189 8.17050 16.4672 7.32122C16.1154 6.47194 15.5998 5.70026 14.9497 5.05025C14.2997 4.40024 13.5281 3.88463 12.6788 3.53284C11.8295 3.18106 10.9193 3 10 3C9.08075 3 8.17050 3.18106 7.32122 3.53284C6.47194 3.88463 5.70026 4.40024 5.05025 5.05025C4.40024 5.70026 3.88463 6.47194 3.53284 7.32122C3.18106 8.17050 3 9.08075 3 10Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>' + 1230 '</svg>' + 1231 '</div>' + 1232 '</form>' + 1233 '</div>'; 1234 1235 $panel.before(topBarHTML); 1236 1237 var hallDetailHTML = 1238 '<div class="plan-hall-detail" style="flex:1 1 40%;min-width:250px;display:flex;flex-direction:column;gap:16px;">' + 1239 '<h2 class="fields-headline">VYSTAVOVATELÃ</h2>' + 1240 '<ul role="list" class="companies-list katalog plan-hall-exhibitor-list"></ul>' + 1241 '</div>'; 1242 1243 $planLayout.append(hallDetailHTML); 1244 1245 var $fieldDetailList = $planLayout.find('.plan-hall-exhibitor-list'); 1246 1247 if (Object.keys(filteredExhibitors).length > 0) { 1248 appendExhibitorListNew($fieldDetailList, filteredExhibitors, true, 'name'); 1249 } else { 1250 $fieldDetailList.append('<div>V této hale nebyli nalezeni žádnà vystavovatelé.</div>'); 1251 } 1252 1253 syncPlanScrollablePanels($panel); 1254 }); 1255 1256 // Search within hall exhibitors 1257 $(document).on('input', '.plan-hall-search-input', debounce(function () { 1258 var searchTerm = $(this).val().trim().toLowerCase(); 1259 var $topbar = $(this).closest('.plan-hall-topbar'); 1260 var $panel = $topbar.next('.js-katalog-panel[data-panel="plan"]'); 1261 var allHallExhibitors = $panel.data('hall-exhibitors') || {}; 1262 var $fieldDetailList = $panel.find('.plan-hall-exhibitor-list'); 1263 1264 if (!searchTerm) { 1265 // No search term â show all hall exhibitors 1266 appendExhibitorListNew($fieldDetailList, allHallExhibitors, true, 'name'); 1267 return; 1268 } 1269 1270 // Filter by name 1271 var filtered = {};
1272 Object.keys(allHallExhibitors).forEach(function (key) { 1273 var item = allHallExhibitors[key]; 1274 var name = safeDecodeURIComponent(item.s_nazev).replace(/\+/g, ' ').toLowerCase(); 1275 if (name.indexOf(searchTerm) !== -1) { 1276 filtered[key] = item; 1277 } 1278 }); 1279 1280 if (Object.keys(filtered).length > 0) { 1281 appendExhibitorListNew($fieldDetailList, filtered, true, 'name'); 1282 } else { 1283 $fieldDetailList.empty(); 1284 $fieldDetailList.append('<div>Vyhledávánà neodpovÃdajà žádnà vystavujÃcÃ.</div>'); 1285 } 1286 }, 300)); 1287 1288 // Back button from hall exhibitor detail - return to hall list 1289 $(document).on('click', '.plan-back-button', function (e) { 1290 e.preventDefault(); 1291 e.stopImmediatePropagation(); 1292 var $topbar = $(this).closest('.plan-hall-topbar'); 1293 var $panel = $topbar.next('.js-katalog-panel[data-panel="plan"]'); 1294 var $planLayout = $panel.find('.plan-layout'); 1295 1296 // Restore main plan image 1297 if (mainPlanImageUrl) { 1298 $planLayout.find('.plan-map-image').attr('src', mainPlanImageUrl); 1299 ensurePlanMapLightbox($planLayout, mainPlanImageUrl); 1300 } 1301 1302 // Remove hall detail, show halls grid, remove top bar 1303 $planLayout.find('.plan-hall-detail').remove(); 1304 $planLayout.find('.plan-halls-grid').show(); 1305 $topbar.remove(); 1306 1307 updateCatalogUrl({ tab: 'plan' }); 1308 }); 1309 1310 // Handle parent category clicks - drill down in hierarchy 1311 $(document).on('click', '.catalog-filter-detail--box.category-parent', function () { 1312 var clickedCode = $(this).data('code'); 1313 var $panel = $('.js-katalog-panel[data-panel="fields"]'); 1314 if ($panel.length > 0) { 1315 renderFieldsLevel($panel, clickedCode); 1316 updateCatalogUrl({ tab: 'fields', obor: clickedCode }); 1317 } 1318 }); 1319 1320 // Handle back button within obory hierarchy 1321 $(document).on('click', '.obory-back-button', function (e) { 1322 e.preventDefault(); 1323 var parentCode = $(this).data('parent'); 1324 var $panel = $('.js-katalog-panel[data-panel="fields"]'); 1325 if ($panel.length > 0) { 1326 renderFieldsLevel($panel, parentCode); 1327 updateCatalogUrl({ tab: 'fields', obor: parentCode || null }); 1328 } 1329 }); 1330 1331 // Handle category filter clicks - show field detail with exhibitor list 1332 $(document).on('click', '.catalog-filter-detail--box.category-filter', function () { 1333 const categoryName = $(this).data('name'); 1334 const categoryCode = $(this).data('code'); 1335 const $interactableWrapper = $(this).closest('.interactable-part-wrapper-catalog'); 1336 1337 updateCatalogUrl({ tab: 'fields', obor: categoryCode }); 1338 1339 // Hide the list-with-filter (obory list) 1340 $interactableWrapper.find('.list-with-filter').hide(); 1341 $interactableWrapper.find('.company-detail-wrapper-new').hide(); 1342 1343 // Remove any existing field detail wrapper 1344 $interactableWrapper.find('.field-detail-wrapper').remove(); 1345 1346 // Create field detail wrapper 1347 const fieldDetailHTML = ` 1348 <div class="field-detail-wrapper" style="display: flex; flex-direction: column; gap: 16px;"> 1349 <a href="#" class="back-katalog-button w-inline-block"> 1350 <div class="back-icon w-embed"> 1351 <svg width="16" height="17" viewbox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg"> 1352 <path d="M2 8.5H14M2 8.5L6 12.5M2 8.5L6 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> 1353 </svg> 1354 </div> 1355 <div>ZpÄt</div> 1356 </a> 1357 <h2 class="fields-headline">${categoryName}</h2> 1358 <div class="sort-buttons-wrapper desktop"><a href="#" class="alphabet-sort sort-button is-active w-inline-block"><div>Podle abecedy</div><div class="sort-arrows-wrapper"><div class="sort-icon is-active w-embed">
1358<svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a><a href="#" class="hall-sort sort-button w-inline-block"><div>Podle haly</div><div class="sort-arrows-wrapper"><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a></div> 1359 <ul role="list" class="companies-list katalog field-detail-exhibitor-list"></ul> 1360 </div> 1361 `; 1362 1363 $interactableWrapper.append(fieldDetailHTML); 1364 1365 const $fieldDetailList = $interactableWrapper.find('.field-detail-exhibitor-list'); 1366 renderCachedExhibitors($fieldDetailList, { categories: [categoryCode] }); 1367 }); 1368 1369 // Back button in field detail wrapper 1370 $(document).on('click', '.field-detail-wrapper .back-katalog-button', function (e) { 1371 e.preventDefault(); 1372 e.stopImmediatePropagation(); 1373 var $btn = $(this); 1374 var backTo = $btn.data('back-to'); 1375 var $interactableWrapper = $btn.closest('.interactable-part-wrapper-catalog'); 1376 1377 // Destination sub-level â back to continent level 1378 if (backTo === 'continent') { 1379 var backContinent = $btn.data('back-continent'); 1380 updateCatalogUrl({ tab: 'destinations', continent: backContinent }); 1381 renderDestinationLevel($interactableWrapper, { 1382 title: backContinent, 1383 continentName: backContinent, 1384 code: null, 1385 filterParams: { continents: [backContinent] }, 1386 backTo: 'list', 1387 }); 1388 return; 1389 } 1390 1391 // Default: return to main tab list 1392 $interactableWrapper.find('.field-detail-wrapper').remove(); 1393 $interactableWrapper.find('.list-with-filter').css('display', ''); 1394 1395 var activeTab = $('.js-katalog-tabs input[type="radio"]:checked').closest('label').data('tab') || 'companies'; 1396 if (activeTab === 'fields') { 1397 var $fieldsPanel = $interactableWrapper.find('.js-katalog-panel[data-panel="fields"]'); 1398 var currentObor = $fieldsPanel.data('current-obor'); 1399 updateCatalogUrl({ tab: 'fields', obor: currentObor || null }); 1400 } else if (activeTab === 'companies') { 1401 updateCatalogUrl({}); 1402 } else { 1403 updateCatalogUrl({ tab: activeTab }); 1404 } 1405 }); 1406 1407 // Handle exhibitor click from field detail view 1408 $(document).on('click', '.company-detail-from-field', function (e) { 1409 e.preventDefault(); 1410 const companyData = $(this).data('company'); 1411 const $interactableWrapper = $(this).closest('.interactable-part-wrapper-catalog'); 1412 1413 setExhibitorParam(companyData.name); 1414 1415 let $detail = $interactableWrapper.find('.company-detail-wrapper-new'); 1416 if ($detail.length === 0) { 1417 $detail = $('.company-detail-wrapper-new'); 1418 } 1419 1420 // Determine navigation source 1421 var isFromOboryDrilled = $(this).closest('.obory-exhibitors').length > 0; 1422 var isFromPlanHall = $(this).closest('.plan-hall-detail').length > 0; 1423 1424 if (isFromOboryDrilled) { 1425 $detail.data('navigation-source', 'obory-drilled'); 1426 $interactableWrapper.find('.list-with-filter').hide(); 1427 } else if (isFromPlanHall) { 1428 $detail.data('navigation-source', 'plan-hall'); 1429 $interactableWrapper.find('.list-with-filter').hide(); 1430 $interactableWrapper.find('.plan-hall-topbar').hide(); 1431 } else {
1432 $detail.data('navigation-source', 'field-detail'); 1433 $interactableWrapper.find('.field-detail-wrapper').hide(); 1434 } 1435 1436 $detail.css({ display: 'flex', opacity: 1 }); 1437 populateCompanyDetail(companyData, $detail); 1438 }); 1439 1440 $(document).on('click', '.filter-reset', function () { 1441 // Uncheck all checkboxes 1442 $('input[type="checkbox"]').prop('checked', false); 1443 $('input[type="checkbox"]').siblings('.w-checkbox-input').removeClass('w--redirected-checked'); 1444 1445 // Clear the selectedCategories array if you have it 1446 selectedCategories = []; 1447 selectedZnacky = []; 1448 selectedCountries = []; 1449 selectedDestinations = []; 1450 1451 // Remove active class from country boxes 1452 $('.catalog-filter-detail--box.country-filter').removeClass('is-active'); 1453 1454 // Optionally re-fetch the exhibitors without any categories 1455 fetchExhibitors($('.search-input').val().trim(), selectedCategories, false, selectedZnacky, selectedCountries, selectedDestinations); 1456 }); 1457 1458}); 1459 1460function debounce(func, delay) { 1461 let timeout; 1462 return function (...args) { 1463 clearTimeout(timeout); // Clear the timeout if the function is called again within the delay 1464 timeout = setTimeout(() => { 1465 func.apply(this, args); // Call the function after the delay 1466 }, delay); 1467 }; 1468} 1469 1470function appendCountries(data) { 1471 const $panel = $('.js-katalog-panel[data-panel="countries"]'); 1472 if ($panel.length === 0) return; 1473 1474 $panel.empty(); 1475 1476 if (!data || data.length === 0) { 1477 $panel.append('<div>Nebyly nalezeny žádné státy.</div>'); 1478 return; 1479 } 1480 1481 data.forEach(function (countryName) { 1482 if (!countryName) return; 1483 1484 const boxHTML = ` 1485 <div class="catalog-filter-detail--box country-filter" data-name="${countryName}" style="cursor: pointer;"> 1486 <div class="img"> 1487 <img class="img" src="${customBlockData.pluginUrl}images/files.svg" /> 1488 </div> 1489 <span class="list-item">${countryName}</span> 1490 </div> 1491 `; 1492 1493 $panel.append(boxHTML); 1494 }); 1495} 1496 1497function appendBrandsPanel(data) { 1498 const $panel = $('.js-katalog-panel[data-panel="brands"]'); 1499 if ($panel.length === 0) return; 1500 1501 $panel.empty(); 1502 1503 if (!data || Object.keys(data).length === 0) { 1504 $panel.append('<div>Nebyly nalezeny žádné znaÄky.</div>'); 1505 return; 1506 } 1507 1508 // Sort brands alphabetically by name 1509 const sorted = Object.keys(data) 1510 .filter(function (key) { return key !== ''; }) 1511 .map(function (key) { return data[key]; }) 1512 .sort(function (a, b) { 1513 var nameA = (a.name || '').toLowerCase(); 1514 var nameB = (b.name || '').toLowerCase(); 1515 if (nameA < nameB) return -1; 1516 if (nameA > nameB) return 1; 1517 return 0; 1518 }); 1519 1520 sorted.forEach(function (item) { 1521 var brandName = item.name || ''; 1522 if (!brandName) return; 1523 1524 var boxHTML = ` 1525 <div class="catalog-filter-detail--box brand-filter" data-name="${brandName}" style="cursor: pointer;"> 1526 <div class="img"> 1527 <img class="img" src="${customBlockData.pluginUrl}images/files.svg" /> 1528 </div> 1529 <span class="list-item">${brandName}</span> 1530 </div> 1531 `; 1532 1533 $panel.append(boxHTML); 1534 }); 1535 1536 if ($panel.children().length === 0) { 1537 $panel.append('<div>Nebyly nalezeny žádné znaÄky.</div>'); 1538 } 1539} 1540 1541function appendDestinationsPanel(data) { 1542 const $panel = $('.js-katalog-panel[data-panel="destinations"]'); 1543 if ($panel.length === 0) return; 1544 1545 $panel.empty(); 1546 1547 if (!data || Object.keys(data).length === 0) { 1548 return; 1549 } 1550 1551 // Render continent boxes (hierarchy is already sorted alphabetically from PHP) 1552 Object.keys(data).forEach(function (continentName) { 1553 if (!continentName) return; 1554 1555 const boxHTML = ` 1556 <div class="catalog-filter-detail--box continent-filter" data-name="${continentName}" style="cursor: pointer;"> 1557 <div class="img"> 1558 <img class="img" src="${customBlockData.pluginUrl}images/files.svg" /> 1559 </div>
1560 <span class="list-item">${continentName}</span> 1561 </div> 1562 `; 1563 1564 $panel.append(boxHTML); 1565 }); 1566} 1567 1568function fetchPlanData() { 1569 var hallsDeferred = $.Deferred(); 1570 var imageDeferred = $.Deferred(); 1571 1572 // Fetch hall data 1573 $.ajax({ 1574 url: '/wp-json/api/v1/exhibition-plan', 1575 method: 'POST', 1576 data: { 1577 hallLoadNeeded: true, 1578 catalog: currentCatalogSourceKey 1579 }, 1580 success: function (response) { 1581 planHallsData = response['haly'] || null; 1582 hallsDeferred.resolve(); 1583 }, 1584 error: function () { 1585 console.log('Error fetching hall data.'); 1586 hallsDeferred.resolve(); 1587 } 1588 }); 1589 1590 // Fetch main plan image 1591 $.ajax({ 1592 url: '/wp-json/api/v1/exhibitor-plan-image', 1593 method: 'POST', 1594 data: { 1595 catalog: currentCatalogSourceKey 1596 }, 1597 success: function (response) { 1598 var file = typeof response === 'string' ? JSON.parse(response) : response; 1599 if (file && file.id) { 1600 mainPlanImageUrl = 'https://is.abf.cz/file/raster?nl_id=' + file.id + '&s_format_to=png'; 1601 } 1602 imageDeferred.resolve(); 1603 }, 1604 error: function () { 1605 console.log('Error fetching plan image.'); 1606 imageDeferred.resolve(); 1607 } 1608 }); 1609 1610 // After both resolve, render the panel and show the tab 1611 $.when(hallsDeferred, imageDeferred).done(function () { 1612 if (planHallsData && Object.keys(planHallsData).length > 0) { 1613 appendPlanPanel(); 1614 $('.js-katalog-tabs label[data-tab="plan"]').show(); 1615 } 1616 }); 1617} 1618 1619function appendPlanPanel() { 1620 var $panel = $('.js-katalog-panel[data-panel="plan"]'); 1621 if ($panel.length === 0) return; 1622 1623 $panel.empty(); 1624 1625 // Sort halls numerically 1626 var sortedHalls = Object.keys(planHallsData) 1627 .map(function (key) { return planHallsData[key]; }) 1628 .sort(function (a, b) { 1629 // ÄÃslované haly nejdÅÃv (ÄÃselnÄ), pojmenované plochy (bez ÄÃsla) na konec. 1630 var numA = (safeDecodeURIComponent(a.s_file_type).match(/\d+/) || [9999])[0]; 1631 var numB = (safeDecodeURIComponent(b.s_file_type).match(/\d+/) || [9999])[0]; 1632 return parseInt(numA, 10) - parseInt(numB, 10); 1633 }); 1634 1635 // Build flex container: map (left) + hall grid (right) 1636 var containerHTML = '<div class="plan-layout" style="display:flex;gap:24px;flex-wrap:wrap;">'; 1637 1638 // Left: map image 1639 containerHTML += '<div class="plan-map-wrapper" style="flex:1 1 55%;min-width:300px;">'; 1640 if (mainPlanImageUrl) { 1641 containerHTML += '<a class="plan-map-lightbox" href="' + mainPlanImageUrl + '" data-lightbox="catalog-plan-map" data-title="Plánek výstaviÅ¡tÄ" aria-label="OtevÅÃt plánek ve vÄtÅ¡Ãm náhledu" style="display:block;">'; 1642 containerHTML += '<img class="plan-map-image" src="' + mainPlanImageUrl + '" style="width:100%;height:auto;cursor:zoom-in;" />'; 1643 containerHTML += '</a>'; 1644 } 1645 containerHTML += '</div>'; 1646 1647 // Right: hall grid (scrollable, height synced to map image) 1648 containerHTML += '<div class="plan-halls-grid" style="flex:1 1 40%;min-width:250px;display:flex;flex-direction:column;gap:8px;overflow-y:auto;">'; 1649 1650 sortedHalls.forEach(function (hall) { 1651 // s_file_type je už zobrazovaný název ("Hala 1", "Vstupnà hala I", "Venkovnà plocha"). 1652 var displayName = safeDecodeURIComponent(hall.s_file_type).replace(/\+/g, ' '); 1653 // data-hall držà normalizovaný klÃÄ haly pro párovánà ("1", "Vstupnà hala I"). 1654 var hallKey = normalizeHallKey(hall.s_hala != null ? hall.s_hala : hall.s_file_type); 1655 var hallImage = hall.s_file_url_jpg || ''; 1656 1657 containerHTML += '<div class="catalog-filter-detail--box hall-filter" data-hall="' + hallKey.replace(/"/g, '"') + '" data-image="' + hallImage + '" style="cursor:pointer; width: 100%;">'; 1658 containerHTML += '<div class="img"><img class="img" src="' + customBlockData.pluginUrl + 'images/files.svg" /></div>'; 1659 containerHTML += '<span class="list-item">' + displayName + '</span>'; 1660 containerHTML += '</div>'; 1661 }); 1662 1663 containerHTML += '</div></div>'; 1664 1665 $panel.append(containerHTML); 1666 1667 var $mapImg = $panel.find('.plan-map-image'); 1668 ensurePlanMapLightbox($panel, mainPlanImageUrl); 1669 function updatePlanLayout() { 1670 syncPlanScrollablePanels($panel); 1671 } 1672 $mapImg.on('load', updatePlanLayout); 1673 // Also sync if image is already cached 1674 if ($mapImg[0] && $mapImg[0].complete) { 1675 updatePlanLayout(); 1676 } 1677 $(window).off('resize.planScrollablePanels').on('resize.planScrollablePanels', debounce(function () { 1678 updatePlanLayout(); 1679 }, 100)); 1680 1681 // Check for pending hall restore from URL 1682 if (pendingHallRestore) { 1683 var hallToRestore = pendingHallRestore; 1684 pendingHallRestore = null; 1685 $('.catalog-filter-detail--box.hall-filter').filter(function () { 1686 return $(this).data('hall').toString() === hallToRestore.toString(); 1687 }).first().trigger('click'); 1688 } 1689} 1690 1691function updateSortIcons(isAscending, $sortButtonSelector) { 1692 // Update sort button icon logic based on the passed sortButtonSelector 1693 1694 $sortButtonSelector.addClass('is-active').siblings().removeClass('is-active'); 1695 $sortButtonSelector.siblings().find('.sort-icon').removeClass('is-active'); 1696 1697 if (isAscending) { 1698 $sortButtonSelector.find('.sort-icon').first().addClass('is-active'); // Show ascending icon 1699 $sortButtonSelector.find('.sort-icon').last().removeClass('is-active'); // Show ascending icon 1700 1701 1702 } else { 1703 $sortButtonSelector.find('.sort-icon').first().removeClass('is-active'); // Show ascending icon 1704 $sortButtonSelector.find('.sort-icon').last().addClass('is-active'); // Show ascending icon 1705 } 1706} 1707 1708function appendHallFilter(filtersWrapper, data) { 1709 const sortedData = Object.keys(data) 1710 .map(key => data[key]) // Convert the object into an array of its values 1711 .sort((a, b) => { 1712 // Decode the names (since they are URL-encoded) 1713 const decodedHallA = safeDecodeURIComponent(a.s_file_type).replace(/\+/g, ' '); 1714 const hallNumberA = decodedHallA.match(/\d+/g); 1715 const hallResultA = hallNumberA ? parseInt(hallNumberA[0], 10) : null; 1716 1717 const decodedHallB = safeDecodeURIComponent(b.s_file_type).replace(/\+/g, ' '); 1718 const hallNumberB = decodedHallB.match(/\d+/g); 1719 const hallResultB = hallNumberB ? parseInt(hallNumberB[0], 10) : null; 1720 1721 // If either value is null, treat it as the lowest priority 1722 if (hallResultA === null) return 1; 1723 if (hallResultB === null) return -1; 1724 1725 // Compare numerically 1726 return hallResultA - hallResultB; 1727 }); 1728 1729 sortedData.unshift({ s_file_type: 'vÅ¡e' }); 1730 1731 // Clear the wrapper before adding new content 1732 filtersWrapper.empty(); 1733 1734 filtersWrapper.append('<div class="filter-category-text">Hala:</div>'); 1735 1736 // Loop through the data and create the labels dynamically
1737 Object.keys(sortedData).forEach(function (key) { 1738 const item = sortedData[key]; 1739 1740 // Decode the name (since it's URL-encoded) 1741 const decodedHall = safeDecodeURIComponent(item.s_file_type).replace(/\+/g, ' '); 1742 const hallNumber = decodedHall.match(/\d+/g); 1743 const hallResult = hallNumber ? hallNumber[0] : decodedHall; 1744 1745 const labelHTML = ` 1746 <label class="filter-button w-radio ${hallResult === "vše" ? 'is-active' : ''}"> 1747 <div class="w-form-formradioinput w-form-formradioinput--inputType-custom radio_button w-radio-input"></div> 1748 <input class="hall-filter-input" data-name="dispozice" id="${item.nl_id}" name="dispozice" style="opacity:0;position:absolute;z-index:-1" type="radio" value="${hallResult}"> 1749 <span class="radio_label w-form-label hall-filter-span" for="${item.nl_id}" fs-cmsfilter-active="is-active" fs-cmsfilter-field="dispozice"> 1750 ${hallResult} 1751 </span> 1752 </label> 1753 `; 1754 1755 // Append to the list and assign data using .data() 1756 $(labelHTML).appendTo(filtersWrapper); 1757 }); 1758} 1759 1760function buildLogoSrc(item) { 1761 // Priorita 1: s_file_url 1762 var fileUrl = safeDecodeURIComponent(item.s_file_url || '').replace(/(?!^)\+/g, ' '); 1763 if (fileUrl) return fileUrl; 1764 1765 // Priorita 2: s_logo_base64 1766 var base64 = item.s_logo_base64 || ''; 1767 if (base64) { 1768 var mimeType = base64.startsWith('/9j/') ? 'image/jpeg' : 'image/png'; 1769 return 'data:' + mimeType + ';base64,' + base64; 1770 } 1771 1772 return ''; 1773} 1774 1775function appendLogosToWrapper(logos) { 1776 // Define the function to render the HTML structure for each logo 1777 function renderLogoHTML(logo, index) { 1778 // Create the anchor element with required attributes 1779 const anchor = document.createElement('a'); 1780 anchor.setAttribute('key', index); 1781 anchor.className = 'logo-wrapper w-inline-block'; 1782 anchor.target = '_blank'; 1783 anchor.rel = 'noopener'; 1784 anchor.href = safeDecodeURIComponent(logo.s_partner_url); 1785 1786 // Create the image element inside the anchor 1787 const img = document.createElement('img'); 1788 img.alt = ''; 1789 img.className = 'partner-logo-image'; 1790 img.loading = 'lazy';
1791 img.src = logo.logo_image; 1792 1793 // Append the image to the anchor 1794 anchor.appendChild(img); 1795 1796 return anchor; 1797 } 1798 1799 // Function to append logos to a specific section 1800 function appendLogosToSection(logosArray, sectionClass) { 1801 const sectionWrapper = document.querySelector(`.${sectionClass} .logos-wrapper`); 1802 if (sectionWrapper) { 1803 logosArray.forEach((logo, index) => { 1804 const logoElement = renderLogoHTML(logo, index); 1805 sectionWrapper.appendChild(logoElement); 1806 }); 1807 } 1808 } 1809 1810 // Append logos to each corresponding section 1811 if (logos.zastita.length > 0) { 1812 appendLogosToSection(logos.zastita, 'zastita'); 1813 } 1814 1815 if (logos.media.length > 0) { 1816 appendLogosToSection(logos.media, 'media'); 1817 } 1818 1819 if (logos.dalsi.length > 0) { 1820 appendLogosToSection(logos.dalsi, 'dalsi'); 1821 } 1822} 1823 1824function appendExhibitorPlan(planListWrapper, data, haly) { 1825 const sortedData = Object.keys(data) 1826 .map(key => data[key]) // Convert the object into an array of its values 1827 .sort((a, b) => { 1828 let valA, valB; 1829 valA = safeDecodeURIComponent(a.s_nazev).toLowerCase(); 1830 valB = safeDecodeURIComponent(b.s_nazev).toLowerCase(); 1831 1832 if (valA < valB) return -1; 1833 if (valA > valB) return 1; 1834 return 0; 1835 }); 1836 1837 const sortedDataHaly = Object.keys(haly) 1838 .map(key => haly[key]) // Convert the object into an array of its values 1839 .sort((a, b) => { 1840 // Decode the names (since they are URL-encoded) 1841 const decodedHallA = safeDecodeURIComponent(a.s_file_type).replace(/\+/g, ' '); 1842 const hallNumberA = decodedHallA.match(/\d+/g); 1843 const hallResultA = hallNumberA ? parseInt(hallNumberA[0], 10) : null; 1844 1845 const decodedHallB = safeDecodeURIComponent(b.s_file_type).replace(/\+/g, ' '); 1846 const hallNumberB = decodedHallB.match(/\d+/g); 1847 const hallResultB = hallNumberB ? parseInt(hallNumberB[0], 10) : null; 1848 1849 // If either value is null, treat it as the lowest priority 1850 if (hallResultA === null) return 1; 1851 if (hallResultB === null) return -1; 1852 1853 // Compare numerically 1854 return hallResultA - hallResultB; 1855 }); 1856 1857 1858 planListWrapper.empty(); 1859 1860 sortedData.forEach(function (item, index) { 1861 1862 let fileUrlJpg = ''; 1863 Object.keys(sortedDataHaly).forEach(function (key) { 1864 const hallItem = sortedDataHaly[key]; 1865 1866 // Decode the name (since it's URL-encoded) 1867 const decodedHall = safeDecodeURIComponent(hallItem.s_file_type).replace(/\+/g, ' '); 1868 const hallNumber = decodedHall.match(/\d+/g); 1869 const hallResult = hallNumber ? hallNumber[0] : decodedHall; 1870 1871 1872 if (hallResult == item.s_hala && hallItem.s_file_url_jpg) { 1873 fileUrlJpg = safeDecodeURIComponent(hallItem.s_file_url_jpg).replace(/(?!^)\+/g, ' '); 1874 } 1875 }); 1876 1877 1878 // Decode the name (since it's URL-encoded) 1879 const decodedName = safeDecodeURIComponent(item.s_nazev).replace(/\+/g, ' '); 1880 const decodedHala = safeDecodeURIComponent(item.s_hala); 1881 const decodedStanek = safeDecodeURIComponent(item.s_stanek); 1882 const decodedStreet = safeDecodeURIComponent(item.s_street).replace(/\+/g, ' '); 1883 const decodedCity = safeDecodeURIComponent(item.s_city).replace(/\+/g, ' '); 1884 const decodedZip = safeDecodeURIComponent(item.s_zip).replace(/\+/g, ' '); 1885 let website = safeDecodeURIComponent(item.s_www).split(',')[0].trim(); 1886 1887 let halaStanek = ''; 1888 if (decodedHala !== '++++++') { 1889 halaStanek = decodedStanek !== '' ? `${decodedHala}-${decodedStanek}` : `${decodedHala}--`; 1890 } else if (decodedStanek !== '') { 1891 halaStanek = `--${decodedStanek}`; 1892 } else { 1893 halaStanek = ''; 1894 } 1895 1896 let companyData = { 1897 halaStanek: halaStanek, 1898 name: decodedName, 1899 description: safeDecodeURIComponent(item.s_profil).replace(/\+/g, ' '), 1900 address: decodedStreet + ' ' + decodedCity + ', ' + decodedZip, 1901 email: safeDecodeURIComponent(item.s_email).split(',')[0].trim(), 1902 phone: safeDecodeURIComponent(item.s_tel1).split(',')[0].trim().replace(/(?!^)\+/g, ' '), 1903 www: website, 1904 categories: [], 1905 fileUrl: buildLogoSrc(item), 1906 planUrlJpg: fileUrlJpg 1907 } 1908 1909 const listItemClass = index > 4 ? 'companies-list-item companies-list-item-js see-more-hidden' : 'companies-list-item';
1910 1911 // Create the label structure 1912 const labelHTML = ` 1913 <li class="${listItemClass}"> 1914 <a class="company-detail-link w-inline-block" href="#"> 1915 <div class="company-name">${decodedName}</div> 1916 <div class="company-location">${halaStanek}</div> 1917 </a> 1918 </li> 1919 `; 1920 1921 // Append to the list and assign data using .data() 1922 const $label = $(labelHTML).appendTo(planListWrapper); 1923 $label.find('.company-detail-link').data('company', companyData); 1924 }); 1925 1926 getDetailData($('.company-detail-link')) 1927 1928 Webflow.require('ix2').init(); 1929 1930 $('.see-more').off('click').on('click', function () { 1931 const $this = $(this); 1932 const isExpanded = $this.data('expanded') || false; 1933 1934 $('.companies-list-item-js').toggleClass('see-more-hidden', isExpanded); 1935 1936 $this.text(isExpanded ? 'Zobrazit vÃce' : 'Zobrazit ménÄ'); 1937 $this.data('expanded', !isExpanded); 1938 }); 1939} 1940 1941function slugify(str) { 1942 str = str.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); // normalize and remove diacritics 1943 str = str.replace(/^\s+|\s+$/g, ''); // trim leading/trailing white space 1944 str = str.toLowerCase(); // convert string to lowercase 1945 str = str.replace(/[^a-z0-9 -]/g, '') // remove any non-alphanumeric characters 1946 .replace(/\s+/g, '-') // replace spaces with hyphens 1947 .replace(/-+/g, '-'); // remove consecutive hyphens 1948 return str; 1949} 1950 1951function appendExhibitorList(exhibitorListWrapper, data, isAscending, sortBy) { 1952 const sortedData = Object.keys(data) 1953 .map(key => data[key]) // Convert the object into an array of its values 1954 .sort((a, b) => { 1955 let valA, valB; 1956 1957 if (sortBy === 'name') { 1958 // Sort by `s_nazev` (name) 1959 valA = safeDecodeURIComponent(a.s_nazev).toLowerCase(); 1960 valB = safeDecodeURIComponent(b.s_nazev).toLowerCase(); 1961 } else if (sortBy === 'hala') { 1962 // Sort by `s_hala` (hall) 1963 valA = safeDecodeURIComponent(a.s_hala).toLowerCase(); 1964 valB = safeDecodeURIComponent(b.s_hala).toLowerCase(); 1965 } 1966 1967 if (valA < valB) return isAscending ? -1 : 1; 1968 if (valA > valB) return isAscending ? 1 : -1; 1969 return 0; 1970 }); 1971 1972 exhibitorListWrapper.empty(); 1973 1974 if (sortedData.length > 0) { 1975 sortedData.forEach(function (item) { 1976 // Decode the name (since it's URL-encoded) 1977 const decodedName = safeDecodeURIComponent(item.s_nazev).replace(/\+/g, ' '); 1978 const decodedHala = safeDecodeURIComponent(item.s_hala); 1979 const decodedStanek = safeDecodeURIComponent(item.s_stanek); 1980 const decodedStreet = safeDecodeURIComponent(item.s_street).replace(/\+/g, ' '); 1981 const decodedCity = safeDecodeURIComponent(item.s_city).replace(/\+/g, ' '); 1982 const decodedZip = safeDecodeURIComponent(item.s_zip).replace(/\+/g, ' '); 1983 let website = safeDecodeURIComponent(item.s_www).split(',')[0].trim(); 1984 let znackyArray = item.znacky_names; 1985 if (!website.startsWith("http://") && !website.startsWith("https://") && website !== '') { 1986 website = "https://" + website; // If not, add "https://" before the URL 1987 } 1988 1989 let halaStanek = ''; 1990 if (decodedHala !== '++++++') { 1991 halaStanek = decodedStanek !== '' ? `${decodedHala}-${decodedStanek}` : `${decodedHala}--`; 1992 } else if (decodedStanek !== '') { 1993 halaStanek = `--${decodedStanek}`; 1994 } else { 1995 halaStanek = ''; 1996 } 1997 1998 1999 let companyData = { 2000 halaStanek: halaStanek, 2001 name: decodedName, 2002 description: safeDecodeURIComponent(item.s_profil).replace(/\+/g, ' '), 2003 address: decodedStreet + ' ' + decodedCity + ', ' + decodedZip, 2004 email: safeDecodeURIComponent(item.s_email).split(',')[0].trim(), 2005 phone: safeDecodeURIComponent(item.s_tel1).split(',')[0].trim().replace(/(?!^)\+/g, ' ').replace(/\//g, ''), 2006 www: website, 2007 categories: item.category_names, 2008 category_codes: item.category_codes, 2009 fileUrl: buildLogoSrc(item),
2010 znacky_names: znackyArray, 2011 destinace_names: item.destinace_names 2012 } 2013 2014 2015 // Create the label structure 2016 const labelHTML = ` 2017 <li class="companies-list-item"> 2018 <a href="#" data-company="${companyData}" class="company-detail-katalog-link w-inline-block"> 2019 <div class="company-name katalog">${decodedName}</div> 2020 <div class="company-location katalog">${halaStanek}</div> 2021 </a> 2022 </li> 2023 `; 2024 2025 // Append to the list and assign data using .data() 2026 const $label = $(labelHTML).appendTo(exhibitorListWrapper); 2027 $label.find('.company-detail-katalog-link').data('company', companyData); 2028 }); 2029 2030 getDetailData($('.company-detail-katalog-link')); 2031 } else { 2032 const labelHTML = ` 2033 <li class="companies-list-item"> 2034 <div class="company-name katalog">Vyhledávánà neodpovÃdajà žádnà vystavujÃcÃ</div> 2035 </li> 2036 `; 2037 2038 // Append to the list and assign data using .data() 2039 const $label = $(labelHTML).appendTo(exhibitorListWrapper); 2040 $label.find('.company-detail-katalog-link').data('company', null); 2041 } 2042 2043 2044 Webflow.require('ix2').init(); 2045} 2046 2047function getDetailData($link) { 2048 $link.on('click', function (e) { 2049 const companyData = $(this).data('company'); 2050 2051 $('#company-detail-title').text(companyData.name); 2052 2053 if (companyData.description === '') { 2054 $('#company-detail-description').hide(); 2055 } else { 2056 $('#company-detail-description').text(companyData.description).show(); 2057 } 2058 2059 if (companyData.halaStanek === '') { 2060 $('#company-detail-hall').hide(); 2061 } else { 2062 $('#company-detail-hall .company-info-list-text').text(companyData.halaStanek); 2063 $('#company-detail-hall').show(); 2064 } 2065 2066 $('#company-detail-address .company-info-list-text').text(companyData.address); 2067 2068 if (companyData.email === '' || companyData.email === '+') { 2069 $('#company-detail-email').hide(); 2070 } else { 2071 $('#company-detail-email .company-info-list-text').attr('href', 'mailto:' + companyData.email).text(companyData.email); 2072 $('#company-detail-email').show(); 2073 } 2074 2075 if (companyData.phone === '' || companyData.phone === '+') { 2076 $('#company-detail-phone').hide(); 2077 } else { 2078 $('#company-detail-phone .company-info-list-text').attr('href', 'tel:' + companyData.phone.replace(/\//g, ''),).text(companyData.phone.replace(/\//g, ''),); 2079 $('#company-detail-phone').show(); 2080 } 2081 2082 if (companyData.www === '') { 2083 $('#company-detail-www').hide(); 2084 } else { 2085 $('#company-detail-www').attr('href', companyData.www).text(companyData.www).show(); 2086 } 2087 2088 if (companyData.fileUrl === '') { 2089 $('.plan-logo-image-wrapper').hide(); 2090 } else { 2091 $('.plan-logo-image').attr('src', companyData.fileUrl); 2092 $('.plan-logo-image-wrapper').show(); 2093 } 2094 2095 if (companyData.planUrlJpg) { 2096 $('.hala-detail-plan-image').attr('src', companyData.planUrlJpg); 2097 } else { 2098 // Hala nemá vlastnà obrázek â zobraz hlavnà plán 2099 $('.hala-detail-plan-image').attr('src', $('.main-plan-image').attr('src')); 2100 } 2101 2102 if (companyData.categories.length > 0) { 2103 $('#company-detail-obory .company-detail-obory-list').empty(); 2104 let hasValidCategory = false; 2105 companyData.categories.forEach(function (category) { 2106 if (category === '') { 2107 return; 2108 } 2109 2110 hasValidCategory = true; 2111 let categoryName = safeDecodeURIComponent(category).replace(/\+/g, ' '); 2112 2113 $('#company-detail-obory .company-detail-obory-list').append(` 2114 <li class="field-list-item"> 2115 <div class="dot-wrapper"> 2116 <div class="dot-icon"></div> 2117 </div> 2118 <div>${categoryName}</div> 2119 </li> 2120 `); 2121 }); 2122 if (hasValidCategory) { 2123 $('#company-detail-obory').show(); 2124 } else { 2125 $('#company-detail-obory').hide(); 2126 } 2127 } else { 2128 $('#company-detail-obory').hide(); 2129 } 2130
2131 if (companyData.znacky_names.length > 0) { 2132 $('#company-detail-znacky .company-detail-znacky-list').empty(); 2133 companyData.znacky_names.forEach(function (znacka) { 2134 $('#company-detail-znacky .company-detail-znacky-list').append(` 2135 <li class="field-list-item"> 2136 <div class="dot-wrapper"> 2137 <div class="dot-icon"></div> 2138 </div> 2139 <div>${znacka}</div> 2140 </li> 2141 `); 2142 }); 2143 $('#company-detail-znacky').show(); 2144 } else { 2145 $('#company-detail-znacky').hide(); 2146 } 2147 }); 2148} 2149 2150 2151function appendOboroveCleneni(filtersWrapper, data) { 2152 // Clear the wrapper before adding new content 2153 filtersWrapper.empty(); 2154 2155 filtersWrapper.append('<div className="filter-body-spacer"></div>'); 2156 2157 let hasValidFilter = false; 2158 2159 // Loop through the data and create the labels dynamically 2160 Object.keys(data).forEach(function (key) { 2161 if (key === "") { 2162 return; 2163 } 2164 2165 hasValidFilter = true; 2166 const item = data[key]; 2167 2168 // Decode the name (since it's URL-encoded) 2169 const decodedName = safeDecodeURIComponent(item.s_name).replace(/\+/g, ' '); 2170 const decodedCode = safeDecodeURIComponent(item.s_code); 2171 2172 // Create a unique ID for each checkbox 2173 const checkboxId = `checkbox-${item.id}`; 2174 2175 // Create the label structure 2176 const labelHTML = ` 2177 <label class="w-checkbox checkbox-field"> 2178 <div class="w-checkbox-input w-checkbox-input--inputType-custom checkbox"></div> 2179 <input type="checkbox" name="${checkboxId}" id="${checkboxId}" data-name="${decodedCode}" data-slug="${slugify(decodedName)}" style="opacity:0;position:absolute;z-index:-1"/> 2180 <span class="checkbox-label w-form-label" for="${checkboxId}">${decodedName}</span> 2181 </label> 2182 `; 2183 2184 // Append the created label to the wrapper 2185 filtersWrapper.append(labelHTML); 2186 }); 2187 2188 if (!hasValidFilter) { 2189 filtersWrapper.append('<div>Nebyly nalezeny žádné obory.</div><div className="filter-body-spacer"></div>'); 2190 } 2191} 2192 2193function appendZnacky(filtersWrapper, data) { 2194 // Clear the wrapper before adding new content 2195 filtersWrapper.empty(); 2196 2197 filtersWrapper.append('<div className="filter-body-spacer"></div>'); 2198 2199 let hasValidFilter = false; 2200 2201 // Loop through the data and create the labels dynamically 2202 Object.keys(data).forEach(function (key) { 2203 const item = data[key]; 2204 2205 if (key === "") { 2206 return; 2207 } 2208 2209 hasValidFilter = true; 2210 2211 // Decode the name (since it's URL-encoded) 2212 const decodedName = item.name; 2213 const decodedCode = safeDecodeURIComponent(item.id); 2214 2215 // Create a unique ID for each checkbox 2216 const checkboxId = `checkbox-${item.id}`; 2217 2218 // Create the label structure 2219 const labelHTML = ` 2220 <label class="w-checkbox checkbox-field"> 2221 <div class="w-checkbox-input w-checkbox-input--inputType-custom checkbox"></div> 2222 <input class="znacky-input-checkbox" type="checkbox" name="${checkboxId}" id="${checkboxId}" data-id="${decodedCode}" data-name="${decodedName}" data-slug="${slugify(decodedName)}" style="opacity:0;position:absolute;z-index:-1"/> 2223 <span class="checkbox-label w-form-label" for="${checkboxId}">${decodedName}</span> 2224 </label> 2225 `; 2226 2227 // Append the created label to the wrapper 2228 filtersWrapper.append(labelHTML); 2229 }); 2230 2231 if (!hasValidFilter) { 2232 filtersWrapper.append('<div>Nebyly nalezeny žádné znaÄky.</div><div className="filter-body-spacer"></div>'); 2233 } 2234} 2235 2236// Newsletter form 2237function newsletterFormSubmit() { 2238 const form = $('.newsletter-form-wrapper'); 2239 const visibleNewsletterButton = $('.button.newsletter:visible'); 2240 2241 if (form.length > 0) { 2242 form.on('submit', function (event) { 2243 event.preventDefault(); // Prevent the default form submission behavior 2244 2245 // Update button text and hide messages 2246 visibleNewsletterButton.val(visibleNewsletterButton.data('wait')); 2247 2248 var formData = $(this).serialize(); 2249 2250 var dataArray = $(this).serializeArray(); 2251 var email = ''; 2252 2253 $.ajax({ 2254 url: '/wp-json/api/v1/newsletter', method: 'POST', data: formData, success: function (data) { 2255 let response = ''; 2256 let isJson = true; 2257 2258 if (typeof data === 'string') { 2259 try { 2260 response = JSON.parse(data); 2261 } catch (e) { 2262 isJson = false;
2263 response = data; 2264 } 2265 } 2266 if (isJson && response.res === 1) { 2267 dataArray.forEach(function (field) { 2268 if (field.name === 's_email') { 2269 email = field.value; 2270 } 2271 }); 2272 2273 dataLayer.push({ 2274 "event": "newsletter_subscription", "email": email 2275 }); 2276 2277 newsletterHandleSuccess(form); 2278 } else { 2279 newsletterHandleError(); 2280 } 2281 }, error: function () { 2282 newsletterHandleError(); 2283 }, complete: function () { 2284 visibleNewsletterButton.val(visibleNewsletterButton.data('done')); 2285 } 2286 }) 2287 }); 2288 } 2289} 2290 2291function newsletterHandleSuccess(form) { 2292 const agreementCustomCheck = form.find('.w-checkbox-input--inputType-custom.checkbox'); 2293 const agreementInput = form.find('#b_agree_gdpr'); 2294 const successMessage = $('.w-form-done'); 2295 const errorMessage = $('.w-form-fail'); 2296 2297 successMessage.fadeIn(); 2298 errorMessage.fadeOut(); 2299 2300 agreementInput.prop('checked', false); 2301 agreementCustomCheck.removeClass('w--redirected-checked'); 2302 $(form)[0].reset(); 2303 2304 setTimeout(function () { 2305 successMessage.fadeOut(); 2306 }, 5000); 2307} 2308 2309function newsletterHandleError(errorMsg = '') { 2310 const successMessage = $('.w-form-done'); 2311 const errorMessage = $('.w-form-fail'); 2312 2313 if (errorMsg !== '') { 2314 errorMessage.find('.error-wrapper').html('<div>' + errorMsg + '</div>'); 2315 } 2316 2317 successMessage.fadeOut(); 2318 errorMessage.fadeIn(); 2319 2320 setTimeout(function () { 2321 errorMessage.fadeOut(); 2322 }, 5000); 2323} 2324 2325// Vystavovatel form 2326function exhibitorFormSubmit() { 2327 const form = $('#exhibitor-registration-form'); 2328 const submitButton = form.find('.button'); 2329 2330 form.on('submit', function (e) { 2331 e.preventDefault(); // Prevent the form from submitting the traditional way 2332 2333 submitButton.val(submitButton.data('wait')); 2334 2335 // Gather the form data 2336 var formData = $(this).serialize(); 2337 2338 var dataArray = $(this).serializeArray(); 2339 var email = ''; 2340 var telefon = ''; 2341 2342 if (window.iti.isValidNumberPrecise()) { 2343 // Make the API call 2344 $.ajax({ 2345 url: '/wp-json/api/v1/exhibitor-registration', 2346 method: 'POST', 2347 data: formData, 2348 success: function (data) { 2349 let response = ''; 2350 let isJson = true; 2351 2352 if (typeof data === 'string') { 2353 try { 2354 response = JSON.parse(data); 2355 } catch (e) { 2356 isJson = false; 2357 response = data; 2358 } 2359 } 2360 if (isJson && response.res === 1) { 2361 dataArray.forEach(function (field) { 2362 if (field.name === 's_email') { 2363 email = field.value; 2364 } else if (field.name === 's_phone') { 2365 if (window.iti) { 2366 telefon = window.iti.getNumber(); // Get the full phone number with country code 2367 } else { 2368 telefon = field.value; 2369 } 2370 } 2371 }); 2372 if (window.iti) { 2373 telefon = window.iti.getNumber(); 2374 } 2375 dataLayer.push({ 2376 "event": "exhibitor_registration_sent", 2377 "email": email, 2378 "phone": telefon 2379 }); 2380 2381 2382 exhibitorHandleSuccess(form); 2383 } else { 2384 exhibitorHandleError(); 2385 } 2386 }, 2387 error: function () { 2388 exhibitorHandleError(); 2389 }, 2390 complete: function () { 2391 submitButton.val(submitButton.data('done')); 2392 } 2393 }) 2394 } else {
2395 // If the phone number is not valid, show an error message 2396 exhibitorHandleError('Zadejte platné telefonnà ÄÃslo.'); 2397 submitButton.val(submitButton.data('done')); 2398 } 2399 }); 2400} 2401 2402function exhibitorHandleSuccess(form) { 2403 const agreementCustomCheck = form.find('.w-checkbox-input--inputType-custom.checkbox'); 2404 const agreementInput = form.find('#b_agree_vop'); 2405 const successMessage = $('.w-form-done'); 2406 const errorMessage = $('.w-form-fail'); 2407 2408 successMessage.fadeIn(); 2409 errorMessage.fadeOut(); 2410 2411 agreementInput.prop('checked', false); 2412 agreementCustomCheck.removeClass('w--redirected-checked'); 2413 $(form)[0].reset(); 2414 2415 setTimeout(function () { 2416 successMessage.fadeOut(); 2417 }, 5000); 2418} 2419 2420function exhibitorHandleError(errorMsg = '') { 2421 const successMessage = $('.w-form-done'); 2422 const errorMessage = $('.w-form-fail'); 2423 2424 if (errorMsg !== '') { 2425 errorMessage.find('.error-wrapper').html('<div>' + errorMsg + '</div>'); 2426 } 2427 2428 successMessage.fadeOut(); 2429 errorMessage.fadeIn(); 2430 2431 setTimeout(function () { 2432 errorMessage.fadeOut(); 2433 }, 5000); 2434} 2435 2436// Kontakt form 2437function contactFormSubmit() { 2438 const form = $('#contact-form'); 2439 const submitButton = form.find('.button'); 2440 2441 form.on('submit', function (e) { 2442 e.preventDefault(); // Prevent the form from submitting the traditional way 2443 2444 submitButton.val(submitButton.data('wait')); 2445 2446 // Gather the form data 2447 var formData = $(this).serialize(); 2448 2449 var dataArray = $(this).serializeArray(); 2450 var email = ''; 2451 var telefon = ''; 2452 2453 2454 if (window.iti.isValidNumberPrecise()) { 2455 // Make the API call 2456 $.ajax({ 2457 url: '/wp-json/api/v1/contact', 2458 method: 'POST', 2459 data: formData, 2460 success: function (data) { 2461 let response = ''; 2462 let isJson = true; 2463 2464 if (typeof data === 'string') { 2465 try { 2466 response = JSON.parse(data); 2467 } catch (e) { 2468 isJson = false; 2469 response = data; 2470 } 2471 } 2472 if (isJson && response.res === 1) { 2473 dataArray.forEach(function (field) { 2474 if (field.name === 's_email') { 2475 email = field.value; 2476 } else if (field.name === 's_phone') { 2477 telefon = field.value; 2478 } 2479 }); 2480 if (window.iti) { 2481 telefon = window.iti.getNumber(); 2482 } 2483 dataLayer.push({ 2484 "event": "contact_form_sent", 2485 "email": email, 2486 "phone": telefon 2487 }); 2488 2489 contactHandleSuccess(form); 2490 } else { 2491 contactHandleError(); 2492 } 2493 }, 2494 error: function () { 2495 contactHandleError(); 2496 }, 2497 complete: function () { 2498 submitButton.val(submitButton.data('done')); 2499 } 2500 }) 2501 } else { 2502 // If the phone number is not valid, show an error message 2503 contactHandleError('Zadejte platné telefonnà ÄÃslo.'); 2504 submitButton.val(submitButton.data('done')); 2505 } 2506 }); 2507} 2508 2509function contactHandleSuccess(form) { 2510 const agreementCustomCheck = form.find('.w-checkbox-input--inputType-custom.checkbox'); 2511 const agreementInput = form.find('#b_agree_vop'); 2512 const successMessage = $('.w-form-done'); 2513 const errorMessage = $('.w-form-fail'); 2514 2515 successMessage.fadeIn(); 2516 errorMessage.fadeOut(); 2517 2518 agreementInput.prop('checked', false); 2519 agreementCustomCheck.removeClass('w--redirected-checked'); 2520 $(form)[0].reset(); 2521 2522 setTimeout(function () { 2523 successMessage.fadeOut(); 2524 }, 5000); 2525} 2526 2527function contactHandleError(errorMsg = '') { 2528 const successMessage = $('.w-form-done'); 2529 const errorMessage = $('.w-form-fail'); 2530 2531 if (errorMsg !== '') { 2532 errorMessage.html('<div>' + errorMsg + '</div>'); 2533 } 2534 2535 successMessage.fadeOut(); 2536 errorMessage.fadeIn(); 2537 2538 setTimeout(function () { 2539 errorMessage.fadeOut(); 2540 }, 5000); 2541} 2542 2543function checkAkreditaceOtevrena() { 2544 const akreditaceForm = $('#accreditation-form'); 2545 const akreditaceButton = akreditaceForm.find('.button'); 2546 2547 if (akreditaceForm.length > 0) { 2548 $.ajax({ 2549 url: '/wp-json/api/v1/config', 2550 method: 'GET', 2551 success: function (data) { 2552 if (data.condition === 60) { 2553 $('#accreditation-form input').prop('disabled', true); 2554 akreditaceButton.prop('disabled', true); 2555 akreditaceBeforeEventError(data.message); 2556 } 2557 2558 if (data.condition === 1) { 2559 $('#accreditation-form').hide() 2560 akreditaceButton.prop('disabled', false); 2561 akreditaceBeforeEventError(data.message); 2562 } 2563 }, 2564 error: function () { 2565 console.log('Nastala chyba pÅi naÄÃtánà dat o configu'); 2566 } 2567 }); 2568 } 2569} 2570 2571// Akreditace form 2572function akreditaceFormSubmit() { 2573 const form = $('#accreditation-form'); 2574 const submitButton = form.find('.button'); 2575 2576 form.on('submit', function (e) { 2577 e.preventDefault(); // Prevent the form from submitting the traditional way 2578 2579 submitButton.val(submitButton.data('wait')); 2580 2581 // Gather the form data 2582 var formData = $(this).serialize(); 2583 2584 var dataArray = $(this).serializeArray(); 2585 var email = ''; 2586 var telefon = ''; 2587 2588 if (window.iti.isValidNumberPrecise()) { 2589 $.ajax({ 2590 url: '/wp-json/api/v1/accreditation', 2591 method: 'POST', 2592 data: formData, 2593 success: function (data) { 2594 let response = ''; 2595 let isJson = true; 2596 2597 if (typeof data === 'string') { 2598 try { 2599 response = JSON.parse(data); 2600 } catch (e) { 2601 isJson = false;
2602 response = data; 2603 } 2604 } 2605 if (isJson && response.res === 1) { 2606 dataArray.forEach(function (field) { 2607 if (field.name === 's_email') { 2608 email = field.value; 2609 } else if (field.name === 's_phone') { 2610 telefon = field.value; 2611 } 2612 }); 2613 if (window.iti) { 2614 telefon = window.iti.getNumber(); 2615 } 2616 dataLayer.push({ 2617 "event": "journalist_registration_sent", 2618 "email": email, 2619 "phone": telefon 2620 }); 2621 2622 akreditaceHandleSuccess(form); 2623 } else { 2624 akreditaceHandleError(response.msg); 2625 } 2626 }, 2627 error: function () { 2628 akreditaceHandleError(); 2629 }, 2630 complete: function () { 2631 submitButton.val(submitButton.data('done')); 2632 } 2633 }) 2634 } else { 2635 akreditaceHandleError('Zadejte platné telefonnà ÄÃslo.'); 2636 submitButton.val(submitButton.data('done')); 2637 } 2638 }); 2639} 2640 2641function akreditaceHandleSuccess(form) { 2642 const agreementCustomCheck = form.find('.w-checkbox-input--inputType-custom.checkbox'); 2643 const agreementInput = form.find('#b_agree_vop'); 2644 const agreementGDPRInput = form.find('#b_agree_gdpr'); 2645 const successMessage = $('.w-form-done'); 2646 const errorMessage = $('.w-form-fail'); 2647 2648 successMessage.fadeIn(); 2649 errorMessage.fadeOut(); 2650 2651 agreementInput.prop('checked', false); 2652 agreementGDPRInput.prop('checked', false); 2653 agreementCustomCheck.each(function () { 2654 $(this).removeClass('w--redirected-checked'); 2655 }); 2656 $(form)[0].reset(); 2657 2658 setTimeout(function () { 2659 successMessage.fadeOut(); 2660 }, 5000); 2661} 2662 2663function akreditaceBeforeEventError(errorMsg = '') { 2664 const errorMessage = $('.w-form-fail.before-event'); 2665 2666 if (errorMsg !== '') { 2667 errorMessage.find('.error-wrapper.before-event').html('<div>' + errorMsg + '</div>'); 2668 } 2669 2670 errorMessage.fadeIn(); 2671} 2672 2673function akreditaceHandleError(errorMsg = '', timeout = true) { 2674 const successMessage = $('.w-form-done'); 2675 const errorMessage = $('.w-form-fail'); 2676 2677 if (errorMsg !== '') { 2678 errorMessage.find('.error-wrapper').html('<div>' + errorMsg + '</div>'); 2679 } 2680 2681 successMessage.fadeOut(); 2682 errorMessage.fadeIn(); 2683 2684 if (timeout) { 2685 setTimeout(function () { 2686 errorMessage.fadeOut(); 2687 }, 5000); 2688 } 2689} 2690 2691// Vstupenka od vystavovatele form 2692function exhibitorTicketFormSubmit() { 2693 const form = $('#exhibitor-ticket-form'); 2694 const submitButton = form.find('.button'); 2695 const urlParams = new URLSearchParams(window.location.search); 2696 const vParam = urlParams.get('v'); 2697 2698 form.on('submit', function (e) { 2699 e.preventDefault(); // Prevent the form from submitting the traditional way 2700 2701 submitButton.val(submitButton.data('wait')); 2702 2703 // Gather the form data 2704 var formData = $(this).serialize(); 2705 2706 // Make the API call 2707 $.ajax({ 2708 url: '/wp-json/api/v1/exhibitor-ticket', 2709 method: 'POST', 2710 data: { 2711 formData: formData, 2712 parameter: vParam 2713 }, 2714 success: function (data) { 2715 let response = ''; 2716 let isJson = true; 2717 2718 if (typeof data === 'string') { 2719 try { 2720 response = JSON.parse(data); 2721 } catch (e) { 2722 isJson = false; 2723 response = data; 2724 } 2725 } 2726 if (isJson && response.res === 1) { 2727 exhibitorTicketHandleSuccess(form); 2728 } else { 2729 if (isJson) { 2730 exhibitorTicketHandleError(response.msg); 2731 } else { 2732 exhibitorTicketHandleError(response); 2733 } 2734 } 2735 }, 2736 error: function () { 2737 exhibitorTicketHandleError(); 2738 }, 2739 complete: function () { 2740 submitButton.val(submitButton.data('done')); 2741 } 2742 }) 2743 }); 2744} 2745 2746function exhibitorTicketHandleSuccess(form) {
2747 const agreementCustomCheck = form.find('.w-checkbox-input--inputType-custom.checkbox'); 2748 const agreementInput = form.find('#b_agree_vop'); 2749 const agreementGDPRInput = form.find('#b_agree_gdpr'); 2750 const successMessage = $('.w-form-done'); 2751 const errorMessage = $('.w-form-fail'); 2752 2753 successMessage.fadeIn(); 2754 errorMessage.fadeOut(); 2755 2756 agreementInput.prop('checked', false); 2757 agreementGDPRInput.prop('checked', false); 2758 agreementCustomCheck.each(function () { 2759 $(this).removeClass('w--redirected-checked'); 2760 }); 2761 $(form)[0].reset(); 2762 2763 setTimeout(function () { 2764 successMessage.fadeOut(); 2765 }, 5000); 2766} 2767 2768function exhibitorTicketHandleError(errorMsg = '') { 2769 const successMessage = $('.w-form-done'); 2770 const errorMessage = $('.w-form-fail'); 2771 2772 if (errorMsg !== '') { 2773 errorMessage.find('.error-wrapper').html('<div>' + errorMsg + '</div>'); 2774 } 2775 2776 successMessage.fadeOut(); 2777 errorMessage.fadeIn(); 2778 2779 setTimeout(function () { 2780 errorMessage.fadeOut(); 2781 }, 5000); 2782} 2783 2784function appendFieldListObory(fieldWrapper, data) { 2785 // Clear the wrapper before adding new content 2786 fieldWrapper.empty(); 2787 2788 Object.keys(data).forEach(function (key) { 2789 const item = data[key]; 2790 const decodedName = safeDecodeURIComponent(item.s_name).replace(/\+/g, ' '); 2791 2792 const labelHTML = ` 2793 <li class="field-list-item"> 2794 <div class="dot-wrapper"> 2795 <div class="dot-icon"></div> 2796 </div> 2797 <div> 2798 ${decodedName} 2799 </div> 2800 </li> 2801 `; 2802 2803 // Append the created label to the wrapper 2804 fieldWrapper.append(labelHTML); 2805 }); 2806} 2807 2808function initKatalogTabs() { 2809 $('.js-katalog-tabs').on('change', 'input[type="radio"]', function () { 2810 var tab = $(this).closest('label').data('tab'); 2811 if (!tab) return; 2812 2813 var $interactableWrapper = $(this).closest('.interactable-part-wrapper-catalog'); 2814 2815 // If not found (new layout), try finding via common parent 2816 if ($interactableWrapper.length === 0) { 2817 $interactableWrapper = $(this).closest('.newsletter-card-wrapper').find('.interactable-part-wrapper-catalog'); 2818 } 2819 2820 var $listFilter = $interactableWrapper.find('.list-with-filter'); 2821 2822 // Reset: hide field detail and company detail, restore list only if it was hidden 2823 $interactableWrapper.find('.field-detail-wrapper').remove(); 2824 $interactableWrapper.find('.obory-nav').remove(); 2825 $interactableWrapper.find('.obory-exhibitors').remove(); 2826 $interactableWrapper.find('.plan-hall-topbar').remove(); 2827 $interactableWrapper.find('.plan-hall-detail').remove(); 2828 $interactableWrapper.find('.plan-halls-grid').show(); 2829 if (mainPlanImageUrl) { 2830 $interactableWrapper.find('.plan-map-image').attr('src', mainPlanImageUrl); 2831 ensurePlanMapLightbox($interactableWrapper, mainPlanImageUrl); 2832 } 2833 $interactableWrapper.find('.company-detail-wrapper-new').hide(); 2834 if ($listFilter.is(':hidden')) { 2835 $listFilter.css('display', ''); 2836 } 2837 2838 // Show/hide panels 2839 $listFilter.find('.js-katalog-panel').addClass('is-hidden'); 2840 $listFilter.find('.js-katalog-panel[data-panel="' + tab + '"]').removeClass('is-hidden'); 2841 2842 // Update URL params when switching tabs (clear filter params) 2843 if (tab === 'companies') { 2844 updateCatalogUrl({}); 2845 } else { 2846 updateCatalogUrl({ tab: tab }); 2847 } 2848 2849 // Reset obory hierarchy to root when switching to the fields tab 2850 if (tab === 'fields' && oboryFullData) { 2851 var $fieldsPanel = $listFilter.find('.js-katalog-panel[data-panel="fields"]'); 2852 if ($fieldsPanel.length > 0) { 2853 renderFieldsLevel($fieldsPanel, ''); 2854 } 2855 } 2856 2857 // Show/hide search & sort for companies tab only 2858 var isCompanies = (tab === 'companies'); 2859 if (isCompanies) { 2860 // Remove inline styles so CSS controls visibility (preserves media query rules) 2861 $listFilter.find('.headline-and-search').css('display', ''); 2862 $listFilter.children('.sort-buttons-wrapper').css('display', ''); 2863 $listFilter.children('.filter-reset').css('display', ''); 2864 } else { 2865 $listFilter.find('.headline-and-search').hide(); 2866 $listFilter.children('.sort-buttons-wrapper').hide(); 2867 $listFilter.children('.filter-reset').hide(); 2868 } 2869 }); 2870} 2871 2872function appendExhibitorListNew(exhibitorListWrapper, data, isAscending, sortBy) { 2873 const sortedData = Object.keys(data) 2874 .map(key => data[key]) 2875 .sort((a, b) => { 2876 let valA, valB; 2877 2878 if (sortBy === 'name') { 2879 valA = safeDecodeURIComponent(a.s_nazev).toLowerCase(); 2880 valB = safeDecodeURIComponent(b.s_nazev).toLowerCase(); 2881 } else if (sortBy === 'hala') { 2882 valA = safeDecodeURIComponent(a.s_hala).toLowerCase(); 2883 valB = safeDecodeURIComponent(b.s_hala).toLowerCase(); 2884 } 2885 2886 if (valA < valB) return isAscending ? -1 : 1; 2887 if (valA > valB) return isAscending ? 1 : -1; 2888 return 0; 2889 }); 2890 2891 let isFieldDetailList = exhibitorListWrapper.hasClass('field-detail-exhibitor-list') || exhibitorListWrapper.hasClass('plan'); 2892 2893 exhibitorListWrapper.empty(); 2894 2895 if (sortedData.length > 0) { 2896 sortedData.forEach(function (item) { 2897 const decodedName = safeDecodeURIComponent(item.s_nazev).replace(/\+/g, ' '); 2898 const decodedHala = safeDecodeURIComponent(item.s_hala); 2899 const decodedStanek = safeDecodeURIComponent(item.s_stanek); 2900 const decodedStreet = safeDecodeURIComponent(item.s_street).replace(/\+/g, ' '); 2901 const decodedCity = safeDecodeURIComponent(item.s_city).replace(/\+/g, ' '); 2902 const decodedZip = safeDecodeURIComponent(item.s_zip).replace(/\+/g, ' '); 2903 let website = safeDecodeURIComponent(item.s_www).split(',')[0].trim(); 2904 let znackyArray = item.znacky_names; 2905 let description = safeDecodeURIComponent(item.s_profil).replace(/\+/g, ' '); 2906 let fileUrl = buildLogoSrc(item); 2907 2908 if (!website.startsWith("http://") && !website.startsWith("https://") && website !== '') { 2909 website = "https://" + website; 2910 } 2911 2912 let halaStanek = ''; 2913 if (decodedHala !== '++++++') { 2914 halaStanek = decodedStanek !== '' ? `${decodedHala}-${decodedStanek}` : `${decodedHala}--`; 2915 } else if (decodedStanek !== '') { 2916 halaStanek = `--${decodedStanek}`; 2917 } else { 2918 halaStanek = ''; 2919 } 2920 2921 let companyData = { 2922 halaStanek: halaStanek, 2923 name: decodedName, 2924 description: description, 2925 address: decodedStreet + ' ' + decodedCity + ', ' + decodedZip, 2926 email: safeDecodeURIComponent(item.s_email).split(',')[0].trim(), 2927 emails: safeDecodeURIComponent(item.s_email).split(',').map(e => e.trim()).filter(e => e), 2928 phone: safeDecodeURIComponent(item.s_tel1).split(',')[0].trim().replace(/(?!^)\+/g, ' ').replace(/\//g, ''), 2929 phones: safeDecodeURIComponent(item.s_tel1).split(',').map(p => p.trim().replace(/(?!^)\+/g, ' ').replace(/\//g, '')).filter(p => p && p !== '+'), 2930 www: website, 2931 urls: safeDecodeURIComponent(item.s_www).split(',').map(u => u.trim()).filter(u => u), 2932 categories: item.category_names, 2933 category_codes: item.category_codes, 2934 fileUrl: fileUrl,
2935 znacky_names: znackyArray, 2936 destinace_names: item.destinace_names, 2937 najdete_na_veletrhu: item.s_najdete_na_veletrhu 2938 }; 2939 2940 let descPreview = description.length > 200 ? description.substring(0, 200) + '...' : description; 2941 2942 const labelHTML = ` 2943 <li class="companies-list-item"> 2944 <a href="#" class="company-detail-katalog-link company-detail-katalog-link-new ${isFieldDetailList ? 'company-detail-from-field' : ''} w-inline-block flex-column p4 gap-16px"> 2945 <div class="flex-row flex-justify-between gap-16px"> 2946 <div class="flex-column gap-16px"> 2947 <div class="company-name katalog">${decodedName}</div> 2948 ${halaStanek ? ` 2949 <ul role="list" class="list-column-wrapper"> 2950 <li class="field-list-item"> 2951 <div class="icon-wrapper"> 2952 <img class="icon-16px w-flex w-embed" src="${customBlockData.pluginUrl}images/pointer.svg" /> 2953 </div> 2954 <div>Hala - stánek: ${halaStanek}</div> 2955 </li> 2956 </ul> 2957 ` : ''} 2958 </div> 2959 ${fileUrl ? ` 2960 <div class="logo"> 2961 <img src="${fileUrl}" class="img" /> 2962 </div> 2963 ` : ''} 2964 </div> 2965 ${descPreview ? `<p class="paragraph under-list shady-text">${descPreview}</p>` : ''} 2966 </a> 2967 </li> 2968 `; 2969 2970 const $label = $(labelHTML).appendTo(exhibitorListWrapper); 2971 if (isFieldDetailList) { 2972 $label.find('.company-detail-from-field').data('company', companyData); 2973 } else { 2974 $label.find('.company-detail-katalog-link-new').data('company', companyData); 2975 } 2976 }); 2977 2978 if (!isFieldDetailList) { 2979 getDetailDataNew($('.company-detail-katalog-link-new').not('.company-detail-from-field')); 2980 } 2981 } else { 2982 exhibitorListWrapper.append(` 2983 <li class="companies-list-item"> 2984 <div class="company-name katalog">Vyhledávánà neodpovÃdajà žádnà vystavujÃcÃ</div> 2985 </li> 2986 `); 2987 } 2988 2989 // Auto-open exhibitor if restoring from URL 2990 if (pendingExhibitorRestore) { 2991 var name = pendingExhibitorRestore; 2992 exhibitorListWrapper.find('.company-detail-from-field').each(function () { 2993 var data = $(this).data('company'); 2994 if (data && data.name === name) { 2995 pendingExhibitorRestore = null; 2996 $(this).trigger('click'); 2997 return false; 2998 } 2999 }); 3000 } 3001 3002 Webflow.require('ix2').init(); 3003} 3004 3005function populateCompanyDetail(companyData, $detail) { 3006 const $readHeight = $detail.find('#readHeight'); 3007 $readHeight.addClass('w-100'); 3008 3009 // Company name (first .fields-headline in the top section) 3010 $readHeight.children('.flex-column').first().find('.fields-headline').first().text(companyData.name); 3011 3012 // Hall/stand 3013 const $hallList = $readHeight.children('.flex-column').first().find('.list-column-wrapper'); 3014 $hallList.empty(); 3015 if (companyData.halaStanek) { 3016 $hallList.append(` 3017 <li class="field-list-item"> 3018 <div class="icon-wrapper"> 3019 <img class="icon-16px w-flex w-embed" src="${customBlockData.pluginUrl}images/pointer.svg" /> 3020 </div> 3021 <div class="company-info-list-text">Hala - stánek: ${companyData.halaStanek}</div> 3022 </li> 3023 `); 3024 $hallList.show(); 3025 } else { 3026 $hallList.hide(); 3027 } 3028 3029 // Logo 3030 const $logoContainer = $readHeight.children('.flex-column').first().find('.logo'); 3031 const $logo = $logoContainer.find('img'); 3032 if (companyData.fileUrl) { 3033 $logo.attr('src', companyData.fileUrl); 3034 $logoContainer.show(); 3035 } else { 3036 $logoContainer.hide(); 3037 } 3038 3039 // Description 3040 const $desc = $readHeight.children('.flex-column').first().find('.paragraph.under-list'); 3041 if (companyData.description) { 3042 $desc.text(companyData.description).show(); 3043 } else { 3044 $desc.hide(); 3045 } 3046 3047 // Address 3048 const $addressItem = $detail.find('#company-detail-address'); 3049 if (companyData.address && companyData.address.trim() !== '' && companyData.address.trim() !== ',') { 3050 $addressItem.find('.company-info-list-text').html( 3051 companyData.address.replace(/, /g, '<br>') 3052 ); 3053 $addressItem.show(); 3054 } else { 3055 $addressItem.hide(); 3056 } 3057 3058 // Email 3059 const $emailItem = $detail.find('#company-detail-email'); 3060 $detail.find('.extra-email').remove(); 3061 const emails = (companyData.emails && companyData.emails.length > 0) ? companyData.emails : (companyData.email ? [companyData.email] : []);
3062 const validEmails = emails.filter(e => e && e !== '' && e !== '+'); 3063 if (validEmails.length > 0) { 3064 $emailItem.find('.company-info-list-text').attr('href', 'mailto:' + validEmails[0]).text(validEmails[0]); 3065 $emailItem.show(); 3066 validEmails.slice(1).forEach(function (email) { 3067 const $clone = $emailItem.clone().removeAttr('id').addClass('extra-email'); 3068 $clone.find('.company-info-list-text').attr('href', 'mailto:' + email).text(email); 3069 $emailItem.after($clone); 3070 }); 3071 } else { 3072 $emailItem.hide(); 3073 } 3074 3075 // Phone 3076 const $phoneItem = $detail.find('#company-detail-phone'); 3077 $detail.find('.extra-phone').remove(); 3078 const phones = (companyData.phones && companyData.phones.length > 0) ? companyData.phones : (companyData.phone ? [companyData.phone] : []); 3079 const validPhones = phones.filter(p => p && p !== '' && p !== '+'); 3080 if (validPhones.length > 0) { 3081 $phoneItem.find('.company-info-list-text').attr('href', 'tel:' + validPhones[0].replace(/\//g, '')).text(validPhones[0]); 3082 $phoneItem.show(); 3083 validPhones.slice(1).forEach(function (phone) { 3084 const $clone = $phoneItem.clone().removeAttr('id').addClass('extra-phone'); 3085 $clone.find('.company-info-list-text').attr('href', 'tel:' + phone.replace(/\//g, '')).text(phone); 3086 $phoneItem.after($clone); 3087 }); 3088 } else { 3089 $phoneItem.hide(); 3090 } 3091 3092 // Website 3093 const $webListItems = $detail.find('#company-detail-web'); 3094 $detail.find('.extra-www').remove(); 3095 const urls = (companyData.urls && companyData.urls.length > 0) ? companyData.urls : (companyData.www ? [companyData.www] : []); 3096 const validUrls = urls.filter(u => u && u !== ''); 3097 if (validUrls.length > 0) { 3098 $webListItems.find('.company-website').attr('href', validUrls[0]).text(validUrls[0].replace(/^https?:\/\//, '')); 3099 $webListItems.show(); 3100 validUrls.slice(1).forEach(function (url) { 3101 const $clone = $webListItems.clone().removeAttr('id').addClass('extra-www'); 3102 $clone.find('.company-website').attr('href', url).text(url.replace(/^https?:\/\//, '')); 3103 $webListItems.after($clone); 3104 }); 3105 } else { 3106 $webListItems.hide(); 3107 } 3108 3109 // Obory/Categories 3110 const $obory = $detail.find('#company-detail-obory'); 3111 const $oboryContainer = $obory.find('.catalog-filter-detail'); 3112 $oboryContainer.empty(); 3113 3114 if (companyData.categories && companyData.categories.length > 0) { 3115 let hasValid = false; 3116 companyData.categories.forEach(function (category, idx) { 3117 if (category === '') return; 3118 hasValid = true; 3119 let categoryName = safeDecodeURIComponent(category).replace(/\+/g, ' '); 3120 let categoryCode = (companyData.category_codes && companyData.category_codes[idx]) ? companyData.category_codes[idx] : ''; 3121 let innerHTML = ` 3122 <div class="icon-wrapper"> 3123 <img class="icon-16px w-flex w-embed" src="${customBlockData.pluginUrl}images/files.svg" /> 3124 </div> 3125 <div>${categoryName}</div> 3126 `; 3127 if (categoryCode) { 3128 $oboryContainer.append(` 3129 <a href="${buildCatalogFilterUrl({ tab: 'fields', obor: categoryCode })}" data-filter-type="obor" data-obor="${categoryCode}" class="company-detail-info-list-item company-detail-info-list-item--flex field-list-item flex-align-center catalog-filter-link">${innerHTML}</a> 3130 `); 3131 } else { 3132 $oboryContainer.append(` 3133 <div class="company-detail-info-list-item company-detail-info-list-item--flex field-list-item flex-align-center">${innerHTML}</div> 3134 `); 3135 } 3136 }); 3137 if (hasValid) { 3138 $obory.show().prev('.companies-list-header').show(); 3139 } else { 3140 $obory.hide().prev('.companies-list-header').hide(); 3141 } 3142 } else { 3143 $obory.hide().prev('.companies-list-header').hide(); 3144 } 3145 3146 // Znacky (first element with id company-detail-znacky) 3147 const $znacky = $detail.find('#company-detail-znacky').first(); 3148 const $znackyContainer = $znacky.find('.catalog-filter-detail'); 3149 $znackyContainer.empty();
3150 let hasValidZnacky = false; 3151 3152 if (companyData.znacky_names && companyData.znacky_names.length > 0) { 3153 companyData.znacky_names.forEach(function (znacka) { 3154 if (znacka) { 3155 hasValidZnacky = true; 3156 $znackyContainer.append(` 3157 <div class="company-detail-info-list-item company-detail-info-list-item--flex_4 flex-column field-list-item gap-16px"> 3158 <div>${znacka}</div> 3159 </div> 3160 `); 3161 } 3162 }); 3163 } 3164 3165 if (hasValidZnacky) { 3166 $znacky.show().prev('.companies-list-header').show(); 3167 } else { 3168 $znacky.hide().prev('.companies-list-header').hide(); 3169 } 3170 3171 // Destinace 3172 const $destinace = $detail.find('#company-detail-destinace'); 3173 const $destinaceContainer = $destinace.find('.catalog-filter-detail'); 3174 $destinaceContainer.empty(); 3175 let hasValidDestinace = false; 3176 3177 if (companyData.destinace_names && companyData.destinace_names.length > 0) { 3178 companyData.destinace_names.forEach(function (destinace) { 3179 if (destinace) { 3180 hasValidDestinace = true; 3181 let continentName = getContinentForDestination(destinace); 3182 if (continentName) { 3183 $destinaceContainer.append(` 3184 <a href="${buildCatalogFilterUrl({ tab: 'destinations', continent: continentName, destination: destinace })}" data-filter-type="destination" data-continent="${continentName}" data-destination="${destinace}" class="company-detail-info-list-item company-detail-info-list-item--flex_4 flex-column field-list-item gap-16px catalog-filter-link"> 3185 <div>${destinace}</div> 3186 </a> 3187 `); 3188 } else { 3189 $destinaceContainer.append(` 3190 <div class="company-detail-info-list-item company-detail-info-list-item--flex_4 flex-column field-list-item gap-16px"> 3191 <div>${destinace}</div> 3192 </div> 3193 `); 3194 } 3195 } 3196 }); 3197 } 3198 3199 if (hasValidDestinace) { 3200 $destinace.show().prev('.companies-list-header').show(); 3201 } else { 3202 $destinace.hide().prev('.companies-list-header').hide(); 3203 } 3204 3205 // Najdete na veletrhu (first element with id company-detail-locations) 3206 const $locations = $detail.find('#company-detail-locations').first(); 3207 const $locationsContainer = $locations.find('.catalog-filter-detail'); 3208 $locationsContainer.empty(); 3209 let hasValidLocations = false; 3210 3211 if (companyData.najdete_na_veletrhu && companyData.najdete_na_veletrhu.length > 0) { 3212 companyData.najdete_na_veletrhu.forEach(function (loc) { 3213 if (loc) { 3214 hasValidLocations = true; 3215 const linkTarget = loc.link_target || '_blank'; 3216 const linkTitle = loc.link_title || loc.title; 3217 $locationsContainer.append(` 3218 <div class="blog-card-link flex-row w-inline-block"> 3219 ${loc.thumbnail ? ` 3220 <div class="blog-card-image-wrapper img-w-30"> 3221 <img src="${loc.thumbnail}" class="blog-image" /> 3222 </div> 3223 ` : ''} 3224 <div class="blog-content-wrapper"> 3225 <div class="blog-text-wrapper"> 3226 <div class="flex-row gap-8px flex-align-end flex-justify-between blog-container-reverse"> 3227 <div class="flex-column"> 3228 <div class="blog-card-headline pb-2">${loc.title}</div> 3229 </div> 3230 ${loc.logo ? ` 3231 <div class="img-w-130"> 3232 <img src="${loc.logo}" class="img" /> 3233 </div> 3234 ` : ''} 3235 </div> 3236 ${loc.description ? `<p class="paragraph blog-card-description no-line">${loc.description}</p>` : ''} 3237 ${loc.url ? ` 3238 <a href="${loc.url}" target="${linkTarget}"> 3239 <div class="blog-card-bottom-wrapper"> 3240 <div>${linkTitle}</div> 3241 <div class="icon-18px w-embed" style="transform: translate3d(0px, 0px, 0px) scale3d(1, 1, 1) rotateX(0deg) rotateY(0deg) rotateZ(0deg) skew(0deg, 0deg); transform-style: preserve-3d;"> 3242 <svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg"> 3243 <path d="M15.7499 9.49988H2.24989M15.7499 9.49988L11.2499 4.99988M15.7499 9.49988L11.2499 13.9999" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> 3244 </svg> 3245 </div> 3246 </div> 3247 </a> 3248 ` : ''} 3249 </div> 3250 </div> 3251 </div> 3252 `); 3253 } 3254 }); 3255 } 3256 3257 if (hasValidLocations) { 3258 $locations.show().prev('.companies-list-header').show(); 3259 } else { 3260 $locations.hide().prev('.companies-list-header').hide(); 3261 } 3262} 3263 3264function renderFieldDetailExhibitors($listWrapper, data, isAscending = true, sortBy = 'name') { 3265 const sortedData = Object.keys(data) 3266 .map(key => data[key]) 3267 .sort((a, b) => { 3268 let valA, valB; 3269 3270 if (sortBy === 'name') { 3271 valA = safeDecodeURIComponent(a.s_nazev).toLowerCase(); 3272 valB = safeDecodeURIComponent(b.s_nazev).toLowerCase(); 3273 } else if (sortBy === 'hala') { 3274 valA = safeDecodeURIComponent(a.s_hala).toLowerCase(); 3275 valB = safeDecodeURIComponent(b.s_hala).toLowerCase(); 3276 } 3277 3278 if (valA < valB) return isAscending ? -1 : 1; 3279 if (valA > valB) return isAscending ? 1 : -1; 3280 return 0; 3281 }); 3282 3283 $listWrapper.empty(); 3284 3285 if (sortedData.length > 0) { 3286 sortedData.forEach(function (item) { 3287 const decodedName = safeDecodeURIComponent(item.s_nazev).replace(/\+/g, ' '); 3288 const decodedHala = safeDecodeURIComponent(item.s_hala); 3289 const decodedStanek = safeDecodeURIComponent(item.s_stanek); 3290 const decodedStreet = safeDecodeURIComponent(item.s_street).replace(/\+/g, ' '); 3291 const decodedCity = safeDecodeURIComponent(item.s_city).replace(/\+/g, ' '); 3292 const decodedZip = safeDecodeURIComponent(item.s_zip).replace(/\+/g, ' '); 3293 let website = safeDecodeURIComponent(item.s_www).split(',')[0].trim(); 3294 let znackyArray = item.znacky_names; 3295 let description = safeDecodeURIComponent(item.s_profil).replace(/\+/g, ' '); 3296 let fileUrl = buildLogoSrc(item); 3297 3298 if (!website.startsWith("http://") && !website.startsWith("https://") && website !== '') { 3299 website = "https://" + website; 3300 } 3301 3302 let halaStanek = ''; 3303 if (decodedHala !== '++++++') { 3304 halaStanek = decodedStanek !== '' ? `${decodedHala}-${decodedStanek}` : `${decodedHala}--`; 3305 } else if (decodedStanek !== '') { 3306 halaStanek = `--${decodedStanek}`; 3307 } else { 3308 halaStanek = ''; 3309 } 3310 3311 let companyData = { 3312 halaStanek: halaStanek, 3313 name: decodedName, 3314 description: description, 3315 address: decodedStreet + ' ' + decodedCity + ', ' + decodedZip, 3316 email: safeDecodeURIComponent(item.s_email).split(',')[0].trim(), 3317 emails: safeDecodeURIComponent(item.s_email).split(',').map(e => e.trim()).filter(e => e), 3318 phone: safeDecodeURIComponent(item.s_tel1).split(',')[0].trim().replace(/(?!^)\+/g, ' ').replace(/\//g, ''), 3319 phones: safeDecodeURIComponent(item.s_tel1).split(',').map(p => p.trim().replace(/(?!^)\+/g, ' ').replace(/\//g, '')).filter(p => p && p !== '+'), 3320 www: website, 3321 urls: safeDecodeURIComponent(item.s_www).split(',').map(u => u.trim()).filter(u => u), 3322 categories: item.category_names, 3323 category_codes: item.category_codes, 3324 fileUrl: fileUrl,
3325 znacky_names: znackyArray, 3326 destinace_names: item.destinace_names, 3327 najdete_na_veletrhu: item.s_najdete_na_veletrhu 3328 }; 3329 3330 let descPreview = description.length > 200 ? description.substring(0, 200) + '...' : description; 3331 3332 const labelHTML = ` 3333 <li class="companies-list-item"> 3334 <a href="#" class="company-detail-katalog-link company-detail-katalog-link-new company-detail-from-field w-inline-block flex-column p4 gap-16px"> 3335 <div class="flex-row flex-justify-between gap-16px"> 3336 <div class="flex-column gap-16px"> 3337 <div class="company-name katalog">${decodedName}</div> 3338 ${halaStanek ? ` 3339 <ul role="list" class="list-column-wrapper"> 3340 <li class="field-list-item"> 3341 <div class="icon-wrapper"> 3342 <img class="icon-16px w-flex w-embed" src="${customBlockData.pluginUrl}images/pointer.svg" /> 3343 </div> 3344 <div>Hala - stánek: ${halaStanek}</div> 3345 </li> 3346 </ul> 3347 ` : ''} 3348 </div> 3349 ${fileUrl ? ` 3350 <div class="logo"> 3351 <img src="${fileUrl}" class="img" /> 3352 </div> 3353 ` : ''} 3354 </div> 3355 ${descPreview ? `<p class="paragraph under-list shady-text">${descPreview}</p>` : ''} 3356 </a> 3357 </li> 3358 `; 3359 3360 const $label = $(labelHTML).appendTo($listWrapper); 3361 $label.find('.company-detail-from-field').data('company', companyData); 3362 }); 3363 } else { 3364 $listWrapper.append(` 3365 <li class="companies-list-item"> 3366 <div class="company-name katalog">Vyhledávánà neodpovÃdajà žádnà vystavujÃcÃ</div> 3367 </li> 3368 `); 3369 } 3370 3371 // Auto-open exhibitor if restoring from URL 3372 if (pendingExhibitorRestore) { 3373 var name = pendingExhibitorRestore; 3374 $listWrapper.find('.company-detail-from-field').each(function () { 3375 var data = $(this).data('company'); 3376 if (data && data.name === name) { 3377 pendingExhibitorRestore = null; 3378 $(this).trigger('click'); 3379 return false; 3380 } 3381 }); 3382 } 3383} 3384 3385function getDetailDataNew($link) { 3386 $link.on('click', function (e) { 3387 const companyData = $(this).data('company'); 3388 if (!companyData) return; 3389 3390 // Pokud je link zároveÅ .company-detail-from-field, ten má vlastnà handler 3391 // s správným navigation-source â necháme ho zpracovat celou logiku 3392 if ($(this).hasClass('company-detail-from-field')) return; 3393 3394 e.preventDefault(); 3395 setExhibitorParam(companyData.name); 3396 3397 // Find detail in the same block/wrapper 3398 let $detail = $(this).closest('.newsletter-card-wrapper').find('.company-detail-wrapper-new'); 3399 3400 // Fallback or specific selection 3401 if ($detail.length === 0) { 3402 $detail = $('.company-detail-wrapper-new'); 3403 } 3404 3405 // Set navigation source to list (regular exhibitor list) 3406 $detail.data('navigation-source', 'list'); 3407 3408 // Show detail, hide list 3409 $(this).closest('.interactable-part-wrapper-catalog').find('.list-with-filter').hide(); 3410 $detail.css('display', 'flex'); 3411 3412 $('html, body').animate({ scrollTop: $('#detail').offset().top }, 300); 3413 3414 populateCompanyDetail(companyData, $detail); 3415 }); 3416 3417 // Back button handler 3418 const $detail = $('.company-detail-wrapper-new'); 3419 $detail.find('.back-katalog-button').off('click').on('click', function (e) { 3420 e.preventDefault(); 3421 e.stopImmediatePropagation(); 3422 setExhibitorParam(null); 3423 var source = $detail.data('navigation-source'); 3424 $detail.hide().css('opacity', ''); 3425 if (source === 'obory-drilled') { 3426 // Return to the obory drilled-in view (list-with-filter has panel + exhibitors) 3427 $('.interactable-part-wrapper-catalog .list-with-filter').css('display', ''); 3428 } else if (source === 'plan-hall') { 3429 // Return to the plan hall exhibitor view 3430 $('.interactable-part-wrapper-catalog .list-with-filter').css('display', ''); 3431 $('.interactable-part-wrapper-catalog .plan-hall-topbar').css('display', 'flex'); 3432 } else if (source === 'field-detail') { 3433 // Return to the field detail view 3434 $('.interactable-part-wrapper-catalog .list-with-filter').hide(); 3435 $('.interactable-part-wrapper-catalog .field-detail-wrapper').css('display', 'flex'); 3436 // Restore URL to reflect the field-detail state (bez exhibitor parametru) 3437 var activeTab = $('.js-katalog-tabs input[type="radio"]:checked').closest('label').data('tab') || 'companies'; 3438 if (activeTab === 'destinations') { 3439 var $fw = $('.interactable-part-wrapper-catalog .field-detail-wrapper'); 3440 var restoredContinent = $fw.data('continent') || ''; 3441 var restoredDestination = $fw.find('h2.fields-headline').text() || ''; 3442 // Nadpis může být "9.2. Guatemala" â extrahujeme jen jméno bez kódu 3443 restoredDestination = restoredDestination.replace(/^\d+(\.\d+)*\.\s*/, '');
3444 var urlParams = { tab: 'destinations', continent: restoredContinent }; 3445 if (restoredDestination && restoredDestination !== restoredContinent) { 3446 urlParams.destination = restoredDestination; 3447 } 3448 updateCatalogUrl(urlParams); 3449 } 3450 // Re-hide/re-show po Webflow IX2 animaci 3451 setTimeout(function () { 3452 $('.interactable-part-wrapper-catalog .list-with-filter').hide(); 3453 $('.interactable-part-wrapper-catalog .field-detail-wrapper').css('display', 'flex'); 3454 }, 50); 3455 } else { 3456 // Return to the regular list 3457 $('.interactable-part-wrapper-catalog .field-detail-wrapper').remove(); 3458 $('.interactable-part-wrapper-catalog .list-with-filter').css('display', ''); 3459 } 3460 }); 3461} 3462 3463function appendFieldsPanelNew(data) { 3464 const $panel = $('.js-katalog-panel[data-panel="fields"]'); 3465 if ($panel.length === 0) return; 3466 oboryFullData = data; 3467 renderFieldsLevel($panel, ''); 3468} 3469 3470function getFieldChildren(parentCode) { 3471 if (!oboryFullData) return []; 3472 3473 // Determine the depth we're looking for 3474 // '' (root) -> depth 1 (codes like '1.') 3475 // '1.' -> depth 2 (codes like '1.2.') 3476 var parentDepth = 0; 3477 if (parentCode) { 3478 parentDepth = parentCode.replace(/\.$/, '').split('.').length; 3479 } 3480 var childDepth = parentDepth + 1; 3481 3482 // Build a set of all codes in the data 3483 var allCodes = {}; 3484 Object.keys(oboryFullData).forEach(function (key) { 3485 if (key === '') return; 3486 var item = oboryFullData[key]; 3487 allCodes[item.s_code] = item; 3488 }); 3489 3490 // Find all codes that are descendants of parentCode 3491 var descendantCodes = {}; 3492 Object.keys(allCodes).forEach(function (code) { 3493 if (parentCode === '') { 3494 // All codes are descendants of root 3495 descendantCodes[code] = true; 3496 } else if (code.indexOf(parentCode) === 0 && code !== parentCode) { 3497 descendantCodes[code] = true; 3498 } 3499 }); 3500 3501 // Group descendants by their direct child prefix at childDepth 3502 var childrenMap = {}; 3503 Object.keys(descendantCodes).forEach(function (code) { 3504 var parts = code.replace(/\.$/, '').split('.'); 3505 if (parts.length >= childDepth) { 3506 var childCode = parts.slice(0, childDepth).join('.') + '.'; 3507 if (!childrenMap[childCode]) { 3508 childrenMap[childCode] = true; 3509 } 3510 } 3511 }); 3512 3513 // Build result array, using real data if available or virtual entries 3514 var result = []; 3515 Object.keys(childrenMap).forEach(function (childCode) { 3516 if (allCodes[childCode]) { 3517 result.push({ 3518 s_code: allCodes[childCode].s_code, 3519 s_name: safeDecodeURIComponent(allCodes[childCode].s_name).replace(/\+/g, ' ') 3520 }); 3521 } else { 3522 // Virtual parent - intermediate code missing from IS data 3523 result.push({ 3524 s_code: childCode, 3525 s_name: childCode, 3526 virtual: true 3527 }); 3528 } 3529 }); 3530 3531 // Sort numerically by code segments 3532 result.sort(function (a, b) { 3533 var aParts = a.s_code.replace(/\.$/, '').split('.').map(Number); 3534 var bParts = b.s_code.replace(/\.$/, '').split('.').map(Number); 3535 for (var i = 0; i < Math.max(aParts.length, bParts.length); i++) { 3536 var aVal = aParts[i] || 0; 3537 var bVal = bParts[i] || 0; 3538 if (aVal !== bVal) return aVal - bVal; 3539 } 3540 return 0; 3541 }); 3542 3543 return result; 3544} 3545 3546function renderFieldsLevel($panel, parentCode) { 3547 // Store the current obor level for URL restoration 3548 $panel.data('current-obor', parentCode); 3549 3550 // Remove any previous obory navigation elements (back button + exhibitor section) 3551 $panel.siblings('.obory-nav').remove(); 3552 $panel.siblings('.obory-exhibitors').remove(); 3553 $panel.empty(); 3554 // Reset panel inline styles from drilled-in view 3555 $panel.css({ 'background': '', 'border-radius': '', 'padding': '' }); 3556 3557 var children = getFieldChildren(parentCode); 3558 3559 if (parentCode) { 3560 // --- Drilled-in view: white card with plain text grid (Figma design) --- 3561 var parts = parentCode.replace(/\.$/, '').split('.'); 3562 var grandparentCode = parts.length > 1 ? parts.slice(0, -1).join('.') + '.' : ''; 3563 3564 var currentName = parentCode; 3565 if (oboryFullData) {
3566 Object.keys(oboryFullData).forEach(function (key) { 3567 if (key === '') return; 3568 if (oboryFullData[key].s_code === parentCode) { 3569 currentName = safeDecodeURIComponent(oboryFullData[key].s_name).replace(/\+/g, ' '); 3570 } 3571 }); 3572 } 3573 3574 // Back button + search input as a sibling BEFORE the panel 3575 var navHTML = ` 3576 <div class="obory-nav" style="display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:16px;"> 3577 <a href="#" class="back-katalog-button obory-back-button w-inline-block" data-parent="${grandparentCode}"> 3578 <div class="back-icon w-embed"> 3579 <svg width="16" height="17" viewbox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg"> 3580 <path d="M2 8.5H14M2 8.5L6 12.5M2 8.5L6 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> 3581 </svg> 3582 </div> 3583 <div>ZpÄt</div> 3584 </a> 3585 <form class="search full-width-mobile w-form obory-search-form" onsubmit="return false;" style="margin:0;"> 3586 <input class="search-input w-input obory-search-input" maxlength="256" placeholder="Hledatâ¦" type="search" /> 3587 <div class="icon-24px search-input-icon w-embed"> 3588 <svg width="24" height="24" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 3589 <path d="M21 21L15 15M3 10C3 10.9193 3.18106 11.8295 3.53284 12.6788C3.88463 13.5281 4.40024 14.2997 5.05025 14.9497C5.70026 15.5998 6.47194 16.1154 7.32122 16.4672C8.17050 16.8189 9.08075 17 10 17C10.9193 17 11.8295 16.8189 12.6788 16.4672C13.5281 16.1154 14.2997 15.5998 14.9497 14.9497C15.5998 14.2997 16.1154 13.5281 16.4672 12.6788C16.8189 11.8295 17 10.9193 17 10C17 9.08075 16.8189 8.17050 16.4672 7.32122C16.1154 6.47194 15.5998 5.70026 14.9497 5.05025C14.2997 4.40024 13.5281 3.88463 12.6788 3.53284C11.8295 3.18106 10.9193 3 10 3C9.08075 3 8.17050 3.18106 7.32122 3.53284C6.47194 3.88463 5.70026 4.40024 5.05025 5.05025C4.40024 5.70026 3.88463 6.47194 3.53284 7.32122C3.18106 8.17050 3 9.08075 3 10Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> 3590 </svg> 3591 </div> 3592 </form> 3593 </div> 3594 `; 3595 $panel.before(navHTML); 3596 3597 // Style the panel as a white card 3598 $panel.css({ 3599 'background': '#fff', 3600 'border-radius': '8px', 3601 'padding': '24px' 3602 }); 3603 3604 // Headline inside the card (full width) 3605 $panel.append( 3606 '<h3 style="width:100%; margin:0; font-weight:700;">' + 3607 parentCode + ' ' + currentName + 3608 '</h3>' 3609 ); 3610 3611 // Children as plain text items â width handled by existing responsive CSS 3612 children.forEach(function (child) { 3613 var grandchildren = getFieldChildren(child.s_code); 3614 var isParent = grandchildren.length > 0; 3615 var cssClass = isParent ? 'category-parent' : 'category-filter'; 3616 3617 var itemHTML = 3618 '<div class="catalog-filter-detail--box ' + cssClass + '"' + 3619 ' data-name="' + child.s_name + '"' + 3620 ' data-code="' + child.s_code + '"' + 3621 ' style="cursor:pointer; background:none; padding:10px 0; border-radius:0;">' + 3622 '<span><strong>' + child.s_code + '</strong> ' + child.s_name + '</span>' + 3623 '</div>'; 3624 $panel.append(itemHTML); 3625 }); 3626 3627 // --- Exhibitor list below the subcategory card --- 3628 // Collect all codes in this subtree (parentCode + all descendants) 3629 var allCategoryCodes = [parentCode]; 3630 Object.keys(oboryFullData).forEach(function (key) { 3631 if (key === '') return; 3632 var code = oboryFullData[key].s_code; 3633 if (code.indexOf(parentCode) === 0 && code !== parentCode) { 3634 allCategoryCodes.push(code); 3635 } 3636 }); 3637 3638 var exhibitorSectionHTML = ` 3639 <div class="obory-exhibitors" style="display: flex; flex-direction: column; gap: 16px;"> 3640 <h2 class="fields-headline">Vystavovatelé v oboru</h2> 3641 <div class="sort-buttons-wrapper desktop"><a href="#" class="alphabet-sort sort-button is-active w-inline-block"><div>Podle abecedy</div><div class="sort-arrows-wrapper"><div class="sort-icon is-active w-embed">
3641<svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a><a href="#" class="hall-sort sort-button w-inline-block"><div>Podle haly</div><div class="sort-arrows-wrapper"><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a></div> 3642 <ul role="list" class="companies-list katalog obory-exhibitor-list"> 3643 <li class="companies-list-item"><div>NaÄÃtánÃ...</div></li> 3644 </ul> 3645 </div> 3646 `; 3647 $panel.after(exhibitorSectionHTML); 3648 3649 var $exhibitorList = $panel.siblings('.obory-exhibitors').find('.obory-exhibitor-list'); 3650 var $searchInput = $panel.siblings('.obory-nav').find('.obory-search-input'); 3651 3652 $.ajax({ 3653 url: '/wp-json/api/v1/oborove-cleneni', 3654 method: 'POST', 3655 data: { 3656 categories: allCategoryCodes, 3657 oboryLoadNeeded: false, 3658 catalog: currentCatalogSourceKey 3659 }, 3660 success: function (response) { 3661 var dataSeznamVystavovatelu = response['seznam-vystavovatelu']; 3662 var isAscendingObory = true; 3663 var sortByObory = 'name'; 3664 var $sortingButtons = $panel.siblings('.obory-exhibitors').find('.sort-buttons-wrapper'); 3665 3666 $exhibitorList.empty(); 3667 appendExhibitorListNew($exhibitorList, dataSeznamVystavovatelu, isAscendingObory, sortByObory); 3668 3669 // Sorting buttons 3670 $sortingButtons.find('.alphabet-sort').on('click', function () { 3671 isAscendingObory = !isAscendingObory; 3672 sortByObory = 'name'; 3673 updateSortIcons(isAscendingObory, $(this)); 3674 appendExhibitorListNew($exhibitorList, dataSeznamVystavovatelu, isAscendingObory, sortByObory); 3675 }); 3676 3677 $sortingButtons.find('.hall-sort').on('click', function () { 3678 isAscendingObory = !isAscendingObory; 3679 sortByObory = 'hala'; 3680 updateSortIcons(isAscendingObory, $(this)); 3681 appendExhibitorListNew($exhibitorList, dataSeznamVystavovatelu, isAscendingObory, sortByObory); 3682 }); 3683 3684 // Client-side search within fetched exhibitors 3685 $searchInput.on('input', debounce(function () { 3686 var query = $(this).val().trim().toLowerCase(); 3687 if (!query) { 3688 $exhibitorList.empty(); 3689 appendExhibitorListNew($exhibitorList, dataSeznamVystavovatelu, isAscendingObory, sortByObory); 3690 return; 3691 } 3692 var filtered = {}; 3693 Object.keys(dataSeznamVystavovatelu).forEach(function (key) { 3694 var name = safeDecodeURIComponent(dataSeznamVystavovatelu[key].s_nazev).replace(/\+/g, ' ').toLowerCase(); 3695 if (name.indexOf(query) !== -1) { 3696 filtered[key] = dataSeznamVystavovatelu[key]; 3697 } 3698 }); 3699 $exhibitorList.empty(); 3700 appendExhibitorListNew($exhibitorList, filtered, isAscendingObory, sortByObory); 3701 }, 300)); 3702 }, 3703 error: function () { 3704 $exhibitorList.empty();
3705 $exhibitorList.append('<li class="companies-list-item"><div>Chyba pÅi naÄÃtánà vystavovatelů.</div></li>'); 3706 } 3707 }); 3708 } else { 3709 // --- Root level: original box layout with icons --- 3710 children.forEach(function (child) { 3711 var grandchildren = getFieldChildren(child.s_code); 3712 var isParent = grandchildren.length > 0; 3713 var cssClass = isParent ? 'category-parent' : 'category-filter'; 3714 3715 var boxHTML = ` 3716 <div class="catalog-filter-detail--box ${cssClass}" data-name="${child.s_name}" data-code="${child.s_code}" style="cursor: pointer;"> 3717 <div class="img"> 3718 <img class="img" src="${customBlockData.pluginUrl}images/files.svg" /> 3719 </div> 3720 <span class="list-item">${child.s_code} ${child.s_name}</span> 3721 </div> 3722 `; 3723 $panel.append(boxHTML); 3724 }); 3725 3726 if (children.length === 0) { 3727 $panel.append('<div>Nebyly nalezeny žádné obory.</div>'); 3728 } 3729 } 3730} 3731 3732function loadRSSfeeds() { 3733 $('.rss-feed-list-wrapper').each(function () { 3734 const $wrapper = $(this); 3735 const feedUrl = $wrapper.data('feed-url'); 3736 const $output = $wrapper.find('.rss-feed-output'); 3737 const $loading = $wrapper.find('.rss-feed-loading'); 3738 3739 $.ajax({ 3740 url: '/wp-json/api/v1/rss-feed/', 3741 method: 'GET', 3742 data: { 3743 feed: feedUrl 3744 }, 3745 dataType: 'json', 3746 success: function (data) { 3747 $loading.remove(); 3748 3749 if (!data.success) { 3750 $output.html(`<div class="rss-error">â ${data.error}</div>`); 3751 return; 3752 } 3753 3754 if (!data.data.length) { 3755 $output.html(`<div class="rss-empty">Žádné Älánky k zobrazenÃ.</div>`); 3756 return; 3757 } 3758 3759 data.data.forEach(function (item) { 3760 const $link = $('<a>', { 3761 href: item.link, 3762 text: item.title, 3763 class: 'rss-link', 3764 target: '_blank' 3765 }); 3766 3767 const $row = $('<div>', { 3768 class: 'rss-feed-row' 3769 }).append($link); 3770 3771 $output.append($row); 3772 }); 3773 }, 3774 error: function (xhr, status, error) { 3775 $loading.remove(); 3776 $output.html(`<div class="rss-error">â Chyba pÅi naÄÃtánÃ: ${error}</div>`); 3777 } 3778 }); 3779 }); 3780}
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.