Liam GoldfinchLiam Goldfinch

Liam Goldfinch

.NET developer

Using default values in Kentico MVC Forms

In Kentico Portal Engine, the Form Builder functionality allows you to create forms for collecting data from your visitors.  You can embed these forms on pages across your website using the on-line form webpart, on-line form widget, or using the embed tool within a rich text area.

A nice feature that comes with the form builder is having the ability to configure default values, or prepopulate field values through query strings using query string macros (e.g. {% QueryString.firstName #%} or {? firstName ?}).  You can then direct visitors to these pages using carefully constructed URLs, so that text boxes are prepopulated with specific text, or dropdowns have default values that are set based on data being passed through from the previous page.

However, when it comes to Kentico MVC, the Form widget no longer resolves query string macros and therefore cannot be used to dynamically prepopulate field values out of the box.  To work around this, I've found that the simplest way to replicate this feature is to add data attributes to fields and then use JavaScript to carry out the prepopulating of values.  The approach I have taken is documented below in four simple steps.

Step 1 - Add a module class to the MVC project (e.g. GlobalEvents/FormBuilderConfigurationModule.cs)

using CMS;
using CMS.DataEngine;
using DancingGoat.GlobalEvents;
using Kentico.Forms.Web.Mvc;
using Kentico.Forms.Web.Mvc.Widgets;
using System;

[assembly: RegisterModule(typeof(FormBuilderConfigurationModule))]
namespace DancingGoat.GlobalEvents
{
    public class FormBuilderConfigurationModule : Module
    {
        public FormBuilderConfigurationModule() : base("DancingGoat.FormBuilderConfigurationModule")
        {
        }

        protected override void OnInit()
        {
            base.OnInit();
        }
    }
}

Step 2 - Customise the form markup by using the new global events, and adding data attributes for all fields within Kentico forms

using CMS;
using CMS.DataEngine;
using DancingGoat.GlobalEvents;
using Kentico.Forms.Web.Mvc;
using Kentico.Forms.Web.Mvc.Widgets;
using System;

[assembly: RegisterModule(typeof(FormBuilderConfigurationModule))]
namespace DancingGoat.GlobalEvents
{
    public class FormBuilderConfigurationModule : Module
    {
        public FormBuilderConfigurationModule() : base("DancingGoat.FormBuilderConfigurationModule")
        {
        }

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

            FormFieldRenderingConfiguration.GetConfiguration.Execute += InjectMarkupIntoKenticoComponents;
        }
        
        private static void InjectMarkupIntoKenticoComponents(object sender, GetFormFieldRenderingConfigurationEventArgs e)
        {
            // Only injects additional markup into default Kentico form components
            if (!e.FormComponent.Definition.Identifier.StartsWith("Kentico", StringComparison.InvariantCultureIgnoreCase))
            {
                return;
            }

            // Assigns additional attribute to fields for the field's codename.
            AddFieldCodenameMarkup(e);
        }

        private static void AddFieldCodenameMarkup(GetFormFieldRenderingConfigurationEventArgs e)
        {
            e.Configuration.EditorHtmlAttributes["data-name-field"] = e.FormComponent.BaseProperties.Name;
        }
    }
}

Step 3 - Customise the Form widget's wrapper element by adding a target JavaScript class

using CMS;
using CMS.DataEngine;
using DancingGoat.GlobalEvents;
using Kentico.Forms.Web.Mvc;
using Kentico.Forms.Web.Mvc.Widgets;
using System;

[assembly: RegisterModule(typeof(FormBuilderConfigurationModule))]
namespace DancingGoat.GlobalEvents
{
    public class FormBuilderConfigurationModule : Module
    {
        public FormBuilderConfigurationModule() : base("DancingGoat.FormBuilderConfigurationModule")
        {
        }

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

            SetGlobalRenderingConfigurations();

            FormFieldRenderingConfiguration.GetConfiguration.Execute += InjectMarkupIntoKenticoComponents;
        }

        private static void SetGlobalRenderingConfigurations()
        {
            FormWidgetRenderingConfiguration.Default = new FormWidgetRenderingConfiguration
            {
                // Elements wrapping the Form element
                FormWrapperConfiguration = new FormWrapperRenderingConfiguration
                {
                    ElementName = "section",
                    HtmlAttributes = { { "class", "js-bizform" } },
                }
            };
        }

        private static void InjectMarkupIntoKenticoComponents(object sender, GetFormFieldRenderingConfigurationEventArgs e)
        {
            // Only injects additional markup into default Kentico form components
            if (!e.FormComponent.Definition.Identifier.StartsWith("Kentico", StringComparison.InvariantCultureIgnoreCase))
            {
                return;
            }

            // Assigns additional attribute to fields for the field's codename.
            AddFieldCodenameMarkup(e);
        }

        private static void AddFieldCodenameMarkup(GetFormFieldRenderingConfigurationEventArgs e)
        {
            e.Configuration.EditorHtmlAttributes["data-name-field"] = e.FormComponent.BaseProperties.Name;
        }
    }
}

Step 4 - Bundle JavaScript with the out of the box Form widget to prepopulate fields based on query string values, this file should be added to Content/Widgets/KenticoForm/KenticoFormWidget.js

const bizform = document.querySelector('.js-bizform');

// If there is a Kentico Form instance on the page
if (bizform) {
    // Get all query string values
    const searchParams = new URL(document.location).searchParams;

    // Loop over query strings
    searchParams.forEach(function (value, key) {
        // Check if the query string key matches a field name
        const field = bizform.querySelector(`[data-name-field="${key}"]`);

        // If there is a match, store the value against the field
        if (field) {
            field.value = value;
        }
    });
}

With all this in place on the Dancing Goat sample site, you can now visit the URL http://localhost:8080/en-US/Contacts?UserFirstName=Liam&UserLastName=Goldfinch and my name will be prepopulated into the first name and last name fields of the contact form.  If you inspect the form using your browser's devtools or view page source, you should be able to see the data attributes on each field that you can use to generate your own URLs.