Background

There comes a need where you may need to present a Web Forms for Marketers form, containing preloaded data from the server, for the user to view/edit. The canonical use case is an “edit profile” form.

Web Forms for Marketers supports preloading its fields, however this is done only via the querystring. In an “edit profile” form, data has to be loaded from the server.

The Solution

To accomplish this goal, we must subclass the Sitecore.Form.Web.UI.Controls.SingleLineText class and add the desired functionality. We need a couple of properties:

  • ProfileField contains the profile field to preload the text box with, as a string. The field is specified relative to the Sitecore.Context.User.Profile property. If this field is complex, we can specify a “child” field using dot notation, e.g. "Address.StreetNumber".
  • ReadOnly is a Yes/No value. If “Yes”, the field becomes read only.

The Code

Let's get on with the code. We start by subclassing Sitecore's SingleLineText field and add the aforementioned properties. The two constructors are necessary for WFFM fields to be instantiated:

public class PreloadedSingleLineText : Sitecore.Form.Web.UI.Controls.SingleLineText
{
    #region Properties

    [System.ComponentModel.DefaultValue("")]
    [VisualCategory("Appearance"), VisualProperty("Preload with:", 500)]
    public string ProfileField
    {
        get;
        set;
    }

    [DefaultValue("No")]
    [VisualCategory("Appearance")]
    [VisualFieldType(typeof(EmptyChoiceField))]
    [VisualProperty("Read-only:", 600)]
    public string ReadOnly
    {
        get;
        set;
    }

    #endregion

    #region Constructors

    public PreloadedSingleLineText() : this(HtmlTextWriterTag.Div) { }
    public PreloadedSingleLineText(HtmlTextWriterTag tag) : base(tag) { }

    #endregion

    #region Rendering
    // See below
    #endregion

    #region Helpers
    // See below
    #endregion
}

To implement the rendering, we must override the OnInit() method. However, we need first to implement the logic to retrieve a profile property from its textual representation, as described above. For this, we create a FindProfileValue(string) method:

protected string FindProfileValue(string prop)
{
    object curr = Sitecore.Context.User.Profile;
    if (curr != null)
    {
        var props = prop.Split('.');
        foreach (var p in props)
        {
            if (curr == null) return string.Empty;
            curr = curr.GetType().GetProperty(p).GetValue(curr, null);
        }
        if (curr != null)
            return curr.ToString();
    }
    return string.Empty;
}

This method simply splits its argument on the dot, and recursively retrieves the context user's profile properties using reflection, until it reaches a final value. It returns that value on success, or the empty string on failure.

So now finally we can implement the OnInit() method which performs the rendering. This is as simple as this:

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

    if (!string.IsNullOrWhiteSpace(ProfileField))
        this.Text = FindProfileValue(ProfileField);
    if (ReadOnly == "Yes")
        this.Enabled = false;
}

As a last step, we need to install our custom field type in Sitecode, somewhere under /sitecore/system/Modules/Web Forms for Marketers/Settings/Field Types, and we're good to go.

Property Picker

So now we have created the custom field type, and if we open a form in the form editor, we can select this field type from the field type picklist. However, aside from visiting our form field item in the content editor, and entering e.g. this in the Parameters field:

<ProfileField>ComplexProfileProperty.SimpleProfileField</ProfileField>

there is no other way for us to define which profile field to use, to preload our form. Ideally, we would like to select the profile field from a picker in the form editor.

So how do we implement this?

First of all, we need to create a folder somewhere in our content to store the various supported profile properties. Let's assume that this folder is stored under /sitecore/system/Modules/Web Forms for Marketers/Settings/Meta data, and called e.g. "Profile Property Definitions". Under this folder, we create our property list, using the /sitecore/templates/Web Forms for Marketers/Meta Data/Extended List Item template. In the "Value" field, we store the property name, which in effect is fed as an argument to the FindPropertyValue() method, implemented above:

profile field

Having done this, we need to implement a Sitecore.Form.Core.Visual.ValidationField subclass, which will be responsible for rendering the picker. We use the constructor to specify that this picker will be based on the <select> HTML element, and in its OnPreRender() method, we loop over all profile field definitions and render <option> elements for each definition:

public class ProfileSelectPropertyField : Sitecore.Form.Core.Visual.ValidationField
{
    public override bool IsCacheable
    {
        get { return false; }
    }

    public ProfileSelectPropertyField() : base(HtmlTextWriterTag.Select.ToString()) { }

    // This is the ID of the folder containing the profile property definitions
    protected static Sitecore.Data.ID ProfileVariablesRoot
    {
        get { return new Sitecore.Data.ID("{94E35D21-47DB-4284-8220-C5859CCC8942}"); }
    }

    protected override void OnPreRender(object sender, EventArgs ev)
    {
        this.Controls.Clear();
        base.OnPreRender(sender, ev);

        var texts = StaticSettings.ContextDatabase.GetItem(ProfileVariablesRoot)
            .Children
            .Select(i => i.DisplayName);
        var values = StaticSettings.ContextDatabase.GetItem(ProfileVariablesRoot)
            .Children
            .Select(i => i.Fields[Sitecore.Form.Core.Configuration.FieldIDs.MetaDataListItemValue].Value);
        for (int i = 0; i < texts.Count(); ++i)
        {
            Literal child = new Literal();
            child.Text = string.Format("<option {0} value='{1}'>{2}</option>", 
                base.DefaultValue == values.ElementAt(i) ? "selected='selected'" : string.Empty, 
                values.ElementAt(i).ToString(), 
                texts.ElementAt(i));
            this.Controls.Add(child);
        }
    }
}

As a final step, we need to decorate the ProfileField property of our PreloadedSingleLineText so that it uses our custom validation field:

[System.ComponentModel.DefaultValue("")]
[VisualCategory("Appearance"), VisualProperty("Preload with:", 500)]
[VisualFieldType(typeof(ProfileSelectPropertyField)]
public string ProfileField
{
    get;
    set;
}

And that's all we need to do for a complete solution. The final result looks somewhat like this:

Happy coding!