Showing products from sub categories

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.
12 years ago
Hi All,

I have a category structure like so:

Rings
    - Eternity Rings
    - Engagement Rings
    - Wedding Rings

The way nopCommerce works is that if I click on the Rings page I see the sub categories listed, and then any of the products within the Rings category.

I'd like to change this so that by visiting the Rings page shows ALL products from either the parent i.e Rings, or any of the sub categories. The navigation allows the user to drill down further into a particular category if they wish.

Can this be done? The ProductService.GetAllProducts only takes a single categoryID so im not sure what the best approach is here.

Thanks in advance
Al
12 years ago
higgsy wrote:
Hi All,

I have a category structure like so:

Rings
    - Eternity Rings
    - Engagement Rings
    - Wedding Rings

The way nopCommerce works is that if I click on the Rings page I see the sub categories listed, and then any of the products within the Rings category.

I'd like to change this so that by visiting the Rings page shows ALL products from either the parent i.e Rings, or any of the sub categories. The navigation allows the user to drill down further into a particular category if they wish.

Can this be done? The ProductService.GetAllProducts only takes a single categoryID so im not sure what the best approach is here.

Thanks in advance
Al

Al:
Yes it can be done. Just assign all rings to category Rings as well as to their corresponding sub-category.
When in Rings page you will have the banners of each sub-categories and bellow the list of all your rings.
12 years ago
Hi Eduardo,

Thanks for your response. Is there no programmatic way of doing this? My concern is that I am depending on the website administrator to always ensure they add a product to both the parent and child categories - which isn't particularly obvious.

thanks
Al
12 years ago
Hi all,

Sorry to bump my own question - but does anyone know of a programtical way of achieving this? To clarify:

I have the following categories:

Rings
    - Wedding Rings

When a user clicks on the category for rings I want to display the products from both Rings and Wedding rings. At this point they are also presented with a menu which shows just the sub categories if for example they only wanted to see wedding rings.

Is the alternative to programatically set the product category from within the admin. i.e. an administrator adds a product to wedding rings - should I write some code that checks whether the category they are adding the product to has a parent, and if so add it to the parent aswell (seems like this could cause problems)...

thanks in advance
Al
12 years ago
Hi higgsy,

we have implemented this functionality a while ago. The example is from nopCommerce 2.0 but the solution will be similar if not exactly the same in the next versions.

In order to show all products from a given category and all subcategories beneath(to any level in depth), you should make the following changes:

1. Get the ids of all categories that you want to show the products from:

private void GetCategoriesIds(int categoryId, List<int> categoriesIds)
        {
            categoriesIds.Add(categoryId);

            var categories = Nop.Core.Infrastructure.EngineContext.Current.Resolve<ICategoryService>().GetAllCategoriesByParentCategoryId(categoryId);
            foreach (var category in categories)
            {
                GetCategoriesIds(category.Id, categoriesIds);
            }
        }

this method will give you all the necessary ids(the parameter categoryId will be the id of the Rings category).

2. Then you can use these ids in the following linq statement

//categories filtering - get the product if its ProductCategories is one of the given in which we are searching
            if (categoryId > 0 && categoriesIds != null && categoriesIds.Count > 0)
            {
                query = from p in query
                        from pc in p.ProductCategories.Where(pc => categoriesIds.Contains(pc.CategoryId))
            where (!featuredProducts.HasValue || featuredProducts.Value == pc.IsFeaturedProduct)
                        select p;
            }


We have implemented these as part of a plugin in order not to change the nopCommerce code, but for that reason more work is necessary to be done in order the paging and sorting to work on the Rings(in your example) category.


3. If you do not intend to isolate these changes in your own assemblies you can make the necessary changes in the ProductService.SearchProducts method in nopCommerce. Just in the beginning of the method get the list of the ids of the categories and then replace the default linq

   //category filtering
            if (categoryId > 0)
            {
                       query = from p in query
                        from pc in p.ProductCategories.Where(pc => pc.CategoryId == categoryId)
                        where (!featuredProducts.HasValue || featuredProducts.Value == pc.IsFeaturedProduct)
                        select p;
            }

with the one stated above in point 2.

Thus whenever ProductService.SearchProducts method is used(in search, showing featured products, etc) the products from the category and all subcategories will be used.

Maybe you will also want not to show the subcategories when click on a category, which can be achieved by commenting out the code in the Category.cshtml view not to show the subcategories. Thus only the products from the category and all subcategoreis will be shown.

Hope this helps!
12 years ago
Hi, could you let me know where I need to put GetCategoriesIds and how I use that with the code in point 2? I know that point 2 replaces the code in ProductService.cs but I can't seem to get it to work with the GetCategoriesIds code.

I'm using 2.10

Many thanks,

Dave
12 years ago
Another possibility is to intercept ProductCategory insertion and deletion in a plugin and if the ProductCategory.Category is a subcategory of "Rings" you add a new ProductCategory to "Rings" (if it does not exist yet).

This is a production implementation:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Nop.Core.Plugins;
using System.Web.Mvc;
using Nop.Core.Events;
using Nop.Core.Domain.Catalog;
using Nop.Services.Tax;
using Nop.Services.Catalog;

namespace MyNamespace
{
    public class MyPlugin : BasePlugin,
        IConsumer<EntityInserted<ProductCategory>>,
        IConsumer<EntityDeleted<ProductCategory>>
    {
        private readonly ICategoryService _categoryService;

    
        public static bool AvoidRecursiveCalls = false;
        public void HandleEvent(EntityInserted<ProductCategory> eventMessage)
        {
            if (AvoidRecursiveCalls)
                return;

            AvoidRecursiveCalls = true;
            try
            {
                var productCategories = _categoryService.GetProductCategoriesByProductId(eventMessage.Entity.ProductId);
                var allCategories = _categoryService.GetAllCategories().ToDictionary(cat => cat.Id);

                var category = allCategories[eventMessage.Entity.CategoryId];

                if (category.ParentCategoryId <= 0)
                    return;

                var parent = allCategories[category.ParentCategoryId];
                

                while (parent != null)
                {
                    if (!productCategories.Any(p => parent.Id == p.CategoryId))
                    {
                        //Create an association with the parent
                        ProductCategory pc = new ProductCategory() { CategoryId = parent.Id, ProductId = eventMessage.Entity.ProductId };
                        _categoryService.InsertProductCategory(pc);
                    }

                    if (parent.ParentCategoryId <= 0)
                        break;

                    parent = allCategories[parent.ParentCategoryId];
                }
            }
            finally
            {
                AvoidRecursiveCalls = false;
            }
        }

        public void HandleEvent(EntityDeleted<ProductCategory> eventMessage)
        {
          if (AvoidRecursiveCalls)
              return;
          AvoidRecursiveCalls = true;
          try
          {

              var productCategories = _categoryService.GetProductCategoriesByProductId(eventMessage.Entity.ProductId);
              var allCategories = _categoryService.GetAllCategories().ToDictionary(cat => cat.Id);
              var category = allCategories[eventMessage.Entity.CategoryId];
              if (category.ParentCategoryId <= 0)
                  return;

              var parent = allCategories[category.ParentCategoryId];

              while (parent != null)
              {
                  //Look if another subcat not deleted has the same parent
                  if (!productCategories.Where(pc=>pc.Category != null).Any(pc => pc.Category.ParentCategoryId == parent.Id))
                  {
                      //Another subcategories has the same parent
                      var productCat = productCategories.Where(pc => pc.CategoryId == parent.Id).FirstOrDefault();
                      if (productCat == null)
                          return;

                      _categoryService.DeleteProductCategory(productCat);
                  }
                  
                  parent = allCategories[parent.ParentCategoryId];
              }
          }
          finally
          {
              AvoidRecursiveCalls = false;
          }
            
        }
    }
}
12 years ago
Any ideas how I can implement this into 2.3 stored procedures?

This function I presume i need to somehow edit?

var products = _dbContext.ExecuteStoredProcedureList<Product>(
                    //"EXEC [ProductLoadAllPaged] @CategoryId, @ManufacturerId, @ProductTagId, @FeaturedProducts, @PriceMin, @PriceMax, @Keywords, @SearchDescriptions, @FilteredSpecs, @LanguageId, @OrderBy, @PageIndex, @PageSize, @ShowHidden, @TotalRecords",
                    "ProductLoadAllPaged",
                    new SqlParameter { ParameterName = "CategoryId", Value = categoryId, SqlDbType = SqlDbType.Int },
                    new SqlParameter { ParameterName = "ManufacturerId", Value = manufacturerId, SqlDbType = SqlDbType.Int },
                    new SqlParameter { ParameterName = "ProductTagId", Value = productTagId, SqlDbType = SqlDbType.Int },
                    new SqlParameter { ParameterName = "FeaturedProducts", Value = featuredProducts.HasValue ? (object)featuredProducts.Value : DBNull.Value, SqlDbType = SqlDbType.Bit },
                    new SqlParameter { ParameterName = "PriceMin", Value = priceMin.HasValue ? (object)priceMin.Value : DBNull.Value, SqlDbType = SqlDbType.Decimal },
                    new SqlParameter { ParameterName = "PriceMax", Value = priceMax.HasValue ? (object)priceMax.Value : DBNull.Value, SqlDbType = SqlDbType.Decimal },
                    new SqlParameter { ParameterName = "Keywords", Value = keywords != null ? (object)keywords : DBNull.Value, SqlDbType = SqlDbType.NVarChar },
                    new SqlParameter { ParameterName = "SearchDescriptions", Value = searchDescriptions, SqlDbType = SqlDbType.Bit },
                    new SqlParameter { ParameterName = "FilteredSpecs", Value = commaSeparatedSpecIds != null ? (object)commaSeparatedSpecIds : DBNull.Value, SqlDbType = SqlDbType.NVarChar },
                    new SqlParameter { ParameterName = "LanguageId", Value = searchLocalizedValue ? languageId : 0, SqlDbType = SqlDbType.Int },
                    new SqlParameter { ParameterName = "OrderBy", Value = (int)orderBy, SqlDbType = SqlDbType.Int },
                    new SqlParameter { ParameterName = "PageIndex", Value = pageIndex, SqlDbType = SqlDbType.Int },
                    new SqlParameter { ParameterName = "PageSize", Value = pageSize, SqlDbType = SqlDbType.Int },
                    new SqlParameter { ParameterName = "ShowHidden", Value = showHidden, SqlDbType = SqlDbType.Bit },
                    pTotalRecords);
                int totalRecords = (pTotalRecords.Value != DBNull.Value) ? Convert.ToInt32(pTotalRecords.Value) : 0;
                return new PagedList<Product>(products, pageIndex, pageSize, totalRecords);


Thanks in advance,

Dave
12 years ago
Thanks a lot. Please see changeset ea1bde4dc483
3 years ago
a.m. wrote:
Thanks a lot. Please see changeset ea1bde4dc483

Sorry i am bumping 8 years old topic, but i asked similar question and no one answered, is there any way to bypass this issue in 4.3 version (since i can't visit given url, can't see whats going on there). Thanks!
This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.