Loading...
Area: Episerver Commerce
Applies to versions: Commerce 10.4 and higher
Other versions:

Custom filtering facets

Recommendations [hide]

Many e-commerce sites have a large number of campaigns and promotions running. Facets in Episerver Commerce allow users to apply filters to campaigns and discounts (promotions), for easier location of specific items in the Campaign view. This topic describes how to customize the facets.

How it works

Key components mentioned here are available in the EPiServer.Commerce.Shell.Facets namespace.

FacetGroup

FacetGroup contains properties that identify a group of items by which to filter a campaign and settings to configure a group. Initialize a FacetGroup through a constructor.

public FacetGroup(string id, string name, IEnumerable<FacetItem> items, FacetGroupSettings settings)
  {
    Id = id; // the id of facet group
    Name = name; // the name of facet group
    Items = new List<FacetItem>(items); // the list facet item 
    Settings = settings; // settings to configuration a facet group
  }

FacetGroup settings

FacetGroupSettings contains properties to configure a facet group. Initialize these properties through a constructor.

public FacetGroupSettings(
  FacetSelectionType selectionType,
  int itemsToShow,
  bool collapsible,
  bool hasIcons,
  bool showMatchingItems,
  IEnumerable<string> dependsOn)
  {
    SelectionType = selectionType; // Determine the selection type of facet group, single or multiple through FacetSelectionType enum
    ItemsToShow = itemsToShow; // The number to determine how many facet items will be show as default. If there are more than facet items, show more option will be available
    Collapsible = collapsible; // Determine the facet group is collapsible or not
    HasIcons = hasIcons; // Determine the icon of facet items are displayed or not
    ShowMatchingItems = showMatchingItems; // Determine the number of matching filtered items are shown or not
    DependsOn = dependsOn; // List of facet group that current group depends on. That mean changes the list facet group could affect the number of matching filterd items in the current group
  }

FacetItem

FacetItem contains properties that identify a facet item in the facet group, which is a value to a filtered campaign. Initialize these properties through a constructor. 

public FacetItem(string id, string name, string iconClass = "")
  {
    Id = id; // the id of facet item that display in the url as a value
    Name = name; // the name of facet item that shown in the facets widget
    IconClass = iconClass; // the css class that determining the icon of facet item. Default it's empty
  }

Custom facets Commerce 12.10 and higher

To customize facet groups, create a class inheriting from FacetGroupModifier, then register it on one of your initialization modules.

Example: Displaying four markets by default in the market facet group, instead of three.

public class CustomFacetGroupModifier : FacetGroupModifier
  {
    public override IEnumerable<FacetGroup> ModifyFacetGroups(IEnumerable<FacetGroup> facetGroups)
      {
        var marketFacetGroup = facetGroups.FirstOrDefault(f => f.Id == CampaignFacetConstants.MarketGroupId);
        if (marketFacetGroup != null)
          {
            marketFacetGroup.Settings.ItemsToShow = 4;
          }
        return facetGroups;
      }
  }

Example: Registering the facet.

public void ConfigureContainer(ServiceConfigurationContext context)
  {
    var services = context.Services;
    services.AddSingleton<FacetGroupModifier, CustomFacetGroupModifier>();
  }

Custom facets Commerce 12.9 and lower

First, you need a custom facet based on a campaign facet. Here we create a custom facet that:

  • Clears all built-in groups.
  • Has a facet group that filters by a campaign's last modified date. This user can choose last modified day, week, or month.
public class CustomFacet : CampaignFacet
  {
    public CustomFacet(
                        FacetFactory facetFactory, 
                        IMarketService marketService, 
                        LocalizationService localizationService
                      ) : base(facetFactory, marketService, localizationService)
      {
        // Clear all built-in facet groups
        Groups.Clear();
        Groups.Add(facetFactory.CreateFacetGroup(
          "lastmodified",
          "Last Modified",
          new List() 
            {
              facetFactory.CreateFacetItem("day", "Today"),
              facetFactory.CreateFacetItem("week", "A Week Ago"),
              facetFactory.CreateFacetItem("month", "A Month Ago")
            },
          new FacetGroupSettings(FacetSelectionType.Single, 0, true, true, false, Enumerable.Empty<string>())));
      }
  }

This example shows that the last modified facet group is single selection with three options: day, week, or month.

new FacetGroupSettings(FacetSelectionType.Single, 0, true, true, true /*showMatchingItems*/, Enumerable.Empty<string>())

Note: To show a item numbers that indicate how many campaigns are filtered, in FacetGroupSettings, set showMatchingItemsto true.

To use a CustomFacet, follow these steps.

  1. Create a rest store named "customfacet" that returns a custom facet.
    [RestStore("customfacet")]
    public class CustomFacetStore : RestControllerBase
      {
        FacetFactory _facetFactory;
        IMarketService _marketService;
        LocalizationService _localizationService;
        public CustomFacetStore(
          FacetFactory facetFactory, 
          LocalizationService localizationService, 
          IMarketService marketService)
          {
            _facetFactory = facetFactory;
            _marketService = marketService;
            _localizationService = localizationService;
          }
        public RestResult Get(string id, string facetString, ContentReference parentLink)
          {
            return Rest(new CustomFacet(_facetFactory, _marketService, _localizationService));
          }
      }

    Note: To show matching items, use the facet query handler to calculate matching numbers.

    public RestResult Get(string id, string facetString, ContentReference parentLink)
      {
        var customFacet = new CustomFacet(_facetFactory, _marketService, _localizationService);
        var contentLoader = ServiceLocator.Current.GetInstance();
        var facetQueryHandler = new FacetQueryHandler();
        facetQueryHandler.CalculateMatchingNumbers(
          contentLoader.GetChildren(parentLink), 
          customFacet.Groups, 
          facetString, 
          new GetContentsByFacet[] { new GetCampaignsByLastModified() });
        return Rest(customFacet);
      }
  2. Register the custom facet store and override the existing store "epi.commerce.facet". Create a module initializer. If an initializer exists, edit it.
    define(
      [
        "dojo/_base/declare",
        "epi/_Module",
        "epi/routes"
      ], 
      function (declare, _Module, routes) 
        {
          return declare([_Module], 
                          {
                            initialize: function () 
                              {
                                this.inherited(arguments);
                                var registry = this.resolveDependency("epi.storeregistry");
                                // remove existing facet store
                                if (registry.get("epi.commerce.facet")) 
                                  {
                                    delete registry._stores["epi.commerce.facet"];
                                  }
                                // register the custom facet
                                registry.create("epi.commerce.facet", routes.getRestPath(
                                  { moduleArea: "app", storeName: "customfacet" } ));
                              }
                          }
                        );
        }
          );
  3. If you do not register a module initializer, you need to update or create the project's module.config file, as shown in the example below.
    <?xml version="1.0" encoding="utf-8"?>
    <module loadFromBin="false">
      <assemblies>
        <add assembly="EpiserverSite5" />
      </assemblies>
      <dojo>
        <paths>
          <add name="app" path="Scripts" />
        </paths>
      </dojo>
      <clientModule initializer="app.ModuleInitializer">
        <moduleDependencies>
          <add dependency="Commerce" type="RunAfter" />
        </moduleDependencies>
      </clientModule>
    </module>
    

    After following the steps above, the Campaign view has a custom facet on the left side, and the built-in groups have disappeared. We can interact in the UI, but still need to filter campaigns.

  4. Create a function to filter campaigns by last modified date, based on GetContentsByFacet.
    public class GetCampaignsByLastModified : GetContentsByFacet
      {
        public override string Key { get { return "lastmodified"; } }
        public override IEnumerable<IContent> GetItems(IEnumerable<IContent> items, IEnumerable<string> facets)
          {
            return items.Where(item => !(item is SalesCampaign) || AvailableFor((SalesCampaign)item, facets));
          }
        private bool AvailableFor(SalesCampaign item, IEnumerable<string> facets)
          {
            var changed = DateTime.UtcNow - item.Changed;
            var filterString = facets.FirstOrDefault();
            var isAvailable = false;
            switch (filterString)
              {
                case "day":
                  isAvailable = changed.Days < 1;
                  break;
                case "week":
                  isAvailable = changed.Days < 7;
                  break;
                case "month":
                  isAvailable = changed.Days < 30;
                  break;
              }
            return isAvailable;
          }
      }
  5. Customize the query to return GetCampaignsByLastModified, by overriding GetSalesCampaignChildrenQuery
    [ServiceConfiguration(typeof(IContentQuery))]
    public class CustomGetSalesCampaignChildrenQuery : GetSalesCampaignChildrenQuery
      {
        public CustomGetSalesCampaignChildrenQuery(
          IContentQueryHelper queryHelper, 
          IContentRepository contentRepository, 
          LanguageSelectorFactory languageSelectorFactory, 
          CampaignInfoExtractor campaignInfoExtractor, 
          FacetQueryHandler facetQueryHandler) 
          : base(queryHelper, contentRepository, languageSelectorFactory, campaignInfoExtractor, facetQueryHandler){}
    
        public override int Rank
          {
            // need set rank to higher
            get { return 1000; }
          }
    
        protected override IEnumerable<GetContentsByFacet> FacetFunctions
          {
            get
              {
                return new GetContentsByFacet[] 
                  {
                    new GetCampaignsByLastModified()
                  };
              }
          }
      }

    Here is an example of the customized Campaign view.

Do you find this information helpful? Please log in to provide feedback.

Last updated: Mar 16, 2017

Recommendations [hide]