Skip to content
Last update: May 8, 2024

Extend Dynamic Expression Tree

This article will show you how to extend the existing promotion expression tree of the Marketing module with these new elements:

  • New expression block:

New expression block

  • New block element:

New block element

Sample code

Prerequisites

Prior to extending your dynamic expression tree, you need to:

Define New Class for Expression Tree Prototype

The following example creates a new derived prototype from PromotionConditionAndRewardTreePrototype that represents the original expression tree used for marketing promotion. Here, we register a new BlockSampleConditionroot block and extend the existing BlockCatalogCondition with a new element, SampleCondition:

SamplePromotionConditionAndRewardTreePrototype.cs
public sealed class SamplePromotionConditionAndRewardTreePrototype : PromotionConditionAndRewardTreePrototype
    {
        public SamplePromotionConditionAndRewardTreePrototype()
        {
            //Extend existing 'If any of these catalog condition' block with a new condition element
            var blockCatalogCondition = AvailableChildren.OfType<BlockCatalogCondition>().FirstOrDefault();
            blockCatalogCondition.AvailableChildren.Add(new SampleCondition());

            // Add a new block with sample condition to the beginning of the tree
            var blockSampleConditions = new BlockSampleCondition().WithAvailConditions(new SampleCondition());
            AvailableChildren.Insert(0, blockSampleConditions);
            Children.Insert(0, blockSampleConditions);
        }
    }

Register Your Extension in module.cs

To register your extension in the module.cs file:

  1. Override the original PromotionConditionAndRewardTreePrototype type with the newly created one in the module.cs file:

    module.cs
    public void Initialize(IServiceCollection serviceCollection)
            {
                // Override the original expression prototype tree with new type
                AbstractTypeFactory<PromotionConditionAndRewardTreePrototype>.OverrideType<PromotionConditionAndRewardTreePrototype, SamplePromotionConditionAndRewardTreePrototype>();
            }
    
  2. Add a dependency to the Marketing module into the module.manifest file:

    module.manifest
    ...
    <dependencies>
            <dependency id="VirtoCommerce.Marketing" version="3.0.0" />
        </dependencies>
    ...
    

Note

This line is important for correct module initialization order.

Readmore Module Initialization

Define HTML Templates for New Elements

It is a best practice to define all HTML templates for new elements within a single file, where the templates are dynamically loaded as resources.

Tip

Use the following schema as a template ID: expression-{C# element class name}.html.

Scripts/all-templates.js
<script type="text/ng-template" id="expression-BlockSampleCondition.html">
    For condition evaluator with
    <a class="__link" left-click-menu data-target="allAny_menu{{element.id}}">{{element.all | boolToValue:'all':'any'}}</a> of these sample values
    <ul class="menu __context" role="menu" id="allAny_menu{{element.id}}">
        <li class="menu-item" ng-click='element.all=true;'>all</li>
        <li class="menu-item" ng-click='element.all=false;'>any</li>
    </ul>
</script>
<script type="text/ng-template" id="expression-SampleCondition.html">
    Sample condition is met: 
    <a class="__link" left-click-menu data-target="yesNo_menu{{element1.id}}">{{element1.isSatisfied | boolToValue:'yes':'no'}}</a>
    <ul class="menu __context" role="menu" id="yesNo_menu{{element1.id}}">
        <li class="menu-item" ng-click='element1.isSatisfied=true;'>yes</li>
        <li class="menu-item" ng-click='element1.isSatisfied=false;'>no</li>
    </ul>
</script>

Register New Elements in Main module.js File

Register your newly created expression elements in dynamicExpressionService that is used as a registry for all known tree elements:

Script/module.js
angular.module(moduleName, [])
    .run(['virtoCommerce.coreModule.common.dynamicExpressionService', '$http', '$compile',
        function (dynamicExpressionService, $http, $compile) {
            //Register Sample expressions
            dynamicExpressionService.registerExpression({
                id: 'BlockSampleCondition',
                newChildLabel: '+ add sample condition',
                getValidationError: function () {
                    return (this.children && this.children.length) ? undefined : 'Promotion requires at least one eligibility';
                }
            });
            dynamicExpressionService.registerExpression({
                id: 'SampleCondition',
                displayName: 'Sample condition is []'
            });

            $http.get('Modules/$(VirtoCommerce.MarketingSample)/Scripts/all-templates.html').then(function (response) {
                // compile the response, which will put stuff into the cache
                $compile(response.data);
            });
        }
    ]);

Build Own Module and Restart Platform

  1. Build your solution and pack the module scripts with the following command:

    npm run webpack:build
    
  2. Restart the platform instance to apply your changes.

  3. Open Platform Manager, go to Marketing → Promotions → New promotion. The new For condition evaluator with any of these sample values block with its single Sample condition is met: no/yes line appears.