Custom Workflow Comments (CWC) Module for Sitecore 7 – Part 1

Posted 11/16/2013 by asura

Based on my previous blog post: Sitecore 7: Workflow with more than a Comment, I got a lot of positive feedback and requests to improve the functionality. Thank you for all the requests and feedback. I appreciate it.

The concept has evolved into using any template with standard fields to accept input in the Workbox. The concept is simple, per workflow we should be able to define one template that represents fields we want to capture. For instance Workflow 1 would like to capture Name and Comments and Workflow 2 would like to capture Date, Name, Comments, Some ID values.

This is an attempt at building version 1 of the Custom Workflow Comments Module (CWC Module). I know there are a lot of areas where we can optimize code and add UI elements. Those will be added on in the later versions.

CWC module allows you to create an item template per workflow to store additional information during the workflow process of an item. When an item is processed in the workflow a dynamic form will render the fields defined in the custom template. The values entered will be store in an Item Bucket.

UI enhancements include changes to the workflow section in the Review tab and the more links in the Workbox.

To implement this I have taken the Rendering Parameters concept in the Layout details. Using a refactor tool like the .NET Reflector or JetBrains dotPeek to decompile Sitecore.Client and grab code for Sitecore.Shell.Applications.WorkboxForm and Sitecore.Shell.Applications.Layouts.DeviceEditor.RenderingParameters, these would act as a base for our code.

Step 1 – Override

To implement this we need to override the functionality in Workbox.xml. To do this, create the following folders under WebsitesitecoreshellOverride folder:

  • Applications

Copy Workbox.xml from WebsitesitecoreshellApplicationsWorkbox

Step 2 – Code Behind

Use a refactor tool like the .NET Reflector or JetBrains dotPeek to decompile Sitecore.Client and grab code for Sitecore.Shell.Applications.WorkboxForm and Sitecore.Shell.Applications.Layouts.DeviceEditor.RenderingParameters.

Add a WorkboxForm class and RenderCustomTemplate (RenderingParameters) in your BL or Sitecore Extensions project to mirror the Sitecore.Shell.Applications.Workbox.WorkboxForm class and Sitecore.Shell.Applications.Layouts.DeviceEditor.RenderingParameters class.

Step 3 – CodeBeside

Modify the Workbox.xml file in the override folder to replace the value in the CodeBeside tag.


Step 4 – Templates

I have setup the following templates

  1. Custom Workflow Comments Settings
  2. Custom Workflow Comments Item
  3. Custom Workflow Comments Base

Custom Workflow Comments Settings
This template is inherited by the /sitecore/templates/System/Workflow/Workflow template so that we can choose to specify a custom template per workflow to gather user input.

The value in the Custom Template field can be left blank so that the default comment input is requested. If a template is selected in this field, the CWC module will dynamically display the editor needed to accept input for that template. The only prerequisite is that the template selected has to inherit the Custom Workflow Comments Base template.

Custom Workflow Comments Item
This template is used to store the Workflow item’s ID, Language and Version. The idea is that we would have one instance of this template and the child items would represent items based on the custom template you decide to use. I also selected the Lock Child Relationship so that the parent and children maintain the relationship in the item bucket.

Custom Workflow Comments Base
The custom template you define to store user input has to inherit this template. This template has the Bucketable option selected. There is also a check in the WorkboxForm class.

Step 4 – Custom Templates

It’s time to define custom templates. I defined two, Client Custom Workflow External and Client Custom Workflow Internal.

Step 5 – Item Buckets
I created a folder under sitecorecontent folder called Workflow Comments and made it an Item Bucket. 

Step 6 – Code, Finally!! – Search Class
The first thing is to define a class to hold search results for Custom Workflow Comments Item.

    public class CustomWorkflowCommentsItem: SearchResultItem
        public string ItemName { get; set; }

        public virtual string WorkflowItemID { get; set; }
        public virtual string Language { get; set; }
        public virtual string Version { get; set; }

        public string Template { get; set; }

        public virtual ID ID { get; set; }

Step 7 – Modify Sitecore.ContentSearch.Lucene.DefaultIndexConfiguration.config
By default, single line text is not indexed and depending on the project, its upto the client to make that decision. We need to add the fields to index the Workflow Item ID, Language and Version.

            Workflow Item ID

Language Version

Step 8 – Magic
The final item for this blog post is the code which pulls the fields from the custom template we pass and called the UI used by FormEditorPage.

    public class RenderCustomTemplate
        /// The current pipeline arguments.

private ClientPipelineArgs args; /// /// The name of the handle. /// /// private string handleName; /// /// Gets or sets the args. /// /// /// /// /// The args. /// /// public ClientPipelineArgs Args { get { return this.args; } set { Assert.ArgumentNotNull((object)value, “value”); this.args = value; } } /// /// Gets or sets the item id. /// /// /// /// /// The item id. /// public ID ItemID { private get; set; } /// /// Gets or sets the custom template id. /// /// /// /// /// The custom template id. /// public ID CustomTemplateID { private get; set; } /// /// Gets or sets the item language. /// /// /// /// /// The item language. /// public Language Language { private get; set; } /// /// Gets or sets the item version. /// /// /// /// /// The item version. /// public Version Version { private get; set; } /// /// Gets or sets the name of the handle. /// /// /// /// /// The name of the handle. /// /// public string HandleName { get { return this.handleName ?? “SC_DEVICEEDITOR”; } set { Assert.ArgumentNotNull((object)value, “value”); this.handleName = value; } } /// /// Shows this instance. /// /// /// /// /// The boolean. /// /// public bool Show() { if (!this.Args.IsPostBack) { //get the fields from the custom template RenderingParametersFieldEditorOptions fieldEditorOptions = new RenderingParametersFieldEditorOptions((IEnumerable)RenderCustomTemplate.GetFields(CustomTemplateID)); fieldEditorOptions.DialogTitle = Consts.Strings.DialogTitle; fieldEditorOptions.HandleName = this.HandleName; fieldEditorOptions.PreserveSections = true; RenderingParametersFieldEditorOptions options = fieldEditorOptions; //create url based on options UrlString urlString = options.ToUrlString(); //display UI SheerResponse.ShowModalDialog(urlString.ToString(), “720”, “480”, string.Empty, true); this.args.WaitForPostBack(); } return false; } /// /// Gets the fields. /// /// /// The custom template id to be used for this UI. /// /// The fields. /// /// public static List GetFields(ID customTemplateID) { List fieldDescriptors = new List(); Item standardValuesItem; using (new SecurityDisabler()) { //get the customTemplateID template TemplateItem templateItem = (TemplateItem)Client.ContentDatabase.GetItem(customTemplateID); if (templateItem == null) return fieldDescriptors; else standardValuesItem = templateItem.StandardValues ?? templateItem.CreateStandardValues(); //if item doesnt have any standard values, create them } //make sure we got the Item if (standardValuesItem == null) return fieldDescriptors; //get all fields FieldCollection fields = standardValuesItem.Fields; fields.ReadAll(); fields.Sort(); //for each field make sure they are not standard system fields and then add the to the List foreach (Field field in fields) { //weed out system fields if (!(field.Name == “Additional Parameters”) && (!(field.Name == “Personalization”) || UserOptions.View.ShowPersonalizationSection) && ((!(field.Name == “Tests”) || UserOptions.View.ShowTestLabSection) && RenderingItem.IsAvalableNotBlobNotSystemField(field))) { FieldDescriptor fieldDescriptor = new FieldDescriptor(standardValuesItem, field.Name) { Value = field.Value }; fieldDescriptors.Add(fieldDescriptor); } } return fieldDescriptors; } }

This is how the custom templates get rendered dynamically.

I have a few more posts to complete this module and I will post the links on the social networks for the upcoming parts.

If you have any questions or need the source, email me at Akshay dot sura at nttdata dot com.