Add index.js
This commit is contained in:
346
index.js
Normal file
346
index.js
Normal 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
|
||||
});
|
||||
})
|
||||
});
|
||||
Reference in New Issue
Block a user