Author: ashish
Date: Thu Nov 12 12:54:22 2009
New Revision: 835372
URL: http://svn.apache.org/viewvc?rev=835372&view=rev
Log:
Applied patch from jira issue OFBIZ-3186 - Layered Navigation of Categories.
Layered navigation in an Ecommerce application allows the user to filter the
product listing based on categories, features and price ranges. The user can
apply multiple filters to the given product listing. He can narrow or expand
his search and try different filter combinations so as to find the desired
product.
I have implemented Layered Navigation in OFBiz ecommerce with following filters:
1. Sub Categories: This filter is shown whenever there are sub-categories
present under the top-level category or current selected category. Count of
products within the category is shown against each category, works with
multilevel deep category hierarchy. Selecting a sub category will show products
within that category and filters will be updated accordingly for the new
product list.
2. Features: Feature filters are grouped by Feature Types. Count of products
that has the feature associated as standard, selectable or distinguishing is
shown against that feature. Current Implementation has feature filters for
ProductFeatureType="COLOR".
3. List Price Range: This filter will always show. When applied filters
product based on List Price Range. Current implementation is done by listing
static price ranges at code level.
The above implementation is completely based on the OOTB Product Search
functionality with few additional methods in ProductSearchSession to derive
count against different type of filters.
This is collaborative contribution from Mridul Pathak, Jacopo Cappellato &
Scott Gray.
Thanks Guys for such a nice contribution - I am sure community will love seeing
the existence of "Layered Navigation" in OFBiz trunk.
Added:
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/actions/catalog/LayeredNavigation.groovy
(with props)
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/LayeredCategoryDetail.ftl
(with props)
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/LayeredNavBar.ftl
(with props)
Modified:
ofbiz/trunk/applications/product/src/org/ofbiz/product/product/ProductSearchSession.java
ofbiz/trunk/specialpurpose/ecommerce/config/EcommerceUiLabels.xml
ofbiz/trunk/specialpurpose/ecommerce/widget/CatalogScreens.xml
ofbiz/trunk/specialpurpose/ecommerce/widget/CommonScreens.xml
Modified:
ofbiz/trunk/applications/product/src/org/ofbiz/product/product/ProductSearchSession.java
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/src/org/ofbiz/product/product/ProductSearchSession.java?rev=835372&r1=835371&r2=835372&view=diff
==============================================================================
---
ofbiz/trunk/applications/product/src/org/ofbiz/product/product/ProductSearchSession.java
(original)
+++
ofbiz/trunk/applications/product/src/org/ofbiz/product/product/ProductSearchSession.java
Thu Nov 12 12:54:22 2009
@@ -49,6 +49,8 @@
import org.ofbiz.entity.GenericValue;
import org.ofbiz.entity.condition.EntityCondition;
import org.ofbiz.entity.condition.EntityOperator;
+import org.ofbiz.entity.model.DynamicViewEntity;
+import org.ofbiz.entity.model.ModelKeyMap;
import org.ofbiz.entity.util.EntityFindOptions;
import org.ofbiz.entity.util.EntityListIterator;
import org.ofbiz.entity.util.EntityUtil;
@@ -1156,4 +1158,219 @@
return searchParamString.toString();
}
+
+ /**
+ * This method returns a list of productId counts grouped by
productFeatureId's of input productFeatureTypeId,
+ * the constraint being applied on current ProductSearchConstraint list in
session.
+ * @param productFeatureTypeId The productFeatureTypeId,
productFeatureId's of which should be considered.
+ * @param session Current session.
+ * @param delegator The delegator object.
+ * @return List of Maps containing productFeatureId, productFeatureTypeId,
description, featureCount.
+ */
+ public static List<Map<String, String>> listCountByFeatureForType(String
productFeatureTypeId, HttpSession session, Delegator delegator) {
+ String visitId = VisitHandler.getVisitId(session);
+
+ ProductSearchContext productSearchContext = new
ProductSearchContext(delegator, visitId);
+ List<ProductSearchConstraint> productSearchConstraintList =
ProductSearchOptions.getConstraintList(session);
+ if (UtilValidate.isNotEmpty(productSearchConstraintList)) {
+
productSearchContext.addProductSearchConstraints(productSearchConstraintList);
+ }
+ productSearchContext.finishKeywordConstraints();
+ productSearchContext.finishCategoryAndFeatureConstraints();
+
+ DynamicViewEntity dynamicViewEntity =
productSearchContext.dynamicViewEntity;
+ List<EntityCondition> entityConditionList =
productSearchContext.entityConditionList;
+ List<String> fieldsToSelect = FastList.newInstance();
+
+ dynamicViewEntity.addMemberEntity("PFAC", "ProductFeatureAppl");
+ dynamicViewEntity.addAlias("PFAC", "pfacProductFeatureId",
"productFeatureId", null, null, Boolean.TRUE, null);
+ dynamicViewEntity.addAlias("PFAC", "pfacFromDate", "fromDate", null,
null, null, null);
+ dynamicViewEntity.addAlias("PFAC", "pfacThruDate", "thruDate", null,
null, null, null);
+ dynamicViewEntity.addAlias("PFAC", "featureCount", "productId", null,
null, null, "count");
+ dynamicViewEntity.addViewLink("PROD", "PFAC", Boolean.FALSE,
ModelKeyMap.makeKeyMapList("productId"));
+ fieldsToSelect.add("pfacProductFeatureId");
+ fieldsToSelect.add("featureCount");
+
entityConditionList.add(EntityCondition.makeCondition(EntityCondition.makeCondition("pfacThruDate",
EntityOperator.EQUALS, null), EntityOperator.OR,
EntityCondition.makeCondition("pfacThruDate", EntityOperator.GREATER_THAN,
UtilDateTime.nowTimestamp())));
+ entityConditionList.add(EntityCondition.makeCondition("pfacFromDate",
EntityOperator.LESS_THAN, UtilDateTime.nowTimestamp()));
+
+ dynamicViewEntity.addMemberEntity("PFC", "ProductFeature");
+ dynamicViewEntity.addAlias("PFC", "pfcProductFeatureTypeId",
"productFeatureTypeId", null, null, Boolean.TRUE, null);
+ dynamicViewEntity.addAlias("PFC", "pfcDescription", "description",
null, null, Boolean.TRUE, null);
+ dynamicViewEntity.addViewLink("PFAC", "PFC", Boolean.FALSE,
ModelKeyMap.makeKeyMapList("productFeatureId"));
+ fieldsToSelect.add("pfcDescription");
+ fieldsToSelect.add("pfcProductFeatureTypeId");
+
entityConditionList.add(EntityCondition.makeCondition("pfcProductFeatureTypeId",
EntityOperator.EQUALS, productFeatureTypeId));
+
+ EntityCondition whereCondition =
EntityCondition.makeCondition(entityConditionList, EntityOperator.AND);
+
+ EntityFindOptions efo = new EntityFindOptions();
+ efo.setResultSetType(EntityFindOptions.TYPE_SCROLL_INSENSITIVE);
+
+ EntityListIterator eli = null;
+ try {
+ eli = delegator.findListIteratorByCondition(dynamicViewEntity,
whereCondition, null, fieldsToSelect, productSearchContext.orderByList, efo);
+ } catch (GenericEntityException e) {
+ Debug.logError(e, "Error in product search", module);
+ return null;
+ }
+
+ List<Map<String, String>> featureCountList = FastList.newInstance();
+ GenericValue searchResult = null;
+ while ((searchResult = (GenericValue) eli.next()) != null) {
+ featureCountList.add(UtilMisc.toMap("productFeatureId", (String)
searchResult.get("pfacProductFeatureId"), "productFeatureTypeId", (String)
searchResult.get("pfcProductFeatureTypeId"), "description", (String)
searchResult.get("pfcDescription"), "featureCount", Long.toString((Long)
searchResult.get("featureCount"))));
+ }
+
+ if (eli != null) {
+ try {
+ eli.close();
+ } catch (GenericEntityException e) {
+ Debug.logError(e, "Error closing ProductSearch
EntityListIterator");
+ }
+ }
+ return featureCountList;
+ }
+
+ public static int getCategoryCostraintIndex(HttpSession session) {
+ int index = 0;
+ List<ProductSearchConstraint> productSearchConstraintList =
ProductSearchOptions.getConstraintList(session);
+ for (ProductSearchConstraint constraint: productSearchConstraintList) {
+ if (constraint instanceof CategoryConstraint) {
+ index++;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * This method returns count of products within a given price range, the
constraint being
+ * applied on current ProductSearchConstraint list in session.
+ * @param priceLow The low price.
+ * @param priceHigh The high price.
+ * @param session Current session.
+ * @param delegator The delegator object.
+ * @return The long value of count of products.
+ */
+ public static long getCountForListPriceRange(BigDecimal priceLow,
BigDecimal priceHigh, HttpSession session, Delegator delegator) {
+ String visitId = VisitHandler.getVisitId(session);
+
+ ProductSearchContext productSearchContext = new
ProductSearchContext(delegator, visitId);
+ List<ProductSearchConstraint> productSearchConstraintList =
ProductSearchOptions.getConstraintList(session);
+ if (UtilValidate.isNotEmpty(productSearchConstraintList)) {
+
productSearchContext.addProductSearchConstraints(productSearchConstraintList);
+ }
+ productSearchContext.finishKeywordConstraints();
+ productSearchContext.finishCategoryAndFeatureConstraints();
+
+ DynamicViewEntity dynamicViewEntity =
productSearchContext.dynamicViewEntity;
+ List<EntityCondition> entityConditionList =
productSearchContext.entityConditionList;
+ List<String> fieldsToSelect = FastList.newInstance();
+
+ dynamicViewEntity.addMemberEntity("PPC", "ProductPrice");
+ dynamicViewEntity.addAlias("PPC", "ppcProductPriceTypeId",
"productPriceTypeId", null, null, null, null);
+ dynamicViewEntity.addAlias("PPC", "ppcFromDate", "fromDate", null,
null, null, null);
+ dynamicViewEntity.addAlias("PPC", "ppcThruDate", "thruDate", null,
null, null, null);
+ dynamicViewEntity.addAlias("PPC", "ppcPrice", "price", null, null,
null, null);
+ dynamicViewEntity.addAlias("PPC", "priceRangeCount", "productId",
null, null, null, "count");
+ dynamicViewEntity.addViewLink("PROD", "PPC", Boolean.FALSE,
ModelKeyMap.makeKeyMapList("productId"));
+ fieldsToSelect.add("priceRangeCount");
+
entityConditionList.add(EntityCondition.makeCondition(EntityCondition.makeCondition("ppcThruDate",
EntityOperator.EQUALS, null), EntityOperator.OR,
EntityCondition.makeCondition("ppcThruDate", EntityOperator.GREATER_THAN,
UtilDateTime.nowTimestamp())));
+ entityConditionList.add(EntityCondition.makeCondition("ppcFromDate",
EntityOperator.LESS_THAN, UtilDateTime.nowTimestamp()));
+ entityConditionList.add(EntityCondition.makeCondition("ppcPrice",
EntityOperator.GREATER_THAN_EQUAL_TO, priceLow));
+ entityConditionList.add(EntityCondition.makeCondition("ppcPrice",
EntityOperator.LESS_THAN_EQUAL_TO, priceHigh));
+
entityConditionList.add(EntityCondition.makeCondition("ppcProductPriceTypeId",
EntityOperator.EQUALS, "LIST_PRICE"));
+
+ EntityCondition whereCondition =
EntityCondition.makeCondition(entityConditionList, EntityOperator.AND);
+
+ EntityFindOptions efo = new EntityFindOptions();
+ efo.setResultSetType(EntityFindOptions.TYPE_SCROLL_INSENSITIVE);
+
+ EntityListIterator eli = null;
+ try {
+ eli = delegator.findListIteratorByCondition(dynamicViewEntity,
whereCondition, null, fieldsToSelect, productSearchContext.orderByList, efo);
+ } catch (GenericEntityException e) {
+ Debug.logError(e, "Error in product search", module);
+ return 0;
+ }
+
+ GenericValue searchResult = null;
+ Long priceRangeCount = Long.valueOf(0);
+ while ((searchResult = (GenericValue) eli.next()) != null) {
+ priceRangeCount = searchResult.getLong("priceRangeCount");
+ }
+
+ if (eli != null) {
+ try {
+ eli.close();
+ } catch (GenericEntityException e) {
+ Debug.logError(e, "Error closing ProductSearch
EntityListIterator");
+ }
+ }
+ return priceRangeCount;
+ }
+
+ /**
+ * This method returns count of products in a given category (including
all sub categories), the constraint being
+ * applied on current ProductSearchConstraint list in session.
+ * @param productCategoryId productCategoryId for which the count should
be returned.
+ * @param session Current session.
+ * @param delegator The delegator object.
+ * @return The long value of count of products.
+ */
+ public static long getCountForProductCategory(String productCategoryId,
HttpSession session, Delegator delegator) {
+ String visitId = VisitHandler.getVisitId(session);
+
+ ProductSearchContext productSearchContext = new
ProductSearchContext(delegator, visitId);
+ List<ProductSearchConstraint> productSearchConstraintList =
ProductSearchOptions.getConstraintList(session);
+ if (UtilValidate.isNotEmpty(productSearchConstraintList)) {
+
productSearchContext.addProductSearchConstraints(productSearchConstraintList);
+ }
+ productSearchContext.finishKeywordConstraints();
+ productSearchContext.finishCategoryAndFeatureConstraints();
+
+ DynamicViewEntity dynamicViewEntity =
productSearchContext.dynamicViewEntity;
+ List<EntityCondition> entityConditionList =
productSearchContext.entityConditionList;
+ List<String> fieldsToSelect = FastList.newInstance();
+
+ dynamicViewEntity.addMemberEntity("PCMC", "ProductCategoryMember");
+ dynamicViewEntity.addAlias("PCMC", "pcmcProductCategoryId",
"productCategoryId", null, null, null, null);
+ dynamicViewEntity.addAlias("PCMC", "pcmcFromDate", "fromDate", null,
null, null, null);
+ dynamicViewEntity.addAlias("PCMC", "pcmcThruDate", "thruDate", null,
null, null, null);
+ dynamicViewEntity.addAlias("PCMC", "categoryCount", "productId", null,
null, null, "count");
+ dynamicViewEntity.addViewLink("PROD", "PCMC", Boolean.FALSE,
ModelKeyMap.makeKeyMapList("productId"));
+ fieldsToSelect.add("categoryCount");
+
entityConditionList.add(EntityCondition.makeCondition(EntityCondition.makeCondition("pcmcThruDate",
EntityOperator.EQUALS, null), EntityOperator.OR,
EntityCondition.makeCondition("pcmcThruDate", EntityOperator.GREATER_THAN,
productSearchContext.nowTimestamp)));
+ entityConditionList.add(EntityCondition.makeCondition("pcmcFromDate",
EntityOperator.LESS_THAN, productSearchContext.nowTimestamp));
+
+ Set<String> productCategoryIdSet = FastSet.newInstance();
+ ProductSearch.getAllSubCategoryIds(productCategoryId,
productCategoryIdSet, delegator, productSearchContext.nowTimestamp);
+
entityConditionList.add(EntityCondition.makeCondition("pcmcProductCategoryId",
EntityOperator.IN, productCategoryIdSet));
+
+ EntityCondition whereCondition =
EntityCondition.makeCondition(entityConditionList, EntityOperator.AND);
+
+ EntityFindOptions efo = new EntityFindOptions();
+ efo.setResultSetType(EntityFindOptions.TYPE_SCROLL_INSENSITIVE);
+
+ EntityListIterator eli = null;
+ try {
+ eli = delegator.findListIteratorByCondition(dynamicViewEntity,
whereCondition, null, fieldsToSelect, productSearchContext.orderByList, efo);
+ } catch (GenericEntityException e) {
+ Debug.logError(e, "Error in product search", module);
+ return 0;
+ }
+
+ GenericValue searchResult = null;
+ Long categoryCount = Long.valueOf(0);
+ while ((searchResult = (GenericValue) eli.next()) != null) {
+ categoryCount = searchResult.getLong("categoryCount");
+ }
+
+ if (eli != null) {
+ try {
+ eli.close();
+ } catch (GenericEntityException e) {
+ Debug.logError(e, "Error closing ProductSearch
EntityListIterator");
+ }
+ }
+ return categoryCount;
+ }
}
Modified: ofbiz/trunk/specialpurpose/ecommerce/config/EcommerceUiLabels.xml
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/ecommerce/config/EcommerceUiLabels.xml?rev=835372&r1=835371&r2=835372&view=diff
==============================================================================
--- ofbiz/trunk/specialpurpose/ecommerce/config/EcommerceUiLabels.xml (original)
+++ ofbiz/trunk/specialpurpose/ecommerce/config/EcommerceUiLabels.xml Thu Nov
12 12:54:22 2009
@@ -2156,6 +2156,9 @@
<value xml:lang="th">ราà¸à¸²</value>
<value xml:lang="zh">ä»·æ ¼</value>
</property>
+ <property key="EcommercePriceRange">
+ <value xml:lang="en">Price Range</value>
+ </property>
<property key="EcommercePrimaryBillingAddress">
<value xml:lang="da">Primær faktureringsadresse</value>
<value xml:lang="de">Haupt Rechnungsadresse</value>
Added:
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/actions/catalog/LayeredNavigation.groovy
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/actions/catalog/LayeredNavigation.groovy?rev=835372&view=auto
==============================================================================
---
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/actions/catalog/LayeredNavigation.groovy
(added)
+++
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/actions/catalog/LayeredNavigation.groovy
Thu Nov 12 12:54:22 2009
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * 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 CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.ofbiz.base.util.UtilHttp;
+import org.ofbiz.entity.util.EntityUtil;
+import org.ofbiz.product.catalog.CatalogWorker;
+import org.ofbiz.product.category.CategoryContentWrapper;
+import org.ofbiz.product.category.CategoryWorker;
+import org.ofbiz.product.product.ProductSearch;
+import org.ofbiz.product.product.ProductSearchSession;
+
+searchCategoryId = parameters.searchCategoryId;
+if (!searchCategoryId) {
+ searchCategoryId = context.productCategoryId;
+}
+if (searchCategoryId) {
+ currentSearchCategory = delegator.findOne("ProductCategory",
[productCategoryId: searchCategoryId], false);
+ CategoryWorker.getRelatedCategories(request, "subCategoryList",
searchCategoryId, false);
+ subCategoryList = request.getAttribute("subCategoryList");
+ CategoryContentWrapper categoryContentWrapper = new
CategoryContentWrapper(currentSearchCategory, request);
+ context.currentSearchCategory = currentSearchCategory;
+ context.categoryContentWrapper = categoryContentWrapper;
+}
+productCategoryId = context.productCategoryId;
+if (productCategoryId) {
+ context.productCategory = delegator.findOne("ProductCategory",
[productCategoryId: productCategoryId], false);
+ parameters.SEARCH_CATEGORY_ID = productCategoryId;
+}
+
+if (!parameters.clearSearch || !"N".equals(parameters.clearSearch)) {
+ ProductSearchSession.searchClear(session);
+}
+
+ProductSearchSession.processSearchParameters(parameters, request);
+prodCatalogId = CatalogWorker.getCurrentCatalogId(request);
+result = ProductSearchSession.getProductSearchResult(request, delegator,
prodCatalogId);
+
+context.index = ProductSearchSession.getCategoryCostraintIndex(session);
+
+searchConstraintList =
ProductSearchSession.getProductSearchOptions(session).getConstraintList();
+
+if (searchCategoryId) {
+ productCategoryRollups = delegator.findByAnd("ProductCategoryRollup",
[productCategoryId: searchCategoryId]);
+ productCategoryRollups = EntityUtil.filterByDate(productCategoryRollups);
+ previousCategoryId = null;
+ if (productCategoryRollups) {
+ for (GenericValue categoryRollup : productCategoryRollups) {
+ categoryConstraint = new
ProductSearch.CategoryConstraint(categoryRollup.parentProductCategoryId, true,
false);
+ if (searchConstraintList.contains(categoryConstraint)) {
+ previousCategoryId = categoryRollup.parentProductCategoryId;
+ context.previousCategoryId = previousCategoryId;
+ }
+ }
+ }
+}
+
+context.showSubCats = true;
+if (subCategoryList) {
+ thisSubCategoryList = [];
+ subCategoryList.each { subCategory ->
+ categoryCount =
ProductSearchSession.getCountForProductCategory(subCategory.productCategoryId,
session, delegator);
+ if (categoryCount > 0) {
+ subCategoryContentWrapper = new
CategoryContentWrapper(subCategory, request);
+ thisSubCategoryList.add([productCategoryId:
subCategory.productCategoryId, categoryName: subCategory.categoryName, count:
categoryCount, categoryContentWrapper: subCategoryContentWrapper]);
+ }
+ }
+ if (thisSubCategoryList) {
+ context.subCategoryList = thisSubCategoryList;
+ } else {
+ context.showSubCats = false;
+ }
+} else {
+ context.showSubCats = false;
+}
+
+context.showColors = true;
+colors = ProductSearchSession.listCountByFeatureForType("COLOR", session,
delegator);
+colorFeatureType = delegator.findOne("ProductFeatureType",
[productFeatureTypeId: "COLOR"], false);
+if (colors) {
+ colors.each { color ->
+ featureConstraint = new
ProductSearch.FeatureConstraint(color.productFeatureId, false);
+ if (searchConstraintList.contains(featureConstraint)) {
+ context.showColors=false;
+ }
+ }
+} else {
+ context.showColors = false;
+}
+if (context.showColors) {
+ context.colors = colors;
+ context.colorFeatureType = colorFeatureType;
+}
+
+availablePriceRangeList = [[low: "0", high: "10"], [low: "10", high: "20"],
[low: "20", high: "30"], [low: "30", high: "40"], [low: "40", high: "50"],
[low: "50", high: "60"], [low: "60", high: "70"], [low: "70", high: "80"],
[low: "80", high: "90"], [low: "90", high: "100"]];
+priceRangeList = [];
+context.showPriceRange = true;
+availablePriceRangeList.each { priceRange ->
+ priceRangeConstraint = new ProductSearch.ListPriceRangeConstraint(new
BigDecimal(priceRange.low), new BigDecimal(priceRange.high),
UtilHttp.getCurrencyUom(request));
+ if (searchConstraintList.contains(priceRangeConstraint)) {
+ context.showPriceRange = false;
+ } else {
+ priceRangeCount = ProductSearchSession.getCountForListPriceRange(new
BigDecimal(priceRange.low), new BigDecimal(priceRange.high), session,
delegator);
+ if (priceRangeCount != 0) {
+ priceRangeList.add([low: priceRange.low, high: priceRange.high,
count: priceRangeCount]);
+ }
+ }
+}
+if (!priceRangeList) {
+ context.showPriceRange = false;
+}
+if (context.showPriceRange) {
+ context.priceRangeList = priceRangeList;
+}
+
+context.productIds = result.productIds;
+context.viewIndex = result.viewIndex;
+context.viewSize = result.viewSize;
+context.listSize = result.listSize;
+context.lowIndex = result.lowIndex;
+context.highIndex = result.highIndex;
+context.paging = result.paging;
+context.previousViewSize = result.previousViewSize;
+context.searchConstraintStrings = result.searchConstraintStrings;
\ No newline at end of file
Propchange:
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/actions/catalog/LayeredNavigation.groovy
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/actions/catalog/LayeredNavigation.groovy
------------------------------------------------------------------------------
svn:keywords = Date Rev Author URL Id
Propchange:
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/actions/catalog/LayeredNavigation.groovy
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added:
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/LayeredCategoryDetail.ftl
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/LayeredCategoryDetail.ftl?rev=835372&view=auto
==============================================================================
---
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/LayeredCategoryDetail.ftl
(added)
+++
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/LayeredCategoryDetail.ftl
Thu Nov 12 12:54:22 2009
@@ -0,0 +1,107 @@
+<#--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+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 CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<#macro paginationControls>
+ <#assign viewIndexMax = Static["java.lang.Math"].ceil((listSize -
1)?double / viewSize?double)>
+ <#if (viewIndexMax?int > 0)>
+ <div class="product-prevnext">
+ <#-- Start Page Select Drop-Down -->
+ <select name="pageSelect"
onchange="window.location=this[this.selectedIndex].value;">
+ <option value="#">${uiLabelMap.CommonPage} ${viewIndex?int +
1} ${uiLabelMap.CommonOf} ${viewIndexMax + 1}</option>
+ <#list 0..viewIndexMax as curViewNum>
+ <option
value="<@ofbizUrl>category/~category_id=${productCategoryId}/~searchCategoryId=${currentSearchCategory.productCategoryId}/~VIEW_SIZE=${viewSize}/~VIEW_INDEX=${curViewNum?int}/~clearSearch=N</@ofbizUrl>">${uiLabelMap.CommonGotoPage}
${curViewNum + 1}</option>
+ </#list>
+ </select>
+ <#-- End Page Select Drop-Down -->
+ <#if (0 < viewIndex?int)>
+ <a
href="<@ofbizUrl>category/~category_id=${productCategoryId}/~searchCategoryId=${currentSearchCategory.productCategoryId}/~VIEW_SIZE=${viewSize}/~VIEW_INDEX=${viewIndex?int
- 1}/~clearSearch=N</@ofbizUrl>"
class="buttontext">${uiLabelMap.CommonPrevious}</a> |
+ </#if>
+ <#if ((listSize?int - viewSize?int) > 0)>
+ <span>${lowIndex + 1} - ${highIndex} ${uiLabelMap.CommonOf}
${listSize}</span>
+ </#if>
+ <#if highIndex?int < listSize?int>
+ | <a
href="<@ofbizUrl>category/~category_id=${productCategoryId}/~searchCategoryId=${currentSearchCategory.productCategoryId}/~VIEW_SIZE=${viewSize}/~VIEW_INDEX=${viewIndex?int
+ 1}/~clearSearch=N</@ofbizUrl>"
class="buttontext">${uiLabelMap.CommonNext}</a>
+ </#if>
+ </div>
+ </#if>
+</#macro>
+
+
+<#if productCategory?exists>
+ <#assign categoryName =
categoryContentWrapper.get("CATEGORY_NAME")?if_exists/>
+ <#assign categoryDescription =
categoryContentWrapper.get("DESCRIPTION")?if_exists/>
+ <#if categoryName?has_content>
+ <h1>${categoryName}</h1>
+ </#if>
+ <#if categoryDescription?has_content>
+ <h1>${categoryDescription}</h1>
+ </#if>
+ <#assign longDescription =
categoryContentWrapper.get("LONG_DESCRIPTION")?if_exists/>
+ <#assign categoryImageUrl =
categoryContentWrapper.get("CATEGORY_IMAGE_URL")?if_exists/>
+ <#if categoryImageUrl?string?has_content || longDescription?has_content>
+ <div>
+ <#if categoryImageUrl?string?has_content>
+ <#assign height=100/>
+ <img src='<@ofbizContentUrl>${categoryImageUrl}</@ofbizContentUrl>'
vspace='5' hspace='5' border='1' height='${height}' align='left'/>
+ </#if>
+ <#if longDescription?has_content>
+ ${longDescription}
+ </#if>
+ </div>
+ </#if>
+</#if>
+
+<#if productIds?has_content>
+ <@paginationControls/>
+ <#assign numCol = numCol?default(1)>
+ <#assign numCol = numCol?number>
+ <#assign tabCol = 1>
+ <div
+ <#if categoryImageUrl?string?has_content>
+ style="position: relative; margin-top: ${height}px;"
+ </#if>
+ class="productsummary-container<#if (numCol?int > 1)> matrix</#if>">
+ <#if (numCol?int > 1)>
+ <table>
+ </#if>
+ <#list productIds as productId>
+ <#if (numCol?int == 1)>
+ ${setRequestAttribute("optProductId", productId)}
+ ${setRequestAttribute("listIndex", productId_index)}
+ ${screens.render(productsummaryScreen)}
+ <#else>
+ <#if (tabCol?int = 1)><tr></#if>
+ <td>
+ ${setRequestAttribute("optProductId", productId)}
+ ${setRequestAttribute("listIndex", productId_index)}
+ ${screens.render(productsummaryScreen)}
+ </td>
+ <#if (tabCol?int = numCol)></tr></#if>
+ <#assign tabCol = tabCol+1><#if (tabCol?int > numCol)><#assign
tabCol = 1></#if>
+ </#if>
+ </#list>
+ <#if (numCol?int > 1)>
+ </table>
+ </#if>
+ </div>
+ <@paginationControls/>
+<#else>
+ <hr/>
+ <div>${uiLabelMap.ProductNoProductsInThisCategory}</div>
+</#if>
\ No newline at end of file
Propchange:
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/LayeredCategoryDetail.ftl
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/LayeredCategoryDetail.ftl
------------------------------------------------------------------------------
svn:keywords = Date Rev Author URL Id
Propchange:
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/LayeredCategoryDetail.ftl
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added:
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/LayeredNavBar.ftl
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/LayeredNavBar.ftl?rev=835372&view=auto
==============================================================================
---
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/LayeredNavBar.ftl
(added)
+++
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/LayeredNavBar.ftl
Thu Nov 12 12:54:22 2009
@@ -0,0 +1,71 @@
+<#--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+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 CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<#if currentSearchCategory?exists>
+ <div id="layeredNav" class="screenlet">
+ <h3>Layered Navigation</h3>
+ <#escape x as x?xml>
+ <#if productCategory.productCategoryId !=
currentSearchCategory.productCategoryId>
+ <#assign currentSearchCategoryName =
categoryContentWrapper.get("CATEGORY_NAME")?string />
+ <#list searchConstraintStrings as searchConstraintString>
+ <#if searchConstraintString.indexOf(currentSearchCategoryName) != -1>
+ <div id="searchConstraints"> <a
href="<@ofbizUrl>category/~category_id=${productCategoryId}?removeConstraint=${searchConstraintString_index}&clearSearch=N<#if
previousCategoryId?exists>&searchCategoryId=${previousCategoryId}</#if></@ofbizUrl>"
class="buttontext">X</a><#noescape> ${searchConstraintString}</#noescape></div>
+ </#if>
+ </#list>
+ </#if>
+ </#escape>
+ <#list searchConstraintStrings as searchConstraintString>
+ <#if searchConstraintString.indexOf("Category: ") = -1 &&
searchConstraintString != "Exclude Variants">
+ <div id="searchConstraints"> <a
href="<@ofbizUrl>category/~category_id=${productCategoryId}?removeConstraint=${searchConstraintString_index}&clearSearch=N<#if
currentSearchCategory?exists>&searchCategoryId=${currentSearchCategory.productCategoryId}</#if></@ofbizUrl>"
class="buttontext">X</a> ${searchConstraintString}</div>
+ </#if>
+ </#list>
+ <#if showSubCats>
+ <div id="searchFilter">
+ <strong>${uiLabelMap.ProductCategories}</strong>
+ <ul>
+ <#list subCategoryList as category>
+ <#assign subCategoryContentWrapper =
category.categoryContentWrapper />
+ <#assign categoryName =
subCategoryContentWrapper.get("CATEGORY_NAME")?if_exists?string />
+ <li><a
href="<@ofbizUrl>category/~category_id=${productCategoryId}?SEARCH_CATEGORY_ID${index}=${category.productCategoryId}&searchCategoryId=${category.productCategoryId}&clearSearch=N</@ofbizUrl>">${categoryName?if_exists}
(${category.count})</li>
+ </#list>
+ </ul>
+ </div>
+ </#if>
+ <#if showColors>
+ <div id="searchFilter">
+ <strong>${colorFeatureType.description}</strong>
+ <ul>
+ <#list colors as color>
+ <li><a
href="<@ofbizUrl>category/~category_id=${productCategoryId}?pft_${color.productFeatureTypeId}=${color.productFeatureId}&clearSearch=N<#if
currentSearchCategory?exists>&searchCategoryId=${currentSearchCategory.productCategoryId}</#if></@ofbizUrl>">${color.description}
(${color.featureCount})</li>
+ </#list>
+ </ul>
+ </div>
+ </#if>
+ <#if showPriceRange>
+ <div id="searchFilter">
+ <strong>${uiLabelMap.EcommercePriceRange}</strong>
+ <ul>
+ <#list priceRangeList as priceRange>
+ <li><a
href="<@ofbizUrl>category/~category_id=${productCategoryId}?LIST_PRICE_LOW=${priceRange.low}&LIST_PRICE_HIGH=${priceRange.high}&clearSearch=N<#if
currentSearchCategory?exists>&searchCategoryId=${currentSearchCategory.productCategoryId}</#if></@ofbizUrl>"><@ofbizCurrency
amount=priceRange.low /> - <@ofbizCurrency amount=priceRange.high />
(${priceRange.count})</a><li>
+ </#list>
+ </ul>
+ </div>
+ </#if>
+ </div>
+</#if>
\ No newline at end of file
Propchange:
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/LayeredNavBar.ftl
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/LayeredNavBar.ftl
------------------------------------------------------------------------------
svn:keywords = Date Rev Author URL Id
Propchange:
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/LayeredNavBar.ftl
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified: ofbiz/trunk/specialpurpose/ecommerce/widget/CatalogScreens.xml
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/ecommerce/widget/CatalogScreens.xml?rev=835372&r1=835371&r2=835372&view=diff
==============================================================================
--- ofbiz/trunk/specialpurpose/ecommerce/widget/CatalogScreens.xml (original)
+++ ofbiz/trunk/specialpurpose/ecommerce/widget/CatalogScreens.xml Thu Nov 12
12:54:22 2009
@@ -164,6 +164,10 @@
<set field="titleProperty" value="PageTitleCategoryPage"/>
<script
location="component://order/webapp/ordermgr/WEB-INF/actions/entry/catalog/Category.groovy"/>
+ <!-- Open this commented section for the demo of Layered
Navigation, navigate through Gizmo and Widgets categories to see it in action.
+ <script
location="component://ecommerce/webapp/ecommerce/WEB-INF/actions/catalog/LayeredNavigation.groovy"/>
+ <set field="detailScreen" value="LayeredCategoryDetail"/>
+ -->
</actions>
<widgets>
<decorator-screen name="main-decorator"
location="${parameters.mainDecoratorLocation}">
@@ -445,4 +449,21 @@
</widgets>
</section>
</screen>
+ <screen name="LayeredNavBar">
+ <section>
+ <widgets>
+ <platform-specific><html><html-template
location="component://ecommerce/webapp/ecommerce/catalog/LayeredNavBar.ftl"/></html></platform-specific>
+ </widgets>
+ </section>
+ </screen>
+ <screen name="LayeredCategoryDetail">
+ <section>
+ <actions>
+ <set field="productsummaryScreen"
value="component://ecommerce/widget/CatalogScreens.xml#productsummary"/>
+ </actions>
+ <widgets>
+ <platform-specific><html><html-template
location="component://ecommerce/webapp/ecommerce/catalog/LayeredCategoryDetail.ftl"/></html></platform-specific>
+ </widgets>
+ </section>
+ </screen>
</screens>
Modified: ofbiz/trunk/specialpurpose/ecommerce/widget/CommonScreens.xml
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/ecommerce/widget/CommonScreens.xml?rev=835372&r1=835371&r2=835372&view=diff
==============================================================================
--- ofbiz/trunk/specialpurpose/ecommerce/widget/CommonScreens.xml (original)
+++ ofbiz/trunk/specialpurpose/ecommerce/widget/CommonScreens.xml Thu Nov 12
12:54:22 2009
@@ -93,6 +93,7 @@
<include-screen name="choosecatalog"
location="component://ecommerce/widget/CatalogScreens.xml"/>
<include-screen name="keywordsearchbox"
location="component://ecommerce/widget/CatalogScreens.xml"/>
<include-screen name="sidedeepcategory"
location="component://ecommerce/widget/CatalogScreens.xml"/>
+ <include-screen name="LayeredNavBar"
location="component://ecommerce/widget/CatalogScreens.xml"/>
<include-screen name="minireorderprods"
location="component://ecommerce/widget/CatalogScreens.xml"/>
<include-screen name="signupforcontactlist"
location="component://ecommerce/widget/EmailContactListScreens.xml"/>
<include-screen name="minipoll"
location="component://ecommerce/widget/ContentScreens.xml"/>