Home » JavaScript

Category Archives: JavaScript

Bundle CSS and JavaScript Files in Sitecore MVC

The other day I was poking around Sitecore.Forms.Mvc.dll — this assembly ships with Web Forms for Marketers (WFFM), and is used when WFFM is running on Sitecore MVC — and noticed WFFM does some bundling of JavaScript and CSS files:

wffm-bundling

WFFM uses the above class as an <initialize> pipeline processor. You can see this defined in Sitecore.Forms.Mvc.config:

Sitecore-Forms-Mvc-config

This got me thinking: why not build my own class to serve as an <initialize> pipeline processor to bundle my CSS and JavaScript files?

As an experiment I whipped up the following class to do just that:

using System.Collections.Generic;
using System.Linq;
using System.Web.Optimization;

using Sitecore.Pipelines;

namespace Sitecore.Sandbox.Forms.Mvc.Pipelines
{         
    public class RegisterAdditionalFormBundles
    {
        public RegisterAdditionalFormBundles()
        {
            CssFiles = new List<string>();
            JavaScriptFiles = new List<string>();
        }

        public void Process(PipelineArgs args)
        {
            BundleCollection bundles = GetBundleCollection();
            if (bundles == null)
            {
                return;
            }

            AddBundle(bundles, CreateCssBundle());
            AddBundle(bundles, CreateJavaScriptBundle());
        }

        protected virtual BundleCollection GetBundleCollection()
        {
            return BundleTable.Bundles;
        }

        protected virtual Bundle CreateCssBundle()
        {
            if (!CanBundleAssets(CssVirtualPath, CssFiles))
            {
                return null;
            }

            return new StyleBundle(CssVirtualPath).Include(CssFiles.ToArray());
        }

        protected virtual Bundle CreateJavaScriptBundle()
        {
            if (!CanBundleAssets(JavaScriptVirtualPath, JavaScriptFiles))
            {
                return null;
            }

            return new ScriptBundle(JavaScriptVirtualPath).Include(JavaScriptFiles.ToArray());
        }

        protected virtual bool CanBundleAssets(string virtualPath, IEnumerable<string> filePaths)
        {
            return !string.IsNullOrWhiteSpace(virtualPath)
                    && filePaths != null
                    && filePaths.Any();
        }

        private static void AddBundle(BundleCollection bundles, Bundle bundle)
        {
            if(bundle == null)
            {
                return;
            }

            bundles.Add(bundle);
        }

        private string CssVirtualPath { get; set; }

        private List<string> CssFiles { get; set; }

        private string JavaScriptVirtualPath { get; set; }

        private List<string> JavaScriptFiles { get; set; }
    }
}

The class above basically takes in a collection of CSS and JavaScript file paths as well as their virtual bundled paths — these are magically populated by Sitecore’s Configuration Factory using values provided by the configuration file shown below — iterates over both collections, and adds them to the BundleTable — the BundleTable is defined in System.Web.Optimization.dll.

I then glued everything together using a patch configuration file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <initialize>
        <processor patch:after="processor[@type='Sitecore.Forms.Mvc.Pipelines.RegisterFormBundles, Sitecore.Forms.Mvc']"
          type="Sitecore.Sandbox.Forms.Mvc.Pipelines.RegisterAdditionalFormBundles, Sitecore.Sandbox">
          <CssVirtualPath>~/wffm-bundles/styles.css</CssVirtualPath>
          <CssFiles hint="list">
            <CssFile>~/css/uniform.aristo.css</CssFile>
          </CssFiles>
          <JavaScriptVirtualPath>~/wffm-bundles/scripts.js</JavaScriptVirtualPath>
          <JavaScriptFiles hint="list">
            <JavaScriptFile>~/js/jquery.min.js</JavaScriptFile>
            <JavaScriptFile>~/js/jquery.uniform.min.js</JavaScriptFile>
            <JavaScriptFile>~/js/bind.uniform.js</JavaScriptFile>
          </JavaScriptFiles>
        </processor>
      </initialize>
    </pipelines>
  </sitecore>
</configuration>

I’m adding the <initialize> pipeline processor shown above after WFFM’s though theoretically you could add it anywhere within the <initialize> pipeline.

The CSS and JavaScript files defined in the configuration file above are from the Uniform project — this project includes CSS, JavaScript and images to make forms look nice, though I am in no way endorsing this project. I only needed some CSS and JavaScript files to spin up something quickly for testing.

For testing, I built the following View — it uses some helpers to render the <link> and <script> tags for the bundles — and tied it to my Layout in Sitecore:

@using System.Web.Optimization
@using Sitecore.Mvc
@using Sitecore.Mvc.Presentation

<!DOCTYPE html>
<html>

<head>
    <title></title>

    @Styles.Render("~/wffm-bundles/styles.css")
    @Scripts.Render("~/wffm-bundles/scripts.js")
    
</head>
<body>
    

    @Html.Sitecore().Placeholder("page content")

</body>
</html>

I then built a “Feedback” form in WFFM; mapped it to the “page content” placeholder defined in the View above; published it; and pulled it up in my browser. As you can see the code from the Uniform project styled the form:

styled-form

For comparison, this is what the form looks like without the <initialize> pipeline processor above:

2014-10-30_2316

If you have any thoughts on this, or have alternative ways of bundling CSS and JavaScript files in your Sitecore MVC solutions, please share in a comment.

Advertisement

Add JavaScript to the Client OnClick Event of the Sitecore WFFM Submit Button

A SDN forum thread popped up a week and a half ago asking whether it were possible to attach a Google Analytics event to the WFFM submit button — such would involve adding a snippet of JavaScript to the OnClick attribute of the WFFM submit button’s HTML — and I was immediately curious how one would go about achieving this, and whether this were possible at all.

I did a couple of hours of research last night — I experimented with custom processors of pipelines used by WFFM — but found no clean way of adding JavaScript to the OnClick event of the WFFM submit button.

However — right before I was about to throw in the towel for the night — I did find a solution on how one could achieve this — albeit not necessarily a clean solution since it involves some HTML manipulation (I would opine using the OnClientClick attribute of an ASP.NET Button to be cleaner, but couldn’t access the WFFM submit button due to its encapsulation and protection level in a WFFM WebControl) — via a custom Sitecore.Form.Core.Renderings.FormRender:

using System.IO;
using System.Linq;
using System.Web.UI;

using Sitecore.Form.Core.Renderings;

using HtmlAgilityPack;

namespace Sitecore.Sandbox.Form.Core.Renderings
{
    public class AddOnClientClickFormRender : FormRender
    {
        private const string ConfirmJavaScriptFormat = "if(!confirm('Are you sure you want to submit this form?')) {{ return false; }} {0} ";

        protected override void DoRender(HtmlTextWriter output)
        {
            string html = string.Empty;
            using (StringWriter stringWriter = new StringWriter())
            {
                using (HtmlTextWriter htmlTextWriter = new HtmlTextWriter(stringWriter))
                {
                    base.DoRender(htmlTextWriter);
                }

                html = AddOnClientClickToSubmitButton(stringWriter.ToString());
            }

            output.Write(html);
        }

        private static string AddOnClientClickToSubmitButton(string html)
        {
            if (string.IsNullOrWhiteSpace(html))
            {
                return html;
            }

            HtmlNode submitButton = GetSubmitButton(html);
            if (submitButton == null && submitButton.Attributes["onclick"] != null)
            {
                return html;
            }

            submitButton.Attributes["onclick"].Value = string.Format(ConfirmJavaScriptFormat, submitButton.Attributes["onclick"].Value);
            return submitButton.OwnerDocument.DocumentNode.InnerHtml;
        }

        private static HtmlNode GetSubmitButton(string html)
        {
            HtmlNode documentNode = GetHtmlDocumentNode(html);
            return documentNode.SelectNodes("//input[@type='submit']").FirstOrDefault();
        }

        private static HtmlNode GetHtmlDocumentNode(string html)
        {
            HtmlDocument htmlDocument = CreateNewHtmlDocument(html);
            return htmlDocument.DocumentNode;
        }

        private static HtmlDocument CreateNewHtmlDocument(string html)
        {
            HtmlDocument htmlDocument = new HtmlDocument();
            htmlDocument.LoadHtml(html);
            return htmlDocument;
        }
    }
}

The FormRender above uses Html Agility Pack — which comes with Sitecore — to retrieve the submit button in the HTML that is constructed by the base FormRender class, and adds a snippet of JavaScript to the beginning of the OnClick attribute (there is already JavaScript in this attribute, and we want to run our JavaScript first).

I didn’t wire up a Google Analytics event to the submit button in this FormRender — it would’ve required me to spin up an account for my local sandbox instance, and I feel this would’ve been overkill for this post.

Instead — as an example of adding JavaScript to the OnClick attribute of the WFFM submit button — I added code to launch a JavaScript confirmation dialog asking the form submitter whether he/she would like to continue submitting the form. If the user clicks the ‘Cancel’ button, the form is not submitted, and is submitted if the user clicks ‘OK’.

I then had to hook this custom FormRender to the WFFM Form Rendering — /sitecore/layout/Renderings/Modules/Web Forms for Marketers/Form — in Sitecore:

form-rendering

I then saved, published, and navigated to a WFFM test form. I then clicked the submit button:

confirmation-box

As you can see, I was prompted with a JavaScript confirmation dialog box.

If you have any thoughts on this implementation, or know of a better way to do this, please drop a comment.

Until next time, have a Sitecorelicious day! 🙂