Loading screen on laptop
Technology
Sitecore

Add a Loading Animation to Sitecore Forms

Jul 07, 2020

Introduction

Sitecore Forms uses the jQuery Unobtrusive AJAX Library to handle basic client-side form functionality such as validations and submissions. Like with any other form that uses jQuery Unobtrusive AJAX, we can configure a form generated by Sitecore Forms to do additional client-side functionality when submitting it, such as the example I'm going to show you here on how to add a loading animation.

What do you need?

This example was done in Sitecore 9.1.1. For this example, I'm using Bootstrap 4.4.1, you can link to the CDN version or download a local copy. In the back-end Visual Studio project, you will need to add NuGet packages for Sitecore.ExperienceForms, Sitecore.ExperienceForms.Mvc, Sitecore.Kernel, Sitecore.Mvc, Sitecore.Mvc.Analytics. Remember to correctly set up your layout with an outer layout so the required styles and scripts for Sitecore Forms are rendered. (You can find a guide on adding a form to a webpage in Sitecore here.) Don't forget to include a link for Bootstrap in the outer layout's head declaration.

How to implement?

Build a form in Sitecore, create a "container" view rendering, create a page, and include the "container" rendering and a form inside of it. You can build any kind of form, for this example I have built a two-page form with a "thank you" third page, and styled it using Bootstrap:

Page 1 of 2-page form without loading animation

Page 2 of 2-page form without loading animation

Page 3: thank-you page

If you take a look at the form's generated HTML code, you will see that the <form> tag has several "data-ajax-" attributes. These are the jQuery Unobtrusive AJAX attributes. (You can read more about Unobtrusive AJAX here.) We want to add a "data-ajax-loading" attribute to the form, so it shows/hides the specified HTML element when an AJAX request is in process.

Edit the "container" view rendering to contain the HTML for the loading animation. In this example, we are using a spinner animation provided by Bootstrap. As you can see, it has "display: none" set so it doesn't show up the first time you load the form.

Container.cshtml
<section class="container-fluid">
    <div class="row">
        <div class="col-md-3">
            @Html.Sitecore().Placeholder("form")
        </div>
    </div>
 
    <!-- The following is the spinner animation provided by Bootstrap -->
    <div id="loader" class="spinner-border" style="display: none" role="status">
        <span class="sr-only">Loading...</span>
    </div>
</section>

Sitecore uses Pipelines to configure the form before rendering it. We can tap into this pipeline (specifically, the forms.renderForm pipeline) to add options to the form. First, let's create our pipeline class that will handle the creation of our "data-ajax-loading" attribute that will be added to the form:

InitAjaxAdditionalOptions.cs
namespace Oshyn.Forms.Samples.Pipelines
{
    public class InitAjaxAdditionalOptions : MvcPipelineProcessor<RenderFormEventArgs>
    {
        public string LoadingElementId { get; set; }
 
        public override void Process(RenderFormEventArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
 
            if (!args.ViewModel.IsAjax || !args.HtmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled)
            {
                return;
            }
 
            var additionalAjaxOptions = new AjaxOptions
            {
                LoadingElementId = LoadingElementId
            };
 
            var attributes = additionalAjaxOptions.ToUnobtrusiveHtmlAttributes();
 
            foreach (var key in attributes.Keys)
            {
                args.Attributes[key] = attributes[key];
            }
        }
    }
}

This class has a property, LoadingElementId, that we can set up in our config patch file (more on that later). That value is assigned to an AjaxOptions object's LoadingElementId property. The properties from AjaxObject are converted to HTML attributes using the ToUnobtrusiveHtmlAttributes() call, and added to the pipeline's RenderFormEventArgs object that will be used at the end of the pipeline to render the HTML. LoadingElementId will be rendered as the "data-ajax-loading" attribute.

Create your config patch file to add the new custom pipeline. Add it AFTER the InitializeAjaxOptions pipeline provided by Sitecore. Set the <LoadingElementId> property to the HTML id of the spinner (in this specific example, "loader"). Make sure to deploy this file under App_Config\Include

Oshyn.Forms.Samples.config
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <forms.renderForm>
        <processor patch:after="processor[@type='Sitecore.ExperienceForms.Mvc.Pipelines.RenderForm.InitializeAjaxOptions, Sitecore.ExperienceForms.Mvc']"
                   type="Oshyn.Forms.Samples.Pipelines.InitAjaxAdditionalOptions, Oshyn.Forms.Samples">
          <LoadingElementId>loader</LoadingElementId>
        </processor>
      </forms.renderForm>
    </pipelines>
  </sitecore>
</configuration>

Deploy your code to your Sitecore environment, and reload your form. When doing any type of submit/navigation action (i.e., pressing buttons), you will see the animation appear (the animation will be longer if you just deployed, or if your submit actions take longer to process):

Page 1 of 2-page form with loading animation

Page 2 of 2-page form with loading animation

If you view at the page source code, you will see that the <form> tag now includes a data-ajax-loading="#loader" attribute.

This solution is not limited to just displaying a loader. If you need more flexibility, such as calling specific JS functions before and after an AJAX call, you can add other Unobtrusive AJAX attibutes such as data-ajax-begin and data-ajax-complete:

InitAjaxAdditionalOptions.cs
namespace Oshyn.Forms.Samples.Pipelines
{
    public class InitAjaxAdditionalOptions : MvcPipelineProcessor<RenderFormEventArgs>
    {
        public string LoadingElementId { get; set; }
        public string OnBegin { get; set; } //Added this property
        public string OnComplete { get; set; } //Added this property
 
        public override void Process(RenderFormEventArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
 
            if (!args.ViewModel.IsAjax || !args.HtmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled)
            {
                return;
            }
 
            //Added OnBegin and OnComplete
            var additionalAjaxOptions = new AjaxOptions
            {
                LoadingElementId = LoadingElementId,
                OnBegin = OnBegin,
                OnComplete = OnComplete
            };
 
            var attributes = additionalAjaxOptions.ToUnobtrusiveHtmlAttributes();
 
            foreach (var key in attributes.Keys)
            {
                args.Attributes[key] = attributes[key];
            }
        }
    }
}

Make sure to add to the config patch file the <OnBegin> and <OnComplete> properties set to the JS functions you want to call:

Oshyn.Forms.Samples.config
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <forms.renderForm>
        <processor patch:after="processor[@type='Sitecore.ExperienceForms.Mvc.Pipelines.RenderForm.InitializeAjaxOptions, Sitecore.ExperienceForms.Mvc']"
                   type="Oshyn.Forms.Samples.Pipelines.InitAjaxAdditionalOptions, Oshyn.Forms.Samples">
          <LoadingElementId>loader</LoadingElementId>
          <OnBegin>beginAjaxCall</OnBegin>
          <OnComplete>completeAjaxCall</OnComplete>
        </processor>
      </forms.renderForm>
    </pipelines>
  </sitecore>
</configuration>

Deploy, open the form in a browser, and look at the source code. You will see that the <form> element now has the data-ajax-begin="beginAjaxCall" and data-ajax-complete="completeAjaxCall" attributes. Make sure you have defined your beginAjaxCall() and completeAjaxCall() functions somewhere in your HTML JS code or referred custom JS file.

WARNING: under any circumstances, NEVER attempt to replace/add the following Unobtrusive AJAX attributes: data-ajax-success (OnSuccess), data-ajax-method (HttpMethod), data-ajax-mode (InsertionMode), data-ajax-update (UpdateTargetId). These are reserved for use by Sitecore Forms.

References