Tuesday, February 28, 2012

Saving a new ExtendedAttribute for a User Object via a Velocity Script in a Telligent Community 6.0 Studio Widget.

I did not notice way to set an ExtendedAttribute on a user (or any object) from the OOTB velocity core API documentation, so this post will attempt to show how you can use an IScriptedContentFragmentExtension to accomplish this.

Step 1:  Create your IScriptedContentFragmentExtension definition

    public class UserExtensionDefinition : IScriptedContentFragmentExtension
    {
        public object Extension
        {
            get { return new UserExtension(); }
        }

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

        public string Description
        {
            get { return "Common user extensions needed by the Community"; }
        }

        public void Initialize()
        {

        }

        public string Name
        {
            get { return "Community User Extensions"; }
        }
    }

Step 2:  Create the underlying class that will contain the method to set the ExtendedAttribute for the user

using System;
using System.Collections.Generic;
using System.Linq;
using Telligent.Evolution.Components;
using Entities=Telligent.Evolution.Extensibility.Api.Entities.Version1;
using Telligent.Evolution.Extensibility.Api.Version1;
using Telligent.Evolution;

namespace Evolution.Extensions.v1
{  
    public class UserExtension
    {
        public bool SaveExtendedAttribute(int userId, string key, string value)
        {
            User user = Telligent.Evolution.Users.GetUser(userId);

            if (user != null)
            {
                user.SetExtendedAttribute(key, value);
                Telligent.Evolution.Users.UpdateUser(user);
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}


Step 3:  Enable the extension from the "Plugins" area in the control panel

Check the box next to the Community User Extensions and click "Save"


Step 4:  Invoke the object via velocity syntax

   #set($attributeSavedBooleanResponse = $companyName_v1_user.SaveExtendedAttribute($core_v2_user.Accessing.Id, "attributeKey","attributeValue"))


I hope this helps people when trying to use velocity script to set an extended attribute for a user (or any other object).  If there is an easier way or something out of the box i'm missing, please leave a comment!

Friday, February 24, 2012

Creating a Custom Resource Provider in Telligent Community 6.0

There may be times when you want to move the localization away from the widget and into a centralized provider.  Or if you're part of a larger company, it's possible there are already many localized resources in the build and there is a separate established process for localization.  The model for Telligent Community 6.0 - namely widget studio is to make each widget self-contained, however, there may be special circumstances or business processes in where it makes more sense to centralize your resources.  This blog post will show you one way to accomplish this.

Telligent Community Server will let you create new velocity extensions through a specific interface called: IScriptedContentFragmentExtension.  We will expose our resource manager by creating a class that implements this interface.  This will allow us to enable the extension through the plugins area in the control panel.

Step 1:  Create a ResourceManagerExtensionDefinition class that implements IScriptedContentFragmentExtension

   
public class ResourceManagerExtensionDefinition : IScriptedContentFragmentExtension
    {
        public object Extension
        {
            get { return new GlobalResourceManager(); }
        }

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

        public string Description
        {
            get { return "Provides access into a custom resource repository for shared resources."; }
        }

        public void Initialize()
        {

        }

        public string Name
        {
            get { return "Custom Resource Manager"; }
        }
    }


Step 2:  Create the base class that contains all of the Resource Manager logic.
In this example our resource manager will simply read from an XML file.  In more complex scenarios we could connect to a database or even implement pre-existing classes and objects used across multiple sites/projects.

This code will populate an XmlDocument when the object is constructed.

 
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Text;
using System.Xml;
using System.IO;
using Telligent.Evolution.Components;

namespace Evolution.WidgetExtensions.Extensions.v1
{
    public class GlobalResourceManager
    {
        #region Properties

        public XmlDocument ResourceDoc { get; set; }

        #endregion

        public CustomResourceManager()
        {
            this.ResourceDoc = new XmlDocument();
            this.ResourceDoc.LoadXml(File.ReadAllText(HttpContext.Current.Server.MapPath(String.Format(ConfigurationManager.AppSettings["LanguageFile"], CSContext.Current.User.Profile.Language))));
        }

        /// 
        /// Get the value of a custom global resource.
        /// 
        /// 
        public string GetResource (string resourceKey)
        {
            XmlNode resourceNode = this.ResourceDoc.SelectSingleNode(String.Format("/root/resource[@name='{0}']", resourceKey));
            if (resourceNode != null)
            {
                return resourceNode.InnerText;
            }
            else
            {
                return String.Empty;
            }
        }
    }
}

Step 3:  Create the Resource File
In this example we're using a basic XML file for our resources.  Save it a location of your choosing in your web directory.

<?xml version="1.0" encoding="utf-8"?>

  Username
 


Step 4: Modify your web.config by adding an appSetting which points to the location of our resource file 
In this example our location contains aplaceholder for token replacement.  In the code above, we detect the current user's language and change the path according to that.  




  <appSettings>
    <add key="Telligent.Glow.MultipleFileUpload.FileManagerProvider" value="Telligent.Evolution.Components.MultipleUploadFileManager, Telligent.Evolution.Components" />
    <add key="Telligent.Glow.MultipleFileUpload.UploadHandlerUrl" value="~/multipleupload.ashx" />
    <add key="LanguageFile" value="~/Customizations/Languages/{0}/Resources.xml"/>
  </appSettings>


Step 5: Next we'll need to recompile our solution and enable the IScriptedContentFragmentExtension from the control panel




Step 6:  Our resource manager is now available in a studio widget.

#set($username = $companyName_v1_resourceManager.GetResource("username"))


How to use Visual Studio to debug a local install of Telligent Community Server

A local instance of Telligent Community Server can not be debugged in the traditional way through Visual Studio debugging by simply tapping F5 or right-clicking a file and selecting "view in browser" if you have followed the Installation instructions on telligent.com and want to browse your site over http://localhost.  Instead you can follow the steps below.  These steps assume you've followed the installation steps and can browse your website from localhost.

  1. Compile your Telligent solution.
  2. Open a browser and navigate to your telligent homepage
  3. In visual studio, select Debug -> Attach To Process
  4. In the dialog that opens, you may need to check the boxes to show all processes by all users AND show processes in all sessions - this will depend on which user your community app pool is running as.  Once you've done that, you will want to look for the w3wp.exe task and attach to it
  5. Next you will want to set your break-points in your code
  6. Finally go back to the browser and navigate to the page that is invoking your code and your breakpoint should be hit.  You can now perform all of the typical debugging steps you'd normally perform.








Thursday, February 2, 2012

Community Server 6.0 Forum API Documentation Error on ForumThread Object - Excerpt is Depreciated

When coding with the velocity script extensions in Community Server 6.0, the API documentation is incorrect when accessing a ForumThread object.

Using Velocity Script, you can get a Thread object like this:
#set($thread = $core_v2_forumThread.Get($threadId))


According to the api documentation, you should be able to access the "Excerpt" of the thread, but this property is depreciated and always empty.
## Never populated and is depreciated
$thread.Excerpt

You can alternatively use the "Body" property of the $thread, but this property does not show up in the API Documentation.
## Returns the thread body, but not in the docs
$thread.Body


You can also display a smaller excerpt based on the body by stripping the HTML and truncating the text:
$core_v2_language.Truncate($core_v2_utility.Replace($thread.Body,"<(.|\n)*?>",""), 200, "...")

Just a quick screenshot of the API documentation for this object - note the "Body" property is not listed.

Creating a Custom Configuration Control in Telligent Community Server 6.0

When creating widgets in Telligent Community Server 6.0 (or previous versions) you may have noticed the use of custom controls that appear in the configuration modal.  Telligent, out of the box, provides several useful controls, but doesn't make it evident how to create your own.  This post will show you how to create a basic Widget Configuration Control that you can then include in the configuration modal.

In this example we will create a simple dropdown control that will allow you to select one of the membership roles that are defined in the control panel.

Step 1:  Configuration Settings for the Widget
When setting the configuration properties of a widget in community server 6.0, you navigate to the configuration tab in widget studio.

Scroll down to the area for "Configuration Content" and add the following:
<propertyGroup id="options" resourceName="Options">
<property id="expertRole" resourceName="CF_ExpertRole" dataType="custom" controlType="Evolution.WidgetExtensions.PropertyControls.RoleSelectionControl, Evolution.WidgetExtensions" width="95%" />
</propertyGroup>

Note the following settings on the "Property" node:
dataType="custom"   Indicates we'll use a custom control type
controlType="Evolution.WidgetExtensions.PropertyControls.RoleSelectionControl, Evolution.WidgetExtensions"  tells the widget which class/namespace and dll to look in

Important!
The format of the controlType attribute is <full class namespace>, <name of dll>

When configured, you will have the value saved to the "expertRole" (set in the id attribute) widget property.

Step 2:  Create the Class for the custom configuration control
The next step is to actually create the class for the new control.  The class you create must inherit from the IPropertyControl interface.  Well create a class called "RoleSelectionControl.cs" in Visual Studio.



Now that we have the class created, we need to reference the proper assemblies:
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using Telligent.DynamicConfiguration.Components;
using System.Web.UI.HtmlControls;
using Telligent.Evolution.Components;
using Telligent.Evolution.Controls;

Next step is to inherit from IPropertyControl - doing this will ensure we've implemented the proper set of methods and properties so community server can properly render, call methods, and retrieve values from our custom control

namespace Evolution.WidgetExtensions.PropertyControls
{
    public class RoleSelectionControl : Control, IPropertyControl
    {
    }
}

Now if you hover over the "IPropertyControl" name you can use the smart tags in Visual Studio to stub-in the required methods:


After  you implement the interface, you will see the methods have been added to your code:

        public ConfigurationDataBase ConfigurationData
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public Property ConfigurationProperty
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public event ConfigurationPropertyChanged ConfigurationValueChanged;

        public new Control Control
        {
            get { throw new NotImplementedException(); }
        }

        public object GetConfigurationPropertyValue()
        {
            throw new NotImplementedException();
        }

        public void SetConfigurationPropertyValue(object value)
        {
            throw new NotImplementedException();
        }

Step 2.1 - Define our custom dropdown control
With the interface now implemented, we can get to work and add our control.  We will declare our dropdown control and an HTML Generic control to use as a wrapper.  Add this directly below the class name

    public class RoleSelectionControl : Control, IPropertyControl
    {

        DropDownList _roleDropdown = null;
        HtmlGenericControl _roleDropdownWrapper = null;

Step 2.2 - Control Overrides
This step is what actually will add our dropdown to the parent control and make it render in the configuration modal.  We need to override two methods:
OnInit
CreateChildControls

        protected override void CreateChildControls()
        {
            base.CreateChildControls();

            _roleDropdown = new DropDownList();

            _roleDropdownWrapper = new HtmlGenericControl("div");
            _roleDropdownWrapper.ID = this.ClientID + "_role";
            _roleDropdownWrapper.Style["display"] = "block";
            Controls.Add(_roleDropdownWrapper);

            _roleDropdownWrapper.Controls.Add(_roleDropdown);

            // Get the roles
            var roles = Roles.GetRoles();
            _roleDropdown.Items.Clear();

            // Populate the dropdown
            foreach (Role role in roles)
            {
                _roleDropdown.Items.Add(new ListItem(role.Name, String.Format("{0}={1}&{2}={3}", "RoleId", role.Id.ToString(), "RoleName", role.Name)));
            }

            Controls.Add(new JQuery());
        }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            EnsureChildControls();
        }

Step 2.3 - Update Interface Methods

The following section will show you how to properly implement each of the interface members for this control:

ConfigurationDataBase
        private ConfigurationDataBase _configurationData = null;
        public ConfigurationDataBase ConfigurationData
        {
            get { return _configurationData; }
            set { _configurationData = value; }
        }

ConfigurationProperty
        private Property _configurationProperty = null;
        public Property ConfigurationProperty
        {
            get { return _configurationProperty; }
            set { _configurationProperty = value; }
        }

ConfigurationPropertyChanged
        public event ConfigurationPropertyChanged ConfigurationValueChanged
        {
            add { throw new NotSupportedException(); }
            remove { }
        }

Control
        public Control Control
        {
            get { return this; }
        }

GetConfigurationPropertyValue
When the modal "save" button is clicked, this method will be invoked and will return the value to be stored for this widget property.  We'll just return a string of the selected value of our dropdown list.

        public object GetConfigurationPropertyValue()
        {
            EnsureChildControls();

            return _roleDropdown.SelectedValue.ToString();
        }

SetConfigurationPropertyValue
When the widget configuration modal is opened, if the widget already has a value configured, this method will select the proper list item in our dropdown list.
        public void SetConfigurationPropertyValue(object value)
        {
            EnsureChildControls();

            // Attempt to select the initial config'd value
            if (_roleDropdown.Items.FindByValue(value.ToString()) != null)
            {
                _roleDropdown.Items.FindByValue(value.ToString()).Selected = true;
            }
            
        }

The Entire class now looks like this:

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using Telligent.DynamicConfiguration.Components;
using System.Web.UI.HtmlControls;
using Telligent.Evolution.Components;
using Telligent.Evolution.Controls;

namespace Evolution.WidgetExtensions.PropertyControls
{
    public class RoleSelectionControl : Control, IPropertyControl
    {

        DropDownList _roleDropdown = null;
        HtmlGenericControl _roleDropdownWrapper = null;

        #region Control Overrides

        protected override void CreateChildControls()
        {
            base.CreateChildControls();

            _roleDropdown = new DropDownList();

            _roleDropdownWrapper = new HtmlGenericControl("div");
            _roleDropdownWrapper.ID = this.ClientID + "_role";
            _roleDropdownWrapper.Style["display"] = "block";
            Controls.Add(_roleDropdownWrapper);

            _roleDropdownWrapper.Controls.Add(_roleDropdown);

            // Get the roles
            var roles = Roles.GetRoles();
            _roleDropdown.Items.Clear();

            // Populate the dropdown
            foreach (Role role in roles)
            {
                _roleDropdown.Items.Add(new ListItem(role.Name, String.Format("{0}={1}&{2}={3}", "RoleId", role.Id.ToString(), "RoleName", role.Name)));
            }

            Controls.Add(new JQuery());
        }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            EnsureChildControls();
        }

        #endregion

        #region IPropertyControl Members

        private ConfigurationDataBase _configurationData = null;
        public ConfigurationDataBase ConfigurationData
        {
            get { return _configurationData; }
            set { _configurationData = value; }
        }

        private Property _configurationProperty = null;
        public Property ConfigurationProperty
        {
            get { return _configurationProperty; }
            set { _configurationProperty = value; }
        }

        public event ConfigurationPropertyChanged ConfigurationValueChanged
        {
            add { throw new NotSupportedException(); }
            remove { }
        }

        public Control Control
        {
            get { return this; }
        }

        public object GetConfigurationPropertyValue()
        {
            EnsureChildControls();

            return _roleDropdown.SelectedValue.ToString();
        }

        public void SetConfigurationPropertyValue(object value)
        {
            EnsureChildControls();

            // Attempt to select the initial config'd value
            if (_roleDropdown.Items.FindByValue(value.ToString()) != null)
            {
                _roleDropdown.Items.FindByValue(value.ToString()).Selected = true;
            }
            
        }

        #endregion
    }
}

Step 3:  Re-Build / deploy the dll
We now have a complete custom control that we can use in the widget configuration modal.  So, re-build your solution (and deploy your dll to your bin folder if it's not already part of your build process).

Step 4:  Test the new configuration control
We've already configured our widget and deployed the dll, so now we can check to see if it all worked.  Drop your widget on a page, enter edit mode and click the pencil to configure the widget.

As you can see, we have an "Expert role" dropdown list that displays all available roles configured in the control panel.  Note, this is not an actual screenshot, but another widget configuration that utilizes this control.  Based on this example, you won't see the "Widget Title" or "Group" config options.

Step 5:  Access the value of the dropdown
The final step is to access the value of the widget configuration control.  The way we coded the widget, we know the value of the dropdown list will be in the format: {RoleId={id}&RoleName={rolename}

Using velocity scripting, we can easily access the role information for use in our widget:

#set ($roleQueryString = $core_v2_widget.getStringValue("expertRole",""))
#set ($roleName = "")
#set ($roleId = "")

#if($roleQueryString != "")
    #set ($roleValues = $core_v2_page.ParseQueryString($roleQueryString))
    #set ($roleName = $roleValues.Value("RoleName"))
    #set ($roleId = $roleValues.Value("RoleId"))
#end


BUG - Recommended Content widget does not honor configuration filter checkbox options in Telligent Community Server 6.0

I recently un-covered a bug in one of the out of the box widgets in Community Server 6.0 (6.0.119.19092). The Widget is the "Recommended Content Widget".

The Problem: 
Recommended content widget was not honoring the checkboxes to limit the post types to display.

$core_v2_widget.GetCustomValue('contentType',''))  was returning a comma separated list of the selected values in widget configuration and the out of the box widget script was expecting a query string.

The Solution:

This is the original code from the out of the box widget:
#set ($contentTypes = $core_v2_page.ParseQueryString($core_v2_widget.GetCustomValue('contentType','')).Values('Selection'))

Here is code that will fix the problem:

#set ($contentString = $core_v2_widget.GetCustomValue('contentType',''))
#set ($contentTypes = $core_v2_utility.Split(",", $contentString))

By recognizing the widget is returning a comma separated string, the widget will now apply the correct filters to the query.