You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by as...@apache.org on 2009/11/12 13:54:23 UTC

svn commit: r835372 - in /ofbiz/trunk: applications/product/src/org/ofbiz/product/product/ specialpurpose/ecommerce/config/ specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/actions/catalog/ specialpurpose/ecommerce/webapp/ecommerce/catalog/ specialpur...

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="<@o...@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='<@o...@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}</#...@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}</#...@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="<@o...@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}</#...@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}</#...@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"/>