Files
hotspots/klasa.js

2155 lines
80 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
///////////////////////////////////////////////////////////
// TEXT
// LITERAŁY
const idmHotspotTextObject = {
["Kod rabatowy"]: <iai:variable vid="Kod rabatowy"/>,
["Okazja"]: <iai:variable vid="Okazja"/>,
["Promocja"]: <iai:variable vid="Promocja"/>,
["Bestseller"]: <iai:variable vid="Bestseller"/>,
["Nowość"]: <iai:variable vid="Nowość"/>,
["Ilość"]: <iai:variable vid="Ilość"/>,
["Porównaj"]: <iai:variable vid="Porównaj"/>,
["Dodaj do ulubionych"]: <iai:variable vid="Dodaj do ulubionych"/>,
["Najniższa cena"]: <iai:variable vid="Najniższa cena"/>,
["Najniższa cena z 30 dni przed obniżką"]: <iai:variable vid="Najniższa cena z 30 dni przed obniżką"/>,
["Zwiększ ilość"]: <iai:variable vid="Zwiększ ilość"/>,
["Zmniejsz ilość"]: <iai:variable vid="Zmniejsz ilość"/>,
["Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki"]: <iai:variable vid="Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki"/>,
["Cena regularna"]: <iai:variable vid="Cena regularna"/>,
["Cena bez kodu"]: <iai:variable vid="Cena bez kodu"/>,
["Cena nadchodząca od"]: <iai:variable vid="Cena nadchodząca od"/>,
["Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę"]: <iai:variable vid="Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę"/>,
["Nie znaleziono produktów"]: <iai:variable vid="Nie znaleziono produktów"/>,
["Błąd przy pobieraniu danych"]: <iai:variable vid="Błąd przy pobieraniu danych"/>,
["Kliknij, by przejść do formularza kontaktu"]: <iai:variable vid="Kliknij, by przejść do formularza kontaktu"/>,
["Cena na telefon"]: <iai:variable vid="Cena na telefon"/>,
["Dodany"]: <iai:variable vid="Dodany"/>,
["Wystąpił błąd"]: <iai:variable vid="Wystąpił błąd"/>,
["Do koszyka"]: <iai:variable vid="Do koszyka"/>,
["Maksymalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]: <iai:variable vid="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:"]: <iai:variable vid="Minimalna liczba sztuk tego towaru które możesz dodać do koszyka to:"/>,
["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"]: <iai:variable vid="Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"/>,
["Nie znaleziono kontenera"]: <iai:variable vid="Nie znaleziono kontenera"/>,
["Nie znaleziono metody graphql"]: <iai:variable vid="Nie znaleziono metody graphql"/>,
["Drugie Zdjęcie"]: <iai:variable vid="Drugie Zdjęcie"/>,
["Zobacz więcej"]: <iai:variable vid="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 = `<span class="label --code --omnibus">${omnibusDetailsTxt?.['Kod rabatowy']}</span>`;
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 = `<span class="label --bargain --omnibus">${omnibusDetailsTxt?.['Okazja']}</span>`;
}
// label promocja
if (Object.keys(activeLabel)?.length === 0) {
activeLabel.bargain = `<span class="label --promo --omnibus">${omnibusDetailsTxt?.['Promocja']}</span>`;
}
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: `<span class="omnibus_price__text">${omnibusDetailsTxt['Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki']}: </span><del class="omnibus_price__value">${omnibusPrice}</del><span class="price_percent">${omnibusPercent}</span>`,
};
const max = (maxPrice) ? {
max: {
price: maxPrice,
visible: true,
percent: `-${sizeData.price.youSavePercent}%`,
html: `<span class="omnibus_label">${omnibusDetailsTxt['Cena regularna']}: </span>
<del>${maxPrice}</del><span class="price_percent">-${sizeData.price.youSavePercent}%</span>`,
},
} : {};
const beforeRebate = (beforeRebatePrice) ? {
beforeRebate: {
price: beforeRebatePrice,
visible: !!classes.add.includes('--omnibus-code'),
percent: `-${sizeData.price.beforeRebateDetails?.youSavePercent}%`,
html: `<span class="omnibus_label">${omnibusDetailsTxt['Cena bez kodu']}: </span>
<del>${beforeRebatePrice}</del>
<span class="price_percent">-${sizeData.price.beforeRebateDetails?.youSavePercent}%</span>`,
},
} : {};
const newPriceEffectiveUntil = (newDate) ? {
newPriceEffectiveUntil: {
date: newDate,
price: maxPrice,
visible: !!classes.add.includes('--omnibus-new-price'),
html: `<span class="omnibus_label">${omnibusDetailsTxt['Cena nadchodząca od']} </span>
<span class="new_price__date">${newDate}: </span>
<span class="new_price__value">${maxPrice}</span>`,
},
} : {};
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;
}
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){
// markup pojedynczego produktu
let singleMarkup = "";
singleMarkup += `
<div class="product hotspot__product swiper-slide d-flex flex-column ${prod.price.price[this.priceType].value === 0 ? "--phone" : ""}" data-id="${prod.id}" data-type="${prod.type}">
${this.markupProductInnerHTML(prod)}
</div>`;
return singleMarkup;
}
markupProductInnerHTML(prod){
// IDM DO POPRAWKI
const prodExchangedData = app_shop?.fn?.getOmnibusDetails?.({productData: prod}) || app_shop.fn?.idmGetOmnibusDetails({productData: prod});
return `<div class="product__yousave --hidden">
<span class="product__yousave --label"></span>
<span class="product__yousave --value"></span>
</div>
<div class="product__additional">
${this.markupAdditional(prod)}
</div>
<a class="product__icon d-flex justify-content-center align-items-center" tabindex="-1" href="${prod.link}">
${this.markupImage(prod)}
<strong class="label_icons">
${this.markupLabel(prod)}
</strong>
</a>
<div class="product__content_wrapper">
${this.markupVersions(prod)}
${this.markupOpinions(prod)}
<a class="product__name" tabindex="0" href="${prod.link}" title="${prod.name}">${prod.name}</a>
<div class="product__prices mb-auto ${prodExchangedData?.classes?.add?.reduce((acc,val) => acc + " " + val,"")}">
${this.markupPrice(prod, prodExchangedData)}
</div>
${this.markupAddToBasket(prod)}
</div>`;
}
markupAdditional(prod){
return `
${this.markupCompare(prod)}
${this.markupFavorite(prod)}
`;
}
markupCompare(prod){
if(!this.options?.addToCompare) return "";
return `<button class="idm-products-banner__compare-btn" data-compare-id="${prod.id}" aria-label="${idmHotspotTextObject["Porównaj"]}">
</button>`
}
markupFavorite(prod){
if(!this.options?.addToFavorites || typeof this.addToFavFn !== "function" || this.hotspotEl.closest(".modal")) return "";
return `<span
data-href="#add_favorite"
class="product__favorite" role="button" tabindex="0"
data-product-id="${prod.id}"
data-product-size="${prod.sizes?.[0]?.id || 'uniw'}" aria-label="${idmHotspotTextObject["Dodaj do ulubionych"]}">
</span>`;
}
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 + `<a class="product__version_more" href="${prod.link}" aria-label="${idmHotspotTextObject["Zobacz więcej"]}">
// +${(prod.group.versions.length - MAX_VERSION_AMOUNT) + 1}
// </a>`;
return acc + `<a class="product__version_single ${prod.id === val.id ? "--active" : ""}" href="${val.link}" data-product-id="${val.id}"><img class="product__version_img" src="${val.icon}" alt="${val.name}"/></a>`;
},"");
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 `<div class="product__versions">
${productVersionsMarkup}
<a class="product__version_more" href="${prod.link}" aria-label="${idmHotspotTextObject["Zobacz więcej"]}">
+
<span class="--desktop-more">${desktopMoreLength > 0 ? desktopMoreLength : ""}</span>
<span class="--tablet-more">${tabletMoreLength > 0 ? tabletMoreLength : ""}</span>
<span class="--mobile-more">${mobileMoreLength > 0 ? mobileMoreLength : ""}</span>
</a>
</div>`
}
markupImage(prod){
let markup = "";
if(prod.iconSmallSecond && prod.iconSecond) markup +=`<picture>
<source media="(min-width: 421px)" type="image/webp" srcset="${prod.icon}"/>
<source media="(min-width: 421px)" type="image/jpeg" srcset="${prod.iconSecond}"/>
<source media="(max-width: 420px)" type="image/webp" srcset="${prod.iconSmall}"/>
<source media="(max-width: 420px)" type="image/jpeg" srcset="${prod.iconSmallSecond}"/>
<img src="${prod.iconSecond}" loading="lazy" alt="${prod.name}" class="product__image --first">
</picture>`;
else if(prod?.iconSmall) markup += `<picture>
<source media="(min-width: 421px)" srcset="${prod.icon}"/>
<source media="(max-width: 420px)" srcset="${prod.iconSmall}"/>
<img src="${prod.iconSecond}" loading="lazy" alt="${prod.name}" class="product__image --first">
</picture>`;
else markup += `<img src="${prod.icon}" loading="lazy" alt="${prod.name}" class="product__image --first">`;
if(this.options?.showSecondImage && prod.enclosuresImages?.[1]?.url) markup += `<img loading="lazy" src="${prod.enclosuresImages[1].url}" alt="${prod.name} - ${idmHotspotTextObject["Drugie Zdjęcie"]}" class="product__image --second"/>`;
return markup;
}
markupLabel(prod){
let labelMarkup = ""
// labele zones
if(prod.zones?.find(zone => zone ==="bestseller")) labelMarkup += `<span class="label --bestseller --omnibus">${idmHotspotTextObject["Bestseller"]}</span>`;
if(prod.zones?.find(zone => zone ==="news")) labelMarkup += `<span class="label --news --omnibus">${idmHotspotTextObject["Nowość"]}</span>`;
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 += `<span class="label --code --omnibus">${idmHotspotTextObject["Kod rabatowy"]}</span>`;
}else if ((!higher || newDate) && !activeLabel?.rebateCodeActive) {
labelMarkup += `<span class="label --bargain --omnibus">${idmHotspotTextObject["Okazja"]}</span>`;
}else {
labelMarkup += `<span class="label --promo --omnibus">${idmHotspotTextObject["Promocja"]}</span>`;
}
}
return labelMarkup;
}
markupOpinions(prod){
if(!this.options?.showOpinions) return "";
return `<div class="product__opinions">
<div class="product__opinions_stars">
<i class="icon-star ${prod.opinion?.rating > 0.5 ? "--active" : ""}"></i>
<i class="icon-star ${prod.opinion?.rating > 1.5 ? "--active" : ""}"></i>
<i class="icon-star ${prod.opinion?.rating > 2.5 ? "--active" : ""}"></i>
<i class="icon-star ${prod.opinion?.rating > 3.5 ? "--active" : ""}"></i>
<i class="icon-star ${prod.opinion?.rating > 4.5 ? "--active" : ""}"></i>
</div>
<span class="product__opinions_score">${prod.opinion.rating.toFixed(2)} / 5.00</span>
</div>`
}
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 `
<strong class="price --normal --main ${price.value === 0 ? "--hidden" : ""}">
<span class="price__sub">${price.formatted}</span>
<span class="price_sellby">
<span class="price_sellby__sep">/</span>
<span class="price_sellby__sellby" data-sellby="${unit?.sellBy}">${unit?.sellBy}</span>
<span class="price_sellby__unit">${unit?.sellBy > 1 ? unit?.plural : unit?.singular}</span>
</span>
${convertedPrice && prod.unit?.unitConvertedFormat ? `<span class="price --convert">(${convertedPrice} / ${prod.unit?.unitConvertedFormat})</span>` : ""}
</strong>
${pointsPrice ? `<span class="price --points">${pointsPrice} pkt.</span>` : ""}
${price.value === 0 ? `<a class="price --phone" href="/contact.php" tabindex="-1" title="${idmHotspotTextObject["Kliknij, by przejść do formularza kontaktu"]}">${idmHotspotTextObject["Cena na telefon"]}</a>` : ""}
${prodExchangedData?.beforeRebate?.visible ? `<span class="price --before-rebate">${prodExchangedData?.beforeRebate?.html}</span>` : ""}
${prodExchangedData?.newPriceEffectiveUntil?.visible ? `<span class="price --new-price new_price">${prodExchangedData?.newPriceEffectiveUntil?.html}</span>` : ""}
${this.markupOmnibus(prodExchangedData?.omnibus)}
${prodExchangedData?.max?.visible ? `<span class="price --max">${prodExchangedData?.max?.html}</span>` : ""}
`;
}
/**
* 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 `<span class="price --omnibus omnibus_price">${omnibus?.html}</span>`;
else return `
<span class="price --omnibus omnibus_price">
<span class="omnibus_price__text">${idmHotspotTextObject["Najniższa cena"]}:</span>
<del class="omnibus_price__value">${omnibus.price}</del>
<span class="price_percent">${omnibus.percent}</span>
<span class="idm_tooltip">
<span class="idm_tooltip__info_icon"></span>
<p class="idm_tooltip__content --one-line">${idmHotspotTextObject["Najniższa cena z 30 dni przed obniżką"]}</p>
</span>
</span>
`;
}
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 = `<a class="btn --solid --medium add_to_basket__link" href="${prod.href}">Zobacz produkt</a>`;
else if(this.options.addToBasket === "range") // +-
markup = `<form class="add_to_basket --range" action="/basketchange.php" type="post">
<input name="mode" type="hidden" value="1">
<input name="product" type="hidden" value="${prod.id}">
${this.markupSize(prod)}
<div class="idm-products-banner__qty"
data-sell-by="${prod.unit?.sellBy}"
data-precision="${prod.unit?.precision}"
data-max="${prod.sizes[0].amount === -1 ? 999999 : prod.sizes[0].amount}"
>
<button type="button" class="idm-products-banner__qty-decrease" aria-label="${idmHotspotTextObject["Zmniejsz ilość"]}"></button>
<input type="number"
name="number"
class="idm-products-banner__qty-input"
value="${prod.unit?.sellBy}"
step="${prod.unit?.sellBy}"
min="${prod.unit?.sellBy}"
max="${prod.sizes[0].amount === -1 ? 999999 : prod.sizes[0].amount}"
aria-label="${idmHotspotTextObject["Ilość"]}"
>
<button type="button" class="idm-products-banner__qty-increase" aria-label="${idmHotspotTextObject["Zwiększ ilość"]}">+</button>
</div>
<button type="submit" class="btn --solid --medium add_to_basket__button" tabindex="0" data-success="${idmHotspotTextObject["Dodany"]}" data-error="${idmHotspotTextObject["Wystąpił błąd"]}" data-text="${idmHotspotTextObject["Do koszyka"]}">
<span>${idmHotspotTextObject["Do koszyka"]}</span>
</button>
</form>`;
else // Zwykłe dodanie do koszyka
markup = `
<form class="add_to_basket" action="/basketchange.php" type="post">
<input name="mode" type="hidden" value="1">
<input name="product" type="hidden" value="${prod.id}">
${this.markupSize(prod)}
<input name="number" type="hidden" value="${prod.unit?.sellBy}">
<button type="submit" class="btn --solid --medium add_to_basket__button" tabindex="0" data-success="${idmHotspotTextObject["Dodany"]}" data-error="${idmHotspotTextObject["Wystąpił błąd"]}" data-text="${idmHotspotTextObject["Do koszyka"]}">
<span>${idmHotspotTextObject["Do koszyka"]}</span>
</button>
</form>`;
return markup;
}
markupSize(prod){
// return `<input name="size" type="hidden" value="${prod.sizes[0].id}">`;
if(!this.options?.selectSize || prod.sizes.length === 1) return `<input name="size" type="hidden" value="${prod.sizes[0].id}">`;
const sizesName = `${this.id}-${prod.id}`;
let selectedDefault = false;
return `
<input name="size" type="hidden" value="${prod.sizes[0].id}">
<div class="product__select_sizes">
${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 + `
<label class="product__size_select" for="${inputId}">
<input type="radio" name="${sizesName}" id="${inputId}" ${isSelected ? "checked" : ""} ${val?.amount === 0 ? "disabled" : ""} data-value="${val.id}"/>
<span>${val.name}</span>
</label>
`
}, "")}
</div>
`;
}
markupHotspotContainer(){
return `<section id="${this.id}" class="hotspot__wrapper idm__hotspot --init --hotspot-loading ${this?.classes}">
${this.markupHotspotInnerDiv()}
</section>`;
}
markupHotspotInnerDiv(){
return `<div class="hotspot --initialized">
${this?.title ? `
<h3 class="hotspot__name headline__wrapper">
<span class="headline"><span class="headline__name" aria-label="${this.title}">${this.title}</span></span>
</h3>
` : ""}
${this.markupHotspotSwiperContainer()}
</div>`
}
markupHotspotSwiperContainer(productsHTML=""){
return `<div class="products__wrapper swiper">
<div class="products hotspot__products swiper-wrapper">
${productsHTML || this.markupSkeleton()}
</div>
</div>
<div class="swiper-button-prev --rounded --edge idm-button-prev"><i class="icon-angle-left"></i></div>
<div class="swiper-button-next --rounded --edge idm-button-next"><i class="icon-angle-right"></i></div>
<div class="swiper-pagination"></div>`;
}
// SKELETONS MARKUPS
markupSkeleton(){
let skeletonMarkup = "";
// Tworzenie skeletonow
for(let i = 0; i <= this.options.limit; i++){
skeletonMarkup += this.markupSingleSkeleton();
}
return `
<div class="idm_hotspot__skeleton">
${skeletonMarkup}
</div>
`;
}
markupSingleSkeleton(){
return `
<div class="idm_hotspot__skeleton_product">
<div class="idm_hotspot__skeleton_img idm-loading"></div>
${this.options.showOpinions ? `<div class="idm_hotspot__skeleton_opinions idm-loading"></div>` : ""}
<div class="idm_hotspot__skeleton_name idm-loading"></div>
<div class="idm_hotspot__skeleton_price idm-loading"></div>
${this.options.addToBasket ? `<div class="idm_hotspot__skeleton_btn idm-loading"></div>` : ""}
</div>
`
}
// ========================================================
// 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 = `<span>${buttonEl.dataset.success}</span>`;
setTimeout(()=>{
buttonEl.innerHTML = `<span>${buttonEl.dataset.text}</span>`;
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 = `<span>${buttonEl.dataset.error}</span>`;
buttonEl.classList.add("--error")
setTimeout(()=>{
buttonEl.classList.remove("--error")
buttonEl.innerHTML = `<span>${buttonEl.dataset.text}</span>`;
}, 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", `
<style>
${this.cssVersionColumnStyle()}
${this.cssSkeletonStyle()}
</style>`)
}
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", `<h3 class="hotspot__name headline__wrapper">
<span class="headline"><span class="headline__name" aria-label="${this.title}">${this.title}</span></span>
</h3>`);
}
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();
}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",
`<div class="idm-scrollbar swiper-scrollbar"><div class="idm-progress"></div></div>`
);
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.querySelectorAll(".hotspot__wrapper.idm__hotspot").forEach(currentHotspot=>{
idmPrepareHotspotObject(currentHotspot);
});