Zmiana funkcji na klasy

This commit is contained in:
2025-11-12 13:35:55 +01:00
parent dddc1f393a
commit 609c579159
10 changed files with 1145 additions and 2316 deletions

185
README.md
View File

@@ -2,55 +2,108 @@
Funkcje js składające się na customowe ramki rekomendacji
## UWAGI PRZEDWDROŻENIOWE ##
- kod zawiera app_shop.fn.idmSetHeight używany do wyrównywania wysokości
- kod zawiera app_shop.fn.idmGetOmnibusDetails który jest przerobionym kodem idosella app_shop.fn.getOmnibusDetails używanym w zwykłych ramkach rekomendacji
- kod zawiera **app_shop.fn.idmGetOmnibusDetails** który jest przerobionym kodem idosella app_shop.fn.getOmnibusDetails używanym w zwykłych ramkach rekomendacji
### Pliki ###
- bundle.js - całość
- 1graphQL.js - graphQL + literały
- 2funkcje.js - ogólne funkcje jak dodawanie do koszyka, czy lazy loading
- 3markup.js - funkcje związane z markupem np zdjęć, cen
- 4init.js - obiekt z ogólnymi ustawieniami Hotspota + init swipera
- 5ainsertHotspotHTML.js - wstawienie ramki po kodzie html
- 5binsertHotspotObject.js - wstawienie ramki po obiekcie js
- 6style.css -
- **style.css** - style wstawiane do css
- **klasa.js** - kod js
### Użycie ###
1. Wstawienie całego kodu do komponentu/dodatku
2. Ustawienie defaultowych ustawień w obiekcie idmGeneralHotspotObjData
1. Wstawienie całego kodu do komponentu (najlepiej chyba Hotspoty javascript RAYPATH - #IdoMods
w zwykły Javascript)/dodatku(uwaga tutaj na literały)
2. Ustawienie defaultowych ustawień na początku klasy w **idmDefaultSwiperConfig** i w **idmDefaultHotspotOptions**
3. Wstawienie HTML lub Obiektu js z odpowienimi danymi i wywołanie funkcji od tworzenia ramek
#### Dodatkowe informacje ####
Można użyć extends w innym miejscu (np tym razem w wydzielonym JS) żeby nadpisać jakąś metodę, bez zmian w kodzie ramki. Będzie to przydatne w przypadku gdzie trzeba będzie zaktualizować kod ramki.
**Przykład**
```
class IdmRaypathHotspot extends IdmHotspot {
markupLabel(prod) {
// Standardowe labelki
let labelMarkup = super.markupLabel(prod);
// Customowe labelki
const awards = prod?.awardedParameters;
if (awards?.length) {
const awardParam = awards.find(award => award.name === "Idm_custom_label");
const values = awardParam?.values?.map(v => v.name) || [];
const html = values
.map(label => {
const [text, bgColor, color] = label.split("||");
return `<span class="label --custom" style="background-color:${bgColor}; color:${color}">${text}</span>`;
})
.join("");
labelMarkup += html;
}
return labelMarkup;
}
}
```
Warto gdzieś później zapisać nową nazwę klasy np w opisie komponentu, albo w opisie szablonu.
### Literały do uzupełnienia w szablonie ###
- Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę
- Błąd przy pobieraniu danych
- Maksymalna liczba sztuk tego towaru które możesz dodać do koszyka to:
- Minimalna liczba sztuk tego towaru które możesz dodać do koszyka to:
- Wystąpił błąd z inicjalizacją. Proszę odśwież stronę
- Nie znaleziono kontenera
- Nie znaleziono metody graphql
#### Przykład ####
##### Jedna ramka - obiekt ######
```
idmInsertHotspotObject({
{
id: "idmMainHotspot1",
title: "Nowoczesna ramka rekomendacji",
classes: "abcdefg",
placement: {
selector: "#content",
insert: "afterbegin"
},
query: {
string: `searchInput: {productsId : [589, 180, 181590,160740, 155978, 153632, 123350, 82542, 37321, 17040, 25065, 25114, 85452]}`,
graphFn: IDM_PRODUCTS_GQL
},
options: {
lazy: false,
addToBasket: "range",
swiper: true,
}
},
})
```
##### Wszystkie ramki - tablica obiektów ######
```
idmInsertAllObjectHotspots(hotspotArr);
new IdmHotspot({
id: "idmTestHotspot1",
title: "tescik",
placement: {
selector: "#content",
insert: "beforeend",
},
source: {
productsMenu: 1649
}
});
```
#### Wszystkie możliwe dane JS ####
```
/**
* @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
* @property {Function} options.callbackFn - Funkcja callback która dzieje się po wywołaniu wszystkiego włącznie ze swiperem
*
* @type {Hotspot[]}
*/
```
###### Jedna ramka - HTML ######
@@ -72,52 +125,24 @@ idmInsertAllObjectHotspots(hotspotArr);
idmInsertHotspotElement(document.getElementByid("idmBlogHotspot1"));
</script>
```
###### Wszystkie ramki - HTML ######
#### Wszystkie możliwe dane HTML####
```
idmInsertAllHTMLHotspots();
/**
* 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
*/
```
### LISTA GLOBALNYCH FUNKCJI, ZMIENNYCH ###
##### INIT #####
- priceQuery
- productQuery
- IDM_PRODUCTS_GQL
- IDM_HOTSPOTS_GQL
- IDM_PRODUCT_GQL
- IDM_HOTSPOT_ADD_TO_BASKET
- idmHotspotTextObject
#### FUNKCJE ####
- app_shop.fn.idmGetOmnibusDetails
- idmHandleAddToBasket
- idmRangeMaxAlert
- idmRangeMinAlert
- idmQuantityButtonClick
- idmQuantityInputChange
- idmGetHotspotData
- idmGetQueryData
- idmObserveOnce
- app_shop.fn.idmSetHeight
#### MARKUP ####
- idmPrepareProductsMarkup
- idmPrepareSingleProductMarkup
- idmPrepareHotspotImgMarkup
- idmPrepareHotspotPriceMarkup
- idmPrepareHotspotAddToBasketMarkup
#### INIT ####
- idmPriceType
- idmGeneralHotspotObjData
- idmHotspotInit
#### INSERT ####
- idmInsertHotspotElement
- idmInsertAllHTMLHotspots
- idmInsertHotspotObject
- idmInsertAllObjectHotspots
Created by • **[IdoMods](https://idomods.pl/)** • 2025

1118
bundle.js

File diff suppressed because it is too large Load Diff

1040
klasa.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,207 +0,0 @@
///////////////////////////////////////////////
// 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",
}

View File

@@ -1,321 +0,0 @@
//////////////////////////////////////////////////////////////////////////\\\\\\\\\\\\
// 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 = graphFn === IDM_HOTSPOTS_GQL ? data?.data?.hotspots?.products : data?.data?.products?.products;
if(!products || !products.length) 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);
}
////////////////////////////////////////////////
// IDM SET HEIGHT
app_shop.fn.idmSetHeight = 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)
}

View File

@@ -1,147 +0,0 @@
//////////////////////////////////////////////////////////////////////////
// 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;
}

View File

@@ -1,167 +0,0 @@
////////////////////////////////////////////////////
// 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();
}
if(typeof options?.callbackFn === "function") options?.callbackFn();
// IDM setHeight
app_shop.fn.idmSetHeight({
selectors: [
`#${id} .product__prices`,
`#${id} .product__name`,
],
container: `#${id} .products__wrapper`,
});
console.log(`Initialized hotspot #${id}`);
}catch(err){
console.error(idmHotspotTextObject["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"], err);
}
}

View File

@@ -1,109 +0,0 @@
////////////////////////////////////////////////////
// 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>
*/

View File

@@ -1,167 +0,0 @@
////////////////////////////////////////////////////
// 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
* @property {Function} options.callbackFn - Funkcja callback która dzieje się po wywołaniu wszystkiego włącznie ze swiperem
*
* @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,
// callbackFn: ()=>{console.log("test")}
// // 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);