Article Registering Tokens for Templates

Tokens separate data from presentation by acting as placeholders that each represent a location in a template that the templating engine uses to embed the correct object property data at render time. Essentially, each token is a way to expose a property of a data object to the UI for manipulation within a template, with the understanding that the underlying information will change based on the context of the action occurring.

Why/When Should I Use Tokens?

If you are creating a new Application or Content, or adding additional data/properties to an existing Content or Application, you should consider adding tokens so that your data can be used in templates.

Registering Tokens

An ITokenRegistrar plugin serves to identify data snippets/properties that can be used in the UI where templates are used. We need to set up a few things before we actually get to the implementation of ITokenRegistrar. First is a sample data class that we can create tokens for. This would correspond to the custom content or other objects that you have created to integrate with Verint Community. Keep in mind that this is an illustrative sample and is not a guide on building data classes.

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public DateTime Birthday { get; set; }

    public List<BlogPost> AuthoredPosts { get; set; }
    public BlogPost FavoritePost { get; set; }

    public bool IsBirthdayToday()
    {
        return DateTime.Now.Date.Equals(Birthday.Date);
    }
}

Another key component is to implement the token class interfaces. We define the interfaces as part of our Extensibility toolset, but leave the implementation to the third party developer to allow them flexibility in how they approach components like translation. For our example, we will use a simplified implementation of ITokenizedTemplatePrimitiveToken that does not include translation. A full implementation of each token type that includes translation is included at the end of this article.

public class SampleBasicToken : ITokenizedTemplatePrimitiveToken
{
    public string Name { get; private set; }
    public string Description { get; private set; }
    public Guid Id { get; private set; }
    public Guid? DataTypeId { get; private set; }
    public PrimitiveType ValueType { get; private set; }
    private readonly Func<TemplateContext, object> _resolve;
    public object Resolve(TemplateContext context)
    {
        return _resolve(context);
    }

    private readonly string _preview;
    public string Preview()
    {
        return _preview;
    }

    public SampleBasicToken(string name, string description, 
        Guid id, Guid? dataTypeId, PrimitiveType valueType, 
        Func<TemplateContext, object> resolveFunction, string preview = null)
    {
        Name = name;
        Description = description;
        Id = id;
        DataTypeId = dataTypeId;
        ValueType = valueType;
        _resolve = resolveFunction;

        _preview = preview ?? Name;
    }
}

With the token defined, we can now register instances of the token that will appear in the template editor when the corresponding context is specified. Registering tokens is as simple as a series of calls to Register(). In the declaration, you define a Func<TemplateContext, object>() that is responsible for extracting the data it needs to provide from the context provided.

// Generate a custom Guid to represent this data type
public static readonly Guid DataTypeId = new Guid("93EB3FBC-A4D3-4551-961A-B42569ACAA1D");

public void RegisterTokens(ITokenizedTemplateTokenController tokenController)
{
    // Naming convention allows consistency in display in the token drop-down menu.
    tokenController.Register(new SampleBasicToken("Person: Name",
        "The name of the person.",
        // A unique, static id is needed for each token
        Guid.Parse("8E616115-5F0C-45B7-B24D-71E6B3E70C3F"),
        DataTypeId,
        PrimitiveType.String,
        context => context.Get<Person>(_dataTypeId).Name));

    tokenController.Register(new SampleBasicToken("Person: Age",
        "The age of the person.",
        Guid.Parse("6AE6E349-2000-4704-9D15-D4860E3719DA"),
        DataTypeId,
        PrimitiveType.Int,
        context => context.Get<Person>(_dataTypeId).Age));

    tokenController.Register(new SampleBasicToken("Person: Birthday",
        "The person's birthday.",
        Guid.Parse("DE874A2F-D72E-4449-BDA5-21DD5452C017"),
        DataTypeId,
        PrimitiveType.DateTime,
        context => context.Get<Person>(_dataTypeId).Birthday));

    tokenController.Register(new SampleBasicToken("Person: Is Birthday Today",
        "True if today is the person's birthday.",
        Guid.Parse("71A654F1-9A4F-4D29-A594-FC350697DFED"),
        DataTypeId,
        PrimitiveType.Bool,
        context => context.Get<Person>(_dataTypeId).IsBirthdayToday()));
}

A note about ContentTypeId vs DataTypeId: each token category needs a unique id to represent when to include those tokens in the token selection list in the template editor. Because some of these tokens are for Content Types such as blog posts, the ContentTypeId was reused in these cases. For tokens that are not for a Content Type - for instance, likes or mentions - a new Id was created and referred to as a DataTypeId.

Types of Tokens

ITokenizedTemplatePrimitiveToken is just one type of token, the simplest type that simply presents a piece of data. Several additional token types exist for specific use cases. A full list is below:

  • ITokenizedTemplatePrimitiveToken - as stated above, directly renders a piece of data.
  • ITokenizedTemplateLinkUrlToken - automatically renders an <a> tag with the token data as the href value.
  • ITokenizedTemplateImageUrlToken - automatically renders an <img> tag with the token data as the src value.
  • ITokenizedTemplateEnumerableToken - renders a sub-template for each item in a list. These items may be objects that in turn have their own properties exposed by tokens. See below for details.
  • ITokenizedTemplateDataTypeContainerToken - used similarly to the C# "dot operator" to expose any related tokens of a object property for access when rendering. See below for details.

The implementations of ITokenizedTemplateEnumerableToken and ITokenizedTemplateDataTypeContainerToken require some manipulation of TemplateContexts when resolving, but allow access to all possible properties for all possible related objects within the same template.

Linking Your Tokens to Tokens of Related Data Types

ITokenizedTemplateDataTypeContainerToken requires adding the object property to the context, identified by the unique id for that object's tokens. 

public class SampleDataTypeContainerToken : ITokenizedTemplateDataTypeContainerToken
{
    public string Name { get; private set; }
    public string Description { get; private set; }
    public Guid Id { get; private set; }
    public Guid? DataTypeId { get; private set; }
    public Guid[] ContextualDataTypeIds { get; private set; }
    private readonly Action<TemplateContext> _resolve;
    public void Resolve(TemplateContext context)
    {
        _resolve(context);
    }

    public SampleDataTypeContainerToken(string name, string description,
        Guid id, Guid? dataTypeId, Guid[] contextualDataTypeIds, 
        Action<TemplateContext> resolveFunction)
    {
        Name = name;
        Description = description;
        Id = id;
        DataTypeId = dataTypeId;
        ContextualDataTypeIds = contextualDataTypeIds;
        _resolve = resolveFunction;
    }
}

Registering the token:

tokenController.Register(new SampleDataTypeContainerToken("Person: Favorite Post",
    "The person's favorite blog post.",
    Guid.Parse("DC9CAAFD-89B9-4EE9-B5C7-403AB6FE1BF9"),
    DataTypeId,
    new[] { PublicApi.Users.ContentTypeId },
    context => context.AddItem(PublicApi.BlogPosts.ContentTypeId, 
                    context.Get<Person>(_dataTypeId).FavoritePost)));

ITokenizedTemplateEnumerableToken is used for list properties and requires you to create a list of sub-contexts, each with one of the objects from the list populated.

public class SampleEnumerableToken : ITokenizedTemplateEnumerableToken
{
    public string Name { get; private set; }
    public string Description { get; private set; }
    public Guid Id { get; private set; }
    public Guid? DataTypeId { get; private set; }
    public Guid[] ContextualDataTypeIds { get; private set; }
    private readonly Func<TemplateContext, IEnumerable<TemplateContext>> _resolve;
    public IEnumerable<TemplateContext> Resolve(TemplateContext context)
    {
        return _resolve(context);
    }

    public SampleEnumerableToken(string name, string description, Guid id,
        Guid? dataTypeId, Guid[] contextualDataTypeIds, 
        Func<TemplateContext, IEnumerable<TemplateContext>> resolveFunction)
    {
        Name = name;
        Description = description;
        Id = id;
        DataTypeId = dataTypeId;
        ContextualDataTypeIds = contextualDataTypeIds;
        _resolve = resolveFunction;
    }
}

Registering the token:

tokenController.Register(new SampleEnumerableToken("Person: Authored Posts List",
    "List of blog posts this person has created.",
    Guid.Parse("079B01BA-65FA-4C93-B22E-2A4C28132553"),
    DataTypeId,
    new[] { PublicApi.Users.ContentTypeId },
    context => context.Get<Person>(_dataTypeId).AuthoredPosts.Select(post =>
    {
        var itemContext = new TemplateContext();
        itemContext.AddItem(PublicApi.BlogPosts.ContentTypeId, post);
        return itemContext;
    })));

Seeing the Results

When editing a template for a plugin that includes the Person data type id in its ContextualDataTypeIds, you would see a list of the tokens available for inserting into the template:

Using Your Tokens In Templates 

You can set up your plugins to have templating available and include your tokens, built-in tokens, or both. For more information, refer to the article on Template-based Email.

 

Token Implementations

using System;
using System.Collections.Generic;
using Telligent.Evolution.Extensibility.Api.Entities.Version1;
using Telligent.Evolution.Extensibility.Templating.Version1;
using Telligent.Evolution.Extensibility.Version1;

namespace Telligent.Evolution.Samples
{
    internal class TokenizedTemplateToken : ITokenizedTemplatePrimitiveToken
    {
        private readonly Func<string> _name;
        public string Name { get { return _name(); } }
        private readonly Func<string> _description;
        public string Description { get { return _description(); } }
        public Guid Id { get; private set; }
        public Guid? DataTypeId { get; private set; }
        public PrimitiveType ValueType { get; private set; }
        private readonly Func<TemplateContext, object> _resolve;
        public object Resolve(TemplateContext context)
        {
            return _resolve(context);
        }

        private readonly Func<string> _preview; 
        public string Preview()
        {
            return _preview();
        }

		public TokenizedTemplateToken(string name, string description, ITranslatablePluginController controller, Guid id, Guid? dataTypeId, Func<TemplateContext, object> resolveFunction, string preview = null)
			: this(name, description, controller, id, dataTypeId, PrimitiveType.String, resolveFunction, preview)
		{ }

        public TokenizedTemplateToken(string name, string description, ITranslatablePluginController controller, Guid id, Guid? dataTypeId, PrimitiveType valueType, Func<TemplateContext, object> resolveFunction, string preview = null)
        {
            _name = () => controller.GetLanguageResourceValue(name);
            _description = () => controller.GetLanguageResourceValue(description);
            Id = id;
            DataTypeId = dataTypeId;
            ValueType = valueType;
            _resolve = resolveFunction;

            _preview = preview != null ? () => controller.GetLanguageResourceValue(preview) : _name;
        }
    }

    internal class TokenizedTemplateImageUrlToken : ITokenizedTemplateImageUrlToken
    {
        private readonly Func<string> _name;
        public string Name { get { return _name(); } }
        private readonly Func<string> _description;
        public string Description { get { return _description(); } }
        public Guid Id { get; private set; }
        public Guid? DataTypeId { get; private set; }
        private readonly Func<TemplateContext, string> _resolve;
        public string Resolve(TemplateContext context)
        {
            return _resolve(context);
        }

        private readonly Func<string> _preview;
        public string Preview()
        {
            return _preview();
        }

        public TokenizedTemplateImageUrlToken(string name, string description, ITranslatablePluginController controller, Guid id, Guid? dataTypeId, Func<TemplateContext, string> resolveFunction, string preview = null)
        {
            _name = () => controller.GetLanguageResourceValue(name);
            _description = () => controller.GetLanguageResourceValue(description);
            Id = id;
            DataTypeId = dataTypeId;
            _resolve = resolveFunction;

            _preview = preview != null ? () => controller.GetLanguageResourceValue(preview) : _name;
        }

        public TokenizedTemplateImageUrlToken(string name, string description, ITranslatablePluginController controller, Guid id, Guid? dataTypeId, Func<TemplateContext, string> resolveFunction, Func<string> preview)
        {
            _name = () => controller.GetLanguageResourceValue(name);
            _description = () => controller.GetLanguageResourceValue(description);
            Id = id;
            DataTypeId = dataTypeId;
            _resolve = resolveFunction;

            _preview = preview ?? _name;
        }
    }

    internal class TokenizedTemplateLinkUrlToken : ITokenizedTemplateLinkUrlToken
    {
        private readonly Func<string> _name;
        public string Name { get { return _name(); } }
        private readonly Func<string> _description;
        public string Description { get { return _description(); } }
        public Guid Id { get; private set; }
        public Guid? DataTypeId { get; private set; }
        private readonly Func<TemplateContext, string> _resolve;
        public string Resolve(TemplateContext context)
        {
            return _resolve(context);
        }

        private readonly Func<string> _preview;
        public string Preview()
        {
            return _preview();
        }

        public TokenizedTemplateLinkUrlToken(string name, string description, ITranslatablePluginController controller, Guid id, Guid? dataTypeId, Func<TemplateContext, string> resolveFunction, string preview = null)
        {
            _name = () => controller.GetLanguageResourceValue(name);
            _description = () => controller.GetLanguageResourceValue(description);
            Id = id;
            DataTypeId = dataTypeId;
            _resolve = resolveFunction;
            _preview = preview != null ? () => controller.GetLanguageResourceValue(preview) : _name;
        }
    }

    internal class TokenizedTemplateEnumerableToken : ITokenizedTemplateEnumerableToken
    {
        private readonly Func<string> _name;
        public string Name { get { return _name(); } }
        private readonly Func<string> _description;
        public string Description { get { return _description(); } }
        public Guid Id { get; private set; }
        public Guid? DataTypeId { get; private set; }
        public Guid[] ContextualDataTypeIds { get; private set; }
        private readonly Func<TemplateContext, IEnumerable<TemplateContext>> _resolve;
        public  IEnumerable<TemplateContext> Resolve(TemplateContext context)
        {
            return _resolve(context);
        }

        public TokenizedTemplateEnumerableToken(string name, string description, ITranslatablePluginController controller, Guid id, 
            Guid? dataTypeId, Guid[] contextualDataTypeIds, Func<TemplateContext, IEnumerable<TemplateContext>> resolveFunction)
        {
            _name = () => controller.GetLanguageResourceValue(name);
            _description = () => controller.GetLanguageResourceValue(description);
            Id = id;
            DataTypeId = dataTypeId;
            ContextualDataTypeIds = contextualDataTypeIds;
            _resolve = resolveFunction;
        }
    }

    internal class TokenizedTemplateDataTypeContainerToken : ITokenizedTemplateDataTypeContainerToken
    {
        private readonly Func<string> _name;
        public string Name { get { return _name(); } }
        private readonly Func<string> _description;
        public string Description { get { return _description(); } }
        public Guid Id { get; private set; }
        public Guid? DataTypeId { get; private set; }
        public Guid[] ContextualDataTypeIds { get; private set; }
        private readonly Action<TemplateContext> _resolve;
        public void Resolve(TemplateContext context)
        {
            _resolve(context);
        }

        public TokenizedTemplateDataTypeContainerToken(string name, string description, ITranslatablePluginController controller,
            Guid id, Guid? dataTypeId, Guid[] contextualDataTypeIds, Action<TemplateContext> resolveFunction)
        {
            _name = () => controller.GetLanguageResourceValue(name);
            _description = () => controller.GetLanguageResourceValue(description);
            Id = id;
            DataTypeId = dataTypeId;
            ContextualDataTypeIds = contextualDataTypeIds;
            _resolve = resolveFunction;
        }
    }
}

Full Token Registrar Sample With Translations

using System;
using System.Collections.Generic;
using System.Linq;
using Telligent.Evolution.Api.Plugins.TokenizedTemplates;
using Telligent.Evolution.Extensibility.Api.Entities.Version1;
using Telligent.Evolution.Extensibility.Api.Version1;
using Telligent.Evolution.Extensibility.Templating.Version1;
using Telligent.Evolution.Extensibility.Version1;

namespace Telligent.Evolution.Samples
{
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public DateTime Birthday { get; set; }

        public List<BlogPost> AuthoredPosts { get; set; }
        public BlogPost FavoritePost { get; set; }

        public bool IsBirthdayToday()
        {
            return DateTime.Now.Date.Equals(Birthday.Date);
        }
    }

    public class SampleBasicToken : ITokenizedTemplatePrimitiveToken
    {
        public string Name { get; private set; }
        public string Description { get; private set; }
        public Guid Id { get; private set; }
        public Guid? DataTypeId { get; private set; }
        public PrimitiveType ValueType { get; private set; }
        private readonly Func<TemplateContext, object> _resolve;
        public object Resolve(TemplateContext context)
        {
            return _resolve(context);
        }

        private readonly string _preview;
        public string Preview()
        {
            return _preview;
        }

        public SampleBasicToken(string name, string description, 
            Guid id, Guid? dataTypeId, PrimitiveType valueType, 
            Func<TemplateContext, object> resolveFunction, string preview = null)
        {
            Name = name;
            Description = description;
            Id = id;
            DataTypeId = dataTypeId;
            ValueType = valueType;
            _resolve = resolveFunction;

            _preview = preview ?? Name;
        }
    }

    public class SampleDataTypeContainerToken : ITokenizedTemplateDataTypeContainerToken
    {
        public string Name { get; private set; }
        public string Description { get; private set; }
        public Guid Id { get; private set; }
        public Guid? DataTypeId { get; private set; }
        public Guid[] ContextualDataTypeIds { get; private set; }
        private readonly Action<TemplateContext> _resolve;
        public void Resolve(TemplateContext context)
        {
            _resolve(context);
        }

        public SampleDataTypeContainerToken(string name, string description,
            Guid id, Guid? dataTypeId, Guid[] contextualDataTypeIds, 
            Action<TemplateContext> resolveFunction)
        {
            Name = name;
            Description = description;
            Id = id;
            DataTypeId = dataTypeId;
            ContextualDataTypeIds = contextualDataTypeIds;
            _resolve = resolveFunction;
        }
    }

    public class SampleEnumerableToken : ITokenizedTemplateEnumerableToken
    {
        public string Name { get; private set; }
        public string Description { get; private set; }
        public Guid Id { get; private set; }
        public Guid? DataTypeId { get; private set; }
        public Guid[] ContextualDataTypeIds { get; private set; }
        private readonly Func<TemplateContext, IEnumerable<TemplateContext>> _resolve;
        public IEnumerable<TemplateContext> Resolve(TemplateContext context)
        {
            return _resolve(context);
        }

        public SampleEnumerableToken(string name, string description, Guid id,
            Guid? dataTypeId, Guid[] contextualDataTypeIds, 
            Func<TemplateContext, IEnumerable<TemplateContext>> resolveFunction)
        {
            Name = name;
            Description = description;
            Id = id;
            DataTypeId = dataTypeId;
            ContextualDataTypeIds = contextualDataTypeIds;
            _resolve = resolveFunction;
        }
    }

    public class PersonTokens : ITokenRegistrar, ITranslatablePlugin
    {
        #region IPlugin
        public string Name
        {
            get { return "Person Tokens"; }
        }

        public string Description
        {
            get { return "Registers tokens for Person data in templates."; }
        }

        public void Initialize() { }
        #endregion

        // Generate a custom Guid to represent
        private readonly Guid _dataTypeId = new Guid("93EB3FBC-A4D3-4551-961A-B42569ACAA1D");

        public void RegisterTokens(ITokenizedTemplateTokenController tokenController)
        {
            // Naming convention allows consistency in display in the token drop-down menu.
            tokenController.Register(new TokenizedTemplateToken("PersonName",
                "PersonName_Description", _controller,
                // A unique, static id is needed for each token
                Guid.Parse("8E616115-5F0C-45B7-B24D-71E6B3E70C3F"),
                _dataTypeId,
                PrimitiveType.String,
                context => context.Get<Person>(_dataTypeId).Name));

            tokenController.Register(new TokenizedTemplateToken("PersonAge",
                "PersonAge_Description", _controller,
                Guid.Parse("6AE6E349-2000-4704-9D15-D4860E3719DA"),
                _dataTypeId,
                PrimitiveType.Int,
                context => context.Get<Person>(_dataTypeId).Age));

            tokenController.Register(new TokenizedTemplateToken("PersonBirthday",
                "PersonBirthday_Description", _controller,
                Guid.Parse("DE874A2F-D72E-4449-BDA5-21DD5452C017"),
                _dataTypeId,
                PrimitiveType.DateTime,
                context => context.Get<Person>(_dataTypeId).Birthday));

            tokenController.Register(new TokenizedTemplateToken("PersonIsBirthdayToday",
                "PersonIsBirthdayToday_Description", _controller,
                Guid.Parse("71A654F1-9A4F-4D29-A594-FC350697DFED"),
                _dataTypeId,
                PrimitiveType.Bool,
                context => context.Get<Person>(_dataTypeId).IsBirthdayToday()));

            tokenController.Register(new TokenizedTemplateDataTypeContainerToken("PersonFavoritePost",
                "PersonFavoritePost_Description", _controller,
                Guid.Parse("DC9CAAFD-89B9-4EE9-B5C7-403AB6FE1BF9"),
                _dataTypeId,
                new[] { PublicApi.Users.ContentTypeId },
                context => context.AddItem(PublicApi.BlogPosts.ContentTypeId, 
                                context.Get<Person>(_dataTypeId).FavoritePost)));

            tokenController.Register(new TokenizedTemplateEnumerableToken("PersonAuthoredPosts_Description",
                "PersonAuthoredPosts_Description", _controller,
                Guid.Parse("079B01BA-65FA-4C93-B22E-2A4C28132553"),
                _dataTypeId,
                new[] { PublicApi.Users.ContentTypeId },
                context => context.Get<Person>(_dataTypeId).AuthoredPosts.Select(post =>
                {
                    var itemContext = new TemplateContext();
                    itemContext.AddItem(PublicApi.BlogPosts.ContentTypeId, post);
                    return itemContext;
                })));
        }

        #region ITranslatablePlugin
        private Translation[] _defaultTranslations;
        public Translation[] DefaultTranslations
        {
            get
            {
                if (_defaultTranslations == null)
                {
                    var enUs = new Translation("en-us");
                    enUs.Set("PersonName", "Person: Name");
                    enUs.Set("PersonName_Description", "The name of the person.");
                    enUs.Set("PersonAge", "Person: Age");
                    enUs.Set("PersonAge_Description", "The age of the person.");
                    enUs.Set("PersonBirthday", "Person: Birthday");
                    enUs.Set("PersonBirthday_Description", "The person's birthday.");
                    enUs.Set("PersonIsBirthdayToday", "Person: Is Birthday Today");
                    enUs.Set("PersonIsBirthdayToday_Description", "True if today is the person's birthday.");
                    enUs.Set("PersonFavoritePost", "Person: Favorite Post");
                    enUs.Set("PersonFavoritePost_Description", "The person's favorite blog post.");
                    enUs.Set("PersonAuthoredPosts", "Person: Authored Posts List");
                    enUs.Set("PersonAuthoredPosts_Description", "List of blog posts this person has created.");

                    _defaultTranslations = new[] { enUs };
                }
                return _defaultTranslations;
            }
        }

        private ITranslatablePluginController _controller;
        public void SetController(ITranslatablePluginController controller)
        {
            _controller = controller;
        }
        #endregion
    }
}