Thursday, March 25, 2010

Custom Fields, Part 3

In my last post I explained how to create a custom user interface for editing a name in Page Editor. I left off suggesting that there's a problem with the current implementation. And there is: it is possible to circumvent the user interface by using the inline editing feature provided by the default field renderer.

Inline editing circumvents my new user interface.

Why is that happening? Well, as far inline editing is concerned, Sitecore doesn't have any special instructions on how to handle a name field. Therefore, Sitecore treats the field like a single-line text field.

Since I created this special user interface to handle the name field, I want Sitecore to use that interface. And while in some cases being able to use inline editing to modify a field value may be advantageous, in this case I want it disabled.

In this post I'm going to cover 3 items:
  1. Creating a custom field type to make it easier to work with the components of a name (currently only first and last names, but could later be expanded to more).
  2. Controlling how the standard Field Renderer web control behaves when it is used to render a custom field.
  3. Configuring a custom field renderer control to make it easier to configure the display formatting for a name field
Step 1 - Create new field type
My first step it to create a new field type. This will make it much easier for people working with the Sitecore API to work with name fields. Since I will need to work with field names in a later step, I will be the first person to benefit from this addition.

Also, this code will serve as a replacement for the NameFieldUtils class. Same logic, different location.
public class NameFieldType : CustomField
{
public static string GetFullName(string firstName, string lastName)
{
var name = new string[2];
name[0] = firstName;
name[1] = lastName;
return String.Join(" ", name);
}
public static string GetFirstName(string fullName)
{
string[] parts = fullName.Split(' ');
if (parts.Length >= 1)
{
return parts[0];
}
return string.Empty;
}
public static string GetLastName(string fullName)
{
string[] parts = fullName.Split(' ');
if (parts.Length >= 2)
return parts[1];
return string.Empty;
}
public NameFieldType(Field innerField) : base(innerField) { }

public static implicit operator NameFieldType(Field field)
{
if (field != null)
return new NameFieldType(field);
return null;
}

public string FirstName
{
get { return NameFieldType.GetFirstName(InnerField.Value); }
}

public string LastName
{
get { return NameFieldType.GetLastName(InnerField.Value); }
}
public string FullName
{
get { return InnerField.Value; }
}
}
Step 2 - Register new field type
When working with content using the Sitecore API, a class is used. Different classes support different types of field. Sitecore needs to know which class should be used for which field type. This mapping is specified in the FieldTypes.config file.

In order for Sitecore to be able to resolve the "Name Field" type to the NameFieldType class I just created, I need to add the following to the FieldTypes.config file:
<fieldType name="Name Field" type="sctest.NameFieldType,sctest" />

Step 3 - Write code that controls the inline editing interface
Next I need to tell Sitecore how a name field value should be displayed in Page Editor. There are a couple of things I want to configure. First, I don't want users to be able to enter a value inline. I want all name values to be entered through my custom interface. I need to disable inline editing. Second, I want my custom interface to appear when a user clicks a name value in Page Editor.
public class GetNameFieldValue
{
public void Process(RenderFieldArgs args)
{
switch (args.FieldTypeKey)
{
case "name field":
{
args.DisableWebEditContentEditing = true;
args.WebEditClick = "return Sitecore.WebEdit.editControl($JavascriptParameters,'sctest:EditNameButton');";
break;
}
}
}
}

Step 4 - Configure Sitecore to use the code
The Sitecore's renderField pipeline is responsible for rendering fields in Page Editor. The pipeline is defined in web.config. One of the steps in the pipeline is to determine what value should be rendered for a specific field. An addition to this pipeline is needed since I want to change the field renderer's default behavior.

I need to add a processor to the renderField pipeline. The processor will use the class I just created. The path to this pipeline in web.config is /configuration/sitecore/pipelines/renderField. The line should be added before the processor that uses the type Sitecore.Pipelines.RenderField.AddBeforeAndAfterValues. After I make my addition the renderField pipeline looks like the following:
<renderField>
<processor type="Sitecore.Pipelines.RenderField.SetParameters, Sitecore.Kernel"/>
<processor type="Sitecore.Pipelines.RenderField.GetFieldValue, Sitecore.Kernel"/>
<processor type="Sitecore.Pipelines.RenderField.ExpandLinks, Sitecore.Kernel"/>
<processor type="Sitecore.Pipelines.RenderField.GetImageFieldValue, Sitecore.Kernel"/>
<processor type="Sitecore.Pipelines.RenderField.GetLinkFieldValue, Sitecore.Kernel"/>
<processor type="Sitecore.Pipelines.RenderField.GetInternalLinkFieldValue, Sitecore.Kernel"/>
<processor type="Sitecore.Pipelines.RenderField.GetMemoFieldValue, Sitecore.Kernel"/>
<processor type="Sitecore.Pipelines.RenderField.GetDateFieldValue, Sitecore.Kernel"/>
<processor type="Sitecore.Pipelines.RenderField.GetDocxFieldValue, Sitecore.Kernel"/>
<processor type="sctest.GetNameFieldValue, sctest"/>
<processor type="Sitecore.Pipelines.RenderField.AddBeforeAndAfterValues, Sitecore.Kernel"/>
<processor type="Sitecore.Pipelines.RenderField.RenderWebEditing, Sitecore.Kernel"/>
</renderField>

After I compile my code I can test my changes. I can no longer directly edit a name field value. In addition, when I click the value in Page Editor, my custom user interface appears.

The next thing I want to do is create a custom field renderer in order to provide some extra functionality for my custom field type.

Step 5 - Create a new field renderer
My new field renderer is going to allow a developer to specify a formatting for a name. By default the first name followed by last name will be displayed. This is the value the default field renderer uses. But my custom field renderer is going to support options that will display only the first name, only the last name, and the last name followed by the first name, in addition to the default display format.

In order to accomodate this I need a custom field renderer that supports a format option. Since a developer is able to set the format option value, I need to make sure this value is made available to Sitecore in the way Sitecore expects. The following class does these things:

Assembly name: sctest.dll
Namespace: sctest
Class name: NameFieldControl
Base class: Sitecore.Web.UI.WebControls.FieldControl
public class NameFieldControl : Sitecore.Web.UI.WebControls.FieldControl
{
public string Format { get; set; }

protected override void PopulateParameters(Sitecore.Collections.SafeDictionary<string> parameters)
{
parameters.Add("format", this.Format);
base.PopulateParameters(parameters);
}
}

Step 6 - Implement support for formatting options
Now that I have the ability to support a format value, I need to implement the logic to use that value to determine the text that is displayed when a name field is displayed. That is going to require a modification to the GetNameFieldValue class (the class that I added to the renderField pipeline).

The format value that was set in the previous step is available in the pipeline through the RenderFieldArgs parameter. I use that value to determine how to format the name field's value.
public void Process(RenderFieldArgs args)
{
switch (args.FieldTypeKey)
{
case "name field":
{
//
//
NameFieldType field = args.Item.Fields[args.FieldName];
string displayValue = null;
//
//
var format = args.Parameters["format"];
switch (format)
{
case "FirstNameOnly":
displayValue = field.FirstName;
break;
case "LastNameOnly":
displayValue = field.LastName;
break;
case "LastCommaFirst":
displayValue = string.Format("{0}, {1}", field.LastName, field.FirstName);
break;
case "Complete":
default:
displayValue = field.FullName;
break;
}
args.Result.FirstPart = displayValue;
args.DisableWebEditContentEditing = true;
args.WebEditClick = "return Sitecore.WebEdit.editControl($JavascriptParameters,'sctest:EditNameButton');";
break;
}
}
}

Step 7 - Use the custom field renderer
Now I'm ready to use my custom field renderer. I need to add the following code to the top of my sublayout:
<%@ Register TagPrefix="sctest" Namespace="sctest" Assembly="sctest" %>

And then I need to use my new field renderer:
<sctest:NameFieldControl ID="NameFieldControl1" runat="server" Field="Name" Format="LastCommaFirst" />
When I view my site, I see that the name field is formatted as specified.

Custom field renderer in action.

Next steps
This is the end of my 3-part series on custom fields. You can download the finished source code here.

Want to learn more?
See my previous post for good links.

No comments:

Post a Comment

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