Walkthrough: Creating a custom submit action that updates contact details

Last updated Monday, October 30, 2017 in Sitecore Experience Platform for Developer
Keywords: Configuration, Forms

To submit a form, a contact must click the Submit button. You can add different types of actions to perform when a user clicks Submit. By default, you can add the Trigger Goal, Trigger Campaign Activity, Trigger Outcome, Redirect to Page, and Save Data submit items.

This walkthrough describes how to create a custom submit action that enables you to select the form fields used to update the contact details.

Map the fields in the form to the contact details that you want to update.

Note

This walkthrough describes one example of building a custom submit action. Depending on your experience and preferences, you might prefer to do things slightly differently.

This walkthrough describes how to:

Create a submit action class

To submit a form (page), a contact must click the Submit button. You can add different types of actions to perform when a user clicks Submit. For example, the Save Data submit action ensures that data is saved to your database, and the Trigger Campaign Activity submit action selects a preset campaign activity.

In this walkthrough, you create the Update Contact submit action.

The submit action stores the parameters of the JSON object that is passed to the action. The JSON object is parsed into an instance of the type specified in the TParametersData class, in this case, the UpdateContactData class.

Therefore, in this example, you create a derived class UpdateContact that inherits from SubmitActionBase<TParametersData> with the UpdateContactData parameter.

To create a submit action class:

  1. Create the UpdateContact class and inherit from the SubmitActionBase<TParametersData> class.
  2. Specify the UpdateContactData parameter.
  3. Override the Execute method.

    For example:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Sitecore.Analytics;
    using Sitecore.Diagnostics;
    using Sitecore.ExperienceForms.Models;
    using Sitecore.ExperienceForms.Processing;
    using Sitecore.ExperienceForms.Processing.Actions;
    using Sitecore.XConnect;
    using Sitecore.XConnect.Client;
    using Sitecore.XConnect.Client.Configuration;
    using Sitecore.XConnect.Collection.Model;
    namespace Sitecore.ExperienceForms.Samples.SubmitActions
    {
        /// <summary>
        /// Submit action for updating <see cref="PersonalInformation"/> and <see cref="EmailAddressList"/> facets of a <see cref="XConnect.Contact"/>.
        /// </summary>
        /// <seealso cref="Sitecore.ExperienceForms.Processing.Actions.SubmitActionBase{UpdateContactData}" />
        public class UpdateContact : SubmitActionBase<UpdateContactData>
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="UpdateContact"/> class.
            /// </summary>
            /// <param name="submitActionData">The submit action data.</param>
            public UpdateContact(ISubmitActionData submitActionData) : base(submitActionData)
            {
            }
            /// <summary>
            /// Gets the current tracker.
            /// </summary>
            protected virtual ITracker CurrentTracker => Tracker.Current;
            /// <summary>
            /// Executes the action with the specified <paramref name="data" />.
            /// </summary>
            /// <param name="data">The data.</param>
            /// <param name="formSubmitContext">The form submit context.</param>
            /// <returns><c>true</c> if the action is executed correctly; otherwise <c>false</c></returns>
            protected override bool Execute(UpdateContactData data, FormSubmitContext formSubmitContext)
            {
                Assert.ArgumentNotNull(data, nameof(data));
                Assert.ArgumentNotNull(formSubmitContext, nameof(formSubmitContext));
                var firstNameField = GetFieldById(data.FirstNameFieldId, formSubmitContext.Fields);
                var lastNameField = GetFieldById(data.LastNameFieldId, formSubmitContext.Fields);
                var emailField = GetFieldById(data.EmailFieldId, formSubmitContext.Fields);
                if (firstNameField == null && lastNameField == null && emailField == null)
                {
                    return false;
                }
                using (var client = CreateClient())
                {
                    try
                    {
                        var source = "Subcribe.Form";
                        var id = CurrentTracker.Contact.ContactId.ToString("N");
                        CurrentTracker.Session.IdentifyAs(source, id);
                        var trackerIdentifier = new IdentifiedContactReference(source, id);
                        var expandOptions = new ContactExpandOptions(
                            CollectionModel.FacetKeys.PersonalInformation,
                            CollectionModel.FacetKeys.EmailAddressList);
                        Contact contact = client.Get(trackerIdentifier, expandOptions);
                        SetPersonalInformation(GetValue(firstNameField), GetValue(lastNameField), contact, client);
                        SetEmail(GetValue(emailField), contact, client);
                        client.Submit();
                        return true;
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError(ex.Message, ex);
                        return false;
                    }
                }
            }
            /// <summary>
            /// Creates the client.
            /// </summary>
            /// <returns>The <see cref="IXdbContext"/> instance.</returns>
            protected virtual IXdbContext CreateClient()
            {
                return SitecoreXConnectClientConfiguration.GetClient();
            }
            /// <summary>
            /// Gets the field by <paramref name="id" />.
            /// </summary>
            /// <param name="id">The identifier.</param>
            /// <param name="fields">The fields.</param>
            /// <returns>The field with the specified <paramref name="id" />.</returns>
            private static IViewModel GetFieldById(Guid id, IList<IViewModel> fields)
            {
                return fields.FirstOrDefault(f => Guid.Parse(f.ItemId) == id);
            }
            /// <summary>
            /// Gets the <paramref name="field" /> value.
            /// </summary>
            /// <param name="field">The field.</param>
            /// <returns>The field value.</returns>
            private static string GetValue(object field)
            {
                return field?.GetType().GetProperty("Value")?.GetValue(field, null)?.ToString() ?? string.Empty;
            }
            /// <summary>
            /// Sets the <see cref="PersonalInformation"/> facet of the specified <paramref name="contact" />.
            /// </summary>
            /// <param name="firstName">The first name.</param>
            /// <param name="lastName">The last name.</param>
            /// <param name="contact">The contact.</param>
            /// <param name="client">The client.</param>
            private static void SetPersonalInformation(string firstName, string lastName, Contact contact, IXdbContext client)
            {
                if (string.IsNullOrEmpty(firstName) && string.IsNullOrEmpty(lastName))
                {
                    return;
                }
                PersonalInformation personalInfoFacet = contact.Personal() ?? new PersonalInformation();
                if (personalInfoFacet.FirstName == firstName && personalInfoFacet.LastName == lastName)
                {
                    return;
                }
                personalInfoFacet.FirstName = firstName;
                personalInfoFacet.LastName = lastName;
                client.SetPersonal(contact, personalInfoFacet);
            }
            /// <summary>
            /// Sets the <see cref="EmailAddressList"/> facet of the specified <paramref name="contact" />.
            /// </summary>
            /// <param name="email">The email address.</param>
            /// <param name="contact">The contact.</param>
            /// <param name="client">The client.</param>
            private static void SetEmail(string email, Contact contact, IXdbContext client)
            {
                if (string.IsNullOrEmpty(email))
                {
                    return;
                }
                EmailAddressList emailFacet = contact.Emails();
                if (emailFacet == null)
                {
                    emailFacet = new EmailAddressList(new EmailAddress(email, false), "Preferred");
                }
                else
                {
                    if (emailFacet.PreferredEmail?.SmtpAddress == email)
                    {
                        return;
                    }
                    emailFacet.PreferredEmail = new EmailAddress(email, false);
                }
                client.SetEmails(contact, emailFacet);
            }
        }
    }
  4. Build and deploy.

Create the SPEAK editor control

The next step is to create the UI that enables mapping the form fields to the contact details fields. For Sitecore Forms, submit action editors are located in the core database: /sitecore/client/Applications/FormsBuilder/Components/Layouts/Actions

Note

In this example, you must have the Sitecore Rocks Visual Studio plug-in.

To create the control:

  1. Navigate to /sitecore/client/Applications/FormsBuilder/Components/Layouts/Actions.
  2. Right-click Actions, click Add, and then click New Item.

    Right-click Actions and add a new item

  3. Select the /sitecore/client/Speak/Templates/Pages/Speak-BasePage template, and in the Enter the name of the new item field, enter UpdateContact and click

    UpdateContact item

  4. Right-click the UpdateContact item you just created and click Tasks, and click Design Layout.

    Right-click the item and click Design Layout and click Task.

  5. In the Layout dialog box, navigate to /sitecore/client/Speak/Layouts/Layouts and select the Speak-FlexLayout layout and click OK.

    Select Speak-FlexLayout

  6. In the upper-left corner, click Add Rendering and in the Select Renderings dialog box, click All and search for PageCode:

    Picture 5

  7. Select PageCode and click OK.
  8. In the PageCode rendering properties, set the PageCodeScriptFileName property to the JavaScript path that contains the page code script:

    /sitecore/shell/client/Applications/FormsBuilder/Layouts/Actions/UpdateContact.js

    Picture 18

  9. Set the SpeakCoreVersion property to Speak 2-x.
  10. Search for and select the Text View rendering and click Add to add three items: HeaderTitle, HeaderSubtitle, and ValueNotInListText.
  11. For the three items, in the Properties section, set the following ID properties:
    • IsVisibleFalse
    • PlaceholderKeyPage.Body

    Note

    These items are used as texts that set the action editor dialog title, subtitle, and the not found value. If you fill in the text property here, the texts will be visible in all languages but will not be localizable.

  12. Add the following renderings:
    • MainBorder of type Border.

      Picture 28

    • MapContactForm of type Form. Set the FieldsLayout property to 1-1-1-1 and set the PlaceholderKey property to MainBorder.Content.

    Your rendering list now looks like this:

    Picture 13

Add a folder that contains parameters for the editor

Next, you must add a folder that contains the parameters for the editor.

To add the PageSettings folder:

  1. Navigate to /sitecore/client/Applications/FormsBuilder/Components/Layouts/Actions, and right-click the UpdateContact item you created earlier, click Add, and click New item.
  2. Search for and select the PageSettings template, enter the name PageSettings and click OK.

    Picture 6

  3. Right-click the PageSettings item that you just created and click Add, New Item.
  4. Search for and select the Text Parameters template and click Add three times and name the items exactly the same as in the IDs in the layout you created previously:
    • HeaderTitle – double-click and in the Text field enter: Map form fields to contact details.
    • HeaderSubtitle – double-click and in the Text field enter: Map the fields in the form to the contact details that you want to update.
    • ValueNotInListText – double-click and in the Text field enter: value not in selection list.

    Picture 22

  5. Navigate to /sitecore/client/Applications/FormsBuilder/Components/Layouts/Actions and right-click the PageSettings item that you just created.
  6. Click New Folder and name it MapContactForm.

    Picture 24

  7. Click the MapContactForm folder and add three FormDropList Parameters templates with the following field values:

    FormDropList Parameter

    ValueFieldName

    DisplayFieldName

    FormLabel

    BindingConfiguration

    FirstName

    itemId

    name

    First name

    firstNameFieldId/SelectedValue

    LastName

    itemId

    name

    Last name

    lastNameFieldId/SelectedValue

    Email

    itemId

    name

    Email

    emailFieldId/SelectedValue

    Picture 7

  8. Navigate to the UpdateContact layout and set the Form rendering ConfigurationItem property to the ID of the MapContactForm folder that contains the FormDropList parameters.

    Picture 27

  9. Navigate to /sitecore/client/Applications/FormsBuilder/Components/Layouts/Actions and right-click the PageSettings item that you created earlier.
  10. Add a new item named Stylesheet of type Page-Stylesheet-File:

    Picture 30

  11. Click the new stylesheet item and set the Stylesheet value to:
  12. /sitecore/shell/client/Applications/FormsBuilder/Layouts/Actions/Actions.css

    Picture 9

    Picture 10

    Picture 11

Create the client script for the editor

Now you must create the client script for the editor. In a previous step, when you created the UpdateContact item, you set the path to the script as follows:

/sitecore/shell/client/Applications/FormsBuilder/Layouts/Actions/UpdateContact.js

To create the script:

  1. Use the base Submit actions editor script.

    The Submit actions editor script always has the following base:

    (function (speak) {
        var parentApp = window.parent.Sitecore.Speak.app.findApplication('EditActionSubAppRenderer');
     
        speak.pageCode(["underscore"],
            function (_) {
                return {
                    initialized: function () {
                        this.on({
                            "loaded": this.loadDone
                        },
                            this);
     
                        if (parentApp) {
                            parentApp.loadDone(this, this.HeaderTitle.Text, this.HeaderSubtitle.Text);
                            parentApp.setSelectability(this, true);
                        }
                    },
     
                    loadDone: function (parameters) {
                        this.Parameters = parameters || {};
                    },
     
                    getData: function () {
                        return this.Parameters;
                    }
                };
            });
    })(Sitecore.Speak);
  2. Use the EditActionSubAppRenderer component. The editors are loaded in a frame in a Speak dialog by the EditActionSubAppRenderer component. They must pass the dialog header title and subtitle to the parent, and set when the submit button is enabled.

    In this example, to create a submit action that updates contact details, you use a script that finds the canvas component FormDesignBoard, gets the data from the fields on the form canvas and transforms them to a simple array: empty item, followed by items with itemId and name properties. This is why, in the FormDropList Parameters items, you filled in the ValueFieldName and DisplayFieldName fields with itemId and name. The script works as follows:

  • initialized – collects the data from the fields into an array. Then it finds all form drop lists and sets their IsSelectionRequired property to false
  • loadDone – iterates the form controls, and sets their dynamic data to the fields array. If the current submit action Parameters property value is not in the fields list (for example, if the field is deleted, or the form copied), it includes an id - value not in the selection list item in the array. Then it binds the SPEAK form to the Parameters object.
  • getData – when the submit button is clicked, the getData function is called. It iterates the form data in order to collect the new Parameters object. Empty selections (field mappings) are omitted.

Your final script should look like the following:

(function (speak) {
    var parentApp = window.parent.Sitecore.Speak.app.findApplication('EditActionSubAppRenderer'),
        designBoardApp = window.parent.Sitecore.Speak.app.findComponent('FormDesignBoard');
    var getFields = function () {
        var fields = designBoardApp.getFieldsData();
        return _.reduce(fields,
            function (memo, item) {
                if (item && item.model && item.model.hasOwnProperty("value")) {
                    memo.push({
                        itemId: item.itemId,
                        name: item.model.name
                    });
                }
                return memo;
            },
            [
                {
                    itemId: '',
                    name: ''
                }
            ],
            this);
    };
    speak.pageCode(["underscore"],
        function (_) {
            return {
                initialized: function () {
                    this.on({
                        "loaded": this.loadDone
                    },
                        this);
                    this.Fields = getFields();
                    this.MapContactForm.children.forEach(function (control) {
                        if (control.deps && control.deps.indexOf("bclSelection") !== -1) {
                            control.IsSelectionRequired = false;
                        }
                    });
                    if (parentApp) {
                        parentApp.loadDone(this, this.HeaderTitle.Text, this.HeaderSubtitle.Text);
                        parentApp.setSelectability(this, true);
                    }
                },
                setDynamicData: function (propKey) {
                    var componentName = this.MapContactForm.bindingConfigObject[propKey].split(".")[0];
                    var component = this.MapContactForm[componentName];
                    var items = this.Fields.slice(0);
                    if (this.Parameters[propKey] &&
                        !_.findWhere(items, { itemId: this.Parameters[propKey] })) {
                        var currentField = {
                            itemId: this.Parameters[propKey],
                            name: this.Parameters[propKey] +
                                " - " +
                                (this.ValueNotInListText.Text || "value not in the selection list")
                        };
                        items.splice(1, 0, currentField);
                        component.DynamicData = items;
                        $(component.el).find('option').eq(1).css("font-style", "italic");
                    } else {
                        component.DynamicData = items;
                    }
                },
                loadDone: function (parameters) {
                    this.Parameters = parameters || {};
                    _.keys(this.MapContactForm.bindingConfigObject).forEach(this.setDynamicData.bind(this));
                    this.MapContactForm.BindingTarget = this.Parameters;
                },
                getData: function () {
                    var formData = this.MapContactForm.getFormData(),
                        keys = _.keys(formData);
                    keys.forEach(function (propKey) {
                        if (formData[propKey] == null || formData[propKey].length === 0) {
                            if (this.Parameters.hasOwnProperty(propKey)) {
                                delete this.Parameters[propKey];
                            }
                        } else {
                            this.Parameters[propKey] = formData[propKey];
                        }
                    }.bind(this));
                    return this.Parameters;
                }
            };
        });
})(Sitecore.Speak);

Create a submit action item

To create the submit action item:

  1. Navigate to /sitecore/system/Settings/Forms/Submit Actions
  2. Right-click Submit Actions, click Insert, and click Insert from template.
  3. Select the /System/Forms/Submit Action template, in the Item Name field, enter the name Update Contact Details and click Insert.
  4. Navigate to the item you just created and in the Settings section, in the Model Type field, set the value to the class type name.

    Picture 8

  5. In the Error Message field, enter an error message, for example, Update contact failed!
  6. In the Editor field, select the editor that you just created, for example, Update contact.
  7. In the Appearance section, select the icon that you want to display in the Form elements pane.

In the Form elements pane, when you click Add a submit action, you can now select the Update Contact action.

Picture 15

Send feedback about the documentation to docsite@sitecore.net.