Files
hotspots/klasa.js

1415 lines
53 KiB
JavaScript
Raw 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 = {
[<iai:variable vid="Kod rabatowy"/>]: <iai:variable vid="Kod rabatowy"/>,
[<iai:variable vid="Okazja"/>]: <iai:variable vid="Okazja"/>,
[<iai:variable vid="Promocja"/>]: <iai:variable vid="Promocja"/>,
[<iai:variable vid="Bestseller"/>]: <iai:variable vid="Bestseller"/>,
[<iai:variable vid="Nowość"/>]: <iai:variable vid="Nowość"/>,
[<iai:variable vid="Ilość"/>]: <iai:variable vid="Ilość"/>,
[<iai:variable vid="Porównaj"/>]: <iai:variable vid="Porównaj"/>,
[<iai:variable vid="Dodaj do ulubionych"/>]: <iai:variable vid="Dodaj do ulubionych"/>,
[<iai:variable vid="Najniższa cena"/>]: <iai:variable vid="Najniższa cena"/>,
[<iai:variable vid="Najniższa cena z 30 dni przed obniżką"/>]: <iai:variable vid="Najniższa cena z 30 dni przed obniżką"/>,
[<iai:variable vid="Zwiększ ilość"/>]: <iai:variable vid="Zwiększ ilość"/>,
[<iai:variable vid="Zmniejsz ilość"/>]: <iai:variable vid="Zmniejsz ilość"/>,
[<iai:variable vid="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"/>,
[<iai:variable vid="Cena regularna"/>]: <iai:variable vid="Cena regularna"/>,
[<iai:variable vid="Cena bez kodu"/>]: <iai:variable vid="Cena bez kodu"/>,
[<iai:variable vid="Cena nadchodząca od"/>]: <iai:variable vid="Cena nadchodząca od"/>,
[<iai:variable vid="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ę"/>,
[<iai:variable vid="Nie znaleziono produktów"/>]: <iai:variable vid="Nie znaleziono produktów"/>,
[<iai:variable vid="Błąd przy pobieraniu danych"/>]: <iai:variable vid="Błąd przy pobieraniu danych"/>,
[<iai:variable vid="Kliknij, by przejść do formularza kontaktu"/>]: <iai:variable vid="Kliknij, by przejść do formularza kontaktu"/>,
[<iai:variable vid="Cena na telefon"/>]: <iai:variable vid="Cena na telefon"/>,
[<iai:variable vid="Dodany"/>]: <iai:variable vid="Dodany"/>,
[<iai:variable vid="Wystąpił błąd"/>]: <iai:variable vid="Wystąpił błąd"/>,
[<iai:variable vid="Do koszyka"/>]: <iai:variable vid="Do koszyka"/>,
[<iai:variable vid="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:"/>,
[<iai:variable vid="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:"/>,
[<iai:variable vid="Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"/>]: <iai:variable vid="Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"/>,
[<iai:variable vid="Nie znaleziono kontenera"/>]: <iai:variable vid="Nie znaleziono kontenera"/>,
[<iai:variable vid="Nie znaleziono metody graphql"/>]: <iai:variable vid="Nie znaleziono metody graphql"/>,
};
// STRING
// const idmHotspotTextObject = {
// ["Kod rabatowy"]: "Kod rabatowy",
// ["Okazja"]: "Okazja",
// ["Promocja"]: "Promocja",
// ["Bestseller"]: "Bestseller",
// ["Nowość"]: "Nowość",
// ["Ilość"]: "Ilość",
// ["Zwiększ ilość"]: "Zwiększ ilość",
// ["Zmniejsz ilość"]: "Zmniejsz ilość",
// ["Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki"]: "Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki",
// ["Cena regularna"]: "Cena regularna",
// ["Cena bez kodu"]: "Cena bez kodu",
// ["Cena nadchodząca od"]: "Cena nadchodząca od",
// ["Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę"]: "Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę",
// ["Nie znaleziono produktów"]: "Nie znaleziono produktów",
// ["Błąd przy pobieraniu danych"]: "Błąd przy pobieraniu danych",
// ["Kliknij, by przejść do formularza kontaktu"]: "Kliknij, by przejść do formularza kontaktu",
// ["Cena na telefon"]: "Cena na telefon",
// ["Dodany"]: "Dodany",
// ["Wystąpił błąd"]: "Wystąpił błąd",
// ["Do koszyka"]: "Do koszyka",
// ["Maksymalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]: "Maksymalna liczba sztuk tego towaru które możesz dodać do koszyka to:",
// ["Minimalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]: "Minimalna liczba sztuk tego towaru które możesz dodać do koszyka to:",
// ["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"]: "Wystąpił błąd z inicjalizacją. Proszę odśwież stronę",
// ["Nie znaleziono kontenera"]: "Nie znaleziono kontenera",
// ["Nie znaleziono metody graphql"]: "Nie znaleziono metody graphql",
// }
///////////////////////////////////////////////
// GraphQL
// ogolne
const IDM_PRICE_QUERY = `price {
rebateCodeActive
price {
gross {
value
formatted
}
}
omnibusPrice {
gross {
value
formatted
}
}
omnibusPriceDetails {
unit {
gross {
value
formatted
}
}
youSavePercent
omnibusPriceIsHigherThanSellingPrice
newPriceEffectiveUntil {
formatted
}
}
max {
gross {
value
formatted
}
}
unit {
gross {
value
formatted
}
}
unitConvertedPrice {
gross {
value
formatted
}
}
youSavePercent
beforeRebate {
gross {
value
formatted
}
}
beforeRebateDetails {
youSavePercent
unit {
gross {
value
formatted
}
}
}
advancePrice {
gross {
value
formatted
}
}
suggested {
gross {
value
formatted
}
}
rebateNumber {
number
gross {
value
formatted
}
}
}`;
const IDM_PRODUCT_QUERY = `id
type
name
icon
iconSecond
iconSmall
iconSmallSecond
link
zones
producer{
name
}
category{
name
}
sizes{
id
amount
name
${IDM_PRICE_QUERY}
}
group{
id
name
link
versions{
id
name
icon
iconSecond
iconSmall
iconSmallSecond
}
}
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}`;
// 1. products
const IDM_PRODUCTS_GQL = (args) => JSON.stringify({
query: `{
products(${args}){
took
products{
${IDM_PRODUCT_QUERY}
}
}
}`
});
// 2. hotspots
const IDM_HOTSPOTS_GQL = (args) => JSON.stringify({
query: `{
hotspots(${args}){
took
name
url
products{
${IDM_PRODUCT_QUERY}
}
}
}`
});
// 3. single product
const IDM_PRODUCT_GQL = (args) => JSON.stringify({
query: `{
product(${args}){
product{
${IDM_PRODUCT_QUERY}
}
}
}`
});
// ADD TO BASKET
const IDM_HOTSPOT_ADD_TO_BASKET = (t, e, a) => JSON.stringify({
query: `mutation {\n addProductsToBasket(ProductInput: {id: ${t}, size: "${e}", quantity: ${a}}) {\n status\n results {\n status\n error {\n code\n message\n }\n productCode\n productId\n sizeId\n quantity\n quantityAvailable\n }\n clientDetailsInBasket {\n id\n login\n firstname\n lastname\n participationPartnerProgram\n usesVat\n email\n isWholesaler\n isWholesalerOrder\n clientIdUpc\n }\n }\n }`
});
/////////////////////////////////////////
// JS
app_shop.fn.idmGetOmnibusDetails = (options) => {
const {
productData, sizeId, priceType = app_shop.vars.priceType,
} = options || {};
if (!productData) return false;
const sizeData = productData.sizes.find((size) => size.id === sizeId) || productData;
if (!sizeData?.price) return false;
const classes = {
add: [],
remove: ['--omnibus', '--omnibus-short', '--omnibus-code', '--omnibus-code-short', '--omnibus-new-price', '--omnibus-higher'],
};
const activeLabel = {};
const omnibusPrice = sizeData.price?.omnibusPriceDetails?.unit?.[priceType]?.formatted || sizeData.price.omnibusPrice[priceType]?.formatted;
if (!omnibusPrice) {
return {
classes,
};
}
// Omnibus
classes.add.push('--omnibus');
classes.remove = classes.remove.filter((item) => item !== '--omnibus');
const sellBy = productData?.unit?.sellBy;
const unitMaxPrice = sizeData?.price?.unit?.[priceType]?.formatted && sizeData.price.max?.[priceType]?.value ? format_price(parseFloat(sizeData.price.max?.[priceType]?.value) * parseFloat(sellBy), {
mask: app_shop.vars.currency_format,
currency: app_shop.vars?.currency?.symbol,
currency_space: app_shop.vars.currency_space,
currency_before_price: app_shop.vars.currency_before_value,
}) : false;
const maxPrice = unitMaxPrice || sizeData.price.max?.[priceType]?.formatted;
// Skrócona wersja omnibusa
if (!maxPrice || maxPrice === omnibusPrice) {
classes.add.push('--omnibus-short');
classes.remove = classes.remove.filter((item) => item !== '--omnibus-short');
}
// Aktywny kod rabatowy
if (app_shop.vars.omnibus?.rebateCodeActivate && sizeData.price?.rebateCodeActive) {
classes.add.push('--omnibus-code');
activeLabel.rebateCodeActive = `<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 = {
options: {
lazy: true,
devMode: false,
callbackFn: ()=>{},
omnibusTooltip: false,
// switchImage: false,
// POKAZANIE
showOpinions: 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}){
this.id = id || "";
this.title = title || "";
this.classes = classes || "";
this.placement = placement || {};
this.source = source || {};
this.query = query || {};
// this.type = type;
this.products = products || null;
this.hotspotEl = hotspotEl || null;
// Merge defaults
this.options = {
...IdmHotspot.idmDefaultHotspotOptions.options,
...options,
};
//
// this.hotspots = {};
this.priceType = app_shop?.vars?.priceType || "gross";
// bind this do funkcji eventowych
this.handleAddToBasket = this.handleAddToBasket.bind(this);
this.handleQuantityButtonClick = this.handleQuantityButtonClick.bind(this);
this.handleQuantityInputChange = this.handleQuantityInputChange.bind(this);
this.handleAddToCompare = this.handleAddToCompare.bind(this);
this.handleAddToFav = this.handleAddToFav.bind(this);
this.init();
}
// ========================================================
// ASYNC POBIERANIE DANYCH Z GRAPHQL
// ========================================================
/**
* Przygotowuje funkcję i zapytanie GraphQL w zależności od typu danych.
*/
getQueryData({productsID, productsMenu, hotspotsType}){
let graphFn, query;
if(productsID){
graphFn = IDM_PRODUCTS_GQL;
query = `searchInput: {productsId: [${productsID}]}`;
}else if(productsMenu){
graphFn = IDM_PRODUCTS_GQL;
query = `searchInput: {navigation: ${productsMenu}}`;
}else if(hotspotsType){
graphFn = IDM_HOTSPOTS_GQL;
query = `searchInput: {hotspot: ${hotspotsType}, limit: 16}`;
}
return [graphFn, query];
}
/**
* Ustawia dane zapytania GraphQL wewnątrz instancji.
*/
setQueryData(queryObj){
const [graphFn, queryString] = this.getQueryData(queryObj);
this.query.graphFn = graphFn;
this.query.string = queryString;
}
/**
* Pobiera dane hotspotu z API GraphQL.
*/
async getHotspotData(){
if(this.products) return;
try{
const res = await fetch(`/graphql/v1/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: this.query.graphFn(this.query.string),
});
const data = await res.json();
const products = data?.data?.[this.query.graphFn === IDM_HOTSPOTS_GQL ? "hotspots" : "products"]?.products;
if(!products || !products.length) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]);
this.products = products;
this.title = this.title || data?.data?.hotspots?.name || "";
}catch(err){
console.error(idmHotspotTextObject["Błąd przy pobieraniu danych"], err);
return null;
}
}
// ========================================================
// MARKUP TWORZENIE HTML PRODUKTÓW
// ========================================================
/**
* Tworzy markup dla wszystkich produktów w hotspotcie.
*/
markup(){
let markup = "";
this.products.forEach((prod)=>{
markup += this.markupProduct(prod);
})
return markup;
}
/**
* Tworzy markup dla pojedynczego produktu.
*/
markupProduct(prod){
// IDM DO POPRAWKI
const prodExchangedData = app_shop?.fn?.getOmnibusDetails?.({productData: prod}) || app_shop.fn?.idmGetOmnibusDetails({productData: 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}">
<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.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>
</div>`;
return singleMarkup;
}
markupAdditional(prod){
return `
${this.markupCompare(prod)}
${this.markupFavorite(prod)}
`;
}
markupCompare(prod){
if(!this.options?.addToCompare) return "";
return `<div class="idm-products-banner__compare-btn" data-compare-id="${prod.id}">
<button class="idm-products-banner__compare-icon" aria-label="${idmHotspotTextObject[<iai:variable vid="Porównaj"/>]}">
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 20 17" fill="none">
<path d="M19.3972 5.76602C19.3889 5.67435 19.3639 5.58268 19.3222 5.49102L16.5639 0.991016C16.5639 0.991016 16.5389 0.966016 16.5306 0.957682C16.4722 0.874349 16.3889 0.799349 16.2972 0.749349C16.1972 0.699349 16.0889 0.666016 16.0056 0.666016H15.9556C15.9556 0.666016 15.9139 0.666016 15.8889 0.666016C15.8472 0.666016 15.7972 0.666016 15.7472 0.682682L4.08056 4.10768C4.08056 4.10768 4.02223 4.13268 4.01389 4.14102C3.98056 4.15768 3.94723 4.17435 3.91389 4.19102C3.83889 4.24102 3.78056 4.29935 3.73889 4.35768L0.938894 8.87435C0.938894 8.87435 0.922228 8.91602 0.913894 8.93268C0.872228 9.01602 0.847228 9.10768 0.838894 9.19935V9.24935C0.788894 10.216 1.12223 11.1493 1.77223 11.866C2.42223 12.5827 3.31389 13.0077 4.29723 13.0577C5.26389 13.0077 6.15556 12.5827 6.81389 11.866C7.46389 11.1493 7.79723 10.216 7.75556 9.25768C7.75556 9.23268 7.75556 9.21602 7.74723 9.19935C7.73889 9.10768 7.71389 9.01602 7.67223 8.93268L5.40556 5.22435L9.39723 4.04935V15.5493H6.56389V16.991H13.6639V15.5493H10.8389V3.62435L14.3472 2.59102L12.5889 5.44102C12.5889 5.44102 12.5722 5.48268 12.5639 5.49935C12.5222 5.58268 12.4972 5.67435 12.4889 5.75768V5.81602C12.4306 6.78268 12.7722 7.71602 13.4222 8.43268C14.0722 9.14935 14.9639 9.57435 15.9472 9.62435C16.9139 9.57435 17.8056 9.14935 18.4639 8.43268C19.1139 7.71602 19.4472 6.78268 19.4056 5.82435V5.76602H19.3972ZM17.8806 6.54935C17.7889 6.96602 17.5639 7.35768 17.2389 7.64935C16.8806 7.97435 16.4222 8.16602 15.9472 8.19102C15.4722 8.15768 15.0139 7.96602 14.6556 7.64935C14.3389 7.35768 14.1139 6.97435 14.0139 6.54935H17.8806ZM14.4972 5.10768L15.9472 2.74935L17.3972 5.10768H14.4972ZM2.84723 8.53268L4.29723 6.17435L5.74723 8.53268H2.84723ZM6.23056 9.97435C6.13056 10.391 5.91389 10.7827 5.58889 11.0743C5.23056 11.3993 4.77223 11.591 4.29723 11.616C3.82223 11.5827 3.36389 11.391 3.00556 11.0743C2.68889 10.7827 2.46389 10.3993 2.36389 9.97435H6.23056Z" fill="#111111"/>
</svg>
</button>
</div>`
}
markupFavorite(prod){
if(!this.options?.addToFavorites || typeof this.addToFavFn !== "function") 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[<iai:variable vid="Dodaj do ulubionych"/>]}">
</span>`;
}
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}">
</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}">
</picture>`;
else markup += `<img src="${prod.icon}" loading="lazy" alt="${prod.name}">`
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 ? `<span class="price --convert">${convertedPrice}</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[<iai:variable vid="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[<iai:variable vid="Najniższa cena z 30 dni przed obniżką"/>]}</p>
</span>
</span>
`;
}
markupAddToBasket(prod){
let markup = "";
if(!this.options.addToBasket) return markup;
// link do produktu jak nie jest to zwykły produkt
if(prod.type !== "product" || prod.sizes[0].amount === 0) markup = `<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}">
<input name="size" type="hidden" value="${prod.sizes[0].id}">
<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}">
<input name="size" type="hidden" value="${prod.sizes[0].id}">
<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;
}
markupHotspotContainer(){
return `<section id="${this.id}" class="hotspot__wrapper idm__hotspot --init idm-loading ${this?.classes}">
<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>
` : ""}
<div class="products__wrapper swiper">
<div class="products hotspot__products swiper-wrapper">
</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>
</div>
</section>`;
}
// ========================================================
// 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(`${<iai:variable vid="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(`${<iai:variable vid="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);
}
/**
* Walidacja zmian ilości w polu input.
*/
handleQuantityInputChange(e){
if(e.target.value > +e.target.max){
this.rangeMaxAlert(e.target.max)
e.target.value = +e.target.max
}else if(e.target.value < +e.target.min){
this.rangeMinAlert(e.target.min)
e.target.value = +e.target.min;
}
}
/**
* Lazy-load hotspotu wczytuje dane dopiero, gdy element pojawi się w viewportcie.
*/
handleObserveHotspotOnce() {
if (!this.hotspotEl) return;
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.fillHotspot(); // run your callback
obs.disconnect(); // stop observing after first trigger
}
});
}, { root: null, rootMargin: "0px", threshold: 0.1 });
observer.observe(this.hotspotEl);
}
// ========================================================
// FUNKCJE POMOCNICZE
// ========================================================
/**
* Wyświetla alert o maksymalnej ilości produktu.
*/
rangeMaxAlert(max){
Alertek.Error(`${idmHotspotTextObject["Maksymalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]} ${max}`)
}
/**
* Wyświetla alert o minimalnej ilości produktu.
*/
rangeMinAlert(min){
Alertek.Error(`${idmHotspotTextObject["Minimalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]} ${min}`)
}
/**
* Ustawia jednakową wysokość elementów (np. nazw lub cen).
*/
setHeight(options){
const { selector, selectors, container } = options || {}
if ((!selector && !selectors) || !container) return
const containerElement = document.querySelector(container)
if (!containerElement) return
const adjustAllHeights = itemSelector => {
const targets = containerElement.querySelectorAll(itemSelector)
if (!targets.length) return
targets.forEach(el => (el.style.minHeight = ''))
const max = Math.max(...[...targets].map(el => el.offsetHeight || 0))
targets.forEach(el => (el.style.minHeight = `${max}px`))
}
if (selector) adjustAllHeights(selector)
if (selectors?.length) selectors.forEach(adjustAllHeights)
}
// ========================================================
// INICJALIZACJA
// ========================================================
/**
* Wykonywana po pełnej inicjalizacji hotspotu (Swiper, eventy, wysokości).
*/
async afterInit(){
try{
if(!this.hotspotEl) throw new Error("Nie znaleziono elementu");
if(this.title && !this.hotspotEl.querySelector(".hotspot__name.headline__wrapper")){
this.hotspotEl.querySelector(".hotspot.--initialized")?.insertAdjacentHTML("afterbegin", `<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}`);
if(typeof this.options?.callbackFn === "function") this.options?.callbackFn();
// WCZYTANIE PONOWNIE DLA kOSZYKA
if(typeof app_shop.fn?.basket?.reloadForm === "function" && this.hotspotEl.closest("#content")){
app_shop.run(()=>{
this.init();
}, "all", "#Basket", true)
}
}catch(err){
console.error(idmHotspotTextObject["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"], err);
}
}
initExternalFunctions(){
this.addToFavFn = app_shop.fn?.shoppingList?.addProductToList;
}
/**
* Pobiera dane, wypełnia markup i inicjuje Swipera.
*/
async fillHotspot(){
// Zdefiniowanie funkcji do dodawania do ulubionych
this.initExternalFunctions();
try{
if(!this.products){
if(!this?.query?.graphFn || !this?.query?.string) this.setQueryData({
productsID: this?.source?.productsId,
productsMenu: this?.source?.productsMenu,
hotspotsType: this?.source?.hotspotsType
});
// pobranie danych o produktach
await this.getHotspotData();
if(!this.products) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]);
}
// Wstawienie markupa na strone
this.hotspotEl.querySelector(".products.hotspot__products")?.insertAdjacentHTML("beforeend", this.markup());
this.hotspotEl.classList.remove("idm-loading");
// init swiper + add to basket
this.afterInit();
}catch(err){
console.error(idmHotspotTextObject["Wystąpił błąd"], err);
this.hotspotEl.remove();
}
}
/**
* Inicjuje instancję Swipera dla hotspotu.
*/
async initSwiper(){
try{
// swiper || slick
if(this.options?.swiper){
// Opcje swipera
if(typeof this?.options?.swiper === "boolean") this.options.swiper = IdmHotspot.idmDefaultSwiperConfig;
// Wywołanie swipera
const selectedSwiper = new HotspotSlider({
selector: `#${this.id} .swiper`,
hotspotName: `${this.id}`,
options: this.options.swiper,
});
await selectedSwiper.init();
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");
if(!addToBasketEl) return;
addToBasketEl.addEventListener("submit", this.handleAddToBasket);
// + -
if(this?.options?.addToBasket === "range"){
addToBasketEl.querySelector(".idm-products-banner__qty")?.addEventListener("click",this.handleQuantityButtonClick);
addToBasketEl.querySelector(".idm-products-banner__qty-input")?.addEventListener("input",this.handleQuantityInputChange);
}
}
// Tooltip
// if(this.options?.omnibusTooltip){
// const tooltipEl = prodEl.querySelector(".idm_tooltip");
// tooltipEl.addEventListener("click", ()=>{
// this.showTooltip(tooltipEl);
// })
// }
// Add to Favorites
if(this.options?.addToFavorites && typeof this.addToFavFn === "function") prodEl.querySelector(".product__favorite")?.addEventListener("click", this.handleAddToFav);
// Compare
if(this.options?.addToCompare) prodEl.querySelector(".idm-products-banner__compare-btn")?.addEventListener("click", this.handleAddToCompare);
}
/**
* Inicjuje kontener hotspotu w określonym miejscu DOM.
*/
initHotspotContainer(){
const selectedEl = document.querySelector(this?.placement.selector);
if(!selectedEl) throw new Error(idmHotspotTextObject["Nie znaleziono kontenera"]);
const markup = this.markupHotspotContainer();
selectedEl.insertAdjacentHTML(this.placement.insert || "afterend", markup);
this.hotspotEl = document.getElementById(this.id);
}
/**
* Główna metoda inicjalizująca hotspot (lazy lub natychmiast).
*/
async init(){
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!`);
if(!this.hotspotEl || !document.contains(this.hotspotEl)) this.initHotspotContainer();
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",
// placement: {
// selector: "#content",
// insert: "beforeend",
// },
// source: {
// productsMenu: 1649
// }
// products: [] // Tablica produktów
// });
// {
// id: "idmMainHotspot2",
// title: "Super ramka rekomendacji",
// placement: {
// selector: "#content",
// insert: "beforeend"
// },
// source: {
// productsMenu: 488,
// },
// options: {
// lazy: true,
// addToBasket: "range",
// swiper: true,
// }
// }
async function idmPrepareHotspotObject(selectedContainerEl){
selectedContainerEl.classList.add("--init");
const source = {};
if(selectedContainerEl?.dataset?.productsId) source.productsId = selectedContainerEl?.dataset?.productsId.split(",");
else if(selectedContainerEl?.dataset?.hotspotsType) source.hotspotsType = selectedContainerEl?.dataset?.hotspotsType;
else if(selectedContainerEl?.dataset?.productsMenu) source.productsMenu = selectedContainerEl?.dataset?.productsMenu;
else{
console.error();
selectedContainerEl?.remove();
return;
}
const idmHotspotObj = {
id: selectedContainerEl?.id,
source,
hotspotEl: selectedContainerEl
};
if(selectedContainerEl?.dataset?.lazy) idmHotspotObj.options = {lazy: selectedContainerEl?.dataset?.lazy === "true" ? true : false};
new IdmHotspot(idmHotspotObj)
}
document.querySelectorAll(".hotspot__wrapper.idm__hotspot").forEach(currentHotspot=>{
idmPrepareHotspotObject(currentHotspot)
})