2155 lines
80 KiB
JavaScript
2155 lines
80 KiB
JavaScript
///////////////////////////////////////////////////////////
|
||
// 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);
|
||
}); |