HomeDev GuideAPI Reference
Dev GuideAPI ReferenceUser GuideGitHubNuGetDev CommunitySubmit a ticketLog In
GitHubNuGetDev CommunitySubmit a ticket

Custom facets in the marketing overview

Describes facets and how to customize facet groups in Optimizely Commerce (PaaS).

Many ecommerce sites have a large number of campaigns and promotions running. Facets in Optimizely Commerce (PaaS) let users apply filters to campaigns and discounts (promotions), for easier location of specific items in the Campaign view. 

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 (PaaS) 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 (PaaS) 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 a 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);
          }
  1. 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" } ));
                                  }
                              }
                            );
            }
              );
  1. 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="OptimizelySite5" />
          </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.

  1. 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;
              }
          }
  1. 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.

1024