Files
hotspots/bundle.js

1081 lines
41 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.
///////////////////////////////////////////////
// GraphQL
// ogolne
const priceQuery = `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 productQuery = `id
type
name
zones
icon
iconSecond
iconSmall
iconSmallSecond
link
zones
producer{
name
}
category{
name
}
sizes{
id
amount
name
${priceQuery}
}
group{
id
name
link
versions{
id
name
icon
iconSecond
iconSmall
iconSmallSecond
}
}
awardedParameters {
name
id
description
values {
name
id
}
}
enclosuresImages {
position
url
}
points
unit{
id, name, singular, plural, fraction, sellBy, precision, unitConvertedFormat
}
${priceQuery}`;
// 1. products
const IDM_PRODUCTS_GQL = (args) => JSON.stringify({
query: `{
products(${args}){
took
products{
${productQuery}
}
}
}`
});
// 2. hotspots
const IDM_HOTSPOTS_GQL = (args) => JSON.stringify({
query: `{
hotspots(${args}){
took
products{
${productQuery}
}
}
}`
});
// 3. single product
const IDM_PRODUCT_GQL = (args) => JSON.stringify({
query: `{
product(${args}){
product{
${productQuery}
}
}
}`
});
// 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 }`
});
///////////////////////////////////////////////////////////
// TEXT
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",
}
//////////////////////////////////////////////////////////////////////////\\\\\\\\\\\\
// IDOSELL omnibus details
// omnibusDetailsTxt - nadpisać na własny obiekt
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">${idmHotspotTextObject["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">${idmHotspotTextObject["Okazja"]}</span>`;
}
// label promocja
if (Object.keys(activeLabel)?.length === 0) {
activeLabel.bargain = `<span class="label --promo --omnibus">${idmHotspotTextObject["Promocja"]}</span>`;
}
// labele zones
if(productData.zones.find(zone => zone ==="bestseller")) activeLabel.bestseller = `<span class="label --bestseller --omnibus">${idmHotspotTextObject["Bestseller"]}</span>`;
if(productData.zones.find(zone => zone ==="news")) activeLabel.news = `<span class="label --news --omnibus">${idmHotspotTextObject["Nowość"]}</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">${idmHotspotTextObject['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">${idmHotspotTextObject['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">${idmHotspotTextObject['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">${idmHotspotTextObject['Cena nadchodząca od']} </span>
<span class="new_price__date">${newDate}: </span>
<span class="new_price__value">${maxPrice}</span>`,
},
} : {};
return {
classes,
omnibus,
...max,
...beforeRebate,
...newPriceEffectiveUntil,
activeLabel,
};
};
//////////////////////////////////////////////////////////////////////////
// EVENTY
// dodawanie do koszyka
async function idmHandleAddToBasket(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{
// Obsługiwanie sukcesu
app_shop.graphql.trackingEvents(res);
buttonEl.classList.add("--success");
// Dodawanie do koszyka na stronie basketedit.php będzie wymagał innego indywidualnego kodu!!!!!
buttonEl.innerHTML = `<span>${buttonEl.dataset.success}</span>`;
setTimeout(()=>{
buttonEl.innerHTML = `<span>${buttonEl.dataset.text}</span>`;
app_shop.fn?.menu_basket_cache?.();
buttonEl.classList.remove("--success");
}, 3000);
}
}catch(err){
console.error(err);
Alertek.Error(idmHotspotTextObject["Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę"]);
buttonEl.innerHTML = `<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")
}
}
///qty
const idmRangeMaxAlert = (max)=> Alertek.Error(`${idmHotspotTextObject["Maksymalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]} ${max}`)
const idmRangeMinAlert = (min)=> Alertek.Error(`${idmHotspotTextObject["Minimalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]} ${min}`)
function idmQuantityButtonClick(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;
idmRangeMaxAlert(max)
}
} else if (e.target.classList.contains("idm-products-banner__qty-decrease")) {
current -= step;
if (current < step){
current = step;
idmRangeMinAlert(step)
}
}
input.value = current.toFixed(precision);
}
function idmQuantityInputChange(e){
if(e.target.value > +e.target.max){
idmRangeMaxAlert(e.target.max)
e.target.value = +e.target.max
}
if(e.target.value < +e.target.min){
idmRangeMinAlert(e.target.min)
e.target.value = +e.target.min;
}
}
//////////////////////////////////////////////
// DANE
// dwie funkcje zamiast jednej
async function idmGetHotspotData(query, graphFn){
try{
const res = await fetch(`/graphql/v1/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: graphFn ? graphFn(query) : IDM_PRODUCTS_GQL(query)
});
const data = await res.json();
const products = data[graphFn === IDM_HOTSPOTS_GQL ? "hotspots" : "data"]?.products?.products;
if(!products) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]);
console.log(data);
return products;
}catch(err){
console.error(idmHotspotTextObject["Błąd przy pobieraniu danych"], err);
return null;
}
}
function idmGetQueryData({
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}
}
//////////////////////////////////////////////
// LAZY LOADING
function idmObserveOnce(element, callback, options = { root: null, rootMargin: "0px", threshold: 0.1 }) {
if (!element) return;
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
callback(entry); // run your callback
obs.disconnect(); // stop observing after first trigger
}
});
}, options);
observer.observe(element);
}
//////////////////////////////////////////////////////////////////////////
// Markup
// Funkcja przygotująca markup dla wszystkich produktów
function idmPrepareProductsMarkup(products, addToBasket){
let markup = "";
products.forEach((prod)=>{
markup += idmPrepareSingleProductMarkup(prod, addToBasket);
})
return markup;
}
// funkcja przygotowująca markup dla wybranego produktu
function idmPrepareSingleProductMarkup(prod, addToBasket){
const prodExchangedData = app_shop.fn?.idmGetOmnibusDetails({productData: prod});
// pobranie labelek
let labelHTMLMarkup = "";
if(typeof prodExchangedData.activeLabel === "object") Object.entries(prodExchangedData.activeLabel).forEach(([key,value])=>{
labelHTMLMarkup += value;
})
// markup pojedynczego produktu
let singleMarkup = "";
singleMarkup += `
<div class="product hotspot__product swiper-slide d-flex flex-column ${prod.price.price[idmPriceType].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>
<a class="product__icon d-flex justify-content-center align-items-center" tabindex="-1" href="${prod.link}">
${idmPrepareHotspotImgMarkup(prod)}
<strong class="label_icons">
${labelHTMLMarkup}
</strong>
</a>
<div class="product__content_wrapper">
<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,"")}">
${idmPrepareHotspotPriceMarkup(prod, prodExchangedData)}
</div>
</div>
${idmPrepareHotspotAddToBasketMarkup(prod, addToBasket)}
</div>`;
return singleMarkup;
}
// markup zdjęcia
function idmPrepareHotspotImgMarkup(prod){
let markup = "";
if(prod.iconSmallSecond !== undefined && prod.iconSecond !== undefined) 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 !== undefined) 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;
}
// markup cen
function idmPrepareHotspotPriceMarkup(prod, prodExchangedData){
const price = prod.price.price[idmPriceType];
const unit = prod.unit;
const pointsPrice = prod?.points;
const convertedPrice = prod.price?.unitConvertedPrice?.[idmPriceType]?.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>` : ""}
${prodExchangedData?.omnibus?.visible ? `<span class="price --omnibus omnibus_price">${prodExchangedData?.omnibus?.html}</span>` : ""}
${prodExchangedData?.max?.visible ? `<span class="price --max">${prodExchangedData?.max?.html}</span>` : ""}
`;
}
// markup dodawania do koszyka
function idmPrepareHotspotAddToBasketMarkup(prod, addToBasket){
let markup = "";
if(!addToBasket && typeof addToBasket !== "undefined" ||
(typeof addToBasket === "undefined" && typeof idmGeneralHotspotObjData === "object" && !idmGeneralHotspotObjData?.options?.addToBasket) ||
!addToBasket && typeof idmGeneralHotspotObjData === "undefined") return markup;
// link do produktu jak nie jest to zwykły produkt
if(prod.type !== "product") markup = `<a class="btn --solid --medium add_to_basket__link" href="${prod.href}">Zobacz produkt</a>`;
else if(addToBasket === "range"
|| typeof addToBasket === "undefined" && typeof idmGeneralHotspotObjData === "object" && idmGeneralHotspotObjData?.options?.addToBasket === "range") // +-
markup = `<form class="add_to_basket --range" action="/basketchange.php" type="post" onsubmit="idmHandleAddToBasket(event)">
<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}"
onclick="idmQuantityButtonClick(event)"
>
<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ść"]}"
oninput="idmQuantityInputChange(event)"
>
<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" onsubmit="idmHandleAddToBasket(event)">
<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;
}
////////////////////////////////////////////////////
// INIT
// brutto/netto
const idmPriceType = app_shop?.vars?.priceType || "gross";
// Zmienna trzymająca dane o ustawieniach customowych ramek rekomendacji na całym sklepie
/**
* Obiekt konfiguracyjny ogólnych ustawień hotspotów rekomendacji.
*
* @typedef {object} idmGeneralHotspotObjData
* @property {object} options - Główne ustawienia hotspotów
* @property {boolean} options.lazy - Czy wczytywać zawartość w trybie lazy (required).
* @property {boolean|string} options.addToBasket - Zachowanie przy dodawaniu do koszyka:
* - true = przycisk dodaj do koszyka
* - false = brak przycisku
* - "range" = dodaj z wyborem zakresu (required).
* @property {boolean|object} options.swiper - Ustawienia slidera:
* - true/false = włącz/wyłącz
* - object = konfiguracja instancji Swiper (required jeśli obiekt).
*/
const idmGeneralHotspotObjData = {
options: {
lazy: true,
addToBasket: true, // true, false, "range"
swiper: { // 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,
},
}
}
}
}
// Funkcja inicjalizująca wybranego hotspota(addtobasket range - swiper)
async function idmHotspotInit(id, options={}){
try{
const hotspotEl = document.getElementById(id);
if(!hotspotEl) throw new Error("Nie znaleziono elementu");
// add to basket + -
if(options?.addToBasket === "range" ||
typeof options?.addToBasket === "undefined" && typeof idmGeneralHotspotObjData === "object" && idmGeneralHotspotObjData?.options?.addToBasket === "range"){
// obsługa i sprawdzanie clicków
// hotspotEl.addEventListener("click", e=>{
// const wrapper = e.target.closest(".idm-products-banner__qty");
// if (!wrapper) return;
// if(e.target.classList.contains("idm-products-banner__qty-input")) return e.target.select();
// 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;
// rangeMaxAlert(max)
// }
// } else if (e.target.classList.contains("idm-products-banner__qty-decrease")) {
// current -= step;
// if (current < step){
// current = step;
// rangeMinAlert(step)
// }
// }
// input.value = current.toFixed(precision);
// });
// sprawdzanie na input
// hotspotEl.querySelectorAll(".idm-products-banner__qty-input").forEach(inp=>{
// inp.addEventListener("input", e=>{
// if(e.target.value > +e.target.max){
// rangeMaxAlert(e.target.max)
// e.target.value = +e.target.max
// }
// if(e.target.value < +e.target.min){
// rangeMinAlert(e.target.min)
// e.target.value = +e.target.min;
// }
// });
// })
}
// swiper || slick
if(options?.swiper ||
typeof options?.swiper === "undefined" && typeof idmGeneralHotspotObjData === "object" && idmGeneralHotspotObjData?.options?.swiper){
// Opcje swipera
let swiperOptions = typeof options.swiper === "object" ? options.swiper : "";
if(typeof options.swiper === "object") swiperOptions = options.swiper;
else if(typeof idmGeneralHotspotObjData === "object" && typeof idmGeneralHotspotObjData?.options?.swiper === "object"){
swiperOptions = idmGeneralHotspotObjData?.options?.swiper;
swiperOptions.navigation = {
nextEl: `#${id} .idm-button-next`,
prevEl: `#${id} .idm-button-prev`,
}
}else{
swiperOptions = {
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,
},
},
navigation: {
nextEl: `#${id} .idm-button-next`,
prevEl: `#${id} .idm-button-prev`,
},
}
}
// Wywołanie swipera
const selectedSwiper = new HotspotSlider({
selector: `#${id} .swiper`,
hotspotName: `${id}`,
options: swiperOptions,
});
await selectedSwiper.init();
}
}catch(err){
console.error(idmHotspotTextObject["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"], err);
}
}
console.log("init")
////////////////////////////////////////////////////
// Funkcja init ELEMENT HTML
async function idmInsertHotspotElement(selectedContainerEl){
selectedContainerEl.classList.add("--init");
const {graphFn, query} = idmGetQueryData({
productsID: selectedContainerEl?.dataset?.productsId,
productsMenu: selectedContainerEl?.dataset?.productsMenu,
hotspotsType: selectedContainerEl.dataset.hotspotsType
});
if(!graphFn || !query){
console.log(idmHotspotTextObject["Nie znaleziono metody graphql"], selectedContainerEl)
return selectedContainerEl.remove();
}
// Funkcja od uzupełniania danych
const idmFill = async ()=>{
try{
// pobranie danych o produktach
const products = await idmGetHotspotData(query, graphFn);
if(!products) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]);
// wstawienie produktów zależnie czy w section jest .hotspot czy nie
const hotspotInsideEl = selectedContainerEl.querySelector(".hotspot");
if(hotspotInsideEl) hotspotInsideEl.innerHTML += `<div class="products__wrapper swiper">
<div class="products hotspot__products swiper-wrapper">
${idmPrepareProductsMarkup(products, true)}
</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>`;
else selectedContainerEl.innerHTML = `<div class="hotspot --initialized"><div class="products__wrapper swiper">
<div class="products hotspot__products swiper-wrapper">
${idmPrepareProductsMarkup(products, true)}
</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>`;
selectedContainerEl.classList.remove("idm-loading")
// init swipera
idmHotspotInit(selectedContainerEl.id)
}catch(err){
console.error(idmHotspotTextObject["Wystąpił błąd"], err);
selectedContainerEl.remove();
}
}
if(selectedContainerEl.dataset?.lazy ||
!selectedContainerEl.dataset?.lazy && typeof idmGeneralHotspotObjData === "object" && idmGeneralHotspotObjData?.options?.lazy) idmObserveOnce(selectedContainerEl, idmFill);
else idmFill();
}
// Zebranie wszystkich ramek HTML i wstawienie ich.
async function idmInsertAllHTMLHotspots(){
try{
const reqArr = []
document.querySelectorAll(".idm__hotspot:not(.--init):not(.--lazy-hotspot)").forEach(hotspot=>{
reqArr.push(idmInsertHotspotElement(hotspot));
});
await Promise.all(reqArr);
}catch(err){
console.error(err)
}
}
idmInsertAllHTMLHotspots();
/////////////////////////////////////////////////////////////////////
// wdrożenie dla elementu HTML
/**
* Struktura sekcji hotspotu w HTML.
*
* @typedef {HTMLElement} HotspotSection
* @property {string} id - Identyfikator elementu (np. "idmBlogHotspot1").
* @property {string} class - Klasy CSS używane do stylowania.
*
* @attribute {string} data-products-id - Lista ID produktów (rozdzielona przecinkami).
* @attribute {number} data-products-menu - Identyfikator menu produktów.
* @attribute {string} data-hotspots-type - Typ hotspotu (np. "promotion").
* @attribute {boolean} data-lazy - Czy sekcja ma być ładowana w trybie lazy.
*
* @example
<section id="idmBlogHotspot1"
class="hotspot__wrapper idm__hotspot idm-loading"
data-products-id="589,180,181590"
data-products-menu="122"
data-hotspots-type="promotion"
data-lazy="true"
>
<div class="hotspot --initialized">
<h3 class="hotspot__name headline__wrapper">
<span class="headline">
<span class="headline__name" aria-label="aaa">aaa</span>
</span>
</h3>
</div>
</section>
*/
////////////////////////////////////////////////////
// Funkcja init OBIEKT JS
async function idmInsertHotspotObject(idmHotspotObj){
// Wstaw kontener
const selectedEl = document.querySelector(idmHotspotObj?.placement.selector);
if(!selectedEl) throw new Error(idmHotspotTextObject["Nie znaleziono kontenera"]);
selectedEl.insertAdjacentHTML(idmHotspotObj.placement.insert || "afterend", `<section id="${idmHotspotObj.id}" class="hotspot__wrapper idm__hotspot --init idm-loading ${idmHotspotObj?.classes}">
<div class="hotspot --initialized">
${idmHotspotObj?.title ? `
<h3 class="hotspot__name headline__wrapper">
<span class="headline"><span class="headline__name" aria-label="${idmHotspotObj.title}">${idmHotspotObj.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>`);
// Utworzenie markupa HTML
const selectedContainerEl = document.getElementById(idmHotspotObj.id);
if(!selectedContainerEl) throw new Error(idmHotspotTextObject["Nie znaleziono kontenera"]);
const idmFill = async ()=>{
try{
let {graphFn, query} = idmGetQueryData({
productsID: idmHotspotObj?.source?.productsId,
productsMenu: idmHotspotObj?.source?.productsMenu,
hotspotsType: idmHotspotObj.source.hotspotsType
});
if(idmHotspotObj?.query?.graphFn && idmHotspotObj?.query?.string){
graphFn = idmHotspotObj.query.graphFn;
query = idmHotspotObj.query.string;
}
// pobranie danych o produktach
const products = await idmGetHotspotData(query, graphFn);
if(!products) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]);
// Wstawienie markupa na strone
const hotspotMarkup = `${idmPrepareProductsMarkup(products, idmHotspotObj?.options?.addToBasket)}`;
selectedContainerEl.querySelector(".products.hotspot__products")?.insertAdjacentHTML("beforeend", hotspotMarkup);
selectedContainerEl.classList.remove("idm-loading");
// init swiper + add to basket
idmHotspotInit(idmHotspotObj.id, idmHotspotObj?.options)
}catch(err){
console.error(idmHotspotTextObject["Wystąpił błąd"], err);
selectedContainerEl.remove();
}
}
if(idmHotspotObj?.options?.lazy ||
typeof idmHotspotObj?.options?.lazy === "undefined" && typeof idmGeneralHotspotObjData === "object" && idmGeneralHotspotObjData?.options?.lazy) idmObserveOnce(selectedContainerEl, idmFill);
else idmFill();
}
// obiekt js z przykładowymi danymi
/**
* Tablica konfiguracji hotspotów rekomendacji.
*
* @typedef {object} Hotspot
* @property {string} id - Identyfikator ramki (required).
* @property {string} title - Tytuł ramki.
* @property {string} classes - Dodatkowe klasy CSS.
* @property {object} placement - Określa, gdzie wstawić ramkę (required).
* @property {string} placement.selector - Selektor miejsca osadzenia.
* @property {string} placement.insert - Pozycja wstawienia względem selektora (np. "afterbegin", "beforeend").
* @property {object} source - Dane źródłowe dla hotspotu (required).
* @property {string} [source.hotspotType] - Typ hotspotu (np. "promotion").
* @property {number[]} [source.productsId] - Tablica ID produktów.
* @property {number} [source.productsMenu] - Identyfikator menu produktów.
* @property {object} query - Dane zapytania, nadpisują source (DEV).
* @property {string} query.string - Zapytanie w formacie GraphQL.
* @property {Function} query.graphFn - Funkcja do pobierania danych.
* @property {object} options - Ustawienia dla hotspotu (required).
* @property {boolean} options.lazy - Czy wczytywać w trybie lazy.
* @property {boolean|string} options.addToBasket - Obsługa koszyka:
* - true = włącz
* - false = wyłącz
* - "range" = dodaj z zakresem
* @property {boolean|object} options.swiper - Slider:
* - true = aktywny
* - false = nieaktywny
* - object = konfiguracja Swiper
*
* @type {Hotspot[]}
*/
// const idmHotspotArr = [
// {
// id: "idmMainHotspot1",//!id ramki
// title: "Nowoczesna ramka rekomendacji",//
// classes: "abcdefg",
// placement: {
// selector: "#content",
// insert: "afterbegin"
// },
// source: {
// hotspotType: "protomtion",
// productsId: [11,12],
// productsMenu: 122,
// },
// query: {
// string: `searchInput: {productsId : [589, 180, 181590,160740, 155978, 153632, 123350, 82542, 37321, 17040, 25065, 25114, 85452]}`,
// graphFn: IDM_PRODUCTS_GQL
// },
// // addToBasket: true,
// options: {
// lazy: false,
// addToBasket: "range",
// swiper: true,
// // swiper: albo true false - albo obiekt z opcjami swipera
// }
// },
// {
// id: "idmMainHotspot2",
// title: "Super ramka rekomendacji",
// placement: {
// selector: "#content",
// insert: "beforeend"
// },
// source: {
// productsMenu: 488,
// },
// query: {
// string: `searchInput: {hotspot: promotion,limit: 16}`,
// graphFn: IDM_HOTSPOTS_GQL
// },
// // addToBasket: true,
// options: {
// lazy: true,
// addToBasket: "range",
// swiper: true,
// // swiper: albo true false - albo obiekt z opcjami swipera
// }
// }
// ];
// Wrzucenie na strone wszystkich ramek z obiektu js
async function idmInsertAllObjectHotspots(hotspotArr){
try{
const reqArr = []
hotspotArr.forEach(hotspotObj=>{
reqArr.push(idmInsertHotspotObject(hotspotObj));
});
await Promise.all(reqArr);
}catch(err){
console.error(err)
}
}
// idmInsertAllObjectHotspots(idmHotspotArr);