Article Creating Custom Applications and Content

The platform provides the ability to create your own types of content and applications.

Why create custom applications and content?

If you are writing a new application or have content (internal or external) that you want to interact with Verint Community's core services, creating a custom content and application type within the community can be beneficial.  Content Types within the community can interact with core services.  Likes, Ratings, Comments, Moderation, Mentions and Hashtags are some of the services your content will have access to once registered with the community.

There is a deeper discussion of how content and applications fit in the community platform in The Content Model article.

Content and Content Types

There are two interfaces that are provided in the platform to identify types of content and individual content entities.  The IContentType interface is used to identify types of content and the IContent interface is used to represent the individual content entities.  Both of these interfaces are found in the Telligent.Evolution.Core.dll.

Applications and Application Types

Similar to content, the platform provides the IApplicationType to identify a type of application and IApplication to represent an individual application entity.  These interfaces are also found in the Telligent.Evolution.Core.dll.

Creating an Application and Content

For our example application, we will create a Links Application.  The Links Application will be a collection of links to websites.  

First, let's outline our entities.

LinksApplication Entity

Our LinksApplication Entity will implement the IApplication interface and represent an instance of a Links Application.

ApplicationTypeId is a unique id that represents a type of application in the community, you define this value. In the example, we have a static class where we define our Id, so it can be referenced from multiple locations. ApplicationId is the unique Id for an individual application in the community.  We will provide this to the community as well.

public Guid ApplicationId { get; set; }

public Guid ApplicationTypeId
{
    get { return ContentTypes.LinksApplicationId; }
}

The Name and Description fields are not required by the IApplication interface, but allow us to store a raw version of the there values.  The HtmlName and HtmlDescription methods are then used to provide the output depending on the target.  These allow the output to be tailored to those targets, for instance if your target was an email you may not want to display images inline in your content.  Our example data just uses text for Name and Descriptions, there is no need to tailor our output to different targets, instead we will just return the text for all targets.

public string Name { get; set; }
public string Description { get; set; }

public string HtmlName(string target)
{
    return Name;
}

public string HtmlDescription(string target)
{
    return Description;
}

For this example we won't allow disabling the application, so the IsEnabled property will always return true.  We wont store our applications in different groups, instead they will all be located in the Site Root group so we will always return the Site group for the Application's Container.  Our application won't have a url to view it or an avatar associated with it, both AvatarUrl and the Url will just return null.

public bool IsEnabled
{
    get { return true; }
}

public IContainer Container
{
    get { return Apis.Get<IGroups>().Root; }
}

string IApplication.AvatarUrl
{
    get { return null; }
}

string IApplication.Url
{
    get { return null; }
}

Here is the completed LinksApplication entity source:

using System;
using Telligent.Evolution.Extensibility;
using Telligent.Evolution.Extensibility.Api.Entities.Version1;
using Telligent.Evolution.Extensibility.Api.Version1;
using Telligent.Evolution.Extensibility.Content.Version1;

namespace Samples.Links
{
    public class LinksApplication : ApiEntity, IApplication
    {
        public string Name { get; set; }
        public string Description { get; set; }

        #region IApplication Members
        public Guid ApplicationId { get; set; }

        public Guid ApplicationTypeId
        {
            get { return ContentTypes.LinksApplicationId; }
        }

        public string HtmlName(string target)
        {
            return Name;
        }

        public string HtmlDescription(string target)
        {
            return Description;
        }

        public bool IsEnabled
        {
            get { return true; }
        }

        public IContainer Container
        {
            get { return Apis.Get<IGroups>().Root; }
        }

        string IApplication.AvatarUrl
        {
            get { return null; }
        }

        string IApplication.Url
        {
            get { return null; }
        }
        #endregion
    }
}

LinksItem Entity

Our LinkItem Entity will implement the IContent interface and represent a single link item in the Links Application.

ContentTypeId is a unique id that represents a type of content in the community, you define this value when creating your content type.  The example uses the same static class that defines the ApplicationTypeId to define this Id.  ContentId is the unique Id for an individual content item in the community.  We will provide this to the community as well.

public Guid ContentId { get; set; }

public Guid ContentTypeId
{
    get { return ContentTypes.LinksItemId; }
}

Each piece of Content is related to an application in the community,  we have defined a property to store the ApplicationId for our content's application.  We use this value to return an instance of our application when we implement the Application property on the IContent interface.

public Guid ApplicationId { get; set; }

public IApplication Application
{
    get { return LinksData.GetApplication(ApplicationId); }
}

The Name and Description fields are not required by the IContent interface, but allow use to store a raw version of the there values.  The HtmlName and HtmlDescription methods are then used to provide the output depending on the target.  These allow the output to be tailored to those targets, for instance if your target was an email you may not want to display images inline in your content.

public string Name { get; set; }
public string Description { get; set; }

public string HtmlName(string target)
{
    return Name;
}

public string HtmlDescription(string target)
{
    return Description;
}

Each link item has a Url and the IContent interface requires a Url, so we will use that property to store the Url and satisfy the interface.

public string Url { get; set; }

The CreatedDate, IsEnabled, AvatarUrl and CreatedByUserId properties are required by the interface but will be static or are not needed in this example, so we will implement those, but they don't provide any additional value.  If this were a fully functional example that implemented core services or other features of the platform these values would become important to populate correctly.

public DateTime CreatedDate { get; set; }

public bool IsEnabled
{
    get { return true; }
}

string IContent.AvatarUrl
{
    get { return null; }
}

int? IContent.CreatedByUserId
{
    get { return null; }
}

Here is the completed LinkItem entity source:

using System;
using Telligent.Evolution.Extensibility.Api.Entities.Version1;
using Telligent.Evolution.Extensibility.Content.Version1;

namespace Samples.Links
{
    public class LinkItem : ApiEntity, IContent
    {
        public Guid ApplicationId { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }

        #region IContent Members
        public Guid ContentId { get; set; }

        public Guid ContentTypeId
        {
            get { return ContentTypes.LinksItemId; }
        }

        public IApplication Application
        {
            get { return LinksData.GetApplication(ApplicationId); }
        }

        public string HtmlName(string target)
        {
            return Name;
        }

        public string HtmlDescription(string target)
        {
            return Description;
        }

        public string Url { get; set; }

        public DateTime CreatedDate { get; set; }

        public bool IsEnabled
        {
            get { return true; }
        }

        string IContent.AvatarUrl
        {
            get { return null; }
        }

        int? IContent.CreatedByUserId
        {
            get { return null; }
        }

        #endregion
    }
}

Next we will define our Application and Content Types that make use of our entities to register those with the platform.

LinksApplication Application Type

The IPlugin and ITranslatablePlugin interfaces are discussed in other articles, we will skip the explanation of those implementations and focus on the IApplicationType implementation.

ApplicationTypeId is the same id that was used when defining our IApplication entity.  ApplicationName is the name of the application that will be displayed in the community.  The example uses a translation so the application name can be translated for each language the community supports.  

Guid IApplicationType.ApplicationTypeId
{
    get { return ContentTypes.LinksApplicationId; ; }
}

string IApplicationType.ApplicationTypeName
{
    get { return _translations.GetLanguageResourceValue("ApplicationTypeName"); }
}

The AttachChangeEvents method provides access to an IApplicationStateChanges object.  This object is used by your application type to inform the platform of changes to your application.  For instance, when you application is deleted, informing the platform of the deletion allows the platform to remove data associated with that application.  

void IApplicationType.AttachChangeEvents(IApplicationStateChanges stateChanges)
{
    _applicationState = stateChanges;
}

The ContainerTypes property should return all the types of containers that can hold this application.  In this case the only container is groups, so that Id is returned.  

Guid[] IApplicationType.ContainerTypes
{
    get { return new Guid[] { Apis.Get<IGroups>().ContentTypeId }; }
}

Lastly, the Get method should return an instance of the Application entity.

IApplication IApplicationType.Get(Guid applicationId)
{
    return LinksData.GetApplication(applicationId);
}

Here is the completed LinksApplicationType source:

using System;
using Telligent.Evolution.Extensibility;
using Telligent.Evolution.Extensibility.Api.Version1;
using Telligent.Evolution.Extensibility.Content.Version1;
using Telligent.Evolution.Extensibility.Version1;

namespace Samples.Links
{
    public class LinksApplicationType : IApplicationType, ITranslatablePlugin
    {
        IApplicationStateChanges _applicationState = null;
        ITranslatablePluginController _translations = null;

        #region IPlugin Members
        string IPlugin.Name
        {
            get { return "Links"; }
        }
        string IPlugin.Description
        {
            get { return "Collections of Links"; }
        }

        void IPlugin.Initialize()
        {

        }
        #endregion

        #region IApplicationType Members
        Guid IApplicationType.ApplicationTypeId
        {
            get { return ContentTypes.LinksApplicationId; ; }
        }

        string IApplicationType.ApplicationTypeName
        {
            get { return _translations.GetLanguageResourceValue("ApplicationTypeName"); }
        }

        void IApplicationType.AttachChangeEvents(IApplicationStateChanges stateChanges)
        {
            _applicationState = stateChanges;
        }

        Guid[] IApplicationType.ContainerTypes
        {
            get { return new Guid[] { Apis.Get<IGroups>().ContentTypeId }; }
        }

        IApplication IApplicationType.Get(Guid applicationId)
        {
            return LinksData.GetApplication(applicationId);
        }
        #endregion

        #region ITranslatablePlugin Members
        Translation[] ITranslatablePlugin.DefaultTranslations
        {
            get
            {
                var t = new Translation("en-US");
                t.Set("ApplicationTypeName", "Links Application");
                return new Translation[] { t };
            }
        }

        void ITranslatablePlugin.SetController(ITranslatablePluginController controller)
        {
            _translations = controller;
        }
        #endregion
    }
}

LinkItem Content Type

Again, the IPlugin and ITranslatablePlugin interfaces are discussed in other articles, we will skip the explanation of those implementations and focus on the IContentType implementation.

ContentTypeId is the same id that was used when defining our IContent entity.  ContentName is the name of the content that will be displayed in the community.  The example uses a translation so the content name can be translated for each language the community supports.  

Guid IContentType.ContentTypeId
{
    get { return ContentTypes.LinksItemId; }
}

string IContentType.ContentTypeName
{
    get { return _translations.GetLanguageResourceValue("ContentTypeName"); }
}

The AttachChangeEvents method provides access to an IContentStateChanges object.  This object is used by your content type to inform the platform of changes to your content.  For instance, when you content is deleted, informing the platform of the deletion allows the platform to remove data associated with that content.  

void IContentType.AttachChangeEvents(IContentStateChanges stateChanges)
{
    _contentState = stateChanges;
}

The ApplicationTypes property should return all the types of applications that can hold this content.  In this case the only application is our LinksApplication, so that Id is returned.  

Guid[] IContentType.ApplicationTypes
{
    get { return new Guid[] { ContentTypes.LinksApplicationId }; }
}

Finally the Get method should return an instance of the Content entity.

IContent IContentType.Get(Guid contentId)
{
    return LinksData.GetLink(contentId);
}

Here is the completed LinkItemContentType entity source:

using System;
using Telligent.Evolution.Extensibility.Content.Version1;
using Telligent.Evolution.Extensibility.Version1;
using IContent = Telligent.Evolution.Extensibility.Content.Version1.IContent;

namespace Samples.Links
{
    public class LinkItemContentType : IContentType, ITranslatablePlugin
    {
        IContentStateChanges _contentState = null;
        ITranslatablePluginController _translations = null;

        #region IPlugin Members
        string IPlugin.Name
        {
            get { return "Link Items"; }
        }
        string IPlugin.Description
        {
            get { return "Items in a Links collection"; }
        }

        void IPlugin.Initialize()
        {

        }
        #endregion

        #region IContentType Members
        Guid[] IContentType.ApplicationTypes
        {
            get { return new Guid[] { ContentTypes.LinksApplicationId }; }
        }

        void IContentType.AttachChangeEvents(IContentStateChanges stateChanges)
        {
            _contentState = stateChanges;
        }

        Guid IContentType.ContentTypeId
        {
            get { return ContentTypes.LinksItemId; }
        }

        string IContentType.ContentTypeName
        {
            get { return _translations.GetLanguageResourceValue("ContentTypeName"); }
        }

        IContent IContentType.Get(Guid contentId)
        {
            return LinksData.GetLink(contentId);
        }
        #endregion

        #region ITranslatablePlugin Members
        Translation[] ITranslatablePlugin.DefaultTranslations
        {
            get
            {
                var t = new Translation("en-US");
                t.Set("ContentTypeName", "Link Item");
                return new Translation[] { t };
            }
        }

        void ITranslatablePlugin.SetController(ITranslatablePluginController controller)
        {
            _translations = controller;
        }
        #endregion
    }
}

The custom content and application plugins are now completed, but we can add some additional functionality to make this easier to use in a community site.

Widget Extensions

As of now, we won't be able to interact with our new application or content from a velocity widget, a couple custom IScriptedContentFragmentExtensions can expose our data methods so they can be called from velocity.  We will add an extension that works with the LinksApplication called telligent_v1_links and an extension for the LinkItem methods called telligent_v1_linkitem. 

Our extension will expose the LinksApplicationMethods class.  $telligent_v1_links.List() will return a list of all the links applications, while telligent_v1_links.Get(Guid applicationId) will return a single links application.

The source code for the telligent_v1_links extension:

using System;
using Telligent.Evolution.Extensibility.Api.Entities.Version1;
using Telligent.Evolution.Extensibility.UI.Version1;

namespace Samples.Links.WidgetApi
{
    public class LinksApplicationWidgetExtension : IScriptedContentFragmentExtension
    {
        #region IScriptedContentFragmentExtension Members

        public string ExtensionName
        {
            get { return "telligent_v1_links"; }
        }

        public object Extension
        {
            get { return new LinksApplicationMethods(); }
        }

        #endregion

        #region IPlugin Members

        public string Name
        {
            get { return "Links Extension (telligent_v1_links)"; }
        }

        public string Description
        {
            get { return "Enables widgets to work with Links Applications."; }
        }

        public void Initialize()
        {
        }
        #endregion
    }

    public class LinksApplicationMethods
    {
        public ApiList<LinksApplication> List()
        {
            return new ApiList<LinksApplication>(LinksData.ListApplications());
        }

        public LinksApplication Get(Guid applicationId)
        {
            return LinksData.GetApplication(applicationId);
        }
    }
}

Similarly, we will use a LinkItemMethods class to expose methods to List all LinkItems in a Link Application and another to Get a single LinkItem. $telligent_v1_linkitem.List(Guid applicationId) will return the list, while $telligent_v1_linkitem.Get(Guid contentId) will return a single instance of a LinkItem.

The source code for the telligent_v1_linkitem extension:

using System;
using Telligent.Evolution.Extensibility.Api.Entities.Version1;
using Telligent.Evolution.Extensibility.UI.Version1;

namespace Samples.Links.WidgetApi
{
    public class LinkItemWidgetExtension : IScriptedContentFragmentExtension
    {
        #region IScriptedContentFragmentExtension Members

        public string ExtensionName
        {
            get { return "telligent_v1_linkitem"; }
        }

        public object Extension
        {
            get { return new LinkItemMethods(); }
        }

        #endregion

        #region IPlugin Members

        public string Name
        {
            get { return "LinkItem Extension (telligent_v1_linkitem)"; }
        }

        public string Description
        {
            get { return "Enables widgets to work with Link Items."; }
        }

        public void Initialize()
        {
        }

        #endregion
    }

    public class LinkItemMethods
    {
        public ApiList<LinkItem> List(Guid applicationId)
        {
            return new ApiList<LinkItem>(LinksData.ListLinks(applicationId));
        }

        public LinkItem Get(Guid contentId)
        {
            return LinksData.GetLink(contentId);
        }
    }
}

Plugin Group

We could deploy this as is, but you would need to search around the admin panel to enable each of the 4 different plugins, not to mention these plugins have dependencies on each other and should not be used independently of one another.  We can use a IPluginGroup to create a single point to enable or disable all related plugins at the same time to care of both of these issues.

Here is the completed source for our Plugin Group:

using System;
using System.Collections.Generic;
using Samples.Links.WidgetApi;
using Telligent.Evolution.Extensibility.Version1;

namespace Samples.Links.Plugins
{
    public class LinksPluginGroup : IPluginGroup
    {
        #region IPlugin Members
        string IPlugin.Name
        {
            get { return "Links Application"; }
        }
        string IPlugin.Description
        {
            get { return "Defines the plugins required by the links application"; }
        }

        void IPlugin.Initialize()
        {

        }
        #endregion

        #region IPluginGroup Members
        IEnumerable<Type> IPluginGroup.Plugins
        {
            get
            {
                return new[]
                {
                    typeof (LinksApplicationType),
                    typeof (LinkItemContentType),
                    typeof (LinkItemWidgetExtension),
                    typeof (LinksApplicationWidgetExtension)
                };
            }
        }
        #endregion
    }
}

Example Widget

We can now deploy the dll containing our Links Application to the community and enable the Links Application Plugin group.  We can use a widget to test that our application is working and our content can use a core service, in this case the Like service.

The widget will list all the Links Applications.  For each application it will list all the Link Items and those items will each display a Like button.

The velocity code for that widget would look like:

#set($linkapps = $telligent_v1_links.List())

#foreach ($linkapp in $linkapps)
#beforeall
    <ul class="content-list simple">
#each
        <li class="content-item simple">
            <h4 style="margin-bottom: 5px;">$linkapp.Name</h4>
            <div class="description" style="margin: 5px 0;">$linkapp.Description</div>
            
            #set($links = $telligent_v1_linkitem.List($linkapp.ApplicationId))
            
            #foreach ($link in $links)
		    #beforeall
		    	<ul class="links">
		    #each
    				<li class="link-item" style="margin-bottom: 5px;">
        				#if ($core_v2_user.IsRegistered($core_v2_user.Accessing.Id))
        					#set ($likeFormat = '{toggle} <span class="count"><span class="icon"></span>{count}</span>')
        				#else
        					#set ($likeFormat = '<span class="count"><span class="icon"></span>{count}</span>')
        				#end    				

    				    <a href="$link.Url">$link.Name</a> - $core_v2_ui.Like($link.ContentId, $link.ContentTypeId, "%{ Format = $likeFormat, IncludeTip = 'true' }")

    				    <div class="description">$link.Description</div>
    				</li>
		    #afterall
                </ul>		    
		    #end
        </li>
#afterall
    </ul>
#end

Once the widget is added to a page you should see the following:

Downloadable Source

Samples.Links.zip

Now that we have a base level application and content type, we can begin to add support for core services to our application.  Additional articles discussing Adding Core Service Support to Content are available.