346 lines
11 KiB
JavaScript
346 lines
11 KiB
JavaScript
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: `<span class='headline'><span class='headline__name'>${title}</span></span>`
|
|
});
|
|
|
|
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 ?
|
|
`<small class='price --convert'>${priceInfo.unitPrice} / ${priceInfo.unitFormat}</small>` : '';
|
|
|
|
return `
|
|
<div class='${productClasses}'>
|
|
<a class='product__icon d-flex justify-content-center align-items-center'
|
|
data-product-id='${id}'
|
|
href='${url}'
|
|
title='${this.escapeHtml(name)}'>
|
|
<img src='${photo}' class='b-loaded' alt='${this.escapeHtml(name)}'>
|
|
</a>
|
|
<div class="product__yousave" bis_skin_checked="1">
|
|
<span class="product__yousave --label">-</span>
|
|
<span class="product__yousave --value">${priceInfo.rabatePercent}</span>
|
|
<span class="product__yousave --percent">%</span>
|
|
</div>
|
|
<h3>
|
|
<a class='product__name' href='${url}' title='${this.escapeHtml(name)}'>
|
|
${this.escapeHtml(name)}
|
|
</a>
|
|
</h3>
|
|
<div class='product__prices'>${priceHTML}</div>
|
|
${unitPriceHTML}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
generatePriceHTML(priceInfo, priceConfig) {
|
|
if (priceInfo.price === 0) {
|
|
return `
|
|
<a class='price price--phone' href='/contact.php' title='Kliknij aby przejść do formularza kontaktu'>
|
|
Cena na telefon
|
|
</a>
|
|
`;
|
|
}
|
|
|
|
let priceHTML = '';
|
|
|
|
// Omnibus
|
|
if (priceInfo.maxPrice) {
|
|
priceHTML += `
|
|
<span class='price__omnibus'>
|
|
<del class='price --max'>
|
|
${priceInfo.maxPrice}
|
|
</del>
|
|
${priceConfig.isVat ? '<span class="price__vat"> brutto</span>' : ''}
|
|
${priceConfig.isOmnibus ? '<span class="price__info">Najniższa cena z 30 dni przed obniżką</span>' : ''}
|
|
</span>
|
|
`;
|
|
}
|
|
|
|
// Main price
|
|
priceHTML += `
|
|
<strong class='price'>
|
|
<span class='price__value ${priceInfo.maxPrice ? '--promotion' : ''}'>
|
|
${priceInfo.price}
|
|
</span>
|
|
${priceConfig.isVat ? '<span class="price__vat"> brutto</span>' : ''}
|
|
</strong>
|
|
`;
|
|
|
|
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: '<a class="slick-prev" href=""><i class="icon-angle-left"></i></a>',
|
|
nextArrow: '<a class="slick-next" href=""><i class="icon-angle-right"></i></a>',
|
|
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
|
|
});
|
|
})
|
|
}); |