Man interacting with web page on laptop
Technology
Sitecore Development

Sitecore Forms Conditional Validation - Part 1

Sep 24, 2020

Out of the box Sitecore Forms includes some very useful validations that are appropriate for most scenarios. However, there is a kind of validation that is not included that is common in more complex scenarios. This is the conditional validation, when a field value is considered valid only if another field fulfills some specific value. In part 1 of this series, we will develop a fully client-side simple conditional validation that works in most one-page Sitecore Forms. In part 2, we will analyze another solution that works with forms that have more than one page.

What do you need?

This example was done in Sitecore 9.1.1. Your project will need the same NuGet packages described in my previous Sitecore Forms blog post (Add a Loading Animation in Sitecore Forms), but additionally you will have to install (without references) the Microsoft.Extensions.DependencyInjection.Abstractions NuGet package (version 2.1.1 if you are using Sitecore 9.1.1).

How to implement?

For this example, we are going to develop a numeric conditional validation that validates if a numeric field value is greater than, greater or equal than, less than, less or equal than, or equals another field value. Let’s first create our NumericConditionalValidation class and associated FieldComparisonParameters class to generate the Unobtrusive JQuery Validation attributes for our fields. First, create the parameters class that will contain the other field’s ID and the operator we want to use for the comparison:

FieldComparisonParameters.cs

namespace Oshyn.Forms.Samples.Validations.Parameters
{
    public class FieldComparisonParameters
    {
        public string OtherFieldId { get; set; }
 
        public string Operator { get; set; }
    }
}

Once we have this class, we can use it to define our NumericConditionalValidation class (using statements omitted for brevity):

NumericConditionalValidation.cs

namespace Oshyn.Forms.Samples.Validations
{
    public class NumericConditionalValidation : ValidationElement<FieldComparisonParameters>
    {
        public override IEnumerable<ModelClientValidationRule> ClientValidationRules
        {
            get
            {
                //Get Form Rendering Context service
                var formContext = ServiceLocator.ServiceProvider.GetService<IFormRenderingContext>();
                 
                //Get the other field’s Title (use name if Title not set)
                var otherFieldItem = Sitecore.Context.Database.GetItem(ID.Parse(OtherFieldId));
                var otherFieldItemTitle = otherFieldItem != null && !string.IsNullOrEmpty(otherFieldItem[“Title”]) ? otherFieldItem[“Title”] :
                    (otherFieldItem != null ? otherFieldItem.Name : “Other Field”);
 
                //Get the other field’s client ID using the Form Rendering Context service
                var otherFieldSitecoreId = OtherFieldId.TrimStart(‘{‘).TrimEnd(‘}’).ToLowerInvariant();
                var fieldPrefix = formContext.CreateFieldPrefix(otherFieldSitecoreId);
                var otherFieldId = Regex.Replace(fieldPrefix, @“[\.\[\]]”, “_”) + “_Value”;
 
                //General rule settings, validation type must match the one used in JS
                var rule = new ModelClientValidationRule
                {
                    ErrorMessage = FormatMessage(Title, otherFieldItemTitle),
                    ValidationType = “numericcond”
                };
 
                //Validation parameters, names must match the ones used in JS
                rule.ValidationParameters.Add(“otherfieldid”, otherFieldId);
                rule.ValidationParameters.Add(“operator”, Operator);
 
                yield return rule;
            }
        }
 
        protected virtual string Title { get; set; }
 
        protected virtual string OtherFieldId { get; set; }
 
        protected virtual string Operator { get; set; }
 
        public NumericConditionalValidation(ValidationDataModel validationItem) : base(validationItem) { }
 
        public override void Initialize(object validationModel)
        {
            base.Initialize(validationModel);
 
            Assert.IsNotNullOrEmpty(Parameters?.OtherFieldId, “Other Field ID must be set.”);
            Assert.IsNotNullOrEmpty(Parameters?.Operator, “Operator must be set to >, >=, <, <= or =“);
 
            var field = validationModel as TitleFieldViewModel;
 
            Title = field.Title;
            OtherFieldId = Parameters?.OtherFieldId;
            Operator = Parameters?.Operator;
        }
 
        public override ValidationResult Validate(object value)
        {
            //Not doing server-side validation for this type of validation.
            return ValidationResult.Success;
        }
    }
}

This class does the following:

  • When declared, you can see that it inherits from Sitecore.ExperienceForms.Mvc.Models.Validation.ValidationElement <TParameters>, we set FieldComparisonParameters as the TParameters generic parameter to use as the validation parameters.
  • In the Initialize() method, it reads in the validation parameters (we will get to that later during the Sitecore item definition).
  • In the ClientValidationRules property’s get, we do the following logic:
    • We get the Title or Name of the other field (the field that will be used for the condition)
    • We use the IFormRenderingContext service to generate the field prefix that is used on the client side to identify the actual HTML form element of the other field.
    • We generate the validation rule that will create the Unobtrusive JQuery Validation attributes that will be added to the field’s HTML element. In this example, we create 3 attributes, data-val-numericcond (the validation type containing the validation error message), data-val-numericcond-otherfieldid (the parameter containing the other field’s client ID) and data-val-numericcond-operator (the parameter containing the operator to evaluate the condition). We will use this information to create our client-side validation method and adapter.
  • We are NOT doing anything on the server side, so the Validate() method will always return success.

Now we need the JavaScript code to do the actual validation. Create a new JS file that MUST be deployed under the following location in the Sitecore installation: sitecore modulesWebExperienceFormsscripts. For this example, we are calling the file form.custom.validations.js:

form.custom.validations.js

(function ($jq) {
    $jq.validator.addMethod(‘numericcond’, function (value, element, parameters) {
        var otherField = $jq(‘#' + parameters['o‘herfieldid']’;
        var operator = parameters['o‘erator']’
 
        if (otherField != undefined && otherField != null && !isNaN(otherField.val()) && !isNaN(value)) {
            var otherFieldValue = otherField.val();
 
            if ((operator == '>‘ ’& value <= otherFieldValue) ||
                (operator == '>‘' ‘& value < otherFieldValue) ||
                (operator == '<‘ ‘& value >= otherFieldValue) ||
                (operator == '<‘' ‘& value > otherFieldValue) ||
                (operator == '=‘ ‘& value != otherFieldValue)) {
                return false;
            }
        }
 
        return true;
    });
 
    $jq.validator.unobtrusive.adapters.add('n‘mericcond',’['o‘herfieldid',’'o‘erator']’ function (options) {
        options.rules['n‘mericcond']’= {
            otherfieldid: options.params['o‘herfieldid']’
            operator: options.params['o‘erator']’        };
 
        options.messages[’n’mericcond’]’= options.message;
    });
})(jQuery);

This JS code defines the validation method containing the actual validation logic. As you can see, it reads the operator parameter to determine the type of comparison to perform, and returns true if the validation passes, false otherwise. The adapter definition is basically boilerplate code that defines and assigns the parameters for the validation from the Unobtrusive JQuery attributes generated for the field.

Build and deploy your code to your Sitecore installation. For this example, we are going to reuse the form created on my previous blog post, and add an “Other Number” field (of type Number) to the form’s first page (make sure the “Number” field is also of type Number):

Empty Sitecore Form


First, let’s create the Validation item. Go to Sitecore Content Editor, navigate to sitecoreSystemSettingsFormsValidations, and add a new item using template /sitecoretemplatesSystemForms/Validation. Name it something specific for your case, but for this example we will only name it “Greater Than Validator”. Fill out the fields with the following data:

  • Type: “Oshyn.Forms.Samples.Validations.NumericConditionalValidator,Oshyn.Forms.Samples” (this is the fully qualified name of the validation class we created, change it to your specific namespaces and assembly name)
  • Message: “{0} must be greater than {1}” (this follows C# string.Format conventions, the first (0) parameter is the title of the field that we will attach this validation, and the second (1) parameter is the title of the other field that we will use to compare)
  • Parameters: “{“otherFieldId”:”{other_field_id}”,”operator”:”>”}”. This is a JSON that defines the parameters using the field names as defined in the FieldComparisonParameters class. For the “otherFieldId” parameter, first navigate in Content Editor to the Sitecore Item that defines the other field (the one we will use to do the conditional comparison), in our case the “Number” field:
    otherFieldId in Sitecore

    Copy the Item ID value, and paste it as the value for “otherFieldId”. For the “operator” parameter, we are going to do a “greater than” comparison, so we use the value “>”.

At the end, your Validation item values should look something like this:

Validation item values in Sitecore


We must allow this validation to be attached to any “Number” field, so we can select it when we attach it to our field in the form. Navigate to sitecoreSystemSettingsField TypesBasicNumber, and in the “Allowed Validations” field, add our new Greater Than Validation to the list:

Add new Greater Than Validation to Allowed Validations field in Sitecore


Now it is time to attach the validation to our “Other Number” field. Open the Forms Editor, and select the “Other Number” field. On the right panel, open the “Validation” section and select the “Greater Than Validator” option:

Sitecore Forms Editor Validation section


Click on “Apply” and save the form. Return to Content Editor, and open the Form item under sitecoreForms (in this example, “Two Page Form”) Look for the Scripts field, and add “form.custom.validations.js” to the script list after “form.validate.js”:

Scripts field in Sitecore Forms


Save, and publish everything that you have created/modified (or do a full site Smart Publish). Open your form and test your validation:

Open form for testing validation


As soon as you try to submit (or go to the next page, as in this example), if you have entered a lower number in the “Other Number” field than the one you entered in “Number”, you will get the validation error message under the field. The message will disappear if you enter a number greater than the one in the “Number” field, and you will be able to submit (or go to the next page).

If you inspect the Other Number field, you will see the Unobtrusive JQuery Validation attributes that have been added to the <input> field: data-val-numericcond, data-val-numericcond-otherfieldid and data-val-numericcond-operator

The main disadvantage of this approach is that it will only work when the other field (the one we are going to compare against) is in the same form page as the field we are attaching the validation. Another disadvantage is that the Validation item you create is very specific. You might have already noticed this because you have to include the other field’s ID in the Validation definition parameters. So you will need to create a Validation item for each pair of fields.

On the next part on this series, we will implement a solution for fields that are in different pages in the form, so stay tuned!