Adding a custom campaign element
Glossary Item Box
Introduction
Use the Campaign designer to set up marketing campaigns. Using this designer, you can create a campaign diagram that consists of interconnected elements. In addition to default campaign elements you can create custom ones.
The general procedure for adding a custom campaign element is as follows:
1. Create a new element for the Campaign designer.
2. Create the element’s edit page.
3. Expand the Campaign designer menu with a new element.
4. Create the element’s server part.
5. Create executable element for the new campaign element.
6. Add custom logic for processing campaign events.
Case description
Create a new campaign element for sending text messages (SMS) for users.
Case implementation algorithm
1. Creating a new element for the Campaign designer
To display the element in the Campaign designer UI, add a new module schema for the campaign element. The procedure for creating a module schema is covered in the “Creating a custom client module schema” article. Set the following properties for the created schema:
- [Title] – "Test SMS Element Schema".
- [Name] – "TestSmsElementSchema".
The schema names in the case below do not contain the Usr prefix. You can change the default prefix in the [Prefix for object name] (SchemaNamePrefix) system setting.
Add a localized string (Fig. 1) to the schema:
- [Name] – "Caption".
- [Value] – "Test SMS".
Fig. 1. Adding localized string to the schema
Add images that will represent the campaign element in the Campaign designer. Use the SmallImage, LargeImage and TitleImage (Fig. 2) properties to add the images.
Fig. 2. Adding a campaign element image
In this example we used a scalable vector graphics (SVG) image available here.
Add following source code on the [Source Code] section of the schema":
define("TestSmsElementSchema", ["TestSmsElementSchemaResources", "CampaignBaseCommunicationSchema"], function(resources) { Ext.define("Terrasoft.manager.TestSmsElementSchema", { // Parent schema. extend: "Terrasoft.CampaignBaseCommunicationSchema", alternateClassName: "Terrasoft.TestSmsElementSchema", // Manager Id. Must be unique. managerItemUId: "a1226f93-f3e3-4baa-89a6-11f2a9ab2d71", // Plugged mixins. mixins: { campaignElementMixin: "Terrasoft.CampaignElementMixin" }, // Element name. name: "TestSms", // Resource binding. caption: resources.localizableStrings.Caption, titleImage: resources.localizableImages.TitleImage, largeImage: resources.localizableImages.LargeImage, smallImage: resources.localizableImages.SmallImage, // Schema name of the edit page. editPageSchemaName: "TestSmsElementPropertiesPage", // Element type. elementType: "TestSms", // Full name of the class that corresponds to the current schema. typeName: "Terrasoft.Configuration.TestSmsElement, Terrasoft.Configuration", // Overriding the properties of visual styles. color: "rgba(249, 160, 27, 1)", width: 69, height: 55, // Setting up element-specific properties. smsText: null, phoneNumber: null, // Determining the types of the elemen's outbound connections. getConnectionUserHandles: function() { return ["CampaignSequenceFlow", "CampaignConditionalSequenceFlow"]; }, // Expnding the properties for serialization. getSerializableProperties: function() { var baseSerializableProperties = this.callParent(arguments); return Ext.Array.push(baseSerializableProperties, ["smsText", "phoneNumber"]); }, // Setting up the icons that are displayed on the campaign diagram. getSmallImage: function() { return this.mixins.campaignElementMixin.getImage(this.smallImage); }, getLargeImage: function() { return this.mixins.campaignElementMixin.getImage(this.largeImage); }, getTitleImage: function() { return this.mixins.campaignElementMixin.getImage(this.titleImage); } }); return Terrasoft.TestSmsElementSchema; });
Specifics:
- The managerItemUId property value must be unique for the new element and not repeat the value of the other elements.
- The typeName property contains the name of the C# class that corresponds to the campaign element name. This class will be saving and reading the element’s properties from the schema metadata.
Save the schema to apply changes.
Adding a group of elements
If a new group of elements, such as [Scripts] must be created for the campaign element, the schema source code must be supplemented with the following code:
// Name of the new group. group: "Scripts", constructor: function() { if (!Terrasoft.CampaignElementGroups.Items.contains("Scripts")) { Terrasoft.CampaignElementGroups.Items.add("Scripts", { name: "Scripts", caption: resources.localizableStrings.ScriptsElementGroupCaption }); } this.callParent(arguments); }
Also, add a localized string with the following properties:
- [Name] – "ScriptsElementGroupCaption".
- [Name] – "Scripts".
Save the schema to apply changes.
2. Creating the element’s edit page
Create the campaign element’s edit page in the custom package to enable the users to view and edit the element’s properties. To do this, create a schema that expands BaseCampaignSchemaElementPage (CampaignDesigner package). The procedure for creating a replacing client schema is covered in the “Creating a custom client module schema” article.
Set the following properties for the created schema:
- [Title] – "TestSmsElementPropertiesPage".
- [Name] – "TestSmsElementPropertiesPage".
- [Parent object] – "BaseCampaignSchemaElementPage".
Add localized strings to the created schema (Fig. 1) with properties given in the table 1.
Table 1. Localized string properties
Name | Value |
---|---|
PhoneNumberCaption | Sender phone number |
SmsTextCaption | Message |
TestSmsText | Which text message should be sent? (Which SMS text to send?) |
Add following source code on the [Source Code] section of the schema":
define("TestSmsElementPropertiesPage", [], function() { return { attributes: { // Attributes that correspond to specific properties of element schema. "PhoneNumber": { "dataValueType": this.Terrasoft.DataValueType.TEXT, "type": this.Terrasoft.ViewModelColumnType.VIRTUAL_COLUMN }, "SmsText": { "dataValueType": this.Terrasoft.DataValueType.TEXT, "type": this.Terrasoft.ViewModelColumnType.VIRTUAL_COLUMN } }, methods: { init: function() { this.callParent(arguments); this.initAcademyUrl(this.onAcademyUrlInitialized, this); }, // Element code for generating a contextual help link. getContextHelpCode: function() { return "CampaignTestSmsElement"; }, // Initialization of attributes with the current schema property values. initParameters: function(element) { this.callParent(arguments); this.set("SmsText", element.smsText); this.set("PhoneNumber", element.phoneNumber); }, // Saving schema properties. saveValues: function() { this.callParent(arguments); var element = this.get("ProcessElement"); element.smsText = this.getSmsText(); element.phoneNumber = this.getPhoneNumber(); }, // Reading current attribute values. getPhoneNumber: function() { var number = this.get("PhoneNumber"); return number ? number : ""; }, getSmsText: function() { var smsText = this.get("SmsText"); return smsText ? smsText : ""; } }, diff: [ // UI container. { "operation": "insert", "name": "ContentContainer", "propertyName": "items", "parentName": "EditorsContainer", "className": "Terrasoft.GridLayoutEdit", "values": { "itemType": Terrasoft.ViewItemType.GRID_LAYOUT, "items": [] } }, // Element primary signature. { "operation": "insert", "name": "TestSmsLabel", "parentName": "ContentContainer", "propertyName": "items", "values": { "layout": { "column": 0, "row": 0, "colSpan": 24 }, "itemType": this.Terrasoft.ViewItemType.LABEL, "caption": { "bindTo": "Resources.Strings.TestSmsText" }, "classes": { "labelClass": ["t-title-label-proc"] } } }, // Caption for the text field where sender name is entered. { "operation": "insert", "name": "PhoneNumberLabel", "parentName": "ContentContainer", "propertyName": "items", "values": { "layout": { "column": 0, "row": 1, "colSpan": 24 }, "itemType": this.Terrasoft.ViewItemType.LABEL, "caption": { "bindTo": "Resources.Strings.PhoneNumberCaption" }, "classes": { "labelClass": ["label-small"] } } }, // Text field for entering phone number. { "operation": "insert", "name": "PhoneNumber", "parentName": "ContentContainer", "propertyName": "items", "values": { "labelConfig": { "visible": false }, "layout": { "column": 0, "row": 2, "colSpan": 24 }, "itemType": this.Terrasoft.ViewItemType.TEXT, "classes": { "labelClass": ["feature-item-label"] }, "controlConfig": { "tag": "PhoneNumber" } } }, // Caption for text field for entering message text. { "operation": "insert", "name": "SmsTextLabel", "parentName": "ContentContainer", "propertyName": "items", "values": { "layout": { "column": 0, "row": 3, "colSpan": 24 }, "classes": { "labelClass": ["label-small"] }, "itemType": this.Terrasoft.ViewItemType.LABEL, "caption": { "bindTo": "Resources.Strings.SmsTextCaption" } } }, // Text field for entering message text. { "operation": "insert", "name": "SmsText", "parentName": "ContentContainer", "propertyName": "items", "values": { "labelConfig": { "visible": false }, "layout": { "column": 0, "row": 4, "colSpan": 24 }, "itemType": this.Terrasoft.ViewItemType.TEXT, "classes": { "labelClass": ["feature-item-label"] }, "controlConfig": { "tag": "SmsText" } } } ] }; } );
Save the schema to apply changes.
3. Expanding the Campaign designer menu with a new element
To display the new element in the Campaign designer menu, expand the campaign element base schema manager. Add a schema that expands CampaignElementSchemaManagerEx (the CampaignDesigner package) to the custom package. The procedure for creating a replacing client schema is covered in the “Creating a custom client module schema” article.
Set the following properties for the created schema:
- [Title] – "TestSmsCampaignElementSchemaManagerEx".
- [Name] – "CampaignElementSchemaManagerEx".
- [Parent object] – "CampaignElementSchemaManagerEx".
Add following source code on the [Source Code] section of the schema":
require(["CampaignElementSchemaManager", "TestSmsElementSchema"], function() { // Adding a new schema to the list of available element schemas in the Campaign designer. var coreElementClassNames = Terrasoft.CampaignElementSchemaManager.coreElementClassNames; coreElementClassNames.push({ itemType: "Terrasoft.TestSmsElementSchema" }); });
Save the schema to apply changes.
4. Creating server part of the custom campaign element
To implement saving the campaign element properties, create a class that interacts with the application server part. The class must inherit CampaignSchemaElement and override the ApplyMetaDataValue() and WriteMetaData() methods.
Create a source code schema with the following properties:
- [Title] – "TestSmsElement".
- [Name] – "TestSmsElement".
For more information on creating source code schemas, please see the Creating the [Source code] schema article.
Add the following source code on the [Source Code] section of the schema":
namespace Terrasoft.Configuration { using System; using Terrasoft.Common; using Terrasoft.Core; using Terrasoft.Core.Campaign; using Terrasoft.Core.Process; [DesignModeProperty(Name = "PhoneNumber", UsageType = DesignModeUsageType.NotVisible, MetaPropertyName = PhoneNumberPropertyName)] [DesignModeProperty(Name = "SmsText", UsageType = DesignModeUsageType.NotVisible, MetaPropertyName = SmsTextPropertyName)] public class TestSmsElement : CampaignSchemaElement { private const string PhoneNumberPropertyName = "PhoneNumber"; private const string SmsTextPropertyName = "SmsText"; // Default constructor. public TestSmsElement() { ElementType = CampaignSchemaElementType.AsyncTask; } // Constructor with parameter. public TestSmsElement(TestSmsElement source) : base(source) { ElementType = CampaignSchemaElementType.AsyncTask; PhoneNumber = source.PhoneNumber; SmsText = source.SmsText; } // Instance action Id. protected override Guid Action { get { return CampaignConsts.CampaignLogTypeMailing; } } // Phone number. [MetaTypeProperty("{A67950E7-FFD7-483D-9E67-3C9A30A733C0}")] public string PhoneNumber { get; set; } // Text message. [MetaTypeProperty("{05F86DF2-B9FB-4487-B7BE-F3955703527C}")] public string SmsText { get; set; } // Applies metadata values. protected override void ApplyMetaDataValue(DataReader reader) { base.ApplyMetaDataValue(reader); switch (reader.CurrentName) { case PhoneNumberPropertyName: PhoneNumber = reader.GetValue<string>(); break; case SmsTextPropertyName: SmsText = reader.GetValue<string>(); break; } } // Records metadata values. public override void WriteMetaData(DataWriter writer) { base.WriteMetaData(writer); writer.WriteValue(PhoneNumberPropertyName, PhoneNumber, string.Empty); writer.WriteValue(SmsTextPropertyName, SmsText, string.Empty); } // Copies element. public override object Clone() { return new TestSmsElement(this); } // Creates a specific ProcessFlowElement instance. public override ProcessFlowElement CreateProcessFlowElement(UserConnection userConnection) { var executableElement = new TestSmsCampaignProcessElement { UserConnection = userConnection, SmsText = SmsText, PhoneNumber = PhoneNumber }; InitializeCampaignProcessFlowElement(executableElement); return executableElement; } } }
Publish the source code schema.
5. Creating executable element for the new campaign element
For the custom campaign element to execute the needed logic, add an executable element. It is a class that inherits the CampaignProcessFlowElement class, where the SafeExecute() method is implemented.
To create an executable element, add a source code schema element with the following properties in the custom package:
- [Title] – "TestSmsCampaignProcessElement".
- [Name] – "TestSmsCampaignProcessElement".
Add following source code on the [Source Code] section of the schema":
namespace Terrasoft.Configuration { using System; using System.Collections.Generic; using Terrasoft.Core.Campaign; using Terrasoft.Core.DB; using Terrasoft.Core.Process; public class TestSmsCampaignProcessElement : CampaignProcessFlowElement { public const string ContactTableName = "Contact"; public TestSmsCampaignProcessElement(ICampaignAudience campaignAudience) { CampaignAudience = campaignAudience; } public TestSmsCampaignProcessElement() { } // Audiences for whom to send texts on the current step. private ICampaignAudience _campaignAudience; private ICampaignAudience CampaignAudience { get { return _campaignAudience ?? (_campaignAudience = new CampaignAudience(UserConnection, CampaignId)); } set { _campaignAudience = value; } } // SMS-specific properties. Passed from an instance of the TestSmsElement class. public string PhoneNumber { get; set; } public string SmsText { get; set; } // Implementation of the element execution method protected override int SafeExecute(ProcessExecutingContext context) { // TODO: Implement sending SMS messages. // // Current step for audiences is set as completed. return CampaignAudience.SetItemCompleted(SchemaElementUId); } } }
Publish the source code schema.
6. Adding custom logic for processing campaign events
Use the event handler mechanism to implement custom logic on saving, copying, deleting, running and stopping campaigns. Create a public sealed handler class that inherits CampaignEventHandlerBase. Implement interfaces that describe specific event handler signatures. This class must not be generic. It must have a constructor available by default.
The following interfaces are supported in the current version:
- IOnCampaignBeforeSave – contains method that will be called before saving the campaign.
- IOnCampaignAfterSave – contains method that will be called after saving the campaign.
- IOnCampaignDelete – contains method that will be called before deleting the campaign.
- IOnCampaignStart – contains method that will be called before running the campaign.
- IOnCampaignStop – contains method that will be called before stopping the campaign.
- IOnCampaignValidate – contains method that will be called on validating the campaign.
- IOnCampaignCopy – contains method that will be called after copying the campaign.
If an exception occurs during the event processing, the call chain is stopped, and campaign status is reverted to the previous one in DB.
When implementing the IOnCampaignValidate interface, save errors in the campaign schema using the AddValidationInfo(string) method.
Additional case conditions
In order for the new custom campaign element to work, SMS gateway connection is required. The connection, account status and other parameters must be checked during campaign validation. The messages must be sent when campaign starts.
To implement these conditions, add a source code schema element with the following properties in the custom package:
- [Title] – "TestSmsEventHandler".
- [Name] – "TestSmsEventHandler".
Add following source code on the [Source Code] section of the schema":
namespace Terrasoft.Configuration { using System; using Terrasoft.Core.Campaign.EventHandler; public sealed class TestSmsEventHandler : CampaignEventHandlerBase, IOnCampaignValidate, IOnCampaignStart { // Implementing handler for the campaign start event. public void OnStart() { // TODO: Text SMS message sending logic... // } // Implementing event handler for campaign validation. public void OnValidate() { try { // TODO: SMS gateway connection validation logic... // } catch (Exception ex) { // If errors are found, add information to the campaign schema. CampaignSchema.AddValidationInfo(ex.Message); } } } }
After making the changes, publish the schema. Compile the application and clear the cache.
As a result, a new [TestSMS] element will be added in the campaign element menu (Fig. 3, 1) that the users can add to the campaign diagram (Fig. 3, 2). When an added element is selected, its edit page will be displayed (Fig. 3, 3).
When saving the campaign, the “Parameter ‘type’ cannot be null” may occur. The error indicates that the configuration library was not updated after the compilation and therefor does not contain the new types.
Recompile the project and clear all possible storages with cached data. You may also need to clear the application pool and restart the website in IIS on the application server.