commit 26bc40da7af719125d70b4ab231227103daee07b Author: mzahorulko Date: Fri Sep 19 15:27:48 2025 +0200 Add index.js diff --git a/index.js b/index.js new file mode 100644 index 0000000..a0806cd --- /dev/null +++ b/index.js @@ -0,0 +1,346 @@ +class ProductSlider { + constructor() { + this.sliders = new Map(); + } + + /** + * Creates a slider container with products + * @param {Object} config - Configuration object + * @param {string} config.urlProducts - URL to fetch products from + * @param {string} config.title - Section title + * @param {string|number} config.id - Unique identifier for the slider + */ + createSliderContainer({ urlProducts, title, id, quantity }) { + const wrapper = this.createElement('div', { + className: 'hotspot mb-5 --slider col-12 p-0' + }); + + const titleElement = this.createElement('h2', { + innerHTML: `${title}` + }); + + const mainContainer = this.createElement('div', { + className: 'products d-flex flex-wrap justify-content-center --adaptive' + }); + + // Slider configuration + this.setDataAttributes(mainContainer, { + idm_id: id, + href: urlProducts, + count: quantity, + slider: 3, + scroll: 1, + autoplay: 2000, + margin: 5 + }); + + wrapper.appendChild(titleElement); + wrapper.appendChild(mainContainer); + + const container = document.querySelector(`[data-idm-id="${id}"]`); + if (container) { + container.appendChild(wrapper); + this.initializeSlider(id); + } else { + console.error('Container element #container not found'); + } + } + + createElement(tag, attributes = {}) { + const element = document.createElement(tag); + Object.assign(element, attributes); + return element; + } + + setDataAttributes(element, attributes) { + Object.entries(attributes).forEach(([key, value]) => { + element.dataset[key] = value; + }); + } + + async initializeSlider(id) { + const container = document.querySelector(`[data-idm-id="${id}"] .products`); + if (!container) { + console.error(`Container products in wrapper with data-idm-id="${id}" not found`); + return; + } + + const config = this.getSliderConfig(container); + + try { + await this.loadAndRenderProducts(container, config); + await this.waitForDOMReady(container); + this.initializeSlickSlider(container, config); + } catch (error) { + console.error('Failed to load products:', error); + } + } + + waitForDOMReady(container) { + return new Promise((resolve) => { + requestAnimationFrame(() => { + requestAnimationFrame(() => { + resolve(); + }); + }); + }); + } + + getSliderConfig(container) { + return { + count: parseInt(container.dataset.count) || 16, + slider: parseInt(container.dataset.slider) || 6, + scroll: parseInt(container.dataset.scroll) || 6, + autoplay: parseInt(container.dataset.autoplay) || 2000, + margin: parseInt(container.dataset.margin) || 5, + url: container.dataset.href + }; + } + + async fetchXMLData(url) { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.timeout = 12000; + xhr.open('GET', `${url}?getProductXML=true`); + xhr.responseType = 'document'; + + xhr.onload = () => { + if (xhr.status === 200) { + resolve(xhr.responseXML); + } else { + reject(new Error(`HTTP ${xhr.status}`)); + } + }; + + xhr.onerror = () => reject(new Error('Network error')); + xhr.ontimeout = () => reject(new Error('Request timeout')); + + xhr.send(); + }); + } + + extractPriceInfo(productXML) { + const priceEl = productXML.querySelector('price'); + + if (!priceEl) return null; + + return { + price: priceEl.getAttribute('price_formatted'), + value: parseFloat(priceEl.getAttribute('value')) || 0, + maxPrice: priceEl.getAttribute('srp_formatted'), + unitPrice: priceEl.getAttribute('unit_converted_price_formatted'), + unitFormat: priceEl.getAttribute('unit_converted_format'), + points: parseInt(priceEl.getAttribute('points')) || 0, + pointsSum: parseInt(priceEl.getAttribute('points_sum')) || 0, + rabatePercent: priceEl.getAttribute('srp_diff_percent') + }; + } + + generateProductHTML(product, productClasses) { + const name = product.querySelectorAll('name')[1]?.textContent || ''; + const id = product.getAttribute('id'); + const photo = product.querySelector('icon_src')?.textContent || ''; + const url = product.getAttribute('link'); + const priceInfo = this.extractPriceInfo(product); + const priceConfig = { + isVat: false, + isOmnibus: false + }; + + if (!priceInfo) return ''; + + const priceHTML = this.generatePriceHTML(priceInfo, priceConfig); + const unitPriceHTML = priceInfo.unitPrice ? + `${priceInfo.unitPrice} / ${priceInfo.unitFormat}` : ''; + + return ` +
+ + ${this.escapeHtml(name)} + +
+ - + ${priceInfo.rabatePercent} + % +
+

+ + ${this.escapeHtml(name)} + +

+
${priceHTML}
+ ${unitPriceHTML} +
+ `; + } + + generatePriceHTML(priceInfo, priceConfig) { + if (priceInfo.price === 0) { + return ` + + Cena na telefon + + `; + } + + let priceHTML = ''; + + // Omnibus + if (priceInfo.maxPrice) { + priceHTML += ` + + + ${priceInfo.maxPrice} + + ${priceConfig.isVat ? ' brutto' : ''} + ${priceConfig.isOmnibus ? 'Najniższa cena z 30 dni przed obniżką' : ''} + + `; + } + + // Main price + priceHTML += ` + + + ${priceInfo.price} + + ${priceConfig.isVat ? ' brutto' : ''} + + `; + + return priceHTML; + } + + + escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + async loadAndRenderProducts(container, config) { + const xml = await this.fetchXMLData(config.url); + const products = xml.querySelectorAll('page > products > product'); + const productClasses = 'product py-3 col-6 col-sm-3 col-xl-2'; + + let html = ''; + let count = 0; + + for (const product of products) { + if (count >= config.count) break; + + const productHTML = this.generateProductHTML(product, productClasses); + if (productHTML) { + html += productHTML; + count++; + } + } + + container.innerHTML = html; + } + + initializeSlickSlider(container, config) { + if (typeof $ === 'undefined' || typeof app_shop === 'undefined') { + console.warn('jQuery and/or app_shop not available. Slider functionality disabled.'); + return; + } + + if (container.offsetWidth === 0 || container.offsetHeight === 0) { + console.warn('Container has no dimensions. Retrying slider initialization...'); + setTimeout(() => { + this.initializeSlickSlider(container, config); + }, 200); + return; + } + + if (app_shop.vars && typeof HotspotSlider !== 'undefined') { + app_shop.vars.hotspot_slider = new HotspotSlider({ + selector: container, + options: { + slidesToShow: config.slider, + slidesToScroll: config.scroll, + dots: false, + prevArrow: '', + nextArrow: '', + infinite: true, + adaptiveHeight: true, + waitForAnimate: false, + // Individual responsivity + responsive: [ + { + breakpoint: 757, + settings: { + slidesToShow: 2, + slidesToScroll: 2, + swipeToSlide: true, + dots: false + } + }, + { + breakpoint: 550, + settings: { + slidesToShow: 1, + slidesToScroll: 1, + swipeToSlide: true, + dots: false + } + } + ] + }, + callbackBefore: (slider) => { + slider.each(function() { + $(this) + .on('init', function(event, slick) { + + setTimeout(() => { + $(this).slick('refresh'); + }, 50); + + if (app_shop.fn?.multiSlideAdaptiveHeight) { + app_shop.fn.multiSlideAdaptiveHeight(this); + } + }) + .on('beforeChange', function() { + if (app_shop.fn?.multiSlideAdaptiveHeight) { + app_shop.fn.multiSlideAdaptiveHeight(this); + } + }); + + if ($.fn.setHeight) { + $(this).find('.product__name').setHeight($(this)); + $(this).find('.product__prices').setHeight($(this)); + } + }); + }, + callbackAfter: () => { + container.parentNode.parentNode.classList.remove('idm-loading'); + console.log('Slider initialized successfully'); + } + }); + } + } +} + +// Initialization +const productSlider = new ProductSlider(); + +// Create slider +document.addEventListener('DOMContentLoaded', () => { + const idmHotspots = document.querySelectorAll('.idm-hotspot'); + idmHotspots.forEach((hotspot) => { + const hotspotId = hotspot.dataset.idmId; + const hotspotUrl = hotspot.dataset.idmUrl; + const hotspotTitle = hotspot.dataset.idmTitle; + const hotspotQuantity = hotspot.dataset.idmQuantity; + + productSlider.createSliderContainer({ + urlProducts: hotspotUrl, + title: hotspotTitle, + id: hotspotId, + quantity: hotspotQuantity + }); + }) +}); \ No newline at end of file