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">&nbsp;<a 
href="<@ofbizUrl>category/~category_id=${productCategoryId}?removeConstraint=${searchConstraintString_index}&clearSearch=N<#if
 
previousCategoryId?exists>&searchCategoryId=${previousCategoryId}</#if></@ofbizUrl>"
 
class="buttontext">X</a><#noescape>&nbsp;${searchConstraintString}</#noescape></div>
+          </#if>
+        </#list>
+      </#if>
+    </#escape>
+    <#list searchConstraintStrings as searchConstraintString>
+      <#if searchConstraintString.indexOf("Category: ") = -1 && 
searchConstraintString != "Exclude Variants">
+        <div id="searchConstraints">&nbsp;<a 
href="<@ofbizUrl>category/~category_id=${productCategoryId}?removeConstraint=${searchConstraintString_index}&clearSearch=N<#if
 
currentSearchCategory?exists>&searchCategoryId=${currentSearchCategory.productCategoryId}</#if></@ofbizUrl>"
 class="buttontext">X</a>&nbsp;${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"/>


Reply via email to