Friday, March 5, 2010

Custom Fields, Part 1

Like most WCM systems, Sitecore supports both structured and unstructured content. The difference between structured and unstructured content can be explained through an example. Let's say you want to allow a person from the marketing department to create a new press release for your website. With structured content the person would need to provide a title, date, description, and an image. With unstructured content the person would get a big HTML field and would provide any values he wants.

What is a field type?
When working with structured content, the concept of "content type" is important. Is the content you're modeling a string, a date or an image? If the content is a string, is formatting supported? The answers to these questions determine what the user interface looks like for the people using the system.

Sitecore has an interesting way of handling content type. Content is defined using fields, and each field has a type. Sitecore comes with a large number of different field types out of the box.

Why should you be interested in custom field types?
As powerful and flexible as Sitecore field types are, it is possible that you will encounter a requirement that would be much easier to meet if you create your own field type.

Not all WCM systems can do this. The impact of this is a significantly more complicated content model. This means more work for developers to create and more work for content authors to use. It also has a potentially devastating effect on workflow (imagine a proliferation of distinct pieces of managed content that somehow must be related).

This functionality has so many applications. It can be used to integrate with external data sources. It can be used to create easier-to-use interfaces for text values (as this article will explain). It can be used to facilitate content migration. And much more.

How are custom field types handled in Sitecore?
As you would expect, Sitecore provides many ways to handle custom field types. And as you would expect, the method you choose depends on your requirements. I'm going to cover a number of these in future posts.

To give you an idea of the scope of this topic, developing a custom field requires you consider the following:
  1. How does a person edit field content in Content Editor mode?
  2. How does a person edit field content in Page Editor mode?
  3. How is field content stored in Sitecore?
  4. How is field content accessed using the Sitecore API?
  5. How is field content rendered in presentation logic?
Not every custom field needs to have a custom solution for each of these. This post covers only the first one. I will explain how to create a custom interface for entering a text value. I think this is a good place to start since a large part of creating custom field types is building the user interface for reading and writing field content.

The Sitecore Developer Network has an excellent article that explains how to create something called a "composite custom field". What this post covers is very similar to that, but is much simpler. This will give me something to build on in subsequent posts.

This post will explain how to create a custom field to make it a little easier to enter a person's name. For the sake of simplicity, I am going to assume that a person has a first name (given name) and a last name (family name or surname). The person's name will be stored as a single value in Sitecore, but the user interface will allow content editors to enter the values separately.

What is a composite custom field?
A composite custom field is made up of existing field types. My custom field needs to support two separate text values. Since Sitecore already has the Text field type, a composite custom field is appropriate. I can use two Text fields to create my custom field.

Step 1 - Create an HTML server control
Using Visual Studio create the following class. This control will generate the user interface Sitecore displays when someone edits the value of a field:

Assembly name: sctest.dll
Namespace: Sctest
Class name: NameField
Base class: Sitecore.Web.UI.HtmlControls.Control

Step 2 - Override the OnLoad method
In the Sctest.NameField class, override the OnLoad method. This method is inherited from System.Web.UI.WebControl, which is a base class for Sitecore.Web.UI.HtmlControls.Control. Just like with a standard ASP.NET web control, the purpose of this method is to load and set properties on child controls.


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

This control needs 2 textbox controls (first name and last name). The first thing this method does is checks to see if these controls have already been loaded. This is accomplished by checking the value of the IsEvent property on the ClientPage.


protected override void OnLoad(EventArgs e)
{
if (!Sitecore.Context.ClientPage.IsEvent)
{
}
else
{
}
base.OnLoad(e);
}

ClientPage represents the current web form. Checking ClientPage.IsEvent is very similar to checking Page.IsPostback in ASP.NET. This layer of abstraction is important because it frees Sitecore from being tied to web forms. But that's a topic for a later post.

If ClientPage.IsEvent is not true it is likely that the 2 textbox controls have not already been created. The textboxes need to be created. The textbox controls also need to be added as children on the NameField control.


//
//create the textbox controls
var textFirstName = new Sitecore.Shell.Applications.ContentEditor.Text();
textFirstName.ID = GetID("textFirstName");
var textLastName = new Sitecore.Shell.Applications.ContentEditor.Text();
textLastName.ID = GetID("textLastName");
//
//add the textbox controls as children
this.Controls.Add(textFirstName);
this.Controls.Add(textLastName);

The current item may already have a value set for the name, so that value needs to be loaded into the control. Sitecore handles the work of actually reading and writing field values. It does so through the control's Value property. The Value property is a string, which should come as no surprise since, internally, Sitecore stores content as text.

The logic we will use is very simple. The first space character in the value will serve as the separator between the first and last names.


var firstName = "";
var lastName = "";
var currentValue = this.Value;
var firstSpace = currentValue.IndexOf(' ');
if (firstSpace == -1)
{
firstName = currentValue;
}
else
{
firstName = currentValue.Substring(0, firstSpace);
lastName = currentValue.Substring(firstSpace).Trim();
}
textFirstName.Value = firstName;
textLastName.Value = lastName;

Next we need to consider the code that belongs in the else-block. This code runs when ClientPage.IsEvent is true. One important example of when this code will run is when an item is saved. Specifically, this code will run when the item is saved using a ClientPage with this control on it. For this reason, this code must take the values from the 2 textbox controls and set a value on the NameField control.


var textFirstName = FindControl(GetID("textFirstName")) as Sitecore.Shell.Applications.ContentEditor.Text;
var textLastName = FindControl(GetID("textLastName")) as Sitecore.Shell.Applications.ContentEditor.Text;
var name = new string[2];
name[0] = textFirstName.Value;
name[1] = textLastName.Value;
this.Value = String.Join(" ", name);

The complete code is the following:


public class NameField : Sitecore.Web.UI.HtmlControls.Control
{
protected override void OnLoad(EventArgs e)
{
if (!Sitecore.Context.ClientPage.IsEvent)
{
//
//create the controls
var textFirstName = new Sitecore.Shell.Applications.ContentEditor.Text();
this.Controls.Add(textFirstName);
textFirstName.ID = GetID("textFirstName");
var textLastName = new Sitecore.Shell.Applications.ContentEditor.Text();
this.Controls.Add(textLastName);
textLastName.ID = GetID("textLastName");
//
//get the current value
var firstName = "";
var lastName = "";
var currentValue = this.Value;
var firstSpace = currentValue.IndexOf(' ');
if (firstSpace == -1)
{
firstName = currentValue;
}
else
{
firstName = currentValue.Substring(0, firstSpace);
lastName = currentValue.Substring(firstSpace).Trim();
}
//
//set the values on the textbox controls
textFirstName.Value = firstName;
textLastName.Value = lastName;
}
else
{
//
//read the values from the textbox controls
var textFirstName = FindControl(GetID("textFirstName")) as Sitecore.Shell.Applications.ContentEditor.Text;
var textLastName = FindControl(GetID("textLastName")) as Sitecore.Shell.Applications.ContentEditor.Text;
var name = new string[2];
name[0] = textFirstName.Value;
name[1] = textLastName.Value;
//
//set the value on the NameField control
this.Value = String.Join(" ", name);
}
base.OnLoad(e);
}
}

Step 3 - Compile your code
Compile your code and put the assembly in a directory that Sitecore will be able to find it.

Step 4 - Define a custom field type in Sitecore
This step involves adding the custom field to the Sitecore client so that developers can use it.

Step 4.1. Log into the Sitecore Content Editor and connect to the core database.

Step 4.2. Create the following item:

Item to create the item on: /sitecore/system/Field types
Template: /sitecore/templates/Common/Folder
Item Name: Custom Field Types

Step 4.3. Create the following item:

Item to create the item on: /sitecore/system/Field types/Custom Field Types
Template: /System/Templates/Template field type
Item Name: Name Field

Step 4.4. Select the Name Field item. Enter the following field values:

Control: myfields:NameField

Step 4.5. Save your changes and switch to the master database.

What you have just done is told Sitecore is that a new field type is available. This field type is named "myfields:NameField". How does Sitecore know what code to use when this field type is used? That is configured in the next step.

Step 5 - Map the field prefix to an assembly and namespace
The custom field's name (configured in the previous step) is a combination of a prefix (myfields) and a class name (NameField). This step involves telling Sitecore which assembly contains the class NameField.

This is done through the web.config file. I added the following line to the /configuration/sitecore/controlSources node. It tells Sitecore that any control identified using the prefix "myfields" can be located in the "sctest" assembly and is a part of the "sctest" namespace:


How to use a composite custom field
Now, when you configure a field in a Data Template, you should see a new option available in the list of field types. I added the following field:

Data template: /sitecore/templates/Sample/Sample Item
Field name: Name
Type: Name Field

The Home item in my installation is an instance of this data template. Using Content Editor, I see the following when I edit content:


I also modified the layout that is used for this item so that the name value appears on the website. I added the following code inside the body:




Next steps
We have created a custom interface for configuring content using Content Editor, but this interface is not available in Page Editor. The next step is to expand this functionality to Page Editor. This will require a custom field renderer web control.

Want to learn more?

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.