We just wrapped up the first annual Sitecore Virtual Developer Day. I am really happy to have had the opportunity to participate! My topic was on using the new Sitecore Forms available now in Sitecore 9 and identifying contacts through it as well as triggering goals which can then translate to performing personalization on a component once someone fills out a form.
During this session, I walked the viewers through a made up use case that a Product Owner might tell a developer or write as a story in the backlog.
When you boil down what is really being asked of, it comes down to three overarching asks.
Behind the Scenes
During portions of the video and my demo, I mention that I will post larger snippets of code on my blog for later viewing.
Creating a Custom Submit Action
When creating a custom submit action there are three things that have to be created:
- The Submit Action Item
2. The Submit Action Code Behind
3. The Editor for additional information. This step is optional if you don’t need to map fields from the form to flow through to your submit action.
Identifying Contact From Form Submit Action Code Behind
The following code is used to create the submit action that can be called up from Sitecore Forms and used to identify a contact.
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 SitecoreHacker.Feature.ExperienceForms.SubmitActions { public class TriggerIdentifyContact : SubmitActionBase { /// <summary> /// Initializes a new instance of the class. /// </summary> /// The submit action data. public TriggerIdentifyContact(ISubmitActionData submitActionData) : base(submitActionData) { } /// <summary> /// Gets the current tracker. /// </summary> protected virtual ITracker CurrentTracker => Tracker.Current; public override void ExecuteAction(FormSubmitContext formSubmitContext, string parameters) { Assert.ArgumentNotNull(formSubmitContext, "formSubmitContext"); UpdateContactData target; if (TryParse(parameters, out target)) try { if (Execute(target, formSubmitContext)) return; } catch (ArgumentNullException) { } formSubmitContext.Errors.Add(new FormActionError { ErrorMessage = SubmitActionData.ErrorMessage }); formSubmitContext.Abort(); } /// <summary> /// Executes the action with the specified . /// </summary> /// The data. /// The form submit context. /// true if the action is executed correctly; otherwise false 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 emailIdent = new IdentifiedContactReference("IdentifiedEmail", GetValue(emailField)); //Identify with Email CurrentTracker.Session.IdentifyAs(emailIdent.Source, emailIdent.Identifier); var expandOptions = new ContactExpandOptions( CollectionModel.FacetKeys.PersonalInformation, CollectionModel.FacetKeys.EmailAddressList); var contact = client.Get(emailIdent, expandOptions); //Set Fields 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> /// The instance. protected virtual IXdbContext CreateClient() { return SitecoreXConnectClientConfiguration.GetClient(); } /// <summary> /// Gets the field by . /// </summary> /// The identifier. /// The fields. /// The field with the specified . private static IViewModel GetFieldById(Guid id, IList fields) { return fields.FirstOrDefault(f => Guid.Parse(f.ItemId) == id); } /// <summary> /// Gets the value. /// </summary> /// The field. /// The field value. private static string GetValue(object field) { return field?.GetType().GetProperty("Value")?.GetValue(field, null)?.ToString() ?? string.Empty; } /// <summary> /// Sets the facet of the specified . /// </summary> /// The first name. /// The last name. /// The contact. /// The client. private static void SetPersonalInformation(string firstName, string lastName, Contact contact, IXdbContext client) { if (string.IsNullOrEmpty(firstName) && string.IsNullOrEmpty(lastName)) return; var 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 facet of the specified . /// </summary> /// The email address. /// The contact. /// The client. private static void SetEmail(string email, Contact contact, IXdbContext client) { if (string.IsNullOrEmpty(email)) return; var 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); } } public class UpdateContactData { public Guid FirstNameFieldId { get; set; } public Guid LastNameFieldId { get; set; } public Guid EmailFieldId { get; set; } } }
Update Contact SPEAK Editor Javascript
During the live demo, I gloss over the fact that there’s a Javascript file that needs to be created to support some of the SPEAK functionality. This file will live in /sitecore/shell/client/Applications/FormsBuilder/Layouts/Actions/UpdateContact.js
(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);<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>
The whole point of this JavaScript is to provide the backing that will take the mapping of fields from the Form, to the mapping of Contact fields in the Editor. The output of this form lies in the Submit Action on the form, in the Parameters field as a JSON object.
Be aware: There is a problem with this JavaScript. It is not my strong suit. If anyone can figure out for me what the problem is (trust me, if you follow all my instructions, you’ll see what I mean) please let me know. (It doesn’t seem to map the first element. I think there’s an index issue somewhere). That said once you understand the values being assigned to the parameters, this can also be manually created.
Summary
I hope that you enjoyed watching this session as much as I enjoyed making it. Please feel free to reach out to me if you have any questions. You can find me at any of the community outlets.
Hi Pete, which Sitecore 9 release are you using, Initial or Update-1? Also, which version of the Sitecore Forms are you using, 9.0.171219 or 9.0.171002?
LikeLike
In all of my talks and Sitecore 9 blog posts, I am using version 9.0.1 rev. 171219.
LikeLike