From 8fadf8c07dd60b17e287573658f655c1ed75a867 Mon Sep 17 00:00:00 2001 From: "pawel.gaca" Date: Fri, 19 Dec 2025 11:09:02 +0100 Subject: [PATCH] Fix bugow zwiazanych z dodawaniem do koszyka --- README.md | 12 ++ klasa.js | 187 +++++++++++++++++++++-------- extends.js => przyklady/extends.js | 0 ramka.txt | 13 +- style.less | 64 +++++++++- 5 files changed, 216 insertions(+), 60 deletions(-) rename extends.js => przyklady/extends.js (100%) diff --git a/README.md b/README.md index f5b9cb6..c400f8a 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,18 @@ new IdmHotspot({ * @property {number} [cssVariables.nameClamp] - liczba wyświetlanych linijek tekstu nazwy produktu + + * @property {object} banner - Obiekt z bannerami gdzie klucz to położenie obiektu. Możliwe klucze obiektów to: + - "top" - banner nad produktami ale pod nazwą + - "left" - banner po lewej stronie (na desktop, na mobile jest na górze) + - "right" - banner po prawej stronie (na desktop, na mobile jest na dole) + - "bottom" - banner pod produktami + - "slider-1" - banner znajdujący się w środku ramek rekomendacji na danej pozycji podanej po myślniku + * @property {object.} [banner.*] - Konfiguracja bannera dla danej pozycji + * @property {string} banner.*.html - Kod HTML bannera (np. ``, ``, itp.) + * @property {Function} [banner.*.callbackFn] - Funkcja callback wywoływana po wyrenderowaniu bannera otrzymująca w argumencie instancje hotspota + + * @type {Hotspot[]} */ ``` diff --git a/klasa.js b/klasa.js index 96e1346..2be5c00 100644 --- a/klasa.js +++ b/klasa.js @@ -441,7 +441,11 @@ class IdmHotspot{ slidesPerView: 4, centeredSlides: false, }, - } + }, + // navigation: { + // nextEl: `#${this.id} .swiper-button-next`, + // prevEl: `#${this.id} .swiper-button-prev`, + // }, } // ============================ // DOMYŚLNE OPCJE HOTSPOTA @@ -481,13 +485,13 @@ class IdmHotspot{ // SWIPER swiper: true, swiperScrollbar: false, - } + }, } /** * Konstruktor * @param {object} object - Dane konfiguracyjne hotspotu */ - constructor({id, title, classes, placement, source, query, options = {}, hotspotEl, products, cssVariables}){ + constructor({id, title, classes, placement, source, query, options = {}, banner, hotspotEl, products, cssVariables}){ this.id = id || ""; this.title = title || ""; this.classes = classes || ""; @@ -509,8 +513,10 @@ class IdmHotspot{ ...IdmHotspot.idmDefaultHotspotOptions.cssVariables, ...cssVariables, }; + + + this.banner = banner || false - // // this.hotspots = {}; this.priceType = app_shop?.vars?.priceType || "gross"; @@ -629,9 +635,9 @@ class IdmHotspot{ return markup; } - /** - * Tworzy markup dla pojedynczego produktu. - */ + /** + * Tworzy markup dla pojedynczego produktu. + */ markupProduct(prod){ // markup pojedynczego produktu let singleMarkup = ""; @@ -663,7 +669,7 @@ class IdmHotspot{ ${this.markupSeries(prod)} ${this.markupOpinions(prod)} ${prod.name} - ${this.markupPrice({prod})} + ${this.markupPrice(prod)} ${this.markupAddToBasket(prod)} `; } @@ -694,7 +700,8 @@ class IdmHotspot{ } markupVersions(prod){ - if(!this.options?.selectVersion || !prod.group?.versions || prod.group?.versions?.length < 2 ) return ""; + if(!this.options?.selectVersion) return ""; + if(!prod.group?.versions || prod.group?.versions?.length < 2 ) return `
`; // let MAX_VERSION_AMOUNT = 5; // if(app_shop.vars.view > 2){ @@ -843,7 +850,7 @@ class IdmHotspot{ } - markupPrice({prod, sizeId}){ + markupPrice(prod, sizeId=false){ let priceRoot = prod.price; const getOmnibusDetailsObject = {productData: prod}; @@ -911,19 +918,26 @@ class IdmHotspot{ } markupAddToBasket(prod){ - let markup = ""; - if(!this.options.addToBasket) return markup; - - const prodTotalAmount = this.getProdTotalAmount(prod); - const prodCurrentSizeAmount = this.options.selectSize ? prod.sizes.find(size=>size.amount !== 0).amount : prod.sizes[0].amount; + let markup = ""; + if(!this.options.addToBasket) return markup; + + const prodCurrentSizeAmount = this.options.selectSize ? prod.sizes.find(size=>size.amount !== 0).amount : prod.sizes[0].amount; + const prodTotalAmount = this.options.selectSize ? this.getProdTotalAmount(prod) : prodCurrentSizeAmount; + + /* Sprawdzanie czy hotpsot znajduje się w formularzu */ + const addToBasketHTMLTag = this.isClosestForm ? "div" : "form"; // link do produktu jak nie jest to zwykły produkt + const buttonMarkup = `` + if(prod.type !== "product" || prodTotalAmount === 0) markup = `Zobacz produkt`; else if(this.options.addToBasket === "range") // +- - markup = `
- - + markup = `<${addToBasketHTMLTag} class="add_to_basket --range" ${this.isClosestForm ? "" : `action="/basketchange.php" type="post"`}> + + ${this.markupSize(prod)}
+
- -
`; + ${buttonMarkup} + `; else // Zwykłe dodanie do koszyka markup = ` -
- - + <${addToBasketHTMLTag} class="add_to_basket" ${this.isClosestForm ? "" : `action="/basketchange.php" type="post"`}> + + ${this.markupSize(prod)} - - -
`; + + ${buttonMarkup} + `; return markup; } markupSize(prod){ // return ``; - if(!this.options?.selectSize || prod.sizes.length === 1) return ``; + if(!this.options?.selectSize || prod.sizes.length === 1) return ``; const sizesName = `${this.id}-${prod.id}`; @@ -973,7 +983,7 @@ class IdmHotspot{ let selectedDefault = false; return ` - +
${prod.sizes.reduce((acc, val, index)=>{ const inputId = `${sizesName}-${val.id}`; @@ -1052,6 +1062,11 @@ class IdmHotspot{ ` } + // Banner + markupBannerContainer({html, position}){ + return `
${html}
` + } + // ======================================================== // HANDLERY ZDARZEŃ // ======================================================== @@ -1060,7 +1075,7 @@ class IdmHotspot{ * Obsługuje dodanie produktu do koszyka (GraphQL). */ async handleAddToBasket(e){ - const formEl = e.target.closest("form.add_to_basket"); + const formEl = e.target.closest(`${this.isClosestForm ? "div" : "form"}.add_to_basket`); if(!formEl) return; try{ // pobieranie danych i elementów @@ -1068,9 +1083,9 @@ class IdmHotspot{ 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; + const id = formEl.querySelector("input.product__add_id")?.value; + const size = formEl.querySelector("input.product__add_size")?.value; + const number = formEl.querySelector("input.product__add_number")?.value; // dodanie do koszyka const res = await fetch(`/graphql/v1/`, { @@ -1096,8 +1111,7 @@ class IdmHotspot{ 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; - + if(existingBasketBlockQuantity) existingBasketBlockQuantity.value = +existingBasketBlockQuantity.value + number; // Przeładowanie koszyka na stronie basketedit.html app_shop.fn?.basket?.reloadForm(); @@ -1250,7 +1264,7 @@ class IdmHotspot{ //1. Zmiana Wybranego inputa const sizeId = inputEl?.dataset?.value; - const hiddenSizeInputEl = e.target.closest("form.add_to_basket")?.querySelector("input[type='hidden'][name='size']"); + const hiddenSizeInputEl = e.target.closest("form.add_to_basket")?.querySelector("input.product__add_size"); if(!hiddenSizeInputEl || !sizeId) return inputEl.checked = false; @@ -1265,7 +1279,7 @@ class IdmHotspot{ const priceEl = productEl.querySelector(".product__prices"); if(!priceEl) return; - priceEl.outerHTML = this.markupPrice({prod: productData, sizeId}); + priceEl.outerHTML = this.markupPrice(productData, sizeId); this.setHeightDefault(); @@ -1853,6 +1867,8 @@ class IdmHotspot{ `); } + if(this.banner) this.placeBanners(); + await this.initSwiper(); // IDM setHeight @@ -1867,6 +1883,43 @@ class IdmHotspot{ } } + placeBanners(){ + if(!this.placeBanners) return; + + const productsWrapperEl = this.hotspotEl.querySelector(".products__wrapper"); + for(let [key, value] of Object.entries(this.banner)){ + const currBannerMarkup = this.markupBannerContainer({html: value?.html || "", position: key.split("-")[0] }); + if(key === "top"){ + productsWrapperEl.insertAdjacentHTML("beforebegin", currBannerMarkup); + } + else if(key === "left"){ + productsWrapperEl.insertAdjacentHTML("beforebegin", currBannerMarkup); + } + else if(key === "right"){ + productsWrapperEl.insertAdjacentHTML("afterend", currBannerMarkup); + } + else if(key === "bottom"){ + productsWrapperEl.insertAdjacentHTML("afterend", currBannerMarkup); + } + else{ + const slidePosition = Number(key.split("-")[1]) - 1; + + if(!Number.isInteger(slidePosition)) return; + + const allProducts = productsWrapperEl.querySelectorAll(".product"); + + let productIndex = slidePosition; + if(slidePosition > allProducts.length - 1) productIndex = allProducts.length - 1; + if(slidePosition < 0) productIndex = 0 + + const bannerPosition = slidePosition < 0 ? "beforebegin" : "afterend"; + + allProducts[productIndex]?.insertAdjacentHTML(bannerPosition, currBannerMarkup) + } + value?.callbackFn?.(this); + } + } + afterInitOnce(){ if(this.initialized) return; @@ -1928,25 +1981,44 @@ class IdmHotspot{ /** * Inicjuje instancję Swipera dla hotspotu. */ - async initSwiper(){ + async initSwiper(isReinitialized){ try{ // swiper || slick if(this.options?.swiper){ // Wywołanie swipera - const selectedSwiper = new HotspotSlider({ + this.swiperFn = new HotspotSlider({ selector: `#${this.id} .swiper`, hotspotName: `${this.id}`, options: this.options.swiper, }); - await selectedSwiper.init(); + await this.swiperFn.init(); + + // // //Wywołanie swipera v2 (nie działa jeszcze) + // const { Slider } = await appModules?.load("Slider"); + // if(!Slider) throw new Error("Brak modułu slidera") + // const idmSwiper = new Slider(); + // await idmSwiper.init(this.options.swiper); + // if(!this.hotspotEl.querySelector(".swiper")) throw new Error("Brak elementu swipera") + // this.swiperFn = await idmSwiper.setupSlider({ element: this.hotspotEl.querySelector(".swiper") }); - if(this.options.swiperScrollbar) new IdmSwiperProgress(selectedSwiper, `#${this.id} .swiper`); + if(this.options.swiperScrollbar && !isReinitialized) this.swiperFnProgress = new IdmSwiperProgress(this.swiperFn, `#${this.id} .swiper`); + else if(this.options.swiperScrollbar && isReinitialized) this.swiperFnProgress.reattachSwiperFn(this.swiperFn); } }catch(err){ console.error(idmHotspotTextObject["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"], err); } } + async reInitSwiper(differentOptions){ + try{ + if(differentOptions) this.options.swiper = differentOptions; + this.swiperFn?.slider?.slider?.destroy?.(); + this.initSwiper(true); + }catch(err){ + console.error(idmHotspotTextObject["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"], err); + } + } + /** * Inicjuje eventy dla produktów w hotspotcie. */ @@ -1958,13 +2030,15 @@ class IdmHotspot{ initSingleEvent(prodEl){ // DODAWANIE DO KOSZYKA if(this.options?.addToBasket){ - const addToBasketEl = prodEl.querySelector("form.add_to_basket"); + + // Warunek this.isClosestForm w przypadku gdy hotspot znajduje się już w środku formularza + const addToBasketEl = prodEl.querySelector(`${this.isClosestForm ? "button.add_to_basket__button" : "form.add_to_basket"}`); + addToBasketEl?.addEventListener(`${this.isClosestForm ? "click" : "submit"}`, this.handleAddToBasket); - 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); + addToBasketEl?.closest(".add_to_basket")?.querySelector(".idm-products-banner__qty")?.addEventListener("click",this.handleQuantityButtonClick); + addToBasketEl?.closest(".add_to_basket")?.querySelector(".idm-products-banner__qty-input")?.addEventListener("input",this.handleQuantityInputChange); } } // Dodaj do ulubionych @@ -2023,6 +2097,9 @@ class IdmHotspot{ // Ustawienie wszystkich zmiennych CSS this.cssSetAll(); + // Sprawdzenie czy hotspot znajduje się w formularzu(dla doawania do koszyka) + this.isClosestForm = !!this.hotspotEl.closest("form"); + if(this.options?.lazy) this.handleObserveHotspotOnce(); else this.fillHotspot(); } @@ -2064,6 +2141,13 @@ class IdmSwiperProgress { this.swiper.on("breakpoint", () => {this.updateBarWidth()}); } + reattachSwiperFn(newSwiperFn){ + this.swiper = newSwiperFn?.slider?.slider ?? newSwiperFn?.slider ?? newSwiperFn; + this.progressEl.style.width = ""; + this.progressEl.style.left = ""; + this.updateBarWidth(); + } + updateBarWidth() { const { slidesPerGroup, slidesPerView } = this.swiper.params; const totalSlides = this.swiper.slides.length; @@ -2138,7 +2222,6 @@ class IdmSwiperProgress { handle.addEventListener("touchstart", startDrag); } } - // ======================================================== // TOOLTIP // ======================================================== @@ -2242,7 +2325,7 @@ async function idmPrepareHotspotObject(selectedContainerEl){ } if(Object.keys(source).length === 0){ - console.error(); + console.error("Brak metody pobrania ramek rekomendacji"); selectedContainerEl?.remove(); return; } diff --git a/extends.js b/przyklady/extends.js similarity index 100% rename from extends.js rename to przyklady/extends.js diff --git a/ramka.txt b/ramka.txt index f8a3a53..9b474e4 100644 --- a/ramka.txt +++ b/ramka.txt @@ -1,16 +1,17 @@ -1. Ramka -- AAAAA - banner na hotspocie +bug - ramka nie działa dla banneru slider-1 itp +problem z funkcją new HotspotSlider i tym że orzekuje tam hotspotów idosellowych!!!! + - sprawdzenie zdjęcia producenta - Wykluczenie powtarzających się wersji - dodanie efektu 3d +- ramka z zakładkami wersja max 5 zdjęć i później + może???? - Wyświetlanie filmu na hover? - zakres cen????????????? -- Wybór kolorystyczny??? +- Wybór kolorystyczny????? - czy zapisywać wszystkie hotspoty do jakiegoś app_shop.fn.idmHotspots = {"#idmMainHotspot1": {}} - czy jakoś inteligentnie ucinać niepotrzebne query na podstawie wybranych opcji? chyba za dużo roboty i nie ma sensu @@ -27,6 +28,4 @@ cacheowanie ramek do indexedD zapisywanie querySelectorów produktów?? -rozmiary i wersje - dropdown - -Dodawanie do this.products klikniętego produktu na wersje, ale tak żeby nie wczytywał się ponownie, jakaś baza danych wersji?? \ No newline at end of file +rozmiary i wersje - dropdown \ No newline at end of file diff --git a/style.less b/style.less index fcf7dc7..29fd41d 100644 --- a/style.less +++ b/style.less @@ -328,6 +328,9 @@ } @media (min-width: 979px){ --idm-hotspot-version-height: 55px; + .product__versions.--desktop-hidden{ + display: none!important; + } } .product__versions{ display: grid; @@ -400,7 +403,7 @@ } @media (min-width: 979px){ - .idm__hotspot .product:has(.product__versions){ + .idm__hotspot .product:has(.product__version_single){ .product__versions{ clip-path: inset(0% 0 100% 0); } @@ -456,6 +459,7 @@ &:has(input:disabled){ opacity: 0.6; cursor: not-allowed!important; + background: #f2f2f2; } &:has(input:checked){ @@ -490,6 +494,64 @@ } +/* Banner */ +.idm__hotspot:has(.idm_hotspot__banner){ + + &:has(.idm_hotspot__banner.--left, .idm_hotspot__banner.--right){ + @media (min-width: 757px){ + .hotspot{ + display: flex; + flex-wrap: wrap; + h3{ + flex-basis: 100%; + } + } + .idm_hotspot__banner{ + &.--left, &.--right{ + width: 25%; + display: inline-block; + // position: relative; + // z-index: 100; + } + } + .products__wrapper { + width: calc(75% - 1rem); + } + } + } + &:has(.idm_hotspot__banner.--left){ + .products__wrapper { + margin-left: 1rem; + } + .swiper-button-prev{ + left: 27%; + } + } + &:has(.idm_hotspot__banner.--right){ + .products__wrapper { + margin-right: 1rem; + } + .swiper-button-next{ + right: 27%; + } + } + + .idm_hotspot__banner.--top{ + margin-bottom: 1rem; + } + &:has(.idm_hotspot__banner.--top) .hotspot{ + .swiper-initialized ~ .swiper-button-prev, .swiper-initialized ~ .swiper-button-next{ + top: auto; + bottom: calc(60% - var(--swiper-navigation-top-offset)); + } + } + + .idm_hotspot__banner.--bottom{ + margin-top: 1rem; + } +} + + /* Ogolne */ .idm__hotspot{ .product__name{