/////////////////////////////////////////////////////////// // TEXT // LITERAŁY const idmHotspotTextObject = { []: , []: , []: , []: , []: , []: , []: , []: , []: , []: , []: , []: , []: , []: , []: , []: , []: , []: , []: , []: , []: , []: , []: , []: , []: , }; // STRING // const idmHotspotTextObject = { // ["Kod rabatowy"]: "Kod rabatowy", // ["Okazja"]: "Okazja", // ["Promocja"]: "Promocja", // ["Bestseller"]: "Bestseller", // ["Nowość"]: "Nowość", // ["Ilość"]: "Ilość", // ["Zwiększ ilość"]: "Zwiększ ilość", // ["Zmniejsz ilość"]: "Zmniejsz ilość", // ["Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki"]: "Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki", // ["Cena regularna"]: "Cena regularna", // ["Cena bez kodu"]: "Cena bez kodu", // ["Cena nadchodząca od"]: "Cena nadchodząca od", // ["Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę"]: "Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę", // ["Nie znaleziono produktów"]: "Nie znaleziono produktów", // ["Błąd przy pobieraniu danych"]: "Błąd przy pobieraniu danych", // ["Kliknij, by przejść do formularza kontaktu"]: "Kliknij, by przejść do formularza kontaktu", // ["Cena na telefon"]: "Cena na telefon", // ["Dodany"]: "Dodany", // ["Wystąpił błąd"]: "Wystąpił błąd", // ["Do koszyka"]: "Do koszyka", // ["Maksymalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]: "Maksymalna liczba sztuk tego towaru które możesz dodać do koszyka to:", // ["Minimalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]: "Minimalna liczba sztuk tego towaru które możesz dodać do koszyka to:", // ["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"]: "Wystąpił błąd z inicjalizacją. Proszę odśwież stronę", // ["Nie znaleziono kontenera"]: "Nie znaleziono kontenera", // ["Nie znaleziono metody graphql"]: "Nie znaleziono metody graphql", // } /////////////////////////////////////////////// // GraphQL // ogolne const IDM_PRICE_QUERY = `price { rebateCodeActive price { gross { value formatted } } omnibusPrice { gross { value formatted } } omnibusPriceDetails { unit { gross { value formatted } } youSavePercent omnibusPriceIsHigherThanSellingPrice newPriceEffectiveUntil { formatted } } max { gross { value formatted } } unit { gross { value formatted } } unitConvertedPrice { gross { value formatted } } youSavePercent beforeRebate { gross { value formatted } } beforeRebateDetails { youSavePercent unit { gross { value formatted } } } advancePrice { gross { value formatted } } suggested { gross { value formatted } } rebateNumber { number gross { value formatted } } }`; const IDM_PRODUCT_QUERY = `id type name zones icon iconSecond iconSmall iconSmallSecond link zones producer{ name } category{ name } sizes{ id amount name ${IDM_PRICE_QUERY} } group{ id name link versions{ id name icon iconSecond iconSmall iconSmallSecond } } awardedParameters { name id description values { name id } } enclosuresImages { position url } points unit{ id, name, singular, plural, fraction, sellBy, precision, unitConvertedFormat } ${IDM_PRICE_QUERY}`; // 1. products const IDM_PRODUCTS_GQL = (args) => JSON.stringify({ query: `{ products(${args}){ took products{ ${IDM_PRODUCT_QUERY} } } }` }); // 2. hotspots const IDM_HOTSPOTS_GQL = (args) => JSON.stringify({ query: `{ hotspots(${args}){ took name url products{ ${IDM_PRODUCT_QUERY} } } }` }); // 3. single product const IDM_PRODUCT_GQL = (args) => JSON.stringify({ query: `{ product(${args}){ product{ ${IDM_PRODUCT_QUERY} } } }` }); // ADD TO BASKET const IDM_HOTSPOT_ADD_TO_BASKET = (t, e, a) => JSON.stringify({ query: `mutation {\n addProductsToBasket(ProductInput: {id: ${t}, size: "${e}", quantity: ${a}}) {\n status\n results {\n status\n error {\n code\n message\n }\n productCode\n productId\n sizeId\n quantity\n quantityAvailable\n }\n clientDetailsInBasket {\n id\n login\n firstname\n lastname\n participationPartnerProgram\n usesVat\n email\n isWholesaler\n isWholesalerOrder\n clientIdUpc\n }\n }\n }` }); ///////////////////////////////////////// // JS app_shop.fn.idmGetOmnibusDetails = (options) => { const { productData, sizeId, priceType = app_shop.vars.priceType, } = options || {}; if (!productData) return false; const sizeData = productData.sizes.find((size) => size.id === sizeId) || productData; if (!sizeData?.price) return false; const classes = { add: [], remove: ['--omnibus', '--omnibus-short', '--omnibus-code', '--omnibus-code-short', '--omnibus-new-price', '--omnibus-higher'], }; const activeLabel = {}; const omnibusPrice = sizeData.price?.omnibusPriceDetails?.unit?.[priceType]?.formatted || sizeData.price.omnibusPrice[priceType]?.formatted; if (!omnibusPrice) { return { classes, }; } // Omnibus classes.add.push('--omnibus'); classes.remove = classes.remove.filter((item) => item !== '--omnibus'); const sellBy = productData?.unit?.sellBy; const unitMaxPrice = sizeData?.price?.unit?.[priceType]?.formatted && sizeData.price.max?.[priceType]?.value ? format_price(parseFloat(sizeData.price.max?.[priceType]?.value) * parseFloat(sellBy), { mask: app_shop.vars.currency_format, currency: app_shop.vars?.currency?.symbol, currency_space: app_shop.vars.currency_space, currency_before_price: app_shop.vars.currency_before_value, }) : false; const maxPrice = unitMaxPrice || sizeData.price.max?.[priceType]?.formatted; // Skrócona wersja omnibusa if (!maxPrice || maxPrice === omnibusPrice) { classes.add.push('--omnibus-short'); classes.remove = classes.remove.filter((item) => item !== '--omnibus-short'); } // Aktywny kod rabatowy if (app_shop.vars.omnibus?.rebateCodeActivate && sizeData.price?.rebateCodeActive) { classes.add.push('--omnibus-code'); activeLabel.rebateCodeActive = `${idmHotspotTextObject["Kod rabatowy"]}`; classes.remove = classes.remove.filter((item) => item !== '--omnibus-code'); } // Skrócona wersja omnibusa, gdy aktywny kod rabatowy const beforeRebatePrice = sizeData.price.beforeRebateDetails?.unit?.[priceType]?.formatted || sizeData.price.beforeRebate[priceType]?.formatted; if (app_shop.vars.omnibus?.rebateCodeActivate && beforeRebatePrice === omnibusPrice && sizeData.price?.rebateCodeActive) { classes.add.push('--omnibus-code-short'); classes.remove = classes.remove.filter((item) => item !== '--omnibus-code-short'); } // Nadchodząca cena const newDate = sizeData.price.omnibusPriceDetails?.newPriceEffectiveUntil?.formatted; if (newDate && maxPrice) { classes.add.push('--omnibus-new-price'); classes.remove = classes.remove.filter((item) => item !== '--omnibus-new-price'); } // Cena omnibusa wyższa niż cena sprzedaży const higher = sizeData.price.omnibusPriceDetails?.omnibusPriceIsHigherThanSellingPrice; if (higher) { classes.add.push('--omnibus-higher'); classes.remove = classes.remove.filter((item) => item !== '--omnibus-higher'); } // label okazja if ((!higher || newDate) && !activeLabel?.rebateCodeActive) { activeLabel.bargain = `${idmHotspotTextObject["Okazja"]}`; } // label promocja if (Object.keys(activeLabel)?.length === 0) { activeLabel.bargain = `${idmHotspotTextObject["Promocja"]}`; } // // labele zones // if(productData.zones.find(zone => zone ==="bestseller")) activeLabel.bestseller = `${idmHotspotTextObject["Bestseller"]}`; // if(productData.zones.find(zone => zone ==="news")) activeLabel.news = `${idmHotspotTextObject["Nowość"]}`; let omnibusPercentSign = ''; if (higher) { omnibusPercentSign = '-'; } else if (sizeData.price.omnibusPriceDetails?.youSavePercent !== 0) { omnibusPercentSign = '+'; } const omnibusPercent = `${omnibusPercentSign}${sizeData.price.omnibusPriceDetails?.youSavePercent}%`; const omnibus = { price: omnibusPrice, visible: true, percent: omnibusPercent, html: `${idmHotspotTextObject['Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki']}: ${omnibusPrice}${omnibusPercent}`, }; const max = (maxPrice) ? { max: { price: maxPrice, visible: true, percent: `-${sizeData.price.youSavePercent}%`, html: `${idmHotspotTextObject['Cena regularna']}: ${maxPrice}-${sizeData.price.youSavePercent}%`, }, } : {}; const beforeRebate = (beforeRebatePrice) ? { beforeRebate: { price: beforeRebatePrice, visible: !!classes.add.includes('--omnibus-code'), percent: `-${sizeData.price.beforeRebateDetails?.youSavePercent}%`, html: `${idmHotspotTextObject['Cena bez kodu']}: ${beforeRebatePrice} -${sizeData.price.beforeRebateDetails?.youSavePercent}%`, }, } : {}; const newPriceEffectiveUntil = (newDate) ? { newPriceEffectiveUntil: { date: newDate, price: maxPrice, visible: !!classes.add.includes('--omnibus-new-price'), html: `${idmHotspotTextObject['Cena nadchodząca od']} ${newDate}: ${maxPrice}`, }, } : {}; return { classes, omnibus, ...max, ...beforeRebate, ...newPriceEffectiveUntil, activeLabel, }; }; /** * Klasa IdmHotspot * ============================ * Odpowiada za tworzenie, renderowanie i obsługę dynamicznych hotspotów produktowych. * Pobiera dane przez GraphQL, renderuje produkty, obsługuje dodawanie do koszyka, * inicjuje Swipera, ustawia wysokości, nasłuchuje zdarzeń i wykonuje lazy loading. */ class IdmHotspot{ // ============================ // DOMYŚLNE USTAWIENIA SWIPERA // ============================ static idmDefaultSwiperConfig = { // true, false, obiekt z opcjami swipera loop: false, autoHeight: false, spaceBetween: 16, slidesPerView: 1.4, centeredSlides: true, centeredSlidesBounds: true, breakpoints: { 550: { slidesPerView: 3, centeredSlides: true, centeredSlidesBounds: true, }, 979: { slidesPerView: 4, centeredSlides: false, }, } } // ============================ // DOMYŚLNE OPCJE HOTSPOTA // ============================ static idmDefaultHotspotOptions = { options: { lazy: true, addToBasket: true, // true, false, "range" swiper: true, callbackFn: ()=>{} } } /** * Konstruktor * @param {object} object - Dane konfiguracyjne hotspotu */ constructor({id, title, classes, placement, source, query, options = {}, hotspotEl, products}){ this.id = id || ""; this.title = title || ""; this.classes = classes || ""; this.placement = placement || {}; this.source = source || {}; this.query = query || {}; // this.type = type; this.products = products || null; this.hotspotEl = hotspotEl || null; // Merge defaults this.options = { ...IdmHotspot.idmDefaultHotspotOptions.options, ...options, }; // // this.hotspots = {}; this.priceType = app_shop?.vars?.priceType || "gross"; // bind this do funkcji eventowych this.handleAddToBasket = this.handleAddToBasket.bind(this); this.handleQuantityButtonClick = this.handleQuantityButtonClick.bind(this); this.handleQuantityInputChange = this.handleQuantityInputChange.bind(this); this.init(); } // ======================================================== // ASYNC – POBIERANIE DANYCH Z GRAPHQL // ======================================================== /** * Przygotowuje funkcję i zapytanie GraphQL w zależności od typu danych. */ getQueryData({productsID, productsMenu, hotspotsType}){ let graphFn, query; if(productsID){ graphFn = IDM_PRODUCTS_GQL; query = `searchInput: {productsId: [${productsID}]}`; }else if(productsMenu){ graphFn = IDM_PRODUCTS_GQL; query = `searchInput: {navigation: ${productsMenu}}`; }else if(hotspotsType){ graphFn = IDM_HOTSPOTS_GQL; query = `searchInput: {hotspot: ${hotspotsType}, limit: 16}`; } return [graphFn, query]; } /** * Ustawia dane zapytania GraphQL wewnątrz instancji. */ setQueryData(queryObj){ const [graphFn, queryString] = this.getQueryData(queryObj); this.query.graphFn = graphFn; this.query.string = queryString; } /** * Pobiera dane hotspotu z API GraphQL. */ async getHotspotData(){ try{ const res = await fetch(`/graphql/v1/`, { method: "POST", headers: { "Content-Type": "application/json", }, body: this.query.graphFn(this.query.string), }); const data = await res.json(); const products = data?.data?.[this.query.graphFn === IDM_HOTSPOTS_GQL ? "hotspots" : "products"]?.products; if(!products || !products.length) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]); this.products = products; this.title = this.title || data?.data?.hotspots?.name || ""; }catch(err){ console.error(idmHotspotTextObject["Błąd przy pobieraniu danych"], err); return null; } } // ======================================================== // MARKUP – TWORZENIE HTML PRODUKTÓW // ======================================================== /** * Tworzy markup dla wszystkich produktów w hotspotcie. */ markup(){ let markup = ""; this.products.forEach((prod)=>{ markup += this.markupProduct(prod); }) return markup; } /** * Tworzy markup dla pojedynczego produktu. */ markupProduct(prod){ // IDM DO POPRAWKI const prodExchangedData = app_shop.fn?.idmGetOmnibusDetails({productData: prod}); // markup pojedynczego produktu let singleMarkup = ""; singleMarkup += `
${this.markupImage(prod)} ${this.markupLabel(prod)}
${prod.name}
${this.markupPrice(prod, prodExchangedData)}
${this.markupAddToBasket(prod)}
`; return singleMarkup; } markupImage(prod){ let markup = ""; if(prod.iconSmallSecond !== undefined && prod.iconSecond !== undefined) markup +=` ${prod.name} `; else if(prod?.iconSmall !== undefined) markup += ` ${prod.name} `; else markup += `${prod.name}` return markup; } markupLabel(prod){ let labelMarkup = "" // labele zones if(prod.zones.find(zone => zone ==="bestseller")) labelMarkup += `${idmHotspotTextObject["Bestseller"]}`; if(prod.zones.find(zone => zone ==="news")) labelMarkup += `${idmHotspotTextObject["Nowość"]}`; const omnibusPrice = prod.price?.omnibusPriceDetails?.unit?.[this.idmPriceType]?.formatted || prod.price.omnibusPrice[this.idmPriceType]?.formatted; if(omnibusPrice){ const newDate = prod.price.omnibusPriceDetails?.newPriceEffectiveUntil?.formatted; const higher = prod.price.omnibusPriceDetails?.omnibusPriceIsHigherThanSellingPrice; if (app_shop.vars.omnibus?.rebateCodeActivate && prod.price?.rebateCodeActive) { labelMarkup += `${idmHotspotTextObject["Kod rabatowy"]}`; }else if ((!higher || newDate) && !activeLabel?.rebateCodeActive) { labelMarkup += `${idmHotspotTextObject["Okazja"]}`; }else { labelMarkup += `${idmHotspotTextObject["Promocja"]}`; } } return labelMarkup; } markupPrice(prod, prodExchangedData){ const price = prod.price.price[this.priceType]; const unit = prod.unit; const pointsPrice = prod?.points; const convertedPrice = prod.price?.unitConvertedPrice?.[this.priceType]?.formatted; return ` ${price.formatted} / ${unit?.sellBy} ${unit?.sellBy > 1 ? unit?.plural : unit?.singular} ${convertedPrice ? `${convertedPrice}` : ""} ${pointsPrice ? `${pointsPrice} pkt.` : ""} ${price.value === 0 ? `${idmHotspotTextObject["Cena na telefon"]}` : ""} ${prodExchangedData?.beforeRebate?.visible ? `${prodExchangedData?.beforeRebate?.html}` : ""} ${prodExchangedData?.newPriceEffectiveUntil?.visible ? `${prodExchangedData?.newPriceEffectiveUntil?.html}` : ""} ${prodExchangedData?.omnibus?.visible ? `${prodExchangedData?.omnibus?.html}` : ""} ${prodExchangedData?.max?.visible ? `${prodExchangedData?.max?.html}` : ""} `; } markupAddToBasket(prod){ let markup = ""; if(!this.options.addToBasket) return markup; // link do produktu jak nie jest to zwykły produkt if(prod.type !== "product" || prod.sizes[0].amount === 0) markup = `Zobacz produkt`; else if(this.options.addToBasket === "range") // +- markup = `
`; else // Zwykłe dodanie do koszyka markup = `
`; return markup; } markupHotspotContainer(){ return `
${this?.title ? `

${this.title}

` : ""}
`; } // ======================================================== // HANDLERY ZDARZEŃ // ======================================================== /** * Obsługuje dodanie produktu do koszyka (GraphQL). */ async handleAddToBasket(e){ const formEl = e.target.closest("form.add_to_basket"); if(!formEl) return; try{ // pobieranie danych i elementów formEl.classList.add("--loading") const buttonEl = formEl.querySelector(".add_to_basket__button"); e.preventDefault(); const id = formEl.querySelector("input[name='product']")?.value; const size = formEl.querySelector("input[type='hidden'][name='size']")?.value; const number = formEl.querySelector("input[name='number']")?.value; // dodanie do koszyka const res = await fetch(`/graphql/v1/`, { method: "POST", headers: { "Content-Type": "application/json", }, body: IDM_HOTSPOT_ADD_TO_BASKET(id, size, number) }); const data = await res.json(); // Błąd if(data?.data?.addProductsToBasket?.status !== "success") throw new Error(data); else{ localStorage.setItem('addedtoBasket', true); // Obsługiwanie sukcesu app_shop.graphql.trackingEvents(res); buttonEl.classList.add("--success"); // Dodawanie do koszyka na stronie basketedit.php będzie wymagał innego indywidualnego kodu!!!!! buttonEl.innerHTML = `${buttonEl.dataset.success}`; setTimeout(()=>{ buttonEl.innerHTML = `${buttonEl.dataset.text}`; app_shop.fn?.menu_basket_cache?.(); buttonEl.classList.remove("--success"); }, 3000); } }catch(err){ console.error(err); Alertek.Error(idmHotspotTextObject["Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę"]); buttonEl.innerHTML = `${buttonEl.dataset.error}`; buttonEl.classList.add("--error") setTimeout(()=>{ buttonEl.classList.remove("--error") buttonEl.innerHTML = `${buttonEl.dataset.text}`; }, 3000); }finally{ formEl.classList.remove("--loading") } } /** * Obsługuje kliknięcia w przyciski +/− przy wyborze ilości. */ handleQuantityButtonClick(e){ if(e.target.classList.contains("idm-products-banner__qty-input")) return e.target.select(); const wrapper = e.target.closest(".idm-products-banner__qty"); const input = wrapper.querySelector(".idm-products-banner__qty-input"); const step = parseFloat(wrapper.dataset.sellBy || "1"); const precision = parseInt(wrapper.dataset.precision || "0"); const max = parseFloat(wrapper.dataset.max || "999999"); let current = parseFloat(input.value) || 0; if (e.target.classList.contains("idm-products-banner__qty-increase")) { current += step; if (current > max){ current = max; this.rangeMaxAlert(max) } } else if (e.target.classList.contains("idm-products-banner__qty-decrease")) { current -= step; if (current < step){ current = step; this.rangeMinAlert(step) } } input.value = current.toFixed(precision); } /** * Walidacja zmian ilości w polu input. */ handleQuantityInputChange(e){ if(e.target.value > +e.target.max){ this.rangeMaxAlert(e.target.max) e.target.value = +e.target.max }else if(e.target.value < +e.target.min){ this.rangeMinAlert(e.target.min) e.target.value = +e.target.min; } } /** * Lazy-load hotspotu – wczytuje dane dopiero, gdy element pojawi się w viewportcie. */ handleObserveHotspotOnce() { if (!this.hotspotEl) return; const observer = new IntersectionObserver((entries, obs) => { entries.forEach(entry => { if (entry.isIntersecting) { this.fillHotspot(); // run your callback obs.disconnect(); // stop observing after first trigger } }); }, { root: null, rootMargin: "0px", threshold: 0.1 }); observer.observe(this.hotspotEl); } // ======================================================== // FUNKCJE POMOCNICZE // ======================================================== /** * Wyświetla alert o maksymalnej ilości produktu. */ rangeMaxAlert(max){ Alertek.Error(`${idmHotspotTextObject["Maksymalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]} ${max}`) } /** * Wyświetla alert o minimalnej ilości produktu. */ rangeMinAlert(min){ Alertek.Error(`${idmHotspotTextObject["Minimalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]} ${min}`) } /** * Ustawia jednakową wysokość elementów (np. nazw lub cen). */ setHeight(options){ const { selector, selectors, container } = options || {} if ((!selector && !selectors) || !container) return const containerElement = document.querySelector(container) if (!containerElement) return const adjustAllHeights = itemSelector => { const targets = containerElement.querySelectorAll(itemSelector) if (!targets.length) return targets.forEach(el => (el.style.minHeight = '')) const max = Math.max(...[...targets].map(el => el.offsetHeight || 0)) targets.forEach(el => (el.style.minHeight = `${max}px`)) } if (selector) adjustAllHeights(selector) if (selectors?.length) selectors.forEach(adjustAllHeights) } // ======================================================== // INICJALIZACJA // ======================================================== /** * Wykonywana po pełnej inicjalizacji hotspotu (Swiper, eventy, wysokości). */ async afterInit(){ try{ if(!this.hotspotEl) throw new Error("Nie znaleziono elementu"); if(this.title && !this.hotspotEl.querySelector(".hotspot__name.headline__wrapper")){ this.hotspotEl.querySelector(".hotspot.--initialized")?.insertAdjacentHTML("afterbegin", `

${this.title}

`); } await this.initSwiper(); // IDM setHeight this.setHeight({ selectors: [ `#${this.id} .product__prices`, `#${this.id} .product__name`, ], container: `#${this.id} .products__wrapper`, }); this.initEvents(); console.log(`Initialized hotspot #${this.id}`); if(typeof this.options?.callbackFn === "function") this.options?.callbackFn(); }catch(err){ console.error(idmHotspotTextObject["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"], err); } } /** * Pobiera dane, wypełnia markup i inicjuje Swipera. */ async fillHotspot(){ try{ if(!this.products){ if(!this?.query?.graphFn || !this?.query?.string) this.setQueryData({ productsID: this?.source?.productsId, productsMenu: this?.source?.productsMenu, hotspotsType: this?.source?.hotspotsType }); // pobranie danych o produktach await this.getHotspotData(); if(!this.products) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]); } // Wstawienie markupa na strone this.hotspotEl.querySelector(".products.hotspot__products")?.insertAdjacentHTML("beforeend", this.markup()); this.hotspotEl.classList.remove("idm-loading"); // init swiper + add to basket this.afterInit(); }catch(err){ console.error(idmHotspotTextObject["Wystąpił błąd"], err); this.hotspotEl.remove(); } } /** * Inicjuje instancję Swipera dla hotspotu. */ async initSwiper(){ try{ // swiper || slick if(this.options?.swiper){ // Opcje swipera if(typeof this?.options?.swiper === "boolean") this.options.swiper = IdmHotspot.idmDefaultSwiperConfig; // Wywołanie swipera const selectedSwiper = new HotspotSlider({ selector: `#${this.id} .swiper`, hotspotName: `${this.id}`, options: this.options.swiper, }); await selectedSwiper.init(); } }catch(err){ console.error(idmHotspotTextObject["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"], err); } } /** * Inicjuje eventy dla produktów w hotspotcie. */ initEvents(){ this.hotspotEl.querySelectorAll(".product").forEach(prodEl=>{ this.initSingleEvent(prodEl); }) } initSingleEvent(prodEl){ // DODAWANIE DO KOSZYKA if(this?.options?.addToBasket){ const addToBasketEl = prodEl.querySelector("form.add_to_basket"); if(!addToBasketEl) return; addToBasketEl.addEventListener("submit", this.handleAddToBasket); // + - if(this?.options?.addToBasket === "range"){ addToBasketEl.querySelector(".idm-products-banner__qty")?.addEventListener("click",this.handleQuantityButtonClick); addToBasketEl.querySelector(".idm-products-banner__qty-input")?.addEventListener("input",this.handleQuantityInputChange); } } } /** * Inicjuje kontener hotspotu w określonym miejscu DOM. */ initHotspotContainer(){ const selectedEl = document.querySelector(this?.placement.selector); if(!selectedEl) throw new Error(idmHotspotTextObject["Nie znaleziono kontenera"]); const markup = this.markupHotspotContainer(); selectedEl.insertAdjacentHTML(this.placement.insert || "afterend", markup); this.hotspotEl = document.getElementById(this.id); } /** * Główna metoda inicjalizująca hotspot (lazy lub natychmiast). */ async init(){ if(!this.hotspotEl || !document.contains(this.hotspotEl)) this.initHotspotContainer(); if(this.options?.lazy) this.handleObserveHotspotOnce(); else this.fillHotspot(); } } // new IdmHotspot({ // id: "idmTestHotspot1", // title: "tescik", // placement: { // selector: "#content", // insert: "beforeend", // }, // source: { // productsMenu: 1649 // } // }); // { // id: "idmMainHotspot2", // title: "Super ramka rekomendacji", // placement: { // selector: "#content", // insert: "beforeend" // }, // source: { // productsMenu: 488, // }, // options: { // lazy: true, // addToBasket: "range", // swiper: true, // } // } async function idmPrepareHotspotObject(selectedContainerEl){ selectedContainerEl.classList.add("--init"); const source = {}; if(selectedContainerEl?.dataset?.productsId) source.productsId = selectedContainerEl?.dataset?.productsId.split(","); else if(selectedContainerEl?.dataset?.hotspotsType) source.hotspotsType = selectedContainerEl?.dataset?.hotspotsType; else if(selectedContainerEl?.dataset?.productsMenu) source.productsMenu = selectedContainerEl?.dataset?.productsMenu; else{ console.error(); selectedContainerEl?.remove(); return; } const idmHotspotObj = { id: selectedContainerEl?.id, source, hotspotEl: selectedContainerEl }; if(selectedContainerEl?.dataset?.lazy) idmHotspotObj.options = {lazy: selectedContainerEl?.dataset?.lazy === "true" ? true : false}; new IdmHotspot(idmHotspotObj) } document.querySelectorAll(".hotspot__wrapper.idm__hotspot").forEach(currentHotspot=>{ idmPrepareHotspotObject(currentHotspot) })