commercetools-8.x-1.2-alpha1/modules/commercetools_decoupled/js/templates/ProductCatalog.js
modules/commercetools_decoupled/js/templates/ProductCatalog.js
class ProductCatalog extends HTMLElement {
constructor() {
super();
this.currentPage = 0;
this.activeFilters = {};
this.initialLoad = true;
this.config = this.loadConfig();
this.handlePageChange = this.handlePageChange.bind(this);
this.handlerFilterChange = this.handlerFilterChange.bind(this);
this.applyProductListToFilters = this.applyProductListToFilters.bind(this);
document.addEventListener('ct.filters.change', this.handlerFilterChange);
document.addEventListener(
'DOMContentLoaded',
this.applyProductListToFilters,
);
}
loadConfig() {
const defaultSettings = window.drupalSettings.commercetoolsDecoupled || {};
const settings = window.commercetools.getComponentConfig(this);
if (!settings?.productListConfigs?.itemsPerPage) {
settings.productListConfigs.itemsPerPage = defaultSettings.itemsPerPage;
}
if (!settings?.productListConfigs?.totalLimit) {
settings.productListConfigs.totalLimit = 0;
}
if (!settings?.productListIndex) {
settings.productListIndex = 0;
}
return settings;
}
async connectedCallback() {
const productListConfig = this.config.productListConfigs;
this.classList.add('commercetools-product-catalog');
if (
!productListConfig.itemsPerPage ||
productListConfig.itemsPerPage <= 0
) {
new Drupal.Message().add(
Drupal.t('Invalid configuration for items per page.'),
{ type: 'error' },
);
return;
}
// Placeholder for the products data.
this.productsData = {
total: -1,
products: Array(productListConfig.itemsPerPage).fill({}),
};
}
applyProductListConfiguration(configuration) {
const productListConfig = this.config.productListConfigs;
let limitForThisCall;
if (productListConfig.totalLimit > 0) {
const totalItemsToShow = productListConfig.totalLimit;
const remainingItems =
totalItemsToShow - this.currentPage * productListConfig.itemsPerPage;
limitForThisCall = Math.min(
productListConfig.itemsPerPage,
remainingItems,
);
} else {
limitForThisCall = productListConfig.itemsPerPage;
}
const productQueryParams = {
offset: this.currentPage * productListConfig.itemsPerPage,
limit: limitForThisCall,
};
if (productListConfig.sortBy || productListConfig.sortOrder) {
productQueryParams.sorts = `${productListConfig.sortBy} ${productListConfig.sortOrder}`;
}
const activeFilters = this.prepareFilters();
configuration.queryFilters = [];
activeFilters.forEach((filter) => configuration.queryFilters.push(filter));
return { ...configuration, ...productQueryParams };
}
applyProductListToFilters() {
// Loading the real products data.
const url = new URL(window.location);
this.currentPage = Number(url.searchParams.get('page')) || 0;
this.updateProductData(url.searchParams);
}
setLoadingState(state) {
this.productList.setLoadingState(state);
}
renderComponent() {
if (this.isReloading) {
this.setLoadingState('reload');
return;
}
this.innerHTML = /* html */ `
<div class="container">
<div class="row">
<div class="col-md-12 col-12 ps-0">
<div class="ct-product-catalog"></div>
</div>
</div>
<div class="ct-pager"></div>
</div>
`;
const productListConfig = this.config.productListConfigs;
this.productList = document.createElement(
`ct-product-${this.config.productListConfigs.style}`,
);
this.productList.products = this.productsData.products;
this.productList.isLoading = this.isLoading;
this.productList.columnsNumber =
this.config.productListConfigs.columnsNumber;
if (productListConfig.totalLimit <= 0) {
productListConfig.totalLimit = this.productsData.total;
}
this.querySelector('.ct-product-catalog').appendChild(this.productList);
if (this.productsData.products.length > 0) {
const pager = document.createElement('ct-pager');
const totalPages = Math.ceil(
Math.min(productListConfig.totalLimit, this.productsData.total) /
productListConfig.itemsPerPage,
);
if (totalPages > 1) {
pager.setPager(this.currentPage, totalPages);
pager.addEventListener('page-change', this.handlePageChange.bind(this));
this.querySelector('.ct-pager').appendChild(pager);
}
} else if (this.currentPage > 0) {
// If we're not on the first page - showing that the page does not exist.
this.innerHTML = '<ct-page-not-found>';
} else {
this.innerHTML = '<ct-no-products-found>';
}
this.initialLoad = false;
this.isLoading = false;
}
async handlePageChange(event) {
this.currentPage = event.detail.page;
const urlParams = new URLSearchParams(window.location.search);
urlParams.set('page', this.currentPage);
window.commercetools.resetProductListResult(this.config.productListIndex);
try {
await this.updateProductData(urlParams);
} catch (error) {
new Drupal.Message().add(
Drupal.t('An error occurred while changing the page.'),
{
type: 'error',
},
);
throw error;
}
}
handlerFilterChange(e) {
const { productListIndex } = e.detail;
if (productListIndex !== e.detail.productListIndex) {
return;
}
const { newUrl } = e.detail;
this.currentPage = 0;
this.updateProductData(newUrl.searchParams);
}
async updateProductData(newSearchParams = null) {
const productListConfig = this.config.productListConfigs;
this.isLoading = true;
this.isReloading = !this.initialLoad;
this.renderComponent();
if (this.config.totalLimit > 0) {
const totalItemsToShow = this.config.totalLimit;
const maxPageNumber = Math.ceil(
totalItemsToShow / this.config.itemsPerPage,
);
if (this.currentPage >= maxPageNumber) {
this.productsData.products = [];
this.isLoading = false;
this.renderComponent();
return;
}
}
try {
const response = await window.commercetools.getProductListResult(
this.config.productListIndex,
);
if (productListConfig.totalLimit > 0) {
response.total = Math.min(response.total, productListConfig.totalLimit);
}
this.productsData = response;
// Trigger an event to update other dependent components.
const changeEvent = new CustomEvent('ct.product-catalog.response', {
detail: {
productListIndex: this.config.productListIndex,
activeFilters: this.activeFilters,
response,
},
});
document.dispatchEvent(changeEvent);
} finally {
this.isLoading = false;
this.isReloading = false;
this.renderComponent();
if (newSearchParams) {
const url = Drupal.url(
`${drupalSettings.path.currentPath}?${newSearchParams.toString()}`,
);
window.history.pushState({ path: url }, '', url);
}
}
}
prepareFilters() {
const productListConfig = this.config.productListConfigs;
const activeFilters = [];
// Handle category filters
let categoryValues = [];
if (
productListConfig.categories &&
Object.keys(productListConfig.categories).length > 0
) {
categoryValues = [
...new Set(
Object.values(productListConfig.categories).map((categoryId) =>
String(categoryId),
),
),
];
} else {
// If no category options provided in configuration, check the query.
const categoryInQuery = window.commercetools.getQueryParamByIndex(
'category',
this.config.productListIndex,
);
if (categoryInQuery) {
categoryValues.push(String(categoryInQuery));
}
}
if (categoryValues.length > 0) {
activeFilters.push(
window.commercetools.buildFilter(
'categories.id',
categoryValues,
'tree',
),
);
}
// Handle filtering by SKU.
if (productListConfig.skus && productListConfig.skus.length > 0) {
const skuValues = productListConfig.skus.map((sku) => String(sku));
activeFilters.push(
window.commercetools.buildFilter('variants.sku', skuValues),
);
}
if (productListConfig.customFilters) {
const conditions = Array.isArray(productListConfig.customFilters)
? productListConfig.customFilters
: [productListConfig.customFilters];
conditions.forEach((condition) => {
if (typeof condition === 'string') {
condition = JSON.parse(condition);
}
activeFilters.push(condition);
});
}
return activeFilters;
}
}
customElements.define('ct-product-catalog', ProductCatalog);
