/////////////////////////////////////////////////////////// // TEXT // LITERAŁY const idmHotspotTextObject = { ["Kod rabatowy"]: , ["Okazja"]: , ["Promocja"]: , ["Bestseller"]: , ["Nowość"]: , ["Ilość"]: , ["Porównaj"]: , ["Dodaj do ulubionych"]: , ["Najniższa cena"]: , ["Najniższa cena z 30 dni przed obniżką"]: , ["Zwiększ ilość"]: , ["Zmniejsz ilość"]: , ["Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki"]: , ["Cena regularna"]: , ["Cena bez kodu"]: , ["Cena nadchodząca od"]: , ["Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę"]: , ["Nie znaleziono produktów"]: , ["Błąd przy pobieraniu danych"]: , ["Kliknij, by przejść do formularza kontaktu"]: , ["Cena na telefon"]: , ["Dodany"]: , ["Wystąpił błąd"]: , ["Do koszyka"]: , ["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:"]: , ["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"]: , ["Nie znaleziono kontenera"]: , ["Nie znaleziono metody graphql"]: , ["Drugie Zdjęcie"]: , ["Zobacz więcej"]: , }; // STRING // const idmHotspotTextObject = { // "Kod rabatowy": "Kod rabatowy", // "Okazja": "Okazja", // "Promocja": "Promocja", // "Bestseller": "Bestseller", // "Nowość": "Nowość", // "Ilość": "Ilość", // "Porównaj": "Porównaj", // "Dodaj do ulubionych": "Dodaj do ulubionych", // "Najniższa cena": "Najniższa cena", // "Najniższa cena z 30 dni przed obniżką": "Najniższa cena z 30 dni przed obniżką", // "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", // "Drugie Zdjęcie": "Drugie Zdjęcie", // "Zobacz więcej": "Zobacz więcej", // }; /////////////////////////////////////////////// // GraphQL // ogolne const IDM_PRICE_QUERY = (priceType) => `price { rebateCodeActive price { ${priceType} { value formatted } } omnibusPrice { ${priceType} { value formatted } } omnibusPriceDetails { unit { ${priceType} { value formatted } } youSavePercent omnibusPriceIsHigherThanSellingPrice newPriceEffectiveUntil { formatted } } max { ${priceType} { value formatted } } unit { ${priceType} { value formatted } } unitConvertedPrice { ${priceType} { value formatted } } youSavePercent beforeRebate { ${priceType} { value formatted } } beforeRebateDetails { youSavePercent unit { ${priceType} { value formatted } } } advancePrice { ${priceType} { value formatted } } suggested { ${priceType} { value formatted } } rebateNumber { number ${priceType} { value formatted } } }`; const IDM_PRODUCT_QUERY = (priceType) => `id type name icon iconSecond iconSmall iconSmallSecond link zones producer{ name } category{ name } sizes{ id amount name ${IDM_PRICE_QUERY(priceType)} } group{ id name link versions{ id name icon iconSecond iconSmall iconSmallSecond link } } opinion{ rating, count } awardedParameters { name id description values { name id } } enclosuresImages { position url } points unit{ id, name, singular, plural, fraction, sellBy, precision, unitConvertedFormat } ${IDM_PRICE_QUERY(priceType)}`; // 1. products const IDM_PRODUCTS_GQL = (args, priceType = "gross") => JSON.stringify({ query: `{ products(${args}){ took products{ ${IDM_PRODUCT_QUERY(priceType)} } } }` }); // 2. hotspots const IDM_HOTSPOTS_GQL = (args, priceType = "gross") => JSON.stringify({ query: `{ hotspots(${args}){ took name url products{ ${IDM_PRODUCT_QUERY(priceType)} } } }` }); // 3. single product const IDM_PRODUCT_GQL = (args, priceType = "gross") => JSON.stringify({ query: `{ product(${args}){ product{ ${IDM_PRODUCT_QUERY(priceType)} } } }` }); // ADD TO BASKET const IDM_HOTSPOT_ADD_TO_BASKET = (t, e, a) => JSON.stringify({ query: `mutation { addProductsToBasket(ProductInput: {id: ${t}, size: "${e}", quantity: ${a}}) { status results { status error { code message } } } }` }); ///////////////////////////////////////// // 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 = `${omnibusDetailsTxt?.['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 = `${omnibusDetailsTxt?.['Okazja']}`; } // label promocja if (Object.keys(activeLabel)?.length === 0) { activeLabel.bargain = `${omnibusDetailsTxt?.['Promocja']}`; } 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: `${omnibusDetailsTxt['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: `${omnibusDetailsTxt['Cena regularna']}: ${maxPrice}-${sizeData.price.youSavePercent}%`, }, } : {}; const beforeRebate = (beforeRebatePrice) ? { beforeRebate: { price: beforeRebatePrice, visible: !!classes.add.includes('--omnibus-code'), percent: `-${sizeData.price.beforeRebateDetails?.youSavePercent}%`, html: `${omnibusDetailsTxt['Cena bez kodu']}: ${beforeRebatePrice} -${sizeData.price.beforeRebateDetails?.youSavePercent}%`, }, } : {}; const newPriceEffectiveUntil = (newDate) ? { newPriceEffectiveUntil: { date: newDate, price: maxPrice, visible: !!classes.add.includes('--omnibus-new-price'), html: `${omnibusDetailsTxt['Cena nadchodząca od']} ${newDate}: ${maxPrice}`, }, } : {}; const omnibusLabel = { omnibusLabel: { name: Object.keys(activeLabel)?.[0] || '', html: activeLabel[Object.keys(activeLabel)?.[0]], }, }; return { classes, omnibus, ...max, ...beforeRebate, ...newPriceEffectiveUntil, ...omnibusLabel, }; }; /** * 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 = { cssVariables: { version: { columnDesktop: 5, columnTablet: 3, columnMobile: 4, }, nameClamp: null// 2, }, options: { limit: 8, lazy: true, devMode: false, callbackFn: ()=>{}, omnibusTooltip: false, // switchImage: false, // POKAZANIE showOpinions: false, showSecondImage: false, // DODAWANIE addToBasket: true, // true, false, "range" addToFavorites: false, // Wymaga zmian szablonowych addToCompare: false, // WYBÓR selectSize: false, selectVersion: false, // SWIPER swiper: true, swiperScrollbar: false, } } /** * Konstruktor * @param {object} object - Dane konfiguracyjne hotspotu */ constructor({id, title, classes, placement, source, query, options = {}, hotspotEl, products, cssVariables}){ 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.cssVariables = { ...IdmHotspot.idmDefaultHotspotOptions.cssVariables, ...cssVariables, }; // // 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.handleAddToCompare = this.handleAddToCompare.bind(this); this.handleAddToFav = this.handleAddToFav.bind(this); this.handleShowSecondImage = this.handleShowSecondImage.bind(this); this.handleHideSecondImage = this.handleHideSecondImage.bind(this); this.handleSelectVersion = this.handleSelectVersion.bind(this); this.handleSelectSize = this.handleSelectSize.bind(this); this.init(); } // ======================================================== // ASYNC – POBIERANIE DANYCH Z GRAPHQL // ======================================================== /** * Przygotowuje funkcję i zapytanie GraphQL w zależności od typu danych. */ getQueryData(){ let graphFn, query; let queryMarkup = ""; let additionalQuery = ""; if(this.source?.hotspotsType){ graphFn = IDM_HOTSPOTS_GQL; queryMarkup += `hotspot: ${this.source.hotspotsType}, limit: ${this.options.limit}`; }else{ graphFn = IDM_PRODUCTS_GQL; if(this.source?.productsId){ queryMarkup += `productsId: [${this.source.productsId}],`; } if(this.source?.productsMenu){ queryMarkup += `navigation: ${this.source.productsMenu},`; } if(this.source?.producersId){ queryMarkup += `producers: [${this.source.producersId}],`; } if(this.source?.seriesId){ queryMarkup += `series: [${this.source.seriesId}],`; } if(this.source?.parametersId){ queryMarkup += `parameters: [${this.source.parametersId.reduce((acc,val)=> acc + `{id: ${val}}`,"")}],`; } if(this.source?.priceRange){ queryMarkup += `priceRange: {from: ${+this.source.priceRange?.from || 0}, to: ${+this.source.priceRange?.to || 0}},`; } if(this.options.limit) additionalQuery += `, settingsInput: {limit: ${this.options.limit}}` } query = `searchInput: { ${queryMarkup} }${additionalQuery}` return [graphFn, query]; } /** * Ustawia dane zapytania GraphQL wewnątrz instancji. */ setQueryData(){ const [graphFn, queryString] = this.getQueryData(); this.query.graphFn = graphFn; this.query.string = queryString; } /** * Pobiera dane hotspotu z API GraphQL. */ async getHotspotData(){ if(this.products) return; try{ let products; if(this.source?.link){ products = await this.xmlGetData(); }else{ const res = await fetch(`/graphql/v1/`, { method: "POST", headers: { "Content-Type": "application/json", }, body: this.query.graphFn(this.query.string, this.priceType), }); const data = await res.json(); products = data?.data?.[this.query.graphFn === IDM_HOTSPOTS_GQL ? "hotspots" : "products"]?.products; this.title = this.title || data?.data?.hotspots?.name || ""; } if(!products || !products.length) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]); this.products = products; }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){ // markup pojedynczego produktu let singleMarkup = ""; singleMarkup += `
${this.markupProductInnerHTML(prod)}
`; return singleMarkup; } markupProductInnerHTML(prod){ // IDM DO POPRAWKI const prodExchangedData = app_shop?.fn?.getOmnibusDetails?.({productData: prod}) || app_shop.fn?.idmGetOmnibusDetails({productData: prod}); return `
${this.markupAdditional(prod)}
${this.markupImage(prod)} ${this.markupLabel(prod)}
${this.markupVersions(prod)} ${this.markupOpinions(prod)} ${prod.name}
${this.markupPrice(prod, prodExchangedData)}
${this.markupAddToBasket(prod)}
`; } markupAdditional(prod){ return ` ${this.markupCompare(prod)} ${this.markupFavorite(prod)} `; } markupCompare(prod){ if(!this.options?.addToCompare) return ""; return `` } markupFavorite(prod){ if(!this.options?.addToFavorites || typeof this.addToFavFn !== "function" || this.hotspotEl.closest(".modal")) return ""; return ` `; } markupVersions(prod){ if(!this.options?.selectVersion || !prod.group?.versions || prod.group?.versions?.length < 2 ) return ""; // let MAX_VERSION_AMOUNT = 5; // if(app_shop.vars.view > 2){ // MAX_VERSION_AMOUNT = this.cssVariables.version.columnDesktop; // }else if(app_shop.vars.view === 2){ // MAX_VERSION_AMOUNT = this.cssVariables.version.columnTablet; // }else if(app_shop.vars.view === 1){ // MAX_VERSION_AMOUNT = this.cssVariables.version.columnMobile; // } const sortedVersions = prod.group.versions.sort(function (a, b) { if (a.name < b.name) { return -1; } if (a.name > b.name) { return 1; } return 0; }) let productVersionsMarkup = sortedVersions.reduce((acc, val, index)=>{ // if(index + 1 > MAX_VERSION_AMOUNT) return acc; // if(index + 1 === MAX_VERSION_AMOUNT && prod.group.versions.length > MAX_VERSION_AMOUNT) // return acc + ` // +${(prod.group.versions.length - MAX_VERSION_AMOUNT) + 1} // `; return acc + `${val.name}`; },""); const desktopMoreLength = (prod.group.versions.length - this.cssVariables.version.columnDesktop) + 1; const tabletMoreLength = (prod.group.versions.length - this.cssVariables.version.columnTablet) + 1; const mobileMoreLength = (prod.group.versions.length - this.cssVariables.version.columnMobile) + 1; return `` } markupImage(prod){ let markup = ""; if(prod.iconSmallSecond && prod.iconSecond) markup +=` ${prod.name} `; else if(prod?.iconSmall) markup += ` ${prod.name} `; else markup += `${prod.name}`; if(this.options?.showSecondImage && prod.enclosuresImages?.[1]?.url) markup += `${prod.name} - ${idmHotspotTextObject[`; 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; } markupOpinions(prod){ if(!this.options?.showOpinions) return ""; return `
${prod.opinion.rating.toFixed(2)} / 5.00
` } 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 && prod.unit?.unitConvertedFormat ? `(${convertedPrice} / ${prod.unit?.unitConvertedFormat})` : ""} ${pointsPrice ? `${pointsPrice} pkt.` : ""} ${price.value === 0 ? `${idmHotspotTextObject["Cena na telefon"]}` : ""} ${prodExchangedData?.beforeRebate?.visible ? `${prodExchangedData?.beforeRebate?.html}` : ""} ${prodExchangedData?.newPriceEffectiveUntil?.visible ? `${prodExchangedData?.newPriceEffectiveUntil?.html}` : ""} ${this.markupOmnibus(prodExchangedData?.omnibus)} ${prodExchangedData?.max?.visible ? `${prodExchangedData?.max?.html}` : ""} `; } /** * Tworzy znacznik HTML dla omnibusa. * * @param {object} omnibus - dane omnibusa * @param {string} omnibus.html - kod html zwykłego omnibusa * @param {string} omnibus.percent - % przeceny omnibusa * @param {string} omnibus.price - cena omnibusa * @param {boolean} omnibus.visible - czy omnibus jest widoczny * @returns {string} Zwraca HTML dla sekcji omnibusa lub pusty string, jeśli niewidoczny. */ markupOmnibus(omnibus){ if(!omnibus?.visible) return ""; if(!this.options.omnibusTooltip) return `${omnibus?.html}`; else return ` ${idmHotspotTextObject["Najniższa cena"]}: ${omnibus.price} ${omnibus.percent}

${idmHotspotTextObject["Najniższa cena z 30 dni przed obniżką"]}

`; } markupAddToBasket(prod){ let markup = ""; if(!this.options.addToBasket) return markup; const prodTotalAmount = this.getProdTotalAmount(prod); // link do produktu jak nie jest to zwykły produkt if(prod.type !== "product" || prodTotalAmount === 0) markup = `Zobacz produkt`; else if(this.options.addToBasket === "range") // +- markup = `
${this.markupSize(prod)}
`; else // Zwykłe dodanie do koszyka markup = `
${this.markupSize(prod)}
`; return markup; } markupSize(prod){ // return ``; if(!this.options?.selectSize || prod.sizes.length === 1) return ``; const sizesName = `${this.id}-${prod.id}`; let selectedDefault = false; return `
${prod.sizes.reduce((acc, val, index)=>{ const inputId = `${sizesName}-${val.id}`; const isSelected = !selectedDefault && val?.amount !== 0 ? true : false; if(isSelected) selectedDefault = true; return acc + ` ` }, "")}
`; } markupHotspotContainer(){ return `
${this.markupHotspotInnerDiv()}
`; } markupHotspotInnerDiv(){ return `
${this?.title ? `

${this.title}

` : ""} ${this.markupHotspotSwiperContainer()}
` } markupHotspotSwiperContainer(productsHTML=""){ return `
${productsHTML || this.markupSkeleton()}
`; } // SKELETONS MARKUPS markupSkeleton(){ let skeletonMarkup = ""; // Tworzenie skeletonow for(let i = 0; i <= this.options.limit; i++){ skeletonMarkup += this.markupSingleSkeleton(); } return `
${skeletonMarkup}
`; } markupSingleSkeleton(){ return `
${this.options.showOpinions ? `
` : ""}
${this.options.addToBasket ? `
` : ""}
` } // ======================================================== // 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!!!!! app_shop.fn?.menu_basket_cache?.(); // STRONA KOSZYKA if(typeof app_shop.fn?.basket?.reloadForm === "function"){ const existingBasketBlockQuantity = document.querySelector(`.basket__block[data-product-id="${id}"][data-product-size="${size}"] input.buy__more_input.quantity__input[type="number"]`); // Dodanie do ilości produktu jeśli już był dodany do koszyka if(existingBasketBlockQuantity) existingBasketBlockQuantity.value = +existingBasketBlockQuantity.value + 1; // Przeładowanie koszyka na stronie basketedit.html app_shop.fn?.basket?.reloadForm(); } buttonEl.innerHTML = `${buttonEl.dataset.success}`; setTimeout(()=>{ buttonEl.innerHTML = `${buttonEl.dataset.text}`; 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 dodanie produktu do Porównania */ async handleAddToCompare(e){ const compareBtnEl = e.target.closest(".idm-products-banner__compare-btn"); const compareId = compareBtnEl?.dataset?.compareId; if (!compareBtnEl || !compareId) return; e.preventDefault(); try { compareBtnEl.classList.add("--loading"); const compareUrl = `/${app_shop?.vars?.language?.symbol || "pl"}/settings.html?comparers=add&product=${compareId}`; const res = await fetch(compareUrl); console.log(res); if (!res.ok) throw new Error(`${idmHotspotTextObject["Wystąpił błąd"]}`); compareBtnEl.classList.add("--success"); const compareContainerQuery = "#menu_compare_product"; if (document.querySelector(compareContainerQuery)) { app_shop.fn?.load( window.location.pathname, [[compareContainerQuery, compareContainerQuery]], function () {}, "?set_render=content" ); } setTimeout(() => { compareBtnEl.classList.remove("--success"); }, 2000); } catch (err) { console.error(err); Alertek.Error(`${idmHotspotTextObject["Coś poszło nie tak. Spróbuj ponownie później"]}`); } finally { compareBtnEl.classList.remove("--loading"); } } /** * Obsługuje dodanie produktu do Listy zakupowej */ handleAddToFav(e){ const favEl = e.target.closest(".product__favorite"); if(!favEl) return; this.addToFavFn([[favEl.dataset.productId, favEl.dataset.productSize]]); } /** * 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); } /** * Obsługuje Pokazywanie zdjęcia na hover */ handleShowSecondImage(e){ const prodIconEl = e.target.closest(".product__icon"); if(!prodIconEl) return; prodIconEl.classList.add("--toggle-icon"); } handleHideSecondImage(e){ const prodIconEl = e.target.closest(".product__icon"); if(!prodIconEl) return; prodIconEl.classList.remove("--toggle-icon"); } /** * 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; } } handleSelectVersion(e){ if(e.target.closest(".product__version_more")) return; e.preventDefault(); const closestVersion = e.target.closest(".product__version_single:not(.--active)"); const prodEl = e.target.closest(".product"); if(!closestVersion || !prodEl) return; this.reloadProduct(prodEl, closestVersion.dataset.productId); } handleSelectSize(e){ const inputEl = e.target.closest("input[type='radio']"); if(!inputEl) return; const newValue = inputEl?.dataset?.value; const hiddenSizeInputEl = e.target.closest("form.add_to_basket")?.querySelector("input[type='hidden'][name='size']"); if(!hiddenSizeInputEl || !newValue) return inputEl.checked = false; hiddenSizeInputEl.value = newValue; } /** * 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); } // ======================================================== // USTAWIANIE ZMIENNYCH CSS DLA RAMKI // ======================================================== // Ustawia się je w dwa sposoby - jako zmienne css przypisywane do ramki + jako dodaktowy tag style cssSetAllVariables(){ this.cssVariableVersionColumnCount(); this.cssVariableNameClamp(); } cssSetAllTags(){ this.cssInsertStyleTag(); } cssVariableNameClamp(){ if(this.cssVariables?.nameClamp) this.cssSetVariable("--hotspot-name-clamp", this.cssVariables.nameClamp); } cssVariableVersionColumnCount(){ this.cssSetVariable("--version-desktop-columns", this.cssVariables?.version?.columnDesktop || 5) this.cssSetVariable("--version-tablet-columns", this.cssVariables?.version?.columnTablet || 3) this.cssSetVariable("--version-mobile-columns", this.cssVariables?.version?.columnMobile || 4) } cssSetVariable(name, value){ this.hotspotEl.style.setProperty(name, value); } cssVersionColumnStyle(){ return ` @media (max-width: 756px){ #${this.hotspotEl.id}.hotspot__wrapper.idm__hotspot .product__version_single:nth-of-type(n + ${this.cssVariables?.version?.columnMobile}){ display: none; } } @media (min-width: 757px) and (max-width: 978px){ #${this.hotspotEl.id}.hotspot__wrapper.idm__hotspot .product__version_single:nth-of-type(n + ${this.cssVariables?.version?.columnTablet}){ display: none; } } @media (min-width: 979px){ #${this.hotspotEl.id}.hotspot__wrapper.idm__hotspot .product__version_single:nth-of-type(n + ${this.cssVariables?.version?.columnDesktop}){ display: none; } } ` } cssSkeletonStyle(){ let skeletonStyles = ""; const skeletonStylesObj = {}; // Budowanie skeletona w zależności od ustawień hotspota if(this.options.swiper){ skeletonStylesObj[0] = this.options.swiper?.slidesPerView; for (const [key, value] of Object.entries(this.options.swiper.breakpoints)) { if(value.slidesPerView) skeletonStylesObj[key] = value.slidesPerView } }else{ // mobile 2 skeletonStylesObj[0] = 2; // desktop + tablet 4 skeletonStylesObj[757] = 4; } for (const [key, value] of Object.entries(skeletonStylesObj)) { let style = ` #${this.hotspotEl.id}.hotspot__wrapper.idm__hotspot.--hotspot-loading .idm_hotspot__skeleton_product{ width: ${(1 / value) * 100}% } ` skeletonStyles += key === 0 ? style : ` @media (min-width: ${key}px){ ${style} } ` } return skeletonStyles; } cssInsertStyleTag(){ this.hotspotEl.insertAdjacentHTML("beforeend", ` `) } cssSetAll(){ this.cssSetAllVariables(); this.cssSetAllTags(); } // ======================================================== // FUNKCJE POMOCNICZE // ======================================================== getProdTotalAmount(prod){ return prod.sizes.reduce((acc, val) => val === -1 || acc === -1 ? -1 : acc + val, 0); } /** * 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) } /** * Przeładowanie pojedynczego produktu */ async reloadProduct(prodEl, newProdId){ try{ prodEl.classList.add("--loading"); const res = await fetch(`/graphql/v1/`, { method: "POST", headers: { "Content-Type": "application/json", }, body: IDM_PRODUCT_GQL(`productId: ${newProdId}`, this.priceType), }); const data = await res.json(); const productData = data?.data?.product?.product; if(!productData) throw new Error("Nie udało się pobrać danych o produkcie"); const prodHTML = this.markupProductInnerHTML(productData); prodEl.dataset.id = newProdId; prodEl.innerHTML = prodHTML; if(productData.price.price[this.priceType].value === 0) prodEl.classList.add("--phone"); else prodEl.classList.remove("--phone"); this.initSingleEvent(prodEl); this.setHeight({ selectors: [ `#${this.id} .product__prices`, `#${this.id} .product__name`, ], container: `#${this.id} .products__wrapper`, }); }catch(err){ Alertek?.Error(idmHotspotTextObject["Błąd przy pobieraniu danych"]); console.error(err); }finally{ prodEl.classList.remove("--loading"); } } // ======================================================== // XML to GraphQL // ======================================================== xmlGetGrossNetPrices({priceNode, name}){ return { gross: { value: priceNode?.getAttribute(`${name}`) !== null ? +priceNode.getAttribute(`${name}`) : undefined, formatted: priceNode?.getAttribute(`${name}_formatted`), }, net: { value: priceNode?.getAttribute(`${name}_net`) !== null ? +priceNode.getAttribute(`${name}_net`) : undefined, formatted: priceNode?.getAttribute(`${name}_net_formatted`), } } } xmlGetPriceFromNode(priceNode){ const priceObj = { price: { gross: { value: priceNode?.getAttribute("value") !== null ? +priceNode.getAttribute("value") : undefined, formatted: priceNode?.getAttribute("price_formatted"), }, net: { value: priceNode?.getAttribute("price_net") !== null ? +priceNode.getAttribute("price_net") : undefined, formatted: priceNode?.getAttribute("price_net_formatted"), } }, rebateCodeActive: priceNode?.getAttribute("rebate_code_active") === "y" ? true : false, omnibusPrice: { ...this.xmlGetGrossNetPrices({priceNode, name: "omnibus_price"}) }, // depositPrice: {}, // depositPriceUnit: {}, // totalDepositPrice: {}, // totalDepositPriceUnit: {}, omnibusPriceDetails: { // unit: {}, youSave: { ...this.xmlGetGrossNetPrices({priceNode, name: "omnibus_yousave"}) }, youSavePercent: priceNode?.getAttribute("price_net") !== null ? +priceNode.getAttribute("omnibus_yousave_percent") : undefined, omnibusPriceIsHigherThanSellingPrice: priceNode?.getAttribute("omnibus_price_is_higher_than_selling_price") === "true" ? true : false, // newPriceEffectiveUntil: {}, }, tax: { // worth: {}, vatPercent: priceNode?.getAttribute("tax") !== null ? +priceNode.getAttribute("tax") : undefined, // vatString: "" }, beforeRebate: { ...this.xmlGetGrossNetPrices({priceNode, name: "beforerebate"}) }, // beforeRebateDetails: { // youSave: { // }, // youSavePercent: "", // unit: {} // }, // crossedPrice: {}, youSave: { ...this.xmlGetGrossNetPrices({priceNode, name: "yousave"}) }, youSavePercent: priceNode?.getAttribute("yousave_percent") !== null ? +priceNode.getAttribute("yousave_percent") : undefined, // unit: {}, max: { ...this.xmlGetGrossNetPrices({priceNode, name: "maxprice"}) }, // maxPriceUnit: {}, // suggested: {}, unitConvertedPrice: { ...this.xmlGetGrossNetPrices({priceNode, name: "unit_converted_price"}) }, // rebateNumber: {}, lastPriceChangeDate: priceNode?.getAttribute("last_price_change_date"), // advancePrice: {}, promotionDuration: { promotionTill: { date: { date: priceNode?.getAttribute("last_price_change_date")?.split("-")?.[2], month: priceNode?.getAttribute("last_price_change_date")?.split("-")?.[1], year: priceNode?.getAttribute("last_price_change_date")?.split("-")?.[0], // weekDay: "", // formatted: "" }, time: { hour: priceNode?.getAttribute("promotiontillhour")?.split(":")?.[0], minutes: priceNode?.getAttribute("promotiontillhour")?.split(":")?.[1], seconds: priceNode?.getAttribute("promotiontillhour")?.split(":")?.[2], }, // timestamp: "" }, // discountTill: {}, // distinguishedTill: {}, // specialTill: {}, }, // subscriptionPrice: {}, }; return priceObj; } xmlGetTraitsFromNode(node){ const awardedParameters = []; node.querySelectorAll(":scope > trait").forEach(trait=>{ const currParameter = awardedParameters.find(param=>param.id === trait.getAttribute("groupid")) || { id: trait.getAttribute("groupid"), name: trait.getAttribute("groupdescription"), // description: "", values: [], // contextValue: "", // search: { // icon: "", // }, } currParameter.values.push({ id: trait.getAttribute("traitid"), name: trait.getAttribute("traitid"), link: trait.getAttribute("link"), // description: "", // search: { // icon: "" // } }) if(currParameter.values.length === 1) awardedParameters.push(currParameter); }); return awardedParameters; } async xmlGetData(){ if(!this.source?.link) return null; try{ const res = await fetch(`${this.source?.link}${this.source?.link.includes("?") ? "&getProductXML=t" : "?getProductXML=t"}`); const str = await res.text(); const xml = new window.DOMParser().parseFromString(str, "text/xml"); const now = new Date(); const allProducts = xml.querySelectorAll("product"); if(allProducts.length === 0) throw new Error("Nie znaleziono produktów"); const data = []; allProducts.forEach((prod, index)=>{ if(this.options.limit <= index) return; // NODES const priceNode = prod.querySelector(":scope >price"); const firmNode = prod.querySelector(":scope > firm"); const categoryNode = prod.querySelector(":scope > category"); const seriesNode = prod.querySelector(":scope > series"); const commentsNode = prod.querySelector(":scope > comments"); const sizesNode = prod.querySelector(":scope > sizes"); const versionsNode = prod.querySelector(":scope > versions"); // STREFY const zones = []; if(prod.getAttribute("promo") === "yes") zones.push("promotion"); if(prod.getAttribute("discount") === "yes") zones.push("discount"); if(prod.getAttribute("distinguished") === "yes") zones.push("distinguished"); if(prod.getAttribute("special") === "yes") zones.push("special"); if(prod.getAttribute("new") === "yes") zones.push("new"); if(prod.getAttribute("bestseller") === "yes") zones.push("bestseller"); if(prod.getAttribute("subscription") === "yes") zones.push("subscription"); // ZDJĘCIA const enclosuresImages = []; prod.querySelectorAll(":scope > enclosures > images > enclosure").forEach(img=>{ enclosuresImages.push({ position: img.getAttribute("position"), type: img.getAttribute("type"), typeSecond: img.getAttribute("type_second"), url: img.getAttribute("url"), urlSecond: img.getAttribute("url_second"), width: img.getAttribute("width"), height: img.getAttribute("height"), iconUrl: img.getAttribute("icon"), iconUrlSecond: img.getAttribute("icon_second"), iconWidth: img.getAttribute("icon_width"), iconHeight: img.getAttribute("icon_height"), mediumUrl: img.getAttribute("medium"), mediumUrlSecond: img.getAttribute("medium_second"), mediumWidth: img.getAttribute("medium_width"), mediumHeight: img.getAttribute("medium_height"), }) }) // SIZES const sizes = []; sizesNode.querySelectorAll(":scope > size").forEach(size=>{ const availabilityNode = size.querySelector(":scope > availability"); const shippingTimeNode = availabilityNode.querySelector(":scope > shipping_time"); const sizePriceNode = size.querySelector(":scope > price"); const weightNode = size.querySelector(":scope > weight"); sizes.push({ id: size?.getAttribute("type"), name: size?.getAttribute("name"), code: size?.getAttribute("code"), codeProducer: size?.getAttribute("code_producer"), codeExtern: size?.getAttribute("code_extern"), amount: size?.getAttribute("amount") !== null ? +size.getAttribute("amount") : undefined, amount_mo: size?.getAttribute("amount_mo") !== null ? +size.getAttribute("amount_mo") : undefined, amount_mw: size?.getAttribute("amount_mw") !== null ? +size.getAttribute("amount_mw") : undefined, amount_mp: size?.getAttribute("amount_mp") !== null ? +size.getAttribute("amount_mp") : undefined, weight: weightNode?.getAttribute("g") !== null ? +weightNode.getAttribute("g") : undefined, availability: { visible: availabilityNode?.getAttribute("visible") === "y" ? true : false, description: availabilityNode?.getAttribute("status_description"), status: availabilityNode?.getAttribute("status"), icon: availabilityNode?.getAttribute("status_gfx"), deliveryDate: shippingTimeNode?.getAttribute("today") === "true" ? `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate().toString().padStart(2, 0)}` : availabilityNode?.getAttribute("delivery_date"), minimumStockOfProduct: availabilityNode?.getAttribute("minimum_stock_of_product"), // descriptionTel: "", // iconTel: "", }, shipping: { today: shippingTimeNode?.getAttribute("today") === "true" ? true : false, }, price: this.xmlGetPriceFromNode(sizePriceNode), // amountWholesale: "", }) }) // VERSIONS const versions = []; versionsNode.querySelectorAll(":scope > version").forEach(ver=>{ versions.push({ id: ver?.getAttribute("id") !== null ? +ver?.getAttribute("id") : undefined, name: ver?.getAttribute("name"), icon: ver?.getAttribute("gfx"), iconSecond: ver?.getAttribute("gfx_second"), iconSmall: ver?.getAttribute("gfx_small"), iconSmallSecond: ver?.getAttribute("gfx_small_second"), productIcon: ver?.getAttribute("icon"), productIconSecond: ver?.getAttribute("icon_second"), productIconSmall: ver?.getAttribute("icon_small"), productIconSmallSecond: ver?.getAttribute("icon_small_second"), link: ver?.getAttribute("link"), // parameterValues: [], }); }); // DATA const typeParts = prod?.getAttribute("product_type").split("_"); data.push({ id: prod?.getAttribute("id") !== null ? +prod?.getAttribute("id") : undefined, type: typeParts[1] === "item" ? typeParts[0] : typeParts[1], code: prod?.getAttribute("code"), name: prod.querySelector(":scope > name")?.textContent, versionName: prod.querySelector(":scope > versions")?.getAttribute("name"), description: prod.querySelector(":scope > description")?.textContent, // longDescription: prod.querySelector("vlongdescription")?.textContent, // longDescriptionSections: "", link: prod?.getAttribute("link"), zones, icon: prod.querySelector(":scope > icon")?.textContent, iconSecond: prod.querySelector(":scope > icon_second")?.textContent, iconSmall: prod.querySelector(":scope > icon_small")?.textContent, iconSmallSecond: prod.querySelector(":scope > icon_small_second")?.textContent, price: this.xmlGetPriceFromNode(priceNode), unit: { id: sizesNode?.getAttribute("unit_id"), name: sizesNode?.getAttribute("unit"), singular: sizesNode?.getAttribute("unit_single"), plural: sizesNode?.getAttribute("unit_plural"), fraction: sizesNode?.getAttribute("unit_fraction"), sellBy: sizesNode?.getAttribute("unit_sellby") !== null ? +sizesNode?.getAttribute("unit_sellby") : undefined, precision: sizesNode?.getAttribute("unit_precision") !== null ? +sizesNode?.getAttribute("unit_precision") : undefined, unitConvertedFormat: sizesNode?.getAttribute("unit_converted_format"), }, producer: { id: firmNode?.getAttribute("id") !== null ? +firmNode?.getAttribute("id") : undefined, name: firmNode?.getAttribute("name"), link: firmNode?.getAttribute("productslink"), searchIcons: { icon: firmNode?.getAttribute("icon"), }, // projectorIcons: {}, }, category: { id: categoryNode?.getAttribute("id") !== null ? +categoryNode?.getAttribute("id") : undefined, name: categoryNode?.getAttribute("name"), link: categoryNode?.getAttribute("productslink") }, group: { id: versionsNode?.getAttribute("id") !== null ? +versionsNode?.getAttribute("id") : undefined, name: versionsNode?.getAttribute("name"), displayAll: versionsNode?.getAttribute("display_all") === "true" ? true : false, link: versionsNode?.getAttribute("link"), versions, groupParameters: this.xmlGetTraitsFromNode(versionsNode.querySelector(":scope > groupParameters")) }, opinion: { rating: commentsNode?.getAttribute("avg") !== null ? +commentsNode?.getAttribute("avg") : undefined, count: commentsNode?.getAttribute("count") !== null ? +commentsNode?.getAttribute("count") : undefined, link: commentsNode?.getAttribute("link") }, enclosuresImages, // enclosuresAttachments: [], series: { id: seriesNode?.getAttribute("id") !== null ? +seriesNode?.getAttribute("id") : undefined, name: seriesNode?.getAttribute("name"), link: seriesNode?.getAttribute("link") }, awardedParameters: this.xmlGetTraitsFromNode(prod.querySelector(":scope > traits")), // parameteresWithContext: [], sizes, points: priceNode?.getAttribute("points") !== null ? +priceNode?.getAttribute("points") : undefined, pointsReceive: priceNode?.getAttribute("points_recive") !== null ? +priceNode?.getAttribute("points_recive") : undefined, // subscription: {}, // bundled: [], // responsibleEntity: {}, }) }) return data; }catch(err){ console.error(err); return null; } } // ======================================================== // 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}`); // funkcja wykonująca się po ramce rekomendacji if(typeof this.options?.callbackFn === "function") this.options?.callbackFn(this); }catch(err){ console.error(idmHotspotTextObject["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"], err); } } afterInitOnce(){ if(this.initialized) return; // WCZYTANIE PONOWNIE DLA KOSZYKA if(typeof app_shop.fn?.basket?.reloadForm === "function" && this.hotspotEl.closest("#content")){ app_shop.run(()=>{ this.init(); console.log("test", this.hotspotEl) }, "all", "#Basket", true) } this.initialized = true; } initExternalFunctions(){ this.addToFavFn = app_shop.fn?.shoppingList?.addProductToList; } /** * Pobiera dane, wypełnia markup i inicjuje Swipera. */ async fillHotspot(){ // Zdefiniowanie funkcji do dodawania do ulubionych try{ if(!this.products){ if((!this?.query?.graphFn || !this?.query?.string) && !this.source?.link) this.setQueryData(); // pobranie danych o produktach await this.getHotspotData(); if(!this.products) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]); } // Skeleton this.hotspotEl.querySelector(".idm_hotspot__skeleton")?.remove(); this.hotspotEl.classList.remove("--hotspot-loading"); this.initExternalFunctions(); // Wstawienie markupa na strone if(this.hotspotEl.querySelector(".products.hotspot__products")) this.hotspotEl.querySelector(".products.hotspot__products").insertAdjacentHTML("beforeend", this.markup()); else if(this.hotspotEl.querySelector(".hotspot")){ this.hotspotEl.querySelector(".hotspot")?.insertAdjacentHTML("beforeend", this.markupHotspotSwiperContainer(this.markup())); } else{ throw new Error("Nie udało się wstawić produktów! Zła struktura HTML") } // init swiper + add to basket await this.afterInit(); this.afterInitOnce(); }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){ // Wywołanie swipera const selectedSwiper = new HotspotSlider({ selector: `#${this.id} .swiper`, hotspotName: `${this.id}`, options: this.options.swiper, }); await selectedSwiper.init(); if(this.options.swiperScrollbar) new IdmSwiperProgress(selectedSwiper, `#${this.id} .swiper`); } }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"); 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); } } // Dodaj do ulubionych if(this.options?.addToFavorites && typeof this.addToFavFn === "function") prodEl.querySelector(".product__favorite")?.addEventListener("click", this.handleAddToFav); // Porównanie if(this.options?.addToCompare) prodEl.querySelector(".idm-products-banner__compare-btn")?.addEventListener("click", this.handleAddToCompare); // Hover drugie zdjęcie if(this.options?.showSecondImage){ const prodIconEl = prodEl.querySelector(".product__icon"); if(prodIconEl.querySelector(".product__image.--second")){ prodIconEl?.addEventListener("mouseover", this.handleShowSecondImage); prodIconEl?.addEventListener("mouseleave", this.handleHideSecondImage); } } // Wybór wersji if(this.options?.selectVersion) prodEl.querySelector(".product__versions")?.addEventListener("click", this.handleSelectVersion); // Wybór rozmiaru if(this.options?.selectSize) prodEl.querySelector(".product__select_sizes")?.addEventListener("click", this.handleSelectSize); } /** * 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). */ init(){ const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); const dev = urlParams.get('dev') if(this.options?.devMode && dev !== "true") return console.error(`Brak włączonego devMode. Ramka ${this.id} nie mogła zostać utworzona!`); // Opcje swipera if(typeof this?.options?.swiper === "boolean") this.options.swiper = IdmHotspot.idmDefaultSwiperConfig; // Wstawienie kontenera if(!this.hotspotEl || !document.contains(this.hotspotEl)) this.initHotspotContainer(); else if(!this.hotspotEl.querySelector(".hotspot")) this.hotspotEl.innerHTML = this.markupHotspotInnerDiv(); else if(!this.hotspotEl.querySelector(".products__wrapper")) this.hotspotEl.querySelector(".hotspot").insertAdjacentHTML("beforeend", this.markupHotspotSwiperContainer()) // Ustawienie wszystkich zmiennych CSS this.cssSetAll(); if(this.options?.lazy) this.handleObserveHotspotOnce(); else this.fillHotspot(); } } /* ============================================================== SWIPER PASEK ============================================================== */ class IdmSwiperProgress { constructor(swiper, selector) { this.swiper = swiper?.slider?.slider ?? swiper?.slider ?? swiper; this.selector = selector; this.scrollbarEl = null; this.progressEl = null; this.isDragging = false; this.init(); } init() { const el = document.querySelector(this.selector); if (!el || el.querySelector(".idm-scrollbar")) return; el.insertAdjacentHTML( "beforeend", `
` ); this.scrollbarEl = el.querySelector(".idm-scrollbar"); this.progressEl = this.scrollbarEl.querySelector(".idm-progress"); this.updateBarWidth(); this.addDragTracking(); this.swiper.on("progress", () => this.updateProgress()); this.swiper.on("breakpoint", () => {this.updateBarWidth()}); } updateBarWidth() { const { slidesPerGroup, slidesPerView } = this.swiper.params; const totalSlides = this.swiper.slides.length; const progressWidth = 100 / (((totalSlides - slidesPerView) / slidesPerGroup) + 1); this.progressEl.style.width = `${progressWidth}%`; if (progressWidth >= 100 || progressWidth <= 0) this.scrollbarEl.style.display = "none"; else this.scrollbarEl.style.display = ""; } updateProgress() { const progress = this.swiper.progress; const { slidesPerGroup, slidesPerView } = this.swiper.params; const totalSlides = this.swiper.slides.length; const progressWidth = 100 / (((totalSlides - slidesPerView) / slidesPerGroup) + 1); const newLeft = (100 - progressWidth) * progress; this.progressEl.style.left = `${Math.min( 100 - progressWidth, Math.max(0, newLeft) )}%`; } addDragTracking() { const handle = this.progressEl; let grabOffset = 0; let scrollbarWidth = 0; let handleWidth = 0; const startDrag = (e) => { this.isDragging = true; this.scrollbarEl.classList.add("--drag-start"); const rect = this.scrollbarEl.getBoundingClientRect(); const handleRect = handle.getBoundingClientRect(); scrollbarWidth = rect.width; handleWidth = handleRect.width; const clientX = e.touches ? e.touches[0].clientX : e.clientX; grabOffset = clientX - handleRect.left; document.addEventListener("mousemove", handleDrag); document.addEventListener("mouseup", stopDrag); document.addEventListener("touchmove", handleDrag); document.addEventListener("touchend", stopDrag); }; const handleDrag = (e) => { if (!this.isDragging) return; const clientX = e.touches ? e.touches[0].clientX : e.clientX; const rect = this.scrollbarEl.getBoundingClientRect(); let newLeftPx = clientX - rect.left - grabOffset; const maxLeft = scrollbarWidth - handleWidth; newLeftPx = Math.max(0, Math.min(maxLeft, newLeftPx)); const progress = newLeftPx / maxLeft; this.swiper.setProgress(progress, 0); }; const stopDrag = () => { if (!this.isDragging) return; this.isDragging = false; document.removeEventListener("mousemove", handleDrag); document.removeEventListener("mouseup", stopDrag); document.removeEventListener("touchmove", handleDrag); document.removeEventListener("touchend", stopDrag); this.scrollbarEl.classList.remove("--drag-start"); this.swiper.slideReset(400); }; handle.addEventListener("mousedown", startDrag); handle.addEventListener("touchstart", startDrag); } } // ======================================================== // TOOLTIP // ======================================================== function idmShowTooltip(tooltipEl){ const tooltipContentEl = tooltipEl.querySelector(".idm_tooltip__content"); if(!tooltipContentEl) return; tooltipContentEl.classList.add("--visible"); // Logika pokazywania się i chowania tooltipa let timeoutVar; function onMouseLeave() { timeoutVar = idmHideTooltipTimer(tooltipEl); } function onMouseEnter() { clearTimeout(timeoutVar); } function onScroll() { idmHideTooltip(tooltipEl); } // Store references for later removal tooltipEl._onMouseLeave = onMouseLeave; tooltipEl._onMouseEnter = onMouseEnter; tooltipEl._onScroll = onScroll; tooltipEl.addEventListener("mouseleave", onMouseLeave); tooltipEl.addEventListener("mouseenter", onMouseEnter); document.addEventListener("scroll", onScroll); } function idmHideTooltipTimer(tooltipEl){ return setTimeout(() => idmHideTooltip(tooltipEl), 1500); } function idmHideTooltip(tooltipEl){ const tooltipContentEl = tooltipEl.querySelector(".idm_tooltip__content"); if (!tooltipContentEl) return; tooltipContentEl.classList.remove("--visible"); tooltipEl.removeEventListener("mouseleave", tooltipEl._onMouseLeave); tooltipEl.removeEventListener("mouseenter", tooltipEl._onMouseEnter); document.removeEventListener("scroll", tooltipEl._onScroll); delete tooltipEl._onMouseLeave; delete tooltipEl._onMouseEnter; delete tooltipEl._onScroll; } document.addEventListener("DOMContentLoaded", ()=>{ document.body.addEventListener("click", e=>{ const tooltipEl = e.target.closest(".idm_tooltip"); if(!e.target.closest(".idm_tooltip__info_icon") || !tooltipEl) return; e.preventDefault(); idmShowTooltip(tooltipEl); }); }); // new IdmHotspot({ // id: "idmTestHotspot1", // title: "tescik", // products: [] // Tablica produktów // placement: { // selector: "#content", // insert: "beforeend", // }, // source: { // productsMenu: 1649, // producersId: [], // seriesId: [], // parametersId: [], // priceRange: { // from: 0, // to: 150, // } // } // options: { // lazy: true, // addToBasket: "range", // swiper: true, // } // }); async function idmPrepareHotspotObject(selectedContainerEl){ selectedContainerEl.classList.add("--init"); const source = {}; if(selectedContainerEl.dataset?.link) source.link = selectedContainerEl.dataset.link; else if(selectedContainerEl.dataset?.hotspotsType) source.hotspotsType = selectedContainerEl.dataset.hotspotsType; else { if(selectedContainerEl.dataset?.productsId) source.productsId = selectedContainerEl.dataset.productsId.split(","); if(selectedContainerEl.dataset?.productsMenu) source.productsMenu = selectedContainerEl.dataset.productsMenu; if(selectedContainerEl.dataset?.producersId) source.producersId = selectedContainerEl.dataset.producersId; if(selectedContainerEl.dataset?.seriesId) source.seriesId = selectedContainerEl.dataset.seriesId; if(selectedContainerEl.dataset?.parametersId) source.seriesId = selectedContainerEl.dataset.parametersId; if(selectedContainerEl.dataset?.priceFrom && selectedContainerEl.dataset?.priceTo) source.priceRange = {from: +selectedContainerEl.dataset.priceFrom, to: +selectedContainerEl.dataset.priceTo}; } if(Object.keys(source).length === 0){ 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.addEventListener("DOMContentLoaded", ()=>{ document.querySelectorAll(".hotspot__wrapper.idm__hotspot:not(.--init)").forEach(currentHotspot=>{ idmPrepareHotspotObject(currentHotspot); }); })