Add index.js

This commit is contained in:
2025-09-19 15:27:48 +02:00
commit 26bc40da7a

346
index.js Normal file
View File

@@ -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: `<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
});
})
});