Add hidden data to Sitecore Forms without hidden fields


Hello happy people πŸ™‚

Sitecore recently released the best release ever – Sitecore 9.1
I mean just look at the wonderful things:

  • JSS – Build Headless JavaScript applications with the power of Sitecore
  • Universal Tracker – The Universal Tracker platform is an opt-in mechanism for tracking interactions and events from headless clients.
  • Sitecore Identity – Sitecore Identity is the platform single sign-on mechanism for Sitecore Experience Platform, Sitecore Experience Commerce and other Sitecore instances that require authentication.
  • Sitecore Cortex – combines advanced machine learning algorithms and a processing engine enabling rapid implementation of ML/AI–based technology by any product/customer on experience data in the xDB.
  • And there is more….

    Read all about it in this great post – What’s new in Sitecore 9.1

    And I must emphasize the importance of digging into Blazor. It’s THE game changer which will allow you to do a single-page web app(built on .NET) that runs in the browser with WebAssembly. In the ASP.NET Core 3.0 release, Microsoft will have a (pre)server-side version of Blazor(called Razor components) using client-side with SignalR.
    I’ve setup a client-side Blazor Sitecore project at github – SitecoreBlazor. Please check out my post Time travel into the future – BLAZOR + SITECORE + HELIX

    Anyway let’s continue with the post. Today I would like to show you how easy it is to add “hidden” data to a Sitecore Form.

    How about following scenario? You have a Sitecore Form(with some fields and a submit button) and that is great. But you also want some “personal data” in the form, in this case the user. How do we do that?

    Easy peasy people, we will use a custom submit action(Sitecore has excellent documentation about it). Today Sitecore has the following submit actions:

  • Trigger Goal
  • Trigger Campaign Activity
  • Trigger Outcome
  • Send Email Campaign Message
  • Redirect to Page
  • Save Data
  • In our scenario the “Save Data” submit action will suit us best. But… we need to do some changes. Let’s look at the code using the good old dotPeek. Here is the Save Data submit action:

    namespace Sitecore.ExperienceForms.Processing.Actions
    {
      public class SaveData : SubmitActionBase
      {
        private IFormDataProvider _dataProvider;
    
        public SaveData(ISubmitActionData submitActionData)
          : base(submitActionData)
        {
        }
    
        internal SaveData(ISubmitActionData submitActionData, IFormDataProvider dataProvider)
          : this(submitActionData)
        {
          Assert.ArgumentNotNull((object) dataProvider, nameof (dataProvider));
          this._dataProvider = dataProvider;
        }
    
        protected virtual IFormDataProvider FormDataProvider
        {
          get
          {
            return this._dataProvider ?? (this._dataProvider = ServiceLocator.ServiceProvider.GetService());
          }
        }
    
        protected override bool TryParse(string value, out string target)
        {
          target = string.Empty;
          return true;
        }
    
        protected override bool Execute(string data, FormSubmitContext formSubmitContext)
        {
          Assert.ArgumentNotNull((object) formSubmitContext, nameof (formSubmitContext));
          return this.SavePostedData(formSubmitContext.FormId, formSubmitContext.SessionId, formSubmitContext.Fields);
        }
    
        protected virtual bool SavePostedData(Guid formId, Guid sessionId, IList postedFields)
        {
          try
          {
            FormEntry formEntry = new FormEntry()
            {
              Created = DateTime.UtcNow,
              FormItemId = formId,
              FormEntryId = sessionId,
              Fields = (ICollection) new List()
            };
            if (postedFields != null)
            {
              foreach (IViewModel postedField in (IEnumerable) postedFields)
                SaveData.AddFieldData(postedField, formEntry);
            }
            this.FormDataProvider.CreateEntry(formEntry);
            return true;
          }
          catch (Exception ex)
          {
            this.Logger.LogError(ex.Message, ex, (object) this);
            return false;
          }
        }
    
        protected static void AddFieldData(IViewModel postedField, FormEntry formEntry)
        {
          Assert.ArgumentNotNull((object) postedField, nameof (postedField));
          Assert.ArgumentNotNull((object) formEntry, nameof (formEntry));
          IValueField valueField = postedField as IValueField;
          if (valueField == null || !valueField.AllowSave)
            return;
          PropertyInfo property = postedField.GetType().GetProperty("Value");
          object postedValue = (object) property != null ? property.GetValue((object) postedField) : (object) null;
          if (postedValue == null)
            return;
          string fullName = postedValue.GetType().FullName;
          string fieldValue = SaveData.ParseFieldValue(postedValue);
          FieldData fieldData = new FieldData()
          {
            FieldDataId = Guid.NewGuid(),
            FieldItemId = Guid.Parse(postedField.ItemId),
            FieldName = postedField.Name,
            FormEntryId = formEntry.FormEntryId,
            Value = fieldValue,
            ValueType = fullName
          };
          formEntry.Fields.Add(fieldData);
        }
    
        protected static string ParseFieldValue(object postedValue)
        {
          Assert.ArgumentNotNull(postedValue, nameof (postedValue));
          List stringList = new List();
          IList list = postedValue as IList;
          if (list != null)
          {
            foreach (object obj in (IEnumerable) list)
              stringList.Add(obj.ToString());
          }
          else
            stringList.Add(postedValue.ToString());
          return string.Join(",", (IEnumerable) stringList);
        }
      }
    }
    

    A lot of code here… But the interesting part is the overridable method – SavePostedData. Notice the parameter IList postedFields, it holds all the fields from the form. That is the one we want to “hook into” or add to πŸ™‚

    We don’t want to duplicate code(messy and ugly) instead we should make it simple.

    Simple is clean πŸ™‚

    We will create a custom submit action called – SaveFormDataWithCurrentUser, then we will inherit the SaveData class and now we can do our customizations πŸ™‚

    
    using Sitecore.ExperienceForms.Models;
    using Sitecore.ExperienceForms.Processing.Actions;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Sitecore.ExperienceForms.Mvc.Models.Fields;
    
    namespace Sandbox.Feature.Forms.Processing.Actions
    {
      public class SaveFormDataWithCurrentUser : SaveData
      {
    
        private const string UserFieldName = "UserField";
    
        public SaveFormDataWithCurrentUser(ISubmitActionData submitActionData) : base(submitActionData)
        {
    
        }
    
        private IViewModel CreateCurrentUserTextField()
        {
          var  userField = new InputViewModel<string>
          {
            Value = Sitecore.Context.User.Name,
            Name = UserFieldName,
            ItemId = Guid.NewGuid().ToString(),
            AllowSave = true,
            IsTrackingEnabled = false
          };
          return userField;
        }
    
    
        protected override bool SavePostedData(Guid formId, Guid sessionId, IList<IViewModel> postedFields)
        {
    
          if (postedFields.Any())
          {
            var userField = CreateCurrentUserTextField();
            postedFields.Add(userField);
          }
    
          return base.SavePostedData(formId, sessionId, postedFields);
    
        }
    
      }
    }
    

    Not much code, nice and clean πŸ™‚
    Now let’s go through the code. Since we are inheriting the SaveData class we can now override the SavePostedData method. Then just before the base.SavePostedData method is called, we will add a new field to the list of postedFields. The new method CreateCurrentUserTextField will create the field containing the current Sitecore user.

    Don’t forget to name the field, give the field an unique id and set AllowSave to true(if not, it will not be saved)

    By the way, we don’t have to create a new field type. Like a “hidden field”. We can use known field types, like the InputViewModel(Base field type which is used by most of the field types in Sitecore Forms).

    Oh I almost forgot, we have to add the new submit action in Sitecore. Let’s locate the “Save Data” action(we will find it in /sitecore/system/Settings/Forms/Submit Actions) and duplicate it with the name “Save Data And User”. We also need to enter the class which will do the magic saving πŸ™‚

    Finally in the form experience designer, the “Submit Button” now have a seventh submit action – Save Data And User.

    The cool thing about this approach is that we don’t have to make a custom field, instead we will do it just before the form is saved. Easy peasy people!

    That’s all for now folks πŸ™‚

    Advertisements

    Leave a Reply

    Fill in your details below or click an icon to log in:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out /  Change )

    Google+ photo

    You are commenting using your Google+ account. Log Out /  Change )

    Twitter picture

    You are commenting using your Twitter account. Log Out /  Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out /  Change )

    Connecting to %s