diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productcollection/ProductCollectionImpl.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productcollection/ProductCollectionImpl.java new file mode 100644 index 0000000000..d5a8cafd21 --- /dev/null +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productcollection/ProductCollectionImpl.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + ******************************************************************************/ +package com.adobe.cq.commerce.core.components.internal.models.v1.productcollection; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.PostConstruct; +import javax.inject.Inject; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.models.annotations.Model; +import org.apache.sling.models.annotations.injectorspecific.ScriptVariable; +import org.apache.sling.models.annotations.injectorspecific.Self; + +import com.adobe.cq.commerce.core.components.models.common.ProductListItem; +import com.adobe.cq.commerce.core.components.models.productcollection.ProductCollection; +import com.adobe.cq.commerce.core.components.services.UrlProvider; +import com.adobe.cq.commerce.core.components.utils.SiteNavigation; +import com.adobe.cq.commerce.core.search.internal.models.SearchOptionsImpl; +import com.adobe.cq.commerce.core.search.internal.models.SearchResultsSetImpl; +import com.adobe.cq.commerce.core.search.models.SearchResultsSet; +import com.adobe.cq.commerce.core.search.services.SearchResultsService; +import com.day.cq.wcm.api.Page; +import com.day.cq.wcm.api.designer.Style; + +import static com.adobe.cq.commerce.core.search.internal.models.SearchOptionsImpl.PAGE_SIZE_DEFAULT; + +@Model( + adaptables = SlingHttpServletRequest.class, + adapters = ProductCollection.class, + resourceType = ProductCollectionImpl.RESOURCE_TYPE) +public class ProductCollectionImpl implements ProductCollection { + protected static final String RESOURCE_TYPE = "core/cif/components/commerce/productcollection/v1/productcollection"; + protected static final boolean LOAD_CLIENT_PRICE_DEFAULT = true; + protected Page productPage; + protected boolean loadClientPrice; + protected int navPageSize; + @Self + protected SlingHttpServletRequest request; + @ScriptVariable + protected ValueMap properties; + @ScriptVariable + protected Style currentStyle; + @Inject + protected Resource resource; + @Inject + protected Page currentPage; + @Inject + protected SearchResultsService searchResultsService; + @Inject + protected UrlProvider urlProvider; + protected SearchOptionsImpl searchOptions; + protected SearchResultsSet searchResultsSet; + + @PostConstruct + private void baseInitModel() { + navPageSize = properties.get(PN_PAGE_SIZE, currentStyle.get(PN_PAGE_SIZE, PAGE_SIZE_DEFAULT)); + loadClientPrice = properties.get(PN_LOAD_CLIENT_PRICE, currentStyle.get(PN_LOAD_CLIENT_PRICE, LOAD_CLIENT_PRICE_DEFAULT)); + // get product template page + productPage = SiteNavigation.getProductPage(currentPage); + if (productPage == null) { + productPage = currentPage; + } + } + + public Integer calculateCurrentPageCursor(final String currentPageIndexCandidate) { + // make sure the current page from the query string is reasonable i.e. numeric and over 0 + try { + int i = Integer.parseInt(currentPageIndexCandidate); + if (i < 1) { + i = 1; + } + return i; + } catch (NumberFormatException x) { + return 1; + } + } + + @Override + public boolean loadClientPrice() { + return loadClientPrice; + } + + protected Map createFilterMap(final Map parameterMap) { + Map filters = new HashMap<>(); + parameterMap.forEach((code, value) -> { + // we'll make sure there is a value defined for the key + if (value.length != 1) { + return; + } + + filters.put(code, value[0]); + }); + + return filters; + } + + @Nonnull + @Override + public Collection getProducts() { + return getSearchResultsSet().getProductListItems(); + } + + @Nonnull + @Override + public SearchResultsSet getSearchResultsSet() { + if (searchResultsSet == null) { + searchResultsSet = new SearchResultsSetImpl(); + } + return searchResultsSet; + } +} diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/ProductListImpl.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/ProductListImpl.java index ab66bf9836..e498bae8fd 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/ProductListImpl.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/ProductListImpl.java @@ -16,47 +16,39 @@ import java.io.IOException; import java.util.Collection; -import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.function.Consumer; import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.PostConstruct; -import javax.inject.Inject; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.sling.api.SlingHttpServletRequest; -import org.apache.sling.api.resource.Resource; -import org.apache.sling.api.resource.ValueMap; import org.apache.sling.models.annotations.Model; import org.apache.sling.models.annotations.injectorspecific.ScriptVariable; -import org.apache.sling.models.annotations.injectorspecific.Self; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.adobe.cq.commerce.core.components.client.MagentoGraphqlClient; +import com.adobe.cq.commerce.core.components.internal.models.v1.productcollection.ProductCollectionImpl; import com.adobe.cq.commerce.core.components.models.common.ProductListItem; import com.adobe.cq.commerce.core.components.models.productlist.ProductList; import com.adobe.cq.commerce.core.components.models.retriever.AbstractCategoryRetriever; -import com.adobe.cq.commerce.core.components.services.UrlProvider; import com.adobe.cq.commerce.core.components.services.UrlProvider.CategoryIdentifierType; -import com.adobe.cq.commerce.core.components.utils.SiteNavigation; import com.adobe.cq.commerce.core.search.internal.converters.ProductToProductListItemConverter; import com.adobe.cq.commerce.core.search.internal.models.SearchOptionsImpl; import com.adobe.cq.commerce.core.search.internal.models.SearchResultsSetImpl; import com.adobe.cq.commerce.core.search.models.SearchResultsSet; -import com.adobe.cq.commerce.core.search.services.SearchResultsService; import com.adobe.cq.commerce.magento.graphql.CategoryProducts; import com.adobe.cq.commerce.magento.graphql.ProductInterfaceQuery; import com.adobe.cq.sightly.SightlyWCMMode; -import com.day.cq.wcm.api.Page; -import com.day.cq.wcm.api.designer.Style; @Model(adaptables = SlingHttpServletRequest.class, adapters = ProductList.class, resourceType = ProductListImpl.RESOURCE_TYPE) -public class ProductListImpl implements ProductList { +public class ProductListImpl extends ProductCollectionImpl implements ProductList { protected static final String RESOURCE_TYPE = "core/cif/components/commerce/productlist/v1/productlist"; protected static final String PLACEHOLDER_DATA = "productlist-component-placeholder-data.json"; @@ -65,41 +57,14 @@ public class ProductListImpl implements ProductList { private static final boolean SHOW_TITLE_DEFAULT = true; private static final boolean SHOW_IMAGE_DEFAULT = true; - private static final boolean LOAD_CLIENT_PRICE_DEFAULT = true; - private Page productPage; private boolean showTitle; private boolean showImage; - private boolean loadClientPrice; - private int navPageSize; - - @Self - private SlingHttpServletRequest request; - - @ScriptVariable - private ValueMap properties; - - @ScriptVariable - private Style currentStyle; @ScriptVariable(name = "wcmmode") - private SightlyWCMMode wcmMode; - - @Inject - private Resource resource; - - @Inject - private Page currentPage; - - @Inject - private SearchResultsService searchResultsService; - - @Inject - private UrlProvider urlProvider; + private SightlyWCMMode wcmMode = null; private AbstractCategoryRetriever categoryRetriever; - private SearchOptionsImpl searchOptions; - private SearchResultsSet searchResultsSet; private boolean usePlaceholderData; @PostConstruct @@ -107,8 +72,6 @@ private void initModel() { // read properties showTitle = properties.get(PN_SHOW_TITLE, currentStyle.get(PN_SHOW_TITLE, SHOW_TITLE_DEFAULT)); showImage = properties.get(PN_SHOW_IMAGE, currentStyle.get(PN_SHOW_IMAGE, SHOW_IMAGE_DEFAULT)); - navPageSize = properties.get(PN_PAGE_SIZE, currentStyle.get(PN_PAGE_SIZE, SearchOptionsImpl.PAGE_SIZE_DEFAULT)); - loadClientPrice = properties.get(PN_LOAD_CLIENT_PRICE, currentStyle.get(PN_LOAD_CLIENT_PRICE, LOAD_CLIENT_PRICE_DEFAULT)); String currentPageIndexCandidate = request.getParameter(SearchOptionsImpl.CURRENT_PAGE_PARAMETER_ID); // make sure the current page from the query string is reasonable i.e. numeric and over 0 @@ -116,12 +79,6 @@ private void initModel() { Map searchFilters = createFilterMap(request.getParameterMap()); - // get product template page - productPage = SiteNavigation.getProductPage(currentPage); - if (productPage == null) { - productPage = currentPage; - } - MagentoGraphqlClient magentoGraphqlClient = MagentoGraphqlClient.create(resource); // Parse category identifier from URL @@ -183,11 +140,6 @@ public boolean showImage() { return showImage; } - @Override - public boolean loadClientPrice() { - return loadClientPrice; - } - @Nonnull @Override public Collection getProducts() { @@ -196,30 +148,14 @@ public Collection getProducts() { ProductToProductListItemConverter converter = new ProductToProductListItemConverter(productPage, request, urlProvider); return categoryProducts.getItems().stream() .map(converter) - .filter(p -> p != null) // the converter returns null if the conversion fails + .filter(Objects::nonNull) // the converter returns null if the conversion fails .collect(Collectors.toList()); } else { return getSearchResultsSet().getProductListItems(); } } - protected Map createFilterMap(final Map parameterMap) { - Map filters = new HashMap<>(); - parameterMap.entrySet().forEach(filterCandidate -> { - String code = filterCandidate.getKey(); - String[] value = filterCandidate.getValue(); - - // we'll make sure there is a value defined for the key - if (value.length != 1) { - return; - } - - filters.put(code, value[0]); - }); - - return filters; - } - + @Nonnull @Override public SearchResultsSet getSearchResultsSet() { if (searchResultsSet == null) { @@ -229,16 +165,8 @@ public SearchResultsSet getSearchResultsSet() { return searchResultsSet; } - protected Integer calculateCurrentPageCursor(final String currentPageIndexCandidate) { - // make sure the current page from the query string is reasonable i.e. numeric and over 0 - return StringUtils.isNumeric(currentPageIndexCandidate) && Integer.valueOf(currentPageIndexCandidate) > 0 - ? Integer.parseInt(currentPageIndexCandidate) - : 1; - } - @Override public AbstractCategoryRetriever getCategoryRetriever() { return categoryRetriever; } - } diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/searchresults/SearchResultsImpl.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/searchresults/SearchResultsImpl.java index 22154f7bed..0b2a1d7e5b 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/searchresults/SearchResultsImpl.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/searchresults/SearchResultsImpl.java @@ -16,33 +16,22 @@ import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.List; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; -import javax.inject.Inject; import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.SlingHttpServletRequest; -import org.apache.sling.api.resource.Resource; -import org.apache.sling.api.resource.ValueMap; import org.apache.sling.models.annotations.Model; -import org.apache.sling.models.annotations.injectorspecific.ScriptVariable; -import org.apache.sling.models.annotations.injectorspecific.Self; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.adobe.cq.commerce.core.components.internal.models.v1.productcollection.ProductCollectionImpl; import com.adobe.cq.commerce.core.components.models.common.ProductListItem; import com.adobe.cq.commerce.core.components.models.searchresults.SearchResults; -import com.adobe.cq.commerce.core.components.utils.SiteNavigation; import com.adobe.cq.commerce.core.search.internal.models.SearchOptionsImpl; -import com.adobe.cq.commerce.core.search.models.SearchAggregation; import com.adobe.cq.commerce.core.search.models.SearchResultsSet; -import com.adobe.cq.commerce.core.search.services.SearchResultsService; -import com.day.cq.wcm.api.Page; -import com.day.cq.wcm.api.designer.Style; /** * Concrete implementation of the {@link SearchResults} Sling Model API @@ -51,51 +40,19 @@ adaptables = SlingHttpServletRequest.class, adapters = SearchResults.class, resourceType = SearchResultsImpl.RESOURCE_TYPE) -public class SearchResultsImpl implements SearchResults { - - static final String RESOURCE_TYPE = "core/cif/components/commerce/searchresults"; - +public class SearchResultsImpl extends ProductCollectionImpl implements SearchResults { private static final Logger LOGGER = LoggerFactory.getLogger(SearchResultsImpl.class); - private static final boolean LOAD_CLIENT_PRICE_DEFAULT = true; + static final String RESOURCE_TYPE = "core/cif/components/commerce/searchresults"; - private boolean loadClientPrice; - private int navPageSize; - private Page searchPage; - private Page productPage; private String searchTerm; - private SearchOptionsImpl searchOptions; - private SearchResultsSet searchResultsSet; - - @Self - private SlingHttpServletRequest request; - - @ScriptVariable - private ValueMap properties; - - @ScriptVariable - private Style currentStyle; - - @Inject - private Resource resource; - - @Inject - private Page currentPage; - - @Inject - private SearchResultsService searchResultsService; @PostConstruct protected void initModel() { - navPageSize = properties.get(PN_PAGE_SIZE, currentStyle.get(PN_PAGE_SIZE, SearchOptionsImpl.PAGE_SIZE_DEFAULT)); - loadClientPrice = properties.get(PN_LOAD_CLIENT_PRICE, currentStyle.get(PN_LOAD_CLIENT_PRICE, LOAD_CLIENT_PRICE_DEFAULT)); - searchTerm = request.getParameter(SearchOptionsImpl.SEARCH_QUERY_PARAMETER_ID); // make sure the current page from the query string is reasonable i.e. numeric and over 0 Integer currentPageIndex = calculateCurrentPageCursor(request.getParameter(SearchOptionsImpl.CURRENT_PAGE_PARAMETER_ID)); - productPage = SiteNavigation.getProductPage(currentPage); - searchPage = SiteNavigation.getSearchResultsPage(currentPage); Map searchFilters = createFilterMap(request.getParameterMap()); LOGGER.debug("Detected search parameter {}", searchTerm); @@ -107,38 +64,9 @@ protected void initModel() { searchOptions.setSearchQuery(searchTerm); } - protected Integer calculateCurrentPageCursor(final String currentPageIndexCandidate) { - // make sure the current page from the query string is reasonable i.e. numeric and over 0 - return StringUtils.isNumeric(currentPageIndexCandidate) && Integer.valueOf(currentPageIndexCandidate) > 0 - ? Integer.valueOf(currentPageIndexCandidate) - : 1; - } - - @Nonnull - @Override - public String getSearchResultsPagePath() { - return searchPage.getPath(); - } - protected Map createFilterMap(final Map parameterMap) { - Map filters = new HashMap<>(); - parameterMap.entrySet().forEach(filterCandidate -> { - String code = filterCandidate.getKey(); - String[] value = filterCandidate.getValue(); - - // we'll remove the search filter - if (code.equalsIgnoreCase(SearchOptionsImpl.SEARCH_QUERY_PARAMETER_ID)) { - return; - } - - // we'll make sure there is a value defined for the key - if (value.length != 1) { - return; - } - - filters.put(code, value[0]); - }); - + Map filters = super.createFilterMap(parameterMap); + filters.remove(SearchOptionsImpl.SEARCH_QUERY_PARAMETER_ID); return filters; } @@ -155,12 +83,6 @@ public Collection getProducts() { return getSearchResultsSet().getProductListItems(); } - @Nonnull - @Override - public List getAggregations() { - return getSearchResultsSet().getSearchAggregations(); - } - @Nonnull @Override public SearchResultsSet getSearchResultsSet() { @@ -169,9 +91,4 @@ public SearchResultsSet getSearchResultsSet() { } return searchResultsSet; } - - @Override - public boolean loadClientPrice() { - return loadClientPrice; - } } diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/productcollection/ProductCollection.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/productcollection/ProductCollection.java new file mode 100644 index 0000000000..6b2acb5905 --- /dev/null +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/productcollection/ProductCollection.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + ******************************************************************************/ +package com.adobe.cq.commerce.core.components.models.productcollection; + +import java.util.Collection; + +import javax.annotation.Nonnull; + +import com.adobe.cq.commerce.core.components.models.common.ProductListItem; +import com.adobe.cq.commerce.core.search.models.SearchResultsSet; + +public interface ProductCollection { + /** + * Name of the String resource property indicating number of products to render on front-end. + */ + String PN_PAGE_SIZE = "pageSize"; + /** + * Name of the boolean resource property indicating if the product list should load prices on the client-side. + */ + String PN_LOAD_CLIENT_PRICE = "loadClientPrice"; + + /** + * Returns the product list's items collection, as {@link ProductListItem}s elements. + * + * @return {@link Collection} of {@link ProductListItem}s + */ + @Nonnull + Collection getProducts(); + + /** + * Get the search result set. This is the actual search result data. + * + * @return the result of the search + */ + @Nonnull + SearchResultsSet getSearchResultsSet(); + + /** + * Should prices be re-loaded client-side. + * + * @return true if prices should be loaded client side + */ + boolean loadClientPrice(); +} diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/productlist/ProductList.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/productlist/ProductList.java index a1905ac911..a942a6a15c 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/productlist/ProductList.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/productlist/ProductList.java @@ -14,16 +14,12 @@ package com.adobe.cq.commerce.core.components.models.productlist; -import java.util.Collection; - -import javax.annotation.Nonnull; import javax.annotation.Nullable; -import com.adobe.cq.commerce.core.components.models.common.ProductListItem; +import com.adobe.cq.commerce.core.components.models.productcollection.ProductCollection; import com.adobe.cq.commerce.core.components.models.retriever.AbstractCategoryRetriever; -import com.adobe.cq.commerce.core.search.models.SearchResultsSet; -public interface ProductList { +public interface ProductList extends ProductCollection { /** * Name of the boolean resource property indicating if the product list should render the category title. @@ -35,24 +31,6 @@ public interface ProductList { */ String PN_SHOW_IMAGE = "showImage"; - /** - * Name of the String resource property indicating number of products to render on front-end. - */ - String PN_PAGE_SIZE = "pageSize"; - - /** - * Name of the boolean resource property indicating if the product list should load prices on the client-side. - */ - String PN_LOAD_CLIENT_PRICE = "loadClientPrice"; - - /** - * Returns the product list's items collection, as {@link ProductListItem}s elements. - * - * @return {@link Collection} of {@link ProductListItem}s - */ - @Nonnull - Collection getProducts(); - /** * Returns {@code true} if the category / product list title should be rendered. * @@ -72,10 +50,6 @@ public interface ProductList { boolean showImage(); - boolean loadClientPrice(); - - SearchResultsSet getSearchResultsSet(); - /** * Returns in instance of the category retriever for fetching category data via GraphQL. * diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/searchresults/SearchResults.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/searchresults/SearchResults.java index 57aec0b341..1c5519e260 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/searchresults/SearchResults.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/searchresults/SearchResults.java @@ -13,67 +13,11 @@ ******************************************************************************/ package com.adobe.cq.commerce.core.components.models.searchresults; -import java.util.Collection; -import java.util.List; - -import javax.annotation.Nonnull; - -import com.adobe.cq.commerce.core.components.models.common.ProductListItem; -import com.adobe.cq.commerce.core.search.models.SearchAggregation; -import com.adobe.cq.commerce.core.search.models.SearchResultsSet; +import com.adobe.cq.commerce.core.components.models.productcollection.ProductCollection; /** * Don't forget the comment */ -public interface SearchResults { - - /** - * Name of the String resource property indicating number of products to render on front-end. - */ - String PN_PAGE_SIZE = "pageSize"; - - /** - * Name of the boolean resource property indicating if the product list should load prices on the client-side. - */ - String PN_LOAD_CLIENT_PRICE = "loadClientPrice"; - - /** - * Returns the product list's items collection, as {@link ProductListItem}s elements. - * - * @return {@link Collection} of {@link ProductListItem}s - */ - @Nonnull - Collection getProducts(); - - /** - * Returns the aggregations resulting from the search, as {@link SearchAggregation}s elements. - * - * @return {@link List} of {@link SearchAggregation}s - */ - @Nonnull - List getAggregations(); - - /** - * Get the search result set. This is the actual search result data. - * - * @return the result of the search - */ - @Nonnull - SearchResultsSet getSearchResultsSet(); - - /** - * This is the path to the search page. - * - * @return the search page page - */ - @Nonnull - String getSearchResultsPagePath(); - - /** - * Should prices be re-loaded client-side. - * - * @return true if prices should be loaded client side - */ - boolean loadClientPrice(); +public interface SearchResults extends ProductCollection { } diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/models/SearchOptionsImpl.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/models/SearchOptionsImpl.java index 29ef0306cd..ff78d63b22 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/models/SearchOptionsImpl.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/models/SearchOptionsImpl.java @@ -64,7 +64,6 @@ public void setAttributeFilters(final Map attributeFilters) { this.attributeFilters = attributeFilters; } - @Override public Optional getCategoryId() { return Optional.ofNullable(categoryId); } diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/models/SearchResultsSetImpl.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/models/SearchResultsSetImpl.java index d9d99b6104..35777b22e8 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/models/SearchResultsSetImpl.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/models/SearchResultsSetImpl.java @@ -18,7 +18,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.stream.Collectors; import javax.annotation.Nonnull; @@ -35,6 +34,7 @@ public class SearchResultsSetImpl implements SearchResultsSet { private Integer totalResults; private List productListItems = new ArrayList<>(); private List searchAggregations = new ArrayList<>(); + private Pager pager; @Nonnull @Override @@ -63,7 +63,10 @@ public List getSearchAggregations() { @Nonnull @Override public Pager getPager() { - return new PagerImpl(getAppliedQueryParameters(), getTotalPages(), getSearchOptions().getCurrentPage()); + if (pager == null) { + pager = new PagerImpl(getAppliedQueryParameters(), getTotalPages(), getSearchOptions().getCurrentPage()); + } + return pager; } private int getTotalPages() { @@ -78,10 +81,8 @@ public Map getAppliedQueryParameters() { if (searchOptions == null) { return new HashMap<>(); } - Map appliedParameters = new HashMap<>(searchOptions.getAllFilters()); - searchOptions.getSearchQuery().ifPresent(query -> appliedParameters.put(SearchOptionsImpl.SEARCH_QUERY_PARAMETER_ID, query)); - return appliedParameters; + return searchOptions.getAllFilters(); } public void setTotalResults(final Integer totalResults) { @@ -121,9 +122,13 @@ public List getAppliedAggregations() { .collect(Collectors.toList()); } - @Nonnull @Override - public Optional getSearchQuery() { - return getSearchOptions().getSearchQuery(); + public boolean hasAggregations() { + return !searchAggregations.isEmpty(); + } + + @Override + public boolean hasPagination() { + return getTotalPages() > 1; } } diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/services/SearchResultsServiceImpl.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/services/SearchResultsServiceImpl.java index 46883cbc78..320ed9efb9 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/services/SearchResultsServiceImpl.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/services/SearchResultsServiceImpl.java @@ -67,7 +67,7 @@ public class SearchResultsServiceImpl implements SearchResultsService { SearchFilterService searchFilterService; @Reference - private UrlProvider urlProvider; + private UrlProvider urlProvider = null; private MagentoGraphqlClient magentoGraphqlClient; diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/models/SearchOptions.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/models/SearchOptions.java index 8cc5db0b2b..1a6d42cbf8 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/models/SearchOptions.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/models/SearchOptions.java @@ -26,8 +26,6 @@ public interface SearchOptions { Optional getSearchQuery(); - Optional getCategoryId(); - int getCurrentPage(); int getPageSize(); diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/models/SearchResultsSet.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/models/SearchResultsSet.java index 32f5d65971..5c05328786 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/models/SearchResultsSet.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/models/SearchResultsSet.java @@ -16,7 +16,6 @@ import java.util.List; import java.util.Map; -import java.util.Optional; import javax.annotation.Nonnull; @@ -47,14 +46,6 @@ public interface SearchResultsSet { @Nonnull Integer getTotalResults(); - /** - * Get the search query used to provide this result set. - * - * @return the search query string used if any - */ - @Nonnull - Optional getSearchQuery(); - /** * Get a map of the applied search query string parameters. * @@ -103,4 +94,13 @@ public interface SearchResultsSet { @Nonnull Pager getPager(); + /** + * @return {@code true} if the result set provides search aggregations for faceted search support, {@code false} otherwise + */ + boolean hasAggregations(); + + /** + * @return {@code true} if the result set provides pagination, {@code false} otherwise + */ + boolean hasPagination(); } diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/productcollection/ProductCollectionImplTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/productcollection/ProductCollectionImplTest.java new file mode 100644 index 0000000000..1fe8600217 --- /dev/null +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/productcollection/ProductCollectionImplTest.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + ******************************************************************************/ + +package com.adobe.cq.commerce.core.components.internal.models.v1.productcollection; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.scripting.SlingBindings; +import org.apache.sling.servlethelpers.MockRequestPathInfo; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.xss.XSSAPI; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import com.adobe.cq.commerce.core.components.internal.services.MockUrlProviderConfiguration; +import com.adobe.cq.commerce.core.components.internal.services.UrlProviderImpl; +import com.adobe.cq.commerce.core.components.services.UrlProvider; +import com.adobe.cq.commerce.core.search.internal.models.SearchOptionsImpl; +import com.adobe.cq.commerce.core.search.internal.services.SearchFilterServiceImpl; +import com.adobe.cq.commerce.core.search.internal.services.SearchResultsServiceImpl; +import com.adobe.cq.sightly.SightlyWCMMode; +import com.day.cq.wcm.api.Page; +import com.day.cq.wcm.api.designer.Style; +import com.day.cq.wcm.scripting.WCMBindingsConstants; +import io.wcm.testing.mock.aem.junit.AemContext; +import io.wcm.testing.mock.aem.junit.AemContextCallback; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class ProductCollectionImplTest { + + @Rule + public final AemContext context = createContext("/context/jcr-content.json"); + + private static AemContext createContext(String contentPath) { + return new AemContext( + (AemContextCallback) context -> { + // Load page structure + context.load().json(contentPath, "/content"); + + UrlProviderImpl urlProvider = new UrlProviderImpl(); + urlProvider.activate(new MockUrlProviderConfiguration()); + context.registerService(UrlProvider.class, urlProvider); + + context.registerInjectActivateService(new SearchFilterServiceImpl()); + context.registerInjectActivateService(new SearchResultsServiceImpl()); + }, + ResourceResolverType.JCR_MOCK); + } + + private static final String PAGE = "/content/pageA"; + private static final String PRODUCT_COLLECTION = "/content/pageA/jcr:content/root/responsivegrid/productcollection"; + private static final String PRODUCT_COLLECTION2 = "/content/pageA/jcr:content/root/responsivegrid/productcollection2"; + + private ProductCollectionImpl productCollectionModel; + + @Before + public void setUp() { + + context.currentResource(PRODUCT_COLLECTION); + + MockRequestPathInfo requestPathInfo = (MockRequestPathInfo) context.request().getRequestPathInfo(); + requestPathInfo.setSelectorString("6"); + + // This sets the page attribute injected in the models with @Inject or @ScriptVariable + Resource productCollectionResource = context.resourceResolver().getResource(PRODUCT_COLLECTION); + SlingBindings slingBindings = getSlingBindings(PRODUCT_COLLECTION); + + XSSAPI xssApi = mock(XSSAPI.class); + when(xssApi.filterHTML(Mockito.anyString())).then(i -> i.getArgumentAt(0, String.class)); + slingBindings.put("xssApi", xssApi); + + Style style = mock(Style.class); + when(style.get(Mockito.anyString(), Mockito.anyInt())).then(i -> i.getArgumentAt(1, Object.class)); + slingBindings.put("currentStyle", style); + + SightlyWCMMode wcmMode = mock(SightlyWCMMode.class); + when(wcmMode.isDisabled()).thenReturn(false); + slingBindings.put("wcmmode", wcmMode); + } + + private SlingBindings getSlingBindings(String resourcePath) { + Page page = context.currentPage(PAGE); + Resource productCollectionResource = context.resourceResolver().getResource(resourcePath); + SlingBindings slingBindings = (SlingBindings) context.request().getAttribute(SlingBindings.class.getName()); + slingBindings.setResource(productCollectionResource); + slingBindings.put(WCMBindingsConstants.NAME_CURRENT_PAGE, page); + slingBindings.put(WCMBindingsConstants.NAME_PROPERTIES, productCollectionResource.getValueMap()); + return slingBindings; + } + + @Test + public void testCreateFilterMap() { + productCollectionModel = context.request().adaptTo(ProductCollectionImpl.class); + + Map queryParameters = new HashMap<>(); + queryParameters.put("color", new String[] {}); + Map filterMap = productCollectionModel.createFilterMap(queryParameters); + Assert.assertEquals("filters out query parameters without values", 0, filterMap.size()); + + queryParameters = new HashMap<>(); + queryParameters.put("color", new String[] { "123" }); + filterMap = productCollectionModel.createFilterMap(queryParameters); + Assert.assertEquals("retails valid query filters", 1, filterMap.size()); + } + + @Test + public void testCalculateCurrentPageCursor() { + productCollectionModel = context.request().adaptTo(ProductCollectionImpl.class); + Assert.assertEquals("negative page indexes are not allowed", 1, productCollectionModel.calculateCurrentPageCursor("-1").intValue()); + Assert.assertEquals("null value is dealt with", 1, productCollectionModel.calculateCurrentPageCursor(null).intValue()); + Assert.assertEquals("non numeric value is dealt with", 1, productCollectionModel.calculateCurrentPageCursor("a").intValue()); + Assert.assertEquals("extra large value is dealt with", 1, + productCollectionModel.calculateCurrentPageCursor("99999999999999").intValue()); + } + + @Test + public void testDefaultProperties() { + productCollectionModel = context.request().adaptTo(ProductCollectionImpl.class); + Assert.assertEquals(ProductCollectionImpl.LOAD_CLIENT_PRICE_DEFAULT, productCollectionModel.loadClientPrice()); + Assert.assertEquals(SearchOptionsImpl.PAGE_SIZE_DEFAULT.intValue(), productCollectionModel.navPageSize); + } + + @Test + public void testProperties() { + getSlingBindings(PRODUCT_COLLECTION2); + productCollectionModel = context.request().adaptTo(ProductCollectionImpl.class); + Assert.assertFalse(productCollectionModel.loadClientPrice()); + Assert.assertEquals(8, productCollectionModel.navPageSize); + } + + @Test + public void testStubMethods() { + productCollectionModel = context.request().adaptTo(ProductCollectionImpl.class); + Assert.assertNotNull(productCollectionModel.getSearchResultsSet()); + Assert.assertNotNull(productCollectionModel.getSearchResultsSet().getProductListItems()); + Assert.assertTrue(productCollectionModel.getSearchResultsSet().getProductListItems().isEmpty()); + Assert.assertNotNull(productCollectionModel.getProducts()); + Assert.assertTrue(productCollectionModel.getProducts().isEmpty()); + + } +} diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/ProductListImplTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/ProductListImplTest.java index 8cef7f7c7f..2a927fb733 100644 --- a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/ProductListImplTest.java +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/ProductListImplTest.java @@ -18,10 +18,8 @@ import java.text.NumberFormat; import java.util.Collection; import java.util.Currency; -import java.util.HashMap; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.stream.Collectors; import org.apache.http.HttpStatus; @@ -231,31 +229,6 @@ public void getProducts() { } } - @Test - public void testCreateFilterMap() { - productListModel = context.request().adaptTo(ProductListImpl.class); - - Map queryParameters; - Map filterMap; - - queryParameters = new HashMap<>(); - queryParameters.put("color", new String[] {}); - filterMap = productListModel.createFilterMap(queryParameters); - Assert.assertEquals("filters out query parameters without values", 0, filterMap.size()); - - queryParameters = new HashMap<>(); - queryParameters.put("color", new String[] { "123" }); - filterMap = productListModel.createFilterMap(queryParameters); - Assert.assertEquals("retails valid query filters", 1, filterMap.size()); - } - - @Test - public void testCalculateCurrentPageCursor() { - productListModel = context.request().adaptTo(ProductListImpl.class); - Assert.assertEquals("negative page indexes are not allowed", 1, productListModel.calculateCurrentPageCursor("-1").intValue()); - Assert.assertEquals("null value is dealt with", 1, productListModel.calculateCurrentPageCursor(null).intValue()); - } - @Test public void testFilterQueriesReturnNull() throws IOException { // We want to make sure that components will not fail if the __type and/or customAttributeMetadata fields are null diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/searchresults/SearchResultsImplTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/searchresults/SearchResultsImplTest.java index e193abe3a4..db3cb6bb31 100644 --- a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/searchresults/SearchResultsImplTest.java +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/searchresults/SearchResultsImplTest.java @@ -171,30 +171,12 @@ public void testNoMagentoGraphqlClient() { @Test public void testCreateFilterMap() { searchResultsModel = context.request().adaptTo(SearchResultsImpl.class); - Map queryParameters; - Map filterMap; - queryParameters = new HashMap<>(); + Map queryParameters = new HashMap<>(); queryParameters.put("search_query", new String[] { "ok" }); - filterMap = searchResultsModel.createFilterMap(queryParameters); - Assert.assertEquals("filters query string parameter out correctly", 0, filterMap.size()); - - queryParameters = new HashMap<>(); - queryParameters.put("color", new String[] {}); - filterMap = searchResultsModel.createFilterMap(queryParameters); - Assert.assertEquals("filters out query parameters without values", 0, filterMap.size()); - - queryParameters = new HashMap<>(); - queryParameters.put("color", new String[] { "123" }); - filterMap = searchResultsModel.createFilterMap(queryParameters); - Assert.assertEquals("retails valid query filters", 1, filterMap.size()); - } + Map filterMap = searchResultsModel.createFilterMap(queryParameters); - @Test - public void testCalculateCurrentPageCursor() { - searchResultsModel = context.request().adaptTo(SearchResultsImpl.class); - Assert.assertEquals("negative page indexes are not allowed", 1, searchResultsModel.calculateCurrentPageCursor("-1").intValue()); - Assert.assertEquals("null value is dealt with", 1, searchResultsModel.calculateCurrentPageCursor(null).intValue()); + Assert.assertEquals("filters query string parameter out correctly", 0, filterMap.size()); } @Test diff --git a/bundles/core/src/test/resources/context/jcr-content.json b/bundles/core/src/test/resources/context/jcr-content.json index 7ef85d5376..9f000252aa 100644 --- a/bundles/core/src/test/resources/context/jcr-content.json +++ b/bundles/core/src/test/resources/context/jcr-content.json @@ -130,6 +130,14 @@ "categoryId": "7" } } + }, + "productcollection": { + "sling:resourceType": "venia/components/commerce/productcollection" + }, + "productcollection2": { + "loadClientPrice": false, + "pageSize": 8, + "sling:resourceType": "venia/components/commerce/productcollection" } } } diff --git a/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/.content.xml b/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/.content.xml new file mode 100644 index 0000000000..6a16f8bdee --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/.content.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/_cq_design_dialog/.content.xml b/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/_cq_design_dialog/.content.xml new file mode 100644 index 0000000000..c728f736b4 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/_cq_design_dialog/.content.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + diff --git a/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/_cq_dialog/.content.xml b/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/_cq_dialog/.content.xml new file mode 100644 index 0000000000..99c9cda72c --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/_cq_dialog/.content.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + diff --git a/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productlist/v1/productlist/clientlibs/.content.xml b/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/clientlibs/.content.xml similarity index 83% rename from ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productlist/v1/productlist/clientlibs/.content.xml rename to ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/clientlibs/.content.xml index cad6c691a1..71a77d7d1d 100644 --- a/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productlist/v1/productlist/clientlibs/.content.xml +++ b/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/clientlibs/.content.xml @@ -2,6 +2,6 @@ diff --git a/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productlist/v1/productlist/clientlibs/js.txt b/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/clientlibs/js.txt similarity index 100% rename from ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productlist/v1/productlist/clientlibs/js.txt rename to ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/clientlibs/js.txt diff --git a/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productlist/v1/productlist/clientlibs/js/productlist.js b/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/clientlibs/js/productcollection.js similarity index 90% rename from ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productlist/v1/productlist/clientlibs/js/productlist.js rename to ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/clientlibs/js/productcollection.js index a6ffbd8fc9..a4c1055cc9 100644 --- a/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productlist/v1/productlist/clientlibs/js/productlist.js +++ b/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/clientlibs/js/productcollection.js @@ -13,7 +13,7 @@ ******************************************************************************/ 'use strict'; -class ProductList { +class ProductCollection { constructor(config) { this._element = config.element; @@ -33,7 +33,7 @@ class ProductList { this._formatter = window.CIF && window.CIF.PriceFormatter && new window.CIF.PriceFormatter(this._element.dataset.locale); - this._element.querySelectorAll(ProductList.selectors.item).forEach(item => { + this._element.querySelectorAll(ProductCollection.selectors.item).forEach(item => { this._state.skus.push(item.dataset.sku); }); @@ -90,7 +90,7 @@ class ProductList { } _updatePrices() { - this._element.querySelectorAll(ProductList.selectors.item).forEach(item => { + this._element.querySelectorAll(ProductCollection.selectors.item).forEach(item => { if (!(item.dataset.sku in this._state.prices)) return; const price = this._state.prices[item.dataset.sku]; @@ -140,22 +140,22 @@ class ProductList { } } - item.querySelector(ProductList.selectors.price).innerHTML = innerHTML; + item.querySelector(ProductCollection.selectors.price).innerHTML = innerHTML; }); } } -ProductList.selectors = { - self: '[data-cmp-is=productlist]', +ProductCollection.selectors = { + self: '[data-cmp-is=productcollection]', price: '.price', item: '.item__root[role=product]' }; (function(document) { function onDocumentReady() { - // Initialize product list component - const productListCmp = document.querySelector(ProductList.selectors.self); - if (productListCmp) new ProductList({ element: productListCmp }); + // Initialize product collection component + const productCollectionCmp = document.querySelector(ProductCollection.selectors.self); + if (productCollectionCmp) new ProductCollection({ element: productCollectionCmp }); } if (document.readyState !== 'loading') { @@ -165,4 +165,4 @@ ProductList.selectors = { } })(window.document); -export default ProductList; +export default ProductCollection; diff --git a/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productlist/v1/productlist/layerednavigation.html b/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/facetselector.html similarity index 98% rename from ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productlist/v1/productlist/layerednavigation.html rename to ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/facetselector.html index cb8d9eac8f..7e7f6e69db 100644 --- a/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productlist/v1/productlist/layerednavigation.html +++ b/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v1/productcollection/facetselector.html @@ -12,7 +12,7 @@ ~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/--> -