1jQuery(document).ready(function () { 2 window.ShopAlert = window.Swal.mixin({ 3 buttonsStyling: false, 4 heightAuto: false, 5 confirmButtonText: 'U redu', 6 cancelButtonText: 'Odustani', 7 customClass: { 8 container: 'shop-alert-container', 9 popup: 'shop-alert', 10 title: 'shop-alert__title', 11 htmlContainer: 'shop-alert__content', 12 actions: 'shop-alert__actions', 13 confirmButton: 'shop-alert__button shop-alert__button--primary', 14 denyButton: 'shop-alert__button shop-alert__button--secondary', 15 cancelButton: 'shop-alert__button shop-alert__button--secondary', 16 closeButton: 'shop-alert__close', 17 icon: 'shop-alert__icon', 18 timerProgressBar: 'shop-alert__timer' 19 } 20 }); 21 22 window.ShopToast = window.ShopAlert.mixin({ 23 position: 'top-end', 24 toast: true, 25 showConfirmButton: false, 26 timer: 2000, 27 timerProgressBar: true, 28 didOpen: (toast) => { 29 toast.addEventListener('mouseenter', window.Swal.stopTimer); 30 toast.addEventListener('mouseleave', window.Swal.resumeTimer); 31 } 32 }); 33 34 const checkoutLoginForm = document.getElementById("checkout_login_form"); 35 if (checkoutLoginForm) { 36 checkoutLoginForm.addEventListener("submit", (e) => { 37 e.preventDefault(); 38 startLoading(); 39 sendData(checkoutLoginForm); 40 }); 41 } 42 43 const checkoutRegisterForm = document.getElementById("checkout_register_form"); 44 if (checkoutRegisterForm) { 45 checkoutRegisterForm.addEventListener("submit", (e) => { 46 e.preventDefault(); 47 startLoading(); 48 sendData(checkoutRegisterForm); 49 }); 50 } 51 52 const loginForm = document.getElementById("login_form"); 53 if (loginForm) { 54 loginForm.addEventListener("submit", (e) => { 55 e.preventDefault(); 56 startLoading(); 57 sendData(loginForm); 58 }); 59 } 60 61 62 const addressForm = document.getElementById("address_form"); 63 if (addressForm) { 64 addressForm.addEventListener("submit", (e) => { 65 e.preventDefault(); 66 startLoading(); 67 sendData(addressForm); 68 }); 69 } 70 71 72 const registerForm = document.getElementById("register_form"); 73 if (registerForm) { 74 registerForm.addEventListener("submit", (e) => { 75 e.preventDefault(); 76 startLoading(); 77 sendData(registerForm); 78 }); 79 } 80 81 const updateProfileForm = document.getElementById("profile_form"); 82 if (updateProfileForm) { 83 updateProfileForm.addEventListener("submit", (e) => { 84 e.preventDefault(); 85 startLoading(); 86 sendData(updateProfileForm, false); 87 }); 88 } 89 90 91 const newsletterForm = document.getElementById("newsletter_form"); 92 if (newsletterForm) { 93 const submitButton = newsletterForm.querySelector("[data-newsletter-submit]"); 94 const submitLabel = newsletterForm.querySelector("[data-newsletter-submit-label]"); 95 const emailInput = newsletterForm.querySelector('input[name="Email"]'); 96 const statusMessage = newsletterForm.querySelector("#newsletter-status"); 97 const defaultSubmitLabel = submitLabel.textContent; 98 99 const setNewsletterStatus = (state, message) => { 100 newsletterForm.classList.remove("is-loading", "is-success", "is-error"); 101 102 if (!state || !message) { 103 statusMessage.hidden = true; 104 statusMessage.textContent = ""; 105 return; 106 } 107 108 newsletterForm.classList.add(`is-${state}`); 109 statusMessage.textContent = Array.isArray(message) ? message.join(" ") : message; 110 statusMessage.hidden = false; 111 }; 112 113 emailInput.addEventListener("input", () => { 114 if (!newsletterForm.classList.contains("is-loading")) { 115 setNewsletterStatus(null, null); 116 submitButton.disabled = false; 117 submitLabel.textContent = defaultSubmitLabel; 118 } 119 }); 120 121 newsletterForm.addEventListener("submit", async (e) => { 122 e.preventDefault(); 123 124 if (!newsletterForm.checkValidity()) { 125 newsletterForm.reportValidity(); 126 return; 127 } 128 129 submitButton.disabled = true; 130 submitLabel.textContent = "Prijavljujemo..."; 131 setNewsletterStatus(null, null); 132 newsletterForm.classList.add("is-loading"); 133 newsletterForm.setAttribute("aria-busy", "true"); 134 135 try { 136 const response = await fetch(newsletterForm.action, { 137 body: new FormData(newsletterForm), 138 method: "POST", 139 cache: "no-cache", 140 credentials: "same-origin", 141 headers: { 142 "Accept": "application/json" 143 } 144 }); 145 146 const contentType = response.headers.get("content-type") || ""; 147 if (!contentType.includes("application/json")) { 148 throw new Error("Server trenutno nije dostupan. Molimo vas, pokušajte ponovo."); 149 } 150 151 const result = await response.json(); 152 if (!response.ok || !result.success) { 153 throw new Error(Array.isArray(result.message) ? result.message.join(" ") : result.message); 154 } 155 156 newsletterForm.reset(); 157 submitLabel.textContent = "Prijavljeni ste"; 158 setNewsletterStatus("success", result.message); 159 } catch (error) { 160 submitButton.disabled = false; 161 submitLabel.textContent = defaultSubmitLabel; 162 setNewsletterStatus("error", error.message || "Nešto nije u redu. Molimo vas, pokušajte ponovo."); 163 } finally { 164 newsletterForm.classList.remove("is-loading"); 165 newsletterForm.removeAttribute("aria-busy"); 166 } 167 }); 168 } 169 170 const wishlistPage = document.querySelector("[data-wishlist-page]"); 171 if (wishlistPage) { 172 const wishlistList = wishlistPage.querySelector("[data-wishlist-list]"); 173 const wishlistEmpty = wishlistPage.querySelector("[data-wishlist-empty]"); 174 const wishlistStatus = wishlistPage.querySelector("[data-wishlist-status]"); 175 const wishlistCounts = wishlistPage.querySelectorAll("[data-wishlist-count]"); 176 let statusTimeout; 177 178 const showWishlistStatus = (state, message) => { 179 window.clearTimeout(statusTimeout); 180 wishlistStatus.classList.remove("is-success", "is-error"); 181 wishlistStatus.classList.add(`is-${state}`); 182 wishlistStatus.textContent = message; 183 wishlistStatus.hidden = false; 184 185 statusTimeout = window.setTimeout(() => { 186 wishlistStatus.hidden = true; 187 }, 5000); 188 }; 189 190 const updateWishlistCount = () => { 191 const count = wishlistPage.querySelectorAll("[data-wishlist-item]").length;
192 wishlistCounts.forEach((element) => { 193 element.textContent = count; 194 }); 195 196 if (count === 0) { 197 wishlistList.classList.add("d-none"); 198 wishlistEmpty.classList.remove("d-none"); 199 } 200 }; 201 202 wishlistPage.addEventListener("click", async (event) => { 203 const removeButton = event.target.closest('[data-action="remove-from-wishlist"]'); 204 if (!removeButton) { 205 return; 206 } 207 208 const wishlistItem = removeButton.closest("[data-wishlist-item]"); 209 const wishlistId = removeButton.dataset.wishlistId; 210 if (!wishlistItem || !wishlistId || removeButton.disabled) { 211 return; 212 } 213 214 removeButton.disabled = true; 215 wishlistItem.classList.add("is-processing"); 216 wishlistItem.setAttribute("aria-busy", "true"); 217 218 try { 219 const response = await fetch("/wishlist/remove", { 220 method: "POST", 221 cache: "no-cache", 222 credentials: "same-origin", 223 headers: { 224 "Accept": "application/json", 225 "Content-Type": "application/json" 226 }, 227 body: JSON.stringify({ id: wishlistId }) 228 }); 229 230 const contentType = response.headers.get("content-type") || ""; 231 const result = contentType.includes("application/json") 232 ? await response.json() 233 : null; 234 235 if (!response.ok || !result?.success) { 236 throw new Error(result?.message || "Proizvod trenutno nije moguÄe ukloniti."); 237 } 238 239 wishlistItem.classList.remove("is-processing"); 240 wishlistItem.classList.add("is-removing"); 241 await new Promise((resolve) => window.setTimeout(resolve, 180)); 242 wishlistItem.remove(); 243 updateWishlistCount(); 244 showWishlistStatus("success", result.message); 245 } catch (error) { 246 removeButton.disabled = false; 247 wishlistItem.classList.remove("is-processing"); 248 wishlistItem.removeAttribute("aria-busy"); 249 showWishlistStatus("error", error.message || "DoÅ¡lo je do greÅ¡ke. Molimo vas, pokuÅ¡ajte ponovo."); 250 } 251 }); 252 } 253 254 const checkoutLoginWrapper = document.getElementById("checkout_login_form_wrapper"); 255 const checkoutRegisterWrapper = document.getElementById("checkout_register_form_wrapper"); 256 const showCreateAccountBtn = document.querySelector('[data-action="show_create_account"]'); 257 if (showCreateAccountBtn) { 258 showCreateAccountBtn.addEventListener("click", (e) => { 259 e.preventDefault(); 260 checkoutLoginWrapper.classList.add("d-none"); 261 checkoutLoginWrapper.setAttribute("aria-hidden", "true"); 262 checkoutRegisterWrapper.classList.remove("d-none"); 263 checkoutRegisterWrapper.setAttribute("aria-hidden", "false"); 264 showCreateAccountBtn.setAttribute("aria-expanded", "true"); 265 checkoutRegisterWrapper.querySelector("h2")?.focus(); 266 }); 267 } 268 269 const showLoginBtn = document.querySelector('[data-action="show_login"]'); 270 if (showLoginBtn) { 271 showLoginBtn.addEventListener("click", (e) => { 272 e.preventDefault(); 273 checkoutRegisterWrapper.classList.add("d-none"); 274 checkoutRegisterWrapper.setAttribute("aria-hidden", "true"); 275 checkoutLoginWrapper.classList.remove("d-none"); 276 checkoutLoginWrapper.setAttribute("aria-hidden", "false"); 277 showCreateAccountBtn?.setAttribute("aria-expanded", "false"); 278 checkoutLoginWrapper.querySelector("input[type='email']")?.focus(); 279 }); 280 } 281 282 const contactForm = document.getElementById("contact-form"); 283 if (contactForm) { 284 contactForm.addEventListener("submit", function (e) { 285 e.preventDefault(); 286 startLoading(); 287 sendData(contactForm, true); 288 }); 289 } 290 291 292}); 293 294 295$('#sidebar_filter_form').on("submit", 296 (e) => { 297 e.preventDefault(); 298 var form = document.getElementById("sidebar_filter_form"); 299 var countries = $('[data-filter="country"]:checked').map((i, el) => el.value).get(); 300 var types = $('[data-filter="type"]:checked').map((i, el) => el.value).get(); 301 var varieties = $('[data-filter="variety"]:checked').map((i, el) => el.value).get(); 302 var prices = $('[data-filter="price"]:checked').map((i, el) => el.value).get(); 303 var styles = $('[data-filter="style"]:checked').map((i, el) => el.value).get(); 304 var fruits = $('[data-filter="fruit"]:checked').map((i, el) => el.value).get(); 305 306 var obj = { 307 countries: countries, 308 types: types, 309 varieties: varieties, 310 prices: prices, 311 styles: styles, 312 fruits: fruits 313 }; 314 315 startLoading(); 316 axios.post(form.action, obj).then(() => { 317 const url = new URL(window.location.href); 318 url.searchParams.delete('page'); 319 window.location.href = url.toString(); 320 }).catch(error => { 321 console.error('Error applying filters:', error); 322 stopLoading(); 323 }); 324 });
325 326$('#sidebar_filter_form_mobile').on("submit", (e) => { 327 e.preventDefault(); 328 var form = document.getElementById("sidebar_filter_form_mobile"); 329 var countries = $('[data-filter="mob-country"]:checked').map((i, el) => el.value).get(); 330 var types = $('[data-filter="mob-type"]:checked').map((i, el) => el.value).get(); 331 var varieties = $('[data-filter="mob-variety"]:checked').map((i, el) => el.value).get(); 332 var prices = $('[data-filter="mob-price"]:checked').map((i, el) => el.value).get(); 333 var styles = $('[data-filter="mob-style"]:checked').map((i, el) => el.value).get(); 334 var fruits = $('[data-filter="mob-fruit"]:checked').map((i, el) => el.value).get(); 335 const selectedSort = $('input[name="sortiranje"]:checked').val(); 336 337 var obj = { 338 countries: countries, 339 types: types, 340 varieties: varieties, 341 prices: prices, 342 styles: styles, 343 fruits: fruits 344 }; 345 346 startLoading(); 347 axios.post(form.action, obj).then(() => { 348 const url = new URL(window.location.href); 349 if (selectedSort) { 350 url.searchParams.set('sort', selectedSort); 351 } else { 352 url.searchParams.delete('sort'); 353 } 354 url.searchParams.delete('page'); 355 window.location.href = url.toString(); 356 }).catch(error => { 357 console.error('Error applying filters:', error); 358 stopLoading(); 359 }); 360 }); 361 362 363 364$("[data-clear-filters]").on("click", (e) => { 365 e.preventDefault(); 366 startLoading(); 367 axios.post('/filters/clear').then(() => { 368 const url = new URL(window.location.href); 369 url.searchParams.delete('filter'); 370 url.searchParams.delete('sort'); 371 url.searchParams.delete('page'); 372 window.location.href = url.toString(); 373 }).catch(error => { 374 console.error('Error clearing filters:', error); 375 stopLoading(); 376 }); 377}); 378 379$('[data-action="submit"]').on('click', function (e) { 380 e.preventDefault(); 381 const form = e.target.closest('form'); 382 startLoading(); 383 sendData(form); 384}); 385 386//SWIPER TO BE MOVED IN MAIN.JS 387 388var swiper = new Swiper(".topRatedSwiper", { 389 slidesPerView: 1, 390 spaceBetween: 30, 391 breakpoints: { 392 700: { 393 slidesPerView: 3, 394 spaceBetween: 20 395 }, 396 1024: { 397 slidesPerView: 4, 398 spaceBetween: 40 399 } 400 }, 401 navigation: { 402 nextEl: ".swiper-button-next", 403 prevEl: ".swiper-button-prev" 404 }, 405 loop: true, // Enable looping 406 autoplay: { 407 delay: 3000, // Set delay to 3 seconds 408 disableOnInteraction: false // Continue autoplay after user interactions 409 } 410}); 411 412var swiper1 = new Swiper(".mixMatchSwiper", { 413 slidesPerView: 1, 414 spaceBetween: 30, 415 breakpoints: { 416 700: { 417 slidesPerView: 3, 418 spaceBetween: 20 419 }, 420 1024: { 421 slidesPerView: 4, 422 spaceBetween: 40 423 } 424 }, 425 navigation: { 426 nextEl: ".swiper-button-next", 427 prevEl: ".swiper-button-prev" 428 }, 429 loop: true, // Enable looping 430 autoplay: { 431 delay: 3000, // Set delay to 3 seconds 432 disableOnInteraction: false // Continue autoplay after user interactions 433 } 434}); 435 436var swiper2 = new Swiper(".latestSwiper", { 437 slidesPerView: 1, 438 spaceBetween: 30, 439 breakpoints: { 440 700: { 441 slidesPerView: 3, 442 spaceBetween: 20 443 }, 444 1024: { 445 slidesPerView: 4, 446 spaceBetween: 40 447 } 448 }, 449 navigation: { 450 nextEl: ".swiper-button-prev", 451 prevEl: ".swiper-button-prev" 452 }, 453 loop: true, // Enable looping 454 autoplay: { 455 delay: 3000, // Set delay to 3 seconds 456 disableOnInteraction: false // Continue autoplay after user interactions 457 } 458 459}); 460 461var swiper3 = new Swiper(".bannerSwiper", { 462 slidesPerView: 1, 463 spaceBetween: 30, 464 breakpoints: { 465 700: { 466 slidesPerView: 1, 467 spaceBetween: 0 468 }, 469 1024: { 470 slidesPerView: 1, 471 spaceBetween: 0 472 } 473 } 474}); 475 476 477const removeFilter = (element) => { 478 const filterName = element.parentElement.getAttribute('data-filter-name'); 479 const filterCategory = element.parentElement.getAttribute('data-filter-category'); 480 const model = { 481 Name: filterName, 482 Category: filterCategory 483 }; 484 485 element.disabled = true; 486 element.setAttribute('aria-busy', 'true'); 487 startLoading(); 488 489 $.ajax({
490 url: '/filters/remove', 491 type: 'POST', 492 contentType: 'application/json', 493 data: JSON.stringify(model), 494 success: function (response) { 495 if (response.success) { 496 const url = new URL(window.location.href); 497 url.searchParams.delete('page'); 498 window.location.href = url.toString(); 499 } else { 500 console.error('Failed to remove filter:', response.message); 501 element.disabled = false; 502 element.removeAttribute('aria-busy'); 503 stopLoading(); 504 } 505 }, 506 error: function (error) { 507 console.error('Error removing filter:', error); 508 element.disabled = false; 509 element.removeAttribute('aria-busy'); 510 stopLoading(); 511 } 512 }); 513}; 514 515 516$(".js-submit").click(function (e) { 517 e.preventDefault(); 518 const form = this.form; 519 520 if (!form || !form.checkValidity()) { 521 form?.reportValidity(); 522 return; 523 } 524 525 startLoading(); 526 sendData(form); 527}); 528 529// Checkboxes that reveal a related block, e.g. the company fields on an address. 530document.addEventListener("change", function (e) { 531 const source = e.target.closest("[data-toggle-target]"); 532 if (!source) { 533 return; 534 } 535 536 const target = document.querySelector(source.getAttribute("data-toggle-target")); 537 if (target) { 538 target.classList.toggle("d-none", !source.checked); 539 } 540}); 541 542// Show/hide password on the account pages. Delegated so it also covers 543// any password field rendered after load. 544document.addEventListener("click", function (e) { 545 const toggle = e.target.closest("[data-password-toggle]"); 546 if (!toggle) { 547 return; 548 } 549 550 const field = toggle.parentElement.querySelector("input"); 551 if (!field) { 552 return; 553 } 554 555 const revealed = field.type === "text"; 556 field.type = revealed ? "password" : "text"; 557 const label = revealed ? "Prikaži lozinku" : "Sakrij lozinku"; 558 const icon = toggle.querySelector(".fa-eye, .fa-eye-slash"); 559 560 toggle.setAttribute("aria-label", label); 561 toggle.setAttribute("title", label); 562 icon?.classList.toggle("fa-eye", revealed); 563 icon?.classList.toggle("fa-eye-slash", !revealed); 564}); 565 566const startLoading = (message = 'Samo trenutakâ¦') => { 567 const loader = document.getElementById("css-loader"); 568 if (!loader) return; 569 const title = loader.querySelector('[data-loading-title]'); 570 if (title) title.textContent = message; 571 loader.setAttribute('aria-hidden', 'false'); 572 loader.classList.add("is-active"); 573} 574 575const stopLoading = () => { 576 const loader = document.getElementById("css-loader"); 577 if (!loader) return; 578 loader.classList.remove("is-active"); 579 loader.setAttribute('aria-hidden', 'true'); 580} 581 582const sendData = (form, reset = true, token = null) => { 583 var formData = new FormData(form); 584 585 const resetTurnstile = () => { 586 if (form.querySelector('.cf-turnstile') && window.turnstile) { 587 window.turnstile.reset(); 588 } 589 }; 590 591 if (token != null) { 592 formData.append("Captcha", token); 593 } 594 595 fetch(form.action, 596 { 597 body: formData, 598 method: "post", 599 cache: 'no-cache' 600 }).then(response => response.json()) 601 .then(result => { 602 resetTurnstile(); 603 604 if (result.success && result.redirectUrl == null) { 605 if (reset) { 606 form.reset(); 607 } 608 609 const parentModal = form.closest(".modal"); 610 if (parentModal && window.bootstrap?.Modal) { 611 bootstrap.Modal.getOrCreateInstance(parentModal).hide(); 612 } 613 614 stopLoading(); 615 window.ShopAlert.fire({ 616 icon: 'success', 617 title: 'Hvala Vam', 618 text: result.message 619 }); 620 } else if (result.success && result.redirectUrl) { 621 location.href = result.redirectUrl; 622 } else { 623 stopLoading(); 624 window.ShopAlert.fire({ 625 icon: 'error', 626 title: 'GreÅ¡ka!', 627 text: result.message 628 }); 629 } 630 }).catch(error => { 631 console.error('Error:', error); 632 resetTurnstile(); 633 stopLoading(); 634 window.ShopAlert.fire({ 635 icon: 'error', 636 title: 'GreÅ¡ka', 637 text: 'NeÅ¡to nije u redu. Molimo vas, pokuÅ¡ajte ponovo.' 638 }); 639 }); 640} 641 642const wine_filters = document.querySelectorAll('[data-action="filter_changed"]'); 643if (wine_filters.length > 0) {
644 wine_filters.forEach((el) => { 645 el.addEventListener('change', 646 (e) => { 647 const url = new URL(window.location.href); 648 const sort = e.currentTarget.value; 649 650 if (sort) { 651 url.searchParams.set('sort', sort); 652 } else { 653 url.searchParams.delete('sort'); 654 } 655 656 url.searchParams.delete('page'); 657 window.location.href = url.toString(); 658 }); 659 }); 660} 661 662$(".modal-backdrop").click(e => { 663 e.stopPropagation(); 664}); 665 666 667const headerSearchCache = new Map(); 668 669function createHeaderSearchResult(item, inputId, index) { 670 const result = document.createElement('li'); 671 result.className = 'header-search__result'; 672 result.id = `${inputId}-option-${index}`; 673 result.setAttribute('role', 'option'); 674 result.setAttribute('aria-selected', 'false'); 675 676 const link = document.createElement('a'); 677 link.className = 'header-search__result-link'; 678 link.href = item.uri || '#'; 679 link.tabIndex = -1; 680 681 const imageWrapper = document.createElement('span'); 682 imageWrapper.className = 'header-search__image'; 683 if (item.imageUri) { 684 const image = document.createElement('img'); 685 image.src = item.imageUri; 686 image.alt = ''; 687 image.loading = 'lazy'; 688 imageWrapper.appendChild(image); 689 } 690 691 const copy = document.createElement('span'); 692 copy.className = 'header-search__copy'; 693 694 const name = document.createElement('span'); 695 name.className = 'header-search__name'; 696 name.textContent = item.displayName; 697 copy.appendChild(name); 698 699 if (item.category) { 700 const category = document.createElement('span'); 701 category.className = 'header-search__category'; 702 category.textContent = item.category; 703 copy.appendChild(category); 704 } 705 706 link.appendChild(imageWrapper); 707 link.appendChild(copy); 708 709 if (item.priceText) { 710 const price = document.createElement('span'); 711 price.className = 'header-search__price'; 712 price.textContent = `${item.priceText} RSD`; 713 link.appendChild(price); 714 } 715 716 result.appendChild(link); 717 return result; 718} 719 720function initialiseHeaderSearch(form) { 721 const input = form.querySelector('[data-header-search-input]'); 722 const clearButton = form.querySelector('[data-header-search-clear]'); 723 const panel = form.querySelector('[data-header-search-panel]'); 724 const resultsList = form.querySelector('[data-header-search-results]'); 725 const loadingState = form.querySelector('[data-header-search-loading]'); 726 const emptyState = form.querySelector('[data-header-search-empty]'); 727 const allResultsLink = form.querySelector('[data-header-search-all]'); 728 const allResultsLabel = form.querySelector('[data-header-search-all-label]'); 729 const liveRegion = form.querySelector('[data-header-search-live]'); 730 const suggestionsUrl = form.dataset.suggestionsUrl; 731 let debounceTimer; 732 let requestController; 733 let activeIndex = -1; 734 735 const getOptions = () => Array.from(resultsList.querySelectorAll('.header-search__result')); 736 737 const buildResultsUrl = (query) => { 738 const url = new URL(form.action, window.location.origin); 739 url.searchParams.set('q', query); 740 return url.toString(); 741 }; 742 743 const openPanel = () => { 744 panel.hidden = false; 745 input.setAttribute('aria-expanded', 'true'); 746 }; 747 748 const closePanel = () => { 749 panel.hidden = true; 750 input.setAttribute('aria-expanded', 'false'); 751 input.removeAttribute('aria-activedescendant'); 752 activeIndex = -1; 753 getOptions().forEach(option => { 754 option.classList.remove('is-active'); 755 option.setAttribute('aria-selected', 'false'); 756 }); 757 }; 758 759 const showLoading = () => { 760 resultsList.replaceChildren(); 761 emptyState.hidden = true; 762 allResultsLink.hidden = true; 763 loadingState.hidden = false;
764 openPanel(); 765 }; 766 767 const updateActiveOption = (nextIndex) => { 768 const options = getOptions(); 769 if (!options.length) return; 770 771 activeIndex = (nextIndex + options.length) % options.length; 772 options.forEach((option, index) => { 773 const isActive = index === activeIndex; 774 option.classList.toggle('is-active', isActive); 775 option.setAttribute('aria-selected', isActive ? 'true' : 'false'); 776 }); 777 778 const activeOption = options[activeIndex]; 779 input.setAttribute('aria-activedescendant', activeOption.id); 780 activeOption.scrollIntoView({ block: 'nearest' }); 781 }; 782 783 const renderResults = (items, query) => { 784 loadingState.hidden = true; 785 resultsList.replaceChildren(...items.map((item, index) => createHeaderSearchResult(item, input.id, index))); 786 emptyState.hidden = items.length !== 0; 787 emptyState.textContent = 'Nema pronaÄenih proizvoda. PokuÅ¡ajte sa drugim pojmom.'; 788 allResultsLink.href = buildResultsUrl(query); 789 allResultsLabel.textContent = `Prikaži sve rezultate za â${query}â`; 790 allResultsLink.hidden = false; 791 activeIndex = -1; 792 input.removeAttribute('aria-activedescendant'); 793 liveRegion.textContent = items.length 794 ? `${items.length} predloga je pronaÄeno.` 795 : 'Nema pronaÄenih predloga.'; 796 openPanel(); 797 }; 798 799 const loadSuggestions = async (query) => { 800 const cacheKey = query.toLocaleLowerCase('sr'); 801 if (headerSearchCache.has(cacheKey)) { 802 renderResults(headerSearchCache.get(cacheKey), query
802); 803 return; 804 } 805 806 requestController?.abort(); 807 requestController = new AbortController(); 808 showLoading(); 809 810 try { 811 const url = new URL(suggestionsUrl, window.location.origin); 812 url.searchParams.set('q', query); 813 const response = await fetch(url, { 814 credentials: 'same-origin', 815 headers: { 'Accept': 'application/json' }, 816 signal: requestController.signal 817 }); 818 819 if (!response.ok) throw new Error(`Search request failed with ${response.status}`); 820 821 const data = await response.json(); 822 const items = data.success && Array.isArray(data.items) ? data.items : []; 823 headerSearchCache.set(cacheKey, items); 824 825 if (input.value.trim() === query) { 826 renderResults(items, query); 827 } 828 } catch (error) { 829 if (error.name === 'AbortError') return; 830 831 loadingState.hidden = true; 832 resultsList.replaceChildren(); 833 emptyState.textContent = 'Pretraga trenutno nije dostupna. Pokušajte ponovo.'; 834 emptyState.hidden = false; 835 allResultsLink.hidden = true; 836 liveRegion.textContent = 'Pretraga trenutno nije dostupna.'; 837 openPanel(); 838 } 839 }; 840 841 const handleInput = () => { 842 const query = input.value.trim(); 843 clearButton.hidden = query.length === 0; 844 window.clearTimeout(debounceTimer); 845 846 if (query.length < 2) { 847 requestController?.abort(); 848 resultsList.replaceChildren(); 849 closePanel(); 850 liveRegion.textContent = query.length === 1 ? 'Unesite još najmanje jedan karakter.' : ''; 851 return; 852 } 853 854 debounceTimer = window.setTimeout(() => loadSuggestions(query), 250); 855 }; 856 857 input.addEventListener('input', handleInput); 858 input.addEventListener('focus', () => { 859 if (input.value.trim().length >= 2 && (getOptions().length || !emptyState.hidden)) { 860 openPanel(); 861 } 862 }); 863 864 input.addEventListener('keydown', (event) => { 865 const options = getOptions(); 866 867 if (event.key === 'ArrowDown' && options.length) { 868 event.preventDefault(); 869 openPanel(); 870 updateActiveOption(activeIndex + 1); 871 } else if (event.key === 'ArrowUp' && options.length) { 872 event.preventDefault(); 873 openPanel(); 874 updateActiveOption(activeIndex - 1); 875 } else if (event.key === 'Enter' && activeIndex >= 0 && options[activeIndex]) { 876 event.preventDefault(); 877 window.location.assign(options[activeIndex].querySelector('a').href); 878 } else if (event.key === 'Escape') { 879 event.preventDefault(); 880 closePanel(); 881 } 882 }); 883 884 clearButton.addEventListener('click', () => { 885 window.clearTimeout(debounceTimer); 886 requestController?.abort(); 887 input.value = ''; 888 clearButton.hidden = true; 889 resultsList.replaceChildren(); 890 closePanel(); 891 input.focus(); 892 liveRegion.textContent = 'Pretraga je obrisana.'; 893 }); 894 895 form.addEventListener('submit', (event) => { 896 const query = input.value.trim(); 897 if (query.length < 2) { 898 event.preventDefault(); 899 loadingState.hidden = true; 900 resultsList.replaceChildren(); 901 allResultsLink.hidden = true; 902 emptyState.textContent = 'Unesite najmanje 2 karaktera za pretragu.'; 903 emptyState.hidden = false; 904 openPanel(); 905 input.focus(); 906 return; 907 } 908 909 input.value = query; 910 closePanel(); 911 if (typeof startLoading === 'function') startLoading(); 912 }); 913 914 allResultsLink.addEventListener('click', () => { 915 if (typeof startLoading === 'function') startLoading(); 916 }); 917 918 resultsList.addEventListener('click', event => { 919 if (event.target.closest('a') && typeof startLoading === 'function') startLoading(); 920 });
921 922 form.addEventListener('pointerdown', event => event.stopPropagation()); 923 document.addEventListener('pointerdown', closePanel); 924 925 clearButton.hidden = input.value.trim().length === 0; 926} 927 928document.querySelectorAll('[data-header-search]').forEach(initialiseHeaderSearch); 929 930 931const url = new URL(window.location.href); 932const params = new URLSearchParams(url.search); 933if (params.has("r")) { 934 params.delete("r"); 935 936 if (params.has("variety")) { 937 params.delete("variety"); 938 } 939 if (params.has("typ")) { 940 params.delete("typ"); 941 } 942 if (params.has("price")) { 943 params.delete("price"); 944 } 945 if (params.has("style")) { 946 params.delete("style"); 947 } 948 if (params.has("country")) { 949 params.delete("country"); 950 } 951 if (params.has("fruit")) { 952 params.delete("fruit"); 953 } 954 console.log(params); 955 let newUrl = window.location.pathname; 956 if (params.length > 0) { 957 newUrl = window.location.pathname + "?" + params; 958 } 959 window.history.pushState({}, document.title, newUrl); 960} 961 962 963window.onload = function () { 964 setTimeout(function () { 965 var showBanner = getCookie("_was_age_check"); 966 if (showBanner == null || showBanner != "true") { 967 const ageCheckElement = document.getElementById("ageCheckModal"); 968 if (!ageCheckElement) { 969 return; 970 } 971 972 const options = { 973 backdrop: "static", 974 keyboard: false 975 }; 976 const ageCheck = new bootstrap.Modal(ageCheckElement, options); 977 ageCheck.show(); 978 979 const confirmButton = ageCheckElement.querySelector("[data-age-confirm]"); 980 if (confirmButton) { 981 confirmButton.addEventListener("click", function () { 982 setCookie("_was_age_check", "true", 2); 983 }, { once: true }); 984 } 985 } 986 987 }, 300); 988 989 setTimeout(function () { 990 var alcoholBanner = getCookie("_was_show_alc_banner"); 991 if (alcoholBanner == null || alcoholBanner !== "true") { 992 var alkoholZabrana = new bootstrap.Offcanvas('#alkoholZabrana'); 993 alkoholZabrana.show(); 994 995 setCookie("_was_show_alc_banner", "true",2); 996 } 997 }, 4000); 998}; 999 1000 1001function setCookie(name, value, days) { 1002 const date = new Date(); 1003 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 1004 const expires = "expires=" + date.toUTCString(); 1005 document.cookie = name + "=" + value + ";" + expires + ";path=/"; 1006} 1007 1008function getCookie(name) { 1009 const nameEQ = name + "="; 1010 const ca = document.cookie.split(';'); 1011 for (let i = 0; i < ca.length; i++) { 1012 let c = ca[i]; 1013 while (c.charAt(0) == ' ') c = c.substring(1, c.length); 1014 if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); 1015 } 1016 return null; 1017} 1018 1019document.addEventListener('DOMContentLoaded', function () { 1020 // TODO (GDPR): This UI currently stores preferences only; connect every optional 1021 // service to these categories before treating the stored choice as consent. 1022 // Also add accept-all/reject-all actions, consent versioning and a way to reopen 1023 // the settings and withdraw consent from every page. 1024 const cookieConsent = document.getElementById('cookie-consent'); 1025 const acceptButton = document.getElementById('accept-cookies'); 1026 const readMoreButton = document.getElementById('read-more'); 1027 const cookieDetails = document.getElementById('cookie-details'); 1028 const settingsLabel = readMoreButton?.querySelector('.cookie-consent__settings-label'); 1029 1030 function checkCookieConsent() { 1031 1032 if (!cookieConsent) return; 1033 1034 if (getCookie('cookieConsent') !== 'accepted' && cookieConsent) { 1035 cookieConsent.style.display = 'block'; 1036 } else { 1037 cookieConsent.style.display = 'none'; 1038 } 1039 } 1040 1041 if (acceptButton) { 1042 acceptButton.addEventListener('click', function () { 1043 const cookieSettings = { 1044 neophodni: true, 1045 funkcionalni: document.getElementById('funkcionalni').checked, 1046 performance: document.getElementById('performance').checked, 1047 analitika: document.getElementById('analitika').checked, 1048 oglasavanje: document.getElementById('oglasavanje').checked 1049 };
1050 setCookie('cookieConsent', 'accepted', 7); 1051 setCookie('cookieSettings', JSON.stringify(cookieSettings), 7); 1052 cookieConsent.style.display = 'none'; 1053 }); 1054 } 1055 1056 if (readMoreButton) { 1057 readMoreButton.addEventListener('click', function () { 1058 const shouldShowDetails = cookieDetails.hidden; 1059 1060 cookieDetails.hidden = !shouldShowDetails; 1061 readMoreButton.setAttribute('aria-expanded', shouldShowDetails.toString()); 1062 1063 if (settingsLabel) { 1064 settingsLabel.textContent = shouldShowDetails ? 'Sakrij postavke' : 'Podesi kolaÄiÄe'; 1065 } 1066 }); 1067 } 1068 1069 checkCookieConsent(); 1070}); 1071 1072document.addEventListener('DOMContentLoaded', function () { 1073 const mobileMenu = document.getElementById('mainMenu'); 1074 if (!mobileMenu) return; 1075 1076 const categories = Array.from(mobileMenu.querySelectorAll('[data-mobile-menu-category]')); 1077 1078 categories.forEach(function (category) { 1079 category.addEventListener('toggle', function () { 1080 if (!category.open) return; 1081 1082 categories.forEach(function (otherCategory) { 1083 if (otherCategory !== category) { 1084 otherCategory.open = false; 1085 } 1086 }); 1087 }); 1088 1089 const groups = Array.from(category.querySelectorAll('.mobile-menu__group')); 1090 groups.forEach(function (group) { 1091 group.addEventListener('toggle', function () { 1092 if (!group.open) return; 1093 1094 groups.forEach(function (otherGroup) { 1095 if (otherGroup !== group) { 1096 otherGroup.open = false; 1097 } 1098 }); 1099 }); 1100 }); 1101 }); 1102 1103 mobileMenu.addEventListener('hidden.bs.offcanvas', function () { 1104 mobileMenu.querySelectorAll('details[open]').forEach(function (details) { 1105 details.open = false; 1106 }); 1107 }); 1108}); 1109 1110document.addEventListener('DOMContentLoaded', function () { 1111 const directory = document.querySelector('[data-brands-directory]'); 1112 if (!directory) return; 1113 1114 const searchInput = directory.querySelector('[data-brand-search]'); 1115 const clearSearchButton = directory.querySelector('[data-brand-search-clear]'); 1116 const resetButton = directory.querySelector('[data-brand-reset]'); 1117 const resultCount = directory.querySelector('[data-brand-visible-count]'); 1118 const resultLabel = directory.querySelector('[data-brand-result-label]'); 1119 const emptyState = directory.querySelector('[data-brands-empty]'); 1120 const cards = Array.from(directory.querySelectorAll('[data-brand-card]')); 1121 const letterButtons = Array.from(directory.querySelectorAll('[data-brand-letter]')); 1122 1123 if (!searchInput || cards.length === 0) return; 1124 1125 let activeLetter = 'all'; 1126 1127 function normalise(value) { 1128 return (value || '') 1129 .toLocaleLowerCase('sr-Latn-RS') 1130 .normalize('NFD') 1131 .replace(/[\u0300-\u036f]/g, '') 1132 .trim(); 1133 } 1134 1135 function updateDirectory() { 1136 const query = normalise(searchInput.value); 1137 let visibleCards = 0; 1138 1139 cards.forEach(function (card) { 1140 const matchesSearch = !query || normalise(card.dataset.brandName).includes(query); 1141 const matchesLetter = activeLetter === 'all' || card.dataset.brandInitial === activeLetter; 1142 const isVisible = matchesSearch && matchesLetter; 1143 1144 card.hidden = !isVisible; 1145 if (isVisible) visibleCards += 1; 1146 }); 1147 1148 if (clearSearchButton) { 1149 clearSearchButton.hidden = searchInput.value.length === 0; 1150 } 1151 1152 if (resultCount) resultCount.textContent = visibleCards.toString(); 1153 if (resultLabel) { 1154 resultLabel.textContent = visibleCards === 1 ? 'pronaÄen brend' : 'pronaÄenih brendova'; 1155 } 1156 if (emptyState) emptyState.hidden = visibleCards !== 0; 1157 } 1158 1159 function selectLetter(selectedButton) { 1160 activeLetter = selectedButton.dataset.brandLetter || 'all'; 1161 1162 letterButtons.forEach(function (button) { 1163 const isActive = button === selectedButton; 1164 button.classList.toggle('is-active', isActive); 1165 button.setAttribute('aria-pressed', isActive.toString()); 1166 }); 1167 1168 updateDirectory(); 1169 } 1170 1171 searchInput.addEventListener('input', updateDirectory); 1172 1173 if (clearSearchButton) { 1174 clearSearchButton.addEventListener('click', function () { 1175 searchInput.value = ''; 1176 updateDirectory(); 1177 searchInput.focus(); 1178 }); 1179 } 1180 1181 letterButtons.forEach(function (button) { 1182 button.addEventListener('click', function () { 1183 selectLetter(button); 1184 }); 1185 });
1186 1187 if (resetButton) { 1188 resetButton.addEventListener('click', function () { 1189 searchInput.value = ''; 1190 const allButton = letterButtons.find(function (button) { 1191 return button.dataset.brandLetter === 'all'; 1192 }); 1193 1194 if (allButton) { 1195 selectLetter(allButton); 1196 } else { 1197 activeLetter = 'all'; 1198 updateDirectory(); 1199 } 1200 1201 searchInput.focus(); 1202 }); 1203 } 1204});
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.