codeflood logo

Custom WFM Action Settings UI

The Sitecore WebForms for Marketers (WFM) module is a great module providing content authors the ability to create and edit their own simple data collection forms without having to tap a developer. It comes with a range of prebuilt “actions” which act on the data being submitted through the form. The most common actions would probably be the “save to database” action which saves the submitted form data to the WFM database for retrieval through the WFM form reports application and the “email” action which emails the submitted form data to a specified email address.

So what happens when you want to do something different with the data which the out-of-the-box actions can’t handle? Let’s say we have an internal legacy system exposing a web service which we need to submit the data into. One way to achieve that would be to create a custom sublayout which statically defines the fields to collect and ties them to the integration to the legacy system. But this will mean any changes required by the content authors will need to be done by a developer. No, we want to leverage WFM so the content author can make those updates themselves.

Like most things in Sitecore, WFM is quite extensible, and we can create our own actions to be used in forms and configured by content authors. To create a custom WFM action:

  1. Install the latest WFM module
  2. Create a new class implementing the Sitecore.Form.Submit.ISaveAction interface
  3. Register the action for use with the WFM module by creating a new item based on the /sitecore/templates/Web Forms for Marketers/Actions/Submit Action template under /sitecore/system/Modules/Web Forms for Marketers/Settings/Actions/Save Actions

So, let’s start with creating an action class which will submit data to that aforementioned legacy web service.

using System.Collections.Specialized; 
using Sitecore; 
using Sitecore.Data; 
using Sitecore.Form.Core.Client.Data.Submit; 
using Sitecore.Form.Core.Controls.Data; 
using Sitecore.Form.Submit;

namespace CustomWFMAction 
{ 
  public class SendToWebService : ISaveAction 
  { 
    public void Execute(ID formid, AdaptedResultList fields,
      params object[] data) 
    {
      // Extract the email field
      var email = fields.GetEntryByName("Email").Value;

      // Get list of additional fields to send
      var submitFields = new NameValueCollection(); 

      foreach(AdaptedControlResult field in fields) 
      {
        if(field.FieldName != "Email")
          submitFields.Add(field.FieldName, field.Value); 
      }

      // Submit data to web service
      var client = new TestService.ServiceSoapClient(); 
      client.SubmitData(email, StringUtil.NameValuesToString(submitFields,
        "&")); 
    }
  } 
} 

Custom actions should implement the Sitecore.Forms.Submit.ISaveAction interface. This interface exposes a single method, the Execute() method which we’ve implemented above. In the above example we first extract the email field which must be sent in a specific parameter to the web service and then we loop through each of the submitted form fields which is passed in the parameter fields, adding each to a NameValueCollection so we can easily format them in the correct format for the web service being submitted to.

Once we’ve written our custom action we need to register it with Sitecore. Create a new “Submit to test service” item based on the /sitecore/templates/Web Forms for Marketers/Actions/Submit Action template under the /sitecore/system/Modules/Web Forms for Marketers/Settings/Actions/Save Actions item to register the action with WFM and have it offer the action as an option in the form designer. Now you can create a test form to use the action on.

custom action definition

Although the above custom action does what it needs to, it’s not an ideal implementation. It’s quite brittle. The action has hard coded the field name containing the email address. If the form author changed the field name the email field wouldn’t be found and the correct data would not be submitted to the web service. It would be better if the action found the email field by ID rather than name so if the name changes the form still works. Also, we may not want to submit all the fields to the web service. It would be good to allow the author to select the fields to submit.

Let’s update the custom action to allow passing the email address field name and fields to send to the web service through the parameters of the action.

using System.Collections.Specialized; 
using System.Linq; 
using Sitecore; 
using Sitecore.Data; 
using Sitecore.Form.Core.Client.Data.Submit; 
using Sitecore.Form.Core.Controls.Data; 
using Sitecore.Form.Submit;

namespace CustomWFMAction 
{ 
  public class SendToWebService : ISaveAction 
  { 
    public string EmailField 
    { 
      get; 
      set; 
    }

    public string FieldsToSend 
    { 
      get; 
      set; 
    }

    public void Execute(ID formid, AdaptedResultList fields,
      params object[] data) 
    {
      // Extract the email field 
      var email = fields.GetEntryByName(EmailField).Value;

      // Get list of additional fields to send 
      var submitFields = new NameValueCollection(); 
      var fieldNamesToSubmit = (FieldsToSend ?? "").Split(',');

      foreach(AdaptedControlResult field in fields) 
      { 
        if (fieldNamesToSubmit.Contains(field.FieldName)) 
          submitFields.Add(field.FieldName, field.Value); 
      }

      // Submit data to web service
      var client = new TestService.ServiceSoapClient(); 
      client.SubmitData(email, StringUtil.NameValuesToString(submitFields,
        "&")); 
    }
  } 
}

We can set the properties of the custom action by setting them in the Parameters field of the action definition item. Each property is set in an XML fragment with the element name matching the property name of the class. WFM will handle setting these properties of the class when it instantiates it.

<EmailField>email</EmailField> 
<FieldsToSend>Category,Query</FieldsToSend>

Now although the above example let’s us set the properties of the custom action class, it sets the properties for all instances of the action and doesn’t allow changing the value for an individual form. It’s also not a good solution for the authors who may not be up to editing XML directly. To allow the authors to set these properties per form, we’ll need to provide an editor for the action.

The custom editor will be written using Sheer UI. Below is the XML layout file used for the editor.

<?xml version="1.0" encoding="utf-8" ?> 
<control xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense" 
  xmlns:def="Definition" 
  xmlns:asp="http://www.sitecore.net/microsoft/webcontrols"> 
  <SendToWebServiceSettings> 
    <FormDialog ID="Dialog" Icon="Applications/32x32/gear.png"
      Header="Web Service Settings"
      Text="Enter the settings for the web service submission"> 
      <CodeBeside
        Type="CustomWFMAction.SendToWebServiceSettings,CustomWFMAction"/> 
      <GridPanel Class="scfContent" Columns="2"
        Margin="20 15 10 15" Width="100%"> 
        <Literal Text="Email Field:" GridPanel.Align="right"/> 
        <Listbox ID="EmailFieldName" Width="97%"
          GridPanel.Width="80%"/> 
        <Literal Text="Additional fields to submit:"
          GridPanel.Align="right"/> 
        <Checklist ID="Fields" Width="97%"
          GridPanel.Width="80%" /> 
      </GridPanel> 
    </FormDialog> 
  </SendToWebServiceSettings> 
</control>

And we’ll also need the code beside class.

using System; 
using System.Collections.Generic; 
using System.Collections.Specialized; 
using System.Linq; 
using System.Web; 
using Sitecore; 
using Sitecore.Configuration; 
using Sitecore.Form.Core.Utility; 
using Sitecore.Forms.Core.Data; 
using Sitecore.Web.UI.HtmlControls; 
using Sitecore.Web.UI.Pages; 
using Sitecore.Web.UI.Sheer;

namespace CustomWFMAction 
{ 
  public class SendToWebServiceSettings : DialogForm 
  { 
    protected Listbox EmailFieldName; 
    protected Checklist Fields;

    protected override void OnLoad(EventArgs e) 
    { 
      base.OnLoad(e); 
      if (!Context.ClientPage.IsEvent) 
      { 
        // Populate WFM fields into settings form fields 
        PopulateFormFields(); 
      } 
    }

    private void PopulateFormFields() 
    { 
      // Read already configured parameters 
      var parametersKey = Sitecore.Web.WebUtil.GetQueryString("params"); 
      var parameterString = HttpContext.Current.Session[parametersKey] as string; 
      var parameters = ParametersUtil.XmlToNameValueCollection(parameterString);

      // Get the email field 
      var emailField = parameters["EmailField"] ?? string.Empty;

      // Get the additional fields to send 
      var fieldsToSend = new string[0]; 
      if (!string.IsNullOrEmpty(parameters["FieldsToSend"])) 
        fieldsToSend = parameters["FieldsToSend"].Split(',');

      // Populate options from the form definition 
      var db = Factory.GetDatabase(Sitecore.Web.WebUtil.GetQueryString("db")); 
      var formId = Sitecore.Web.WebUtil.GetQueryString("id"); 
      var form = new FormItem(db.GetItem(formId)); 

      if (form != null) 
      { 
        // Iterate WFM form fields 
        foreach (var field in form.Fields) 
        { 
          // Add field to email droplist 
          EmailFieldName.Controls.Add(new ListItem() { 
            Header = field.Name, 
            Selected = field.Name == emailField, 
            ID = Control.GetUniqueID("efn"), 
            Value = field.ID.ToString() 
          });

          // Add field to additional fields to send 
          Fields.Controls.Add(new ChecklistItem 
          { 
            Header = field.Name, 
            Checked = fieldsToSend.Contains(field.ID.ToString()), 
            Value = field.ID.ToString() 
          }); 
        }
      } 
    }

    protected override void OnOK(object sender, EventArgs args) 
    { 
      // Get list of selected additional fields to send 
      var selectedFields = new List<string>();

      foreach (var item in Fields.Items) 
      { 
        if(item.Checked) 
          selectedFields.Add(item.Value); 
      }

      // Get XML fragment of settings to store 
      SheerResponse.SetDialogValue( 
        ParametersUtil.NameValueCollectionToXml(new NameValueCollection() 
          { 
            {"EmailField", EmailFieldName.SelectedItem.Value}, 
            {"FieldsToSend", string.Join(",", selectedFields)} 
          }));

      base.OnOK(sender, args); 
    }
  } 
}

Note in the above code example in the OnOK() method that we’re constructing an XML fragment of the settings. By saving the settings in XML they’re already in the format expected by WFM and it will auto populate these properties on the custom action when it’s instantiated by WFM.

To register this control as the editor for the action, add a reference to this Sheer UI control in the editor field of the action definition item. Remember, in order to refer to a control in Sheer UI we reference the name of the child element of the root control element inside the XML layout file, in this case SendToWebServiceSettings.

custom action editor definition

With the editor field populated the edit button will now be enabled when the action is selected in the save actions dialog box.

save actions

And clicking the edit button will open the custom settings editor.

custom editor

And now we have a user friendly editor for the settings of our custom action. But it’s not just about presenting a friendly UI, this editor has made the action more robust, allowing authors to change the names of fields in the WFM and then use this UI to map the required data for the action.

Comments

Great post! One additional thing I am trying to figure out. How do you save the responses so the next time the editor UI opens up the user can still see what the settings where?

Thanks for the article. Cleared a few concepts I was looking for.

Thank you for the Idea and understanding. But, I try to add it on the "Save Action" but it gives me an error that it could not accept it because it does not support the configuration. I did was just to supply the fields "Assembly" and "Class" without parameter and without editor control. Was it ok that I just did that? I am just new to WFFM Thanks once again

Leave a comment

All fields are required.