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 }); }) });