commercetools-8.x-1.2-alpha1/modules/commercetools_decoupled/components/blocks/filters-block/js/FiltersBlock.js
modules/commercetools_decoupled/components/blocks/filters-block/js/FiltersBlock.js
class FiltersBlock extends HTMLElement {
constructor() {
super();
this.filtersForm = null;
this.activeFilters = {};
this.activeSorts = {};
this.filterSubmitHandler = this.filterSubmitHandler.bind(this);
this.applyProductListToFilters = this.applyProductListToFilters.bind(this);
document.addEventListener(
'DOMContentLoaded',
this.applyProductListToFilters,
);
document.addEventListener(
'ct.filters.change',
this.applyProductListToFilters,
);
}
connectedCallback() {
const filtersForm = document.createElement('ct-filters-form');
filtersForm.isLoading = true;
this.append(filtersForm);
this.componentConfig = window.commercetools.getComponentConfig(this);
const url = new URL(window.location);
this.setActiveFilters(url.searchParams);
this.setActiveSorts(url.searchParams);
}
applyProductListConfiguration(configuration) {
const facets = this.componentConfig.productListConfigs.filters
.filter((filter) => {
return ['facet', 'facet_count', 'custom'].includes(filter.widget_type);
})
.map((facetObj) => facetObj.graphql);
configuration.facets = !configuration.facets
? facets
: FiltersBlock.mergeFacetsByPath(configuration.facets, facets);
// Apply sorting from URL if available.
if (this.activeSorts && this.activeSorts?.sorts?.length > 0) {
configuration.sorts = this.activeSorts.sorts;
}
if (!this.activeFilters || Object.keys(this.activeFilters).length === 0) {
return configuration;
}
configuration.queryFilters = configuration.queryFilters || [];
Object.keys(this.activeFilters).forEach((filterName) => {
const filterValues = this.activeFilters[filterName];
if (filterName === 'variants.price.centAmount') {
const type = 'range';
if (Array.isArray(filterValues)) {
const range = {
from: filterValues.from || '*',
to: filterValues.to || '*',
};
configuration.queryFilters.push(
window.commercetools.buildFilter(filterName, range, type),
);
}
} else {
configuration.queryFilters.push(
window.commercetools.buildFilter(filterName, filterValues),
);
}
});
return configuration;
}
static mergeFacetsByPath(oldFacets, newFacets) {
const facetMap = {};
// Index existing old facets by path
oldFacets.forEach((facet) => {
const { path } = facet.model.terms;
facetMap[path] = facet;
});
// Merge or overwrite with new facets
newFacets.forEach((facet) => {
const { path } = facet.model.terms;
if (facetMap[path]) {
const oldCount = facetMap[path].model.terms.countProducts;
const newCount = facet.model.terms.countProducts;
facet.model.terms.countProducts = oldCount || newCount;
}
facetMap[path] = facet;
});
return Object.values(facetMap);
}
applyProductListToFilters() {
if (this.filtersForm) {
this.filtersForm.setLoadingState('reload');
}
const url = new URL(window.location);
this.setActiveFilters(url.searchParams);
const response = window.commercetools.getProductListResult(
this.componentConfig.productListIndex,
);
response.then((response) => {
this.renderFiltersForm(response);
});
}
setActiveFilters(params) {
this.activeFilters = {};
const paramName = window.commercetools.getParamNameByIndex(
'filters',
this.componentConfig.productListIndex,
);
// This matches all variations: filters[path], filters[path][subkey], filters[path][]
const regex = new RegExp(`^${paramName}\\[(.+?)\\](?:\\[(.*)\\])?$`);
params.forEach((filterValue, key) => {
const match = key.match(regex);
if (match) {
const [, path, subkey] = match;
// Initialize the path if it doesn't exist.
if (!this.activeFilters[path]) {
this.activeFilters[path] = [];
}
// Handle different cases based on subkey.
if (subkey === '' || subkey === undefined) {
// Case: filters[path] or filters[path][].
this.activeFilters[path].push(filterValue);
} else {
// Case: filters[path][from], filters[path][to].
if (!Array.isArray(this.activeFilters[path])) {
this.activeFilters[path] = [];
}
if (!this.activeFilters[path][subkey]) {
this.activeFilters[path][subkey] = '';
}
// Convert price values when setting them.
const processedValue = window.commercetools.convertPrice(
filterValue,
this.filtersForm?.fractionDigits ?? 0,
'multiply',
);
this.activeFilters[path][subkey] = processedValue;
}
}
});
return this.activeFilters;
}
setActiveSorts(params) {
this.activeSorts = {};
const paramName = window.commercetools.getParamNameByIndex(
'sorts',
this.componentConfig.productListIndex,
);
params.forEach((filterValue, key) => {
if (paramName === key) {
this.activeSorts[key] = filterValue;
}
});
return this.activeSorts;
}
renderFiltersForm(response) {
this.innerHTML = '';
this.filtersForm = document.createElement('ct-filters-form');
this.filtersForm.productListIndex = this.componentConfig.productListIndex;
this.filtersForm.filters =
this.componentConfig.productListConfigs.filters || [];
this.filtersForm.activeFilters = this.activeFilters;
this.filtersForm.filterSubmitHandler = this.filterSubmitHandler;
this.filtersForm.response = response;
// Get fraction digits from first product's master variant price.
if (response?.products?.length > 0) {
this.filtersForm.fractionDigits =
response.products[0].masterVariant?.price?.fractionDigits ?? 0;
}
this.append(this.filtersForm);
}
filterSubmitHandler(e) {
e.preventDefault();
const newUrl = window.commercetools.generateNewUrl(e.currentTarget);
this.setActiveFilters(newUrl.searchParams);
this.setActiveSorts(newUrl.searchParams);
newUrl.searchParams.delete('page');
if (
this.componentConfig.targetPage.trim() !== '' &&
this.componentConfig.targetPage !== newUrl.pathname
) {
newUrl.pathname = this.componentConfig.targetPage;
window.location = newUrl.toString();
}
window.history.pushState(
{ path: newUrl.toString() },
'',
newUrl.toString(),
);
window.commercetools.resetProductListResult(
this.componentConfig.productListIndex,
);
const changeEvent = new CustomEvent('ct.filters.change', {
detail: {
productListIndex: this.componentConfig.productListIndex,
newUrl,
},
});
document.dispatchEvent(changeEvent);
const response = window.commercetools.getProductListResult(
this.componentConfig.productListIndex,
);
response.then((response) => {
this.renderFiltersForm(response);
});
}
}
customElements.define('ct-filters-block', FiltersBlock);
