Tuesday, January 31, 2012

Custom Data Lists in SmartForms


Intro

Ektron Smartforms are one of the key content types that all Ektron customers take advantage of in one way or another. They are reusable, extendable, provide a great way of structuring data, and provide your content authors with easy to input forms for content entry.  But out of the box, the Ektron smartforms aren’t customized to fit with YOUR information architecture.  Yes, we’ve made great recent improvements especially regarding the Resource Selector which allows you to associate content, folder, and taxonomy items to your content item. But what if you want to associate other organizational items or custom types not defined within the selector? The following guide will provide you with code and instructions on extending your SmartForms to allow for custom list types in an easy to extend class that uses our newest Framework API functionality.

Getting Ready

To get ready there’s a few things you’ll need to make this easier:
·         Visual Studio 2010 (Express will work)
·         Write access to: \Workarea\ContentDesigner\DataListSpec.xml
·         Write access to: \Workarea\ContentDesigner\Resources\DataListSpec[.*].resx

First Step

Alright, to get started I have created a fairly straightforward ashx file that is really at the heart of all this. What this file does, is provide a XML structured string to your SmartForm select field with your list data.  I have created the following basic lists and hopefully have made it easy enough to extend for you to further customize for your own site architecture:
·         Content List
·         Folder List
·         Taxonomy List
·         Collection List
·         Menu List
Copy the following code and paste it in a new .ASHX file in your site root or somewhere that makes sense within your site architecture:


<%@ WebHandler Language="C#" Class="CustomList" %>

using System;
using System.Web;
using System.Collections.Generic;
using System.Text;
using Ektron.Cms.Content;

public class CustomList : IHttpHandler {
    
    

    public void ProcessRequest (HttpContext context) {
        string type = context.Request.QueryString["type"] ?? "ContentList";
        long parentId = QueryString("parentId", 0, context);

        context.Response.ContentType = "text/xml";
        
        context.Response.Write("");
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }

    public static long QueryString(string paramName, int defaultValue, HttpContext context)
    {
        long value;
        if (!long.TryParse(context.Request.QueryString[paramName], out value))
            return defaultValue;
        return value;
    }
}

public class EktLists
{

    long _parentId;
    
    public EktLists(long parentId)
    {
        _parentId = parentId;
    }
    
    public string ContentList()
    {
        StringBuilder options = new StringBuilder();

        Ektron.Cms.Framework.Content.ContentManager cm = new Ektron.Cms.Framework.Content.ContentManager();
        ContentCriteria criteria = new ContentCriteria(Ektron.Cms.Common.ContentProperty.Id, Ektron.Cms.Common.EkEnumeration.OrderByDirection.Ascending);
        criteria.AddFilter(Ektron.Cms.Common.ContentProperty.FolderId, Ektron.Cms.Common.CriteriaFilterOperator.EqualTo, _parentId);
        
        List list = cm.GetList(criteria);

        foreach (Ektron.Cms.ContentData item in list)
        {
            options.AppendFormat("", item.Id, item.Title);
        }
        
        return options.ToString();  
    }

    public string FolderList()
    {
        StringBuilder options = new StringBuilder();

        Ektron.Cms.Framework.Organization.FolderManager fm = new Ektron.Cms.Framework.Organization.FolderManager();
        Ektron.Cms.FolderCriteria criteria = new Ektron.Cms.FolderCriteria(Ektron.Cms.Common.FolderProperty.Id, Ektron.Cms.Common.EkEnumeration.OrderByDirection.Ascending);
        criteria.AddFilter(Ektron.Cms.Common.FolderProperty.Id, Ektron.Cms.Common.CriteriaFilterOperator.EqualTo, _parentId);
        
        List list = fm.GetList(criteria);

        foreach (Ektron.Cms.FolderData item in list)
        {
            options.AppendFormat("", item.Id, item.Name);
        }

        return options.ToString();  
    }

    public string TaxonomyList()
    {
        StringBuilder options = new StringBuilder();

        Ektron.Cms.Framework.Organization.TaxonomyManager tm = new Ektron.Cms.Framework.Organization.TaxonomyManager();
        Ektron.Cms.Organization.TaxonomyCriteria criteria = new Ektron.Cms.Organization.TaxonomyCriteria(Ektron.Cms.Organization.TaxonomyProperty.Name, Ektron.Cms.Common.EkEnumeration.OrderByDirection.Ascending);
        criteria.AddFilter(Ektron.Cms.Organization.TaxonomyProperty.Id, Ektron.Cms.Common.CriteriaFilterOperator.EqualTo, _parentId);
        
        List list = tm.GetList(criteria);

        foreach (Ektron.Cms.TaxonomyData item in list)
        {
            options.AppendFormat("", item.Id, item.Name);
        }

        return options.ToString();  
    }

    public string CollectionList()
    {
        StringBuilder options = new StringBuilder();

        Ektron.Cms.Framework.Organization.CollectionManager cm = new Ektron.Cms.Framework.Organization.CollectionManager();
        Ektron.Cms.CollectionCriteria criteria = new Ektron.Cms.CollectionCriteria(Ektron.Cms.Common.ContentCollectionProperty.Title, Ektron.Cms.Common.EkEnumeration.OrderByDirection.Ascending);
        criteria.AddFilter(Ektron.Cms.Common.ContentCollectionProperty.Id, Ektron.Cms.Common.CriteriaFilterOperator.EqualTo, _parentId);

        List list = cm.GetList(criteria);
        foreach (Ektron.Cms.Organization.ContentCollectionData item in list)
        {
            options.AppendFormat("", item.Id, item.Title);
        }

        return options.ToString();
    }

    public string MenuList()
    {
        StringBuilder options = new StringBuilder();

        Ektron.Cms.Framework.Organization.MenuManager mm = new Ektron.Cms.Framework.Organization.MenuManager();

        Ektron.Cms.Organization.MenuData list = mm.GetTree(_parentId);
        foreach (Ektron.Cms.Organization.MenuItemData item in list.Items)
        {
            options.AppendFormat("", item.Id, item.Text);
        }

        return options.ToString();
    }
Now that you’ve pasted this in, let’s go over it a little.  You’ll notice the first Class, “CustomList” is simply a generic ashx handler, which looks at two arguments, nothing too special:
string Type: Used to define the type of list being loaded, current acceptable values are ContentList, FolderList, TaxonomyList, CollectionList, and MenuList.
long parentId: Used to define the containing organizational identifier. E.g. A parent folder id.
The second class “EkLists” however is where all the action happens. This class is instantiated right above the switch case and given the parentId for the container your list will be in.  The proper method which fills the list is then called. Your resulting string which is received by the editor will look a lot like:
<select>
<option value="30">Sample Content Block</option>
<option value="32">testing</option>
</select>

But we still need to tell the editor about our lists!

So now that we have our new super ektlist class outputting all the xml lists we can imagine, the editor still has no idea this file or our cool lists exist.  The good news is, all our list information is read in from a configuration file called DataListSpec.xml located in \Workarea\ContentDesigner\.
Open this file, and right before the ending </datalists> paste:
  <datalist name="ContentList" localeRef="ContentList" src="[path]/CustomList.ashx?type=ContentList&amp;parentId=0" cache="false" select="/select/option" captionxpath="." valuexpath="@value" validation="select-req">
    <item value="" localeRef="sSel"/>
  </datalist>

If you read the above statement, there are a few key parts:
  •  Name: Can be whatever you’d like localeRef: Name Key referenced in DataListSpec[.*].res
  • src: “[path]/CustomList.ashx” Location to file you imported earlier
  • type: Defaults to ContentList, can be modified to any list type defined.
  • parentId: Containing element of list items
  • validation: Default is select-req, which makes this field required

And that’s it; the rest of the values will normally not change. 

The only piece left is adding the reference into the resx file and we’re ready to start viewing our list(s).

Open “\Workarea\ContentDesigner\Resources\DataListSpec[.*].resx” which is a two column spreadsheet of a Name/Value pair. Create a new entry for any datalists you defined, where the Name equals “localeRef” and the Value can be whatever you’d like. For the above configuration I entered:

Name: ContentList, Value: ContentList(FolderID 0)

OK! If everything was put in the right spot, when we go into the SmartForm data designer we should now be able to see our new list for a “Choices Field” option.





Thanks for reading, please leave comments below for ideas or extensions on this. Also feel free to talk with me on twitter @andrew_eddy about this article or Ektron in general!


1 comment:

  1. How would you deal with different languages. So if I am in french version of the workarea and I would like to use french data list, where will i specify to use the french datalist?I hope this make sense.

    ReplyDelete