Improve Sitecore Web Forms for Marketers styling with Grunt

A few months ago I blogged about “Sitecore Frontend Approach – Quick Start Guide”which got some awesome feedback on Twitter.

However I noticed I’d missed something from my post which is pretty important to SItecore Frontend developers…. It’s a question I get asked a lot about at work and that is…

What’s the best way to add custom styles to Web Forms for Marketers?

To answer this question I need to start at the beginning…

The Old Way

When I first started working with Web Forms For Marketers (WFFM) I used to create a reset stylesheet (normalize.css) which had all the default WFFM CSS classes defined; a copy and paste of the default.css that’s installed with WFFM with some modifications to LESSify the file.

I would strip this file back to it’s bare bones and start adding in project specific styles. This worked well for most projects. However, I would sometimes have to override styles with !importants (which is bad) and have to repeat styles in two different locations one for my “base forms” LESS partial and another for my “WFFM styles”.

This process would always take considerable time and I always disliked the fact I was loading in my main forms styles and the WFFM specific styles. I would be making two HTTP requests one for the default WFFM stylesheet and another for my custom styles, which were nearly identical and the whole process just felt messy.

So I took a step back on a recent project and re-planned my WFFM workflow and wrote a list of goals wanted to achieve.

  1. Streamline workflow process
  2. Use built in WFFM functionality
  3. Reduce repeated styles
  4. Reduce the WFFM output file size

Streamline Workflow Process

Streamlining the workflow process was helped by using a task runner. Within the company I work for we used to use “Web Essentials” for Visual Studio which did a good enough job at the time but quickly became out-dated and support started to wither away.

We moved task running workflow over to Grunt and had to address some of our build processes. That gave me the opportunity to try out a some new workflow methods and make things easier for our frontend team.

So, I created a “WFFM” task which would:-

  • Compile my “_web-forms.less”
  • Run the file through a auto-prefixer
  • Minify the output CSS file
  • Copy the CSS file to the Sitecore Web Forms for Marketers folder directory

To give an example of my Grunt setup I’ve included some code snippets.

The folder structure:

// Example of solution folder structure for Sitecore frontend projects
web_application/
dist/
css/
images/
scripts/
grunt_tasks/
browserSync.js
concat.js
copy.js
cssmin.js
imagemin.js
jasmin.js
jshint.js
less.js
postcss.js
svgmin.js
uglify.js
watch.js
src/
css/
core/
_base-forms.less
modules/
wffm/
_web-forms.less
main.less
images/
**
scripts/
modules/
utilities/
vendor/
app.js
specs/
sitecore mdoules/
Shell/
Web Forms for Marketers/
Themes/
example-wffm.css
example-wffm.min.css
gruntfile.js
package.json

Example gruntfile.js (This is the cut down version):

// Gruntfile configuration v0.0.1
module.exports = function (grunt) {
// load nod tasks from package.json
require('load-grunt-tasks')(grunt);
// show timing of build tasks
require('time-grunt')(grunt);
// just in time load tasks / faster task running times
require('jit-grunt')(grunt)({
customTasksDir: 'grunt_tasks'
});
// global folder configuration
var options = {
config: {
src: "grunt_tasks/*.js"
},
paths: {
localDir: 'C:/inetpub/wwwroot/WEBSITE NAME EDIT ME/Website', // sitecore inetpub directory
localHost: 'LOCALHOST EDIT ME/',
// css paths
cssSrc: 'src/css',
cssDist: 'dist/css',
// scripts paths
jsSrc: 'src/scripts',
jsDist: 'dist/scripts',
jsTest: 'src/scripts/specs',
// image paths
imgSrc: 'src/images',
imgDist: 'dist/images',
// Sitecore modules
wffmSrc: 'src/css/modules/wffm', // src to webforms less file
wffmDist: 'sitecore modules/Shell/Web Forms for Marketers/Themes', // main sitecore modules directory
wffmCssFileName: 'wffm-example' // css file name output to sitecore modules folder
}
};
// Load grunt configurations automatically
var configs = require('load-grunt-configs')(grunt, options);
// Define the configuration for all the tasks
grunt.initConfig(configs);
// Default task(s).
// Each task requires a folder to be added named "grunt_tasks", each task will run from that folder
// Task to compile web forms for marketers less file to css, auto-prefix, minify and move compiled file to sitecore inetpub directory
grunt.registerTask('wffm', ['less:wffm', 'postcss:wffm', 'cssmin:wffm', 'copy:wffm']);
};
view raw gruntfile.js hosted with ❤ by GitHub

The package.json file:

{
"name": "ExampleGruntSetup",
"version": "1.0.0",
"description": "This is a base template for Frontend developers using Sitecore",
"main": "gruntfile.js",
"author": "@Pho3n1x84 - Matthew Neil",
"license": "(MIT)",
"devDependencies": {
"autoprefixer": "^6.3.1",
"grunt": "~0.4.5",
"grunt-contrib-concat": "^0.5.1",
"grunt-contrib-copy": "^0.8.2",
"grunt-contrib-cssmin": "^0.14.0",
"grunt-contrib-imagemin": "^1.0.0",
"grunt-contrib-jasmine": "^0.9.2",
"grunt-contrib-jshint": "^0.12.0",
"grunt-contrib-less": "^1.1.0",
"grunt-contrib-uglify": "^0.11.0",
"grunt-contrib-watch": "^0.6.1",
"grunt-newer": "^1.1.1",
"grunt-postcss": "^0.7.1",
"grunt-svgmin": "^3.1.2",
"jit-grunt": "^0.9.1",
"load-grunt-configs": "^0.4.3",
"load-grunt-tasks": "^3.4.0"
},
"dependencies": {
"jshint-stylish": "^2.1.0",
"time-grunt": "^1.3.0"
}
}
view raw package.json hosted with ❤ by GitHub

The LESS.js configuration:

module.exports = {
// Less
main: {
options: {
sourceMap: true,
paths: ['<%= paths.cssSrc %>'] // scans for @import
},
files: [{
expand: true, // Enable dynamic expansion.
cwd: '<%= paths.cssSrc %>', // Src matches are relative to this path.
src: ['**/main.less'], // Actual pattern(s) to match.
dest: '<%= paths.cssDist %>', // Destination path prefix.
ext: '.css', // Dest filepaths will have this extension.
}],
},
// Sitecore Webforms For Marketeers compilation
wffm: {
options: {
sourceMap: false,
paths: ['<%= paths.scModule %>']
},
files: {
'<%= paths.wffmDist %>/<%= paths.wffmCssFileName %>.css': '<%= paths.wffmSrc %>/_web-forms.less'
}
}
};
view raw less.js hosted with ❤ by GitHub

The copy.js configuration:

module.exports = {
// Copy files from WebDeb folder to inetpub
styles: {
expand: true,
src: [
'<%= paths.cssDist %>/*.css',
'<%= paths.cssDist %>/**/*.css'
],
dest: '<%= paths.localDir %>'
},
// Sitecore Web Forms
wffm: {
expand: true,
src: [
'<%= paths.wffmDist %>/*.css'
],
dest: '<%= paths.localDir %>'
},
};
view raw copy.js hosted with ❤ by GitHub

Use Built in WFFM Functionality

If you’re a frontend developer new to Sitecore, styling WFFM can be a little daunting and feel very “hacky” when writing your stylesheets, but this section will help you on your way to creating custom WFFM stylesheets (Win!)..

To create the custom WFFM stylesheet you need to navigate to:

/sitecore/system/Modules/Web Forms for Marketers/Settings/Meta data/Themes/ or {A7776ACD-88D9-4D2D-87BC-E73A1A1F6259}

Then create a new list item (/sitecore/templates/Web Forms for Marketers/Meta Data/List Item – {18319B48-5D68-4418-A2CF-418D911A5F72}) and name it something meaningful to your project.

In this example I named mine “example-form” (which is not meaningful at all).

web-forms-themes

Then create your custom WFFM folder inside the Sitecore folder structure and select from the theme drop down the newly create “example-form” style.

This will inject the example-form.css file into the closingtag when a WFFM is added to a page.

web-forms-custom-style

Reduce Repeated Styles

As mentioned in “The Old Way…” When I created a custom WFFM stylesheet I would end up with duplicated styles and a whole lot of bloated CSS which didn’t really perform as well as I wanted.

There are a few caveats to WFFM and that is WFFM will load in a few CSS and JS files into the main layout separately which creates those pesky additional HTTP requests which I would like to avoid, however that bit is out of my control (This is not an issue with HTTP2).

To try and improve the performance I avoided using LESS mixins and started to use “@extend()” methods instead (LESS Extend Explained) to try and use the code I had already written in my base forms LESS partial.

* These code snippets have been stripped back as the files had a lot of selectors

Example of the base forms LESS partial:

label {
display:block;
text-align:left;
font-size: @base-font-size * @p;
margin:30px 0 0;
line-height:1;
}
input {
&[type="text"],
&[type="email"],
&[type="password"],
&[type="date"],
&[type="month"],
&[type="number"],
&[type="datetime"],
&[type="datetime-local"],
&[type="tel"],
&[type="time"],
&[type="week"],
&[type="url"],
&[type="search"] {
box-sizing: border-box;
border-radius:0;
border: 2px solid @grey-100;
background: @grey-100;
color:@grey-80;
min-height: 50px;
margin: @division-small 0 0;
padding: 10px @division-small;
width: 100%;
text-align: left;
-webkit-appearance: none; /* iOS input madness */
&:focus {
-webkit-appearance: none; /* iOS input madness */
outline: none; /* remove chrome blue outline */
}
&:required {
border-bottom-color: @red;
}
}
}
view raw _form.less hosted with ❤ by GitHub

Code snippet of the WFFM LESS partial:

.scfForm {
.scfSectionBorderAsFieldSet {
&:extend(fieldset);
}
.scfSectionLegend {
&:extend(legend);
}
.scfDropListLabel,
.scfEmailLabel,
.scfMultipleLineTextLabel,
.scfSingleLineTextLabel,
.scfPasswordLabel,
.scfNumberLabel,
.scfDatePickerLabel,
.scfDateLabel,
.scfRadioButtonListLabel,
.scfListBoxLabel,
.scfFileUploadLabel,
.scfDateSelectorLabel,
.scfCreditCardLabel,
.scfConfirmPasswordLabel,
.scfCaptchaLabel,
.scfTelephoneLabel,
.scfSmsTelephoneLabel {
&:extend(label);
order: 0;
}
.scfCheckBoxListLabel {
&:extend(label);
order: 0;
}
select {
&:extend(select);
}
textarea {
&:extend(textarea);
}
input {
&[type="text"],
&[type="date"],
&[type="email"],
&[type="month"],
&[type="number"],
&[type="password"],
&[type="tel"],
&[type="time"],
&[type="week"],
&[type="url"] {
&:extend(input[type="text"]);
}
}
}
view raw _web-forms.less hosted with ❤ by GitHub

When I run the Grunt command “Grunt wffm”, this generates a base set of form styles in my main.css file look something like this:

label,
.scfForm .scfDropListLabel,
.scfForm .scfEmailLabel,
.scfForm .scfMultipleLineTextLabel,
.scfForm .scfSingleLineTextLabel,
.scfForm .scfPasswordLabel,
.scfForm .scfNumberLabel,
.scfForm .scfDatePickerLabel,
.scfForm .scfDateLabel,
.scfForm .scfRadioButtonListLabel,
.scfForm .scfListBoxLabel,
.scfForm .scfFileUploadLabel,
.scfForm .scfDateSelectorLabel,
.scfForm .scfCreditCardLabel,
.scfForm .scfConfirmPasswordLabel,
.scfForm .scfCaptchaLabel,
.scfForm .scfTelephoneLabel,
.scfForm .scfSmsTelephoneLabel,
.scfForm .scfCheckBoxListLabel {
display: block;
text-align: left;
font-size: 18px;
margin: 30px 0 0;
line-height: 1;
}

Inside my inetpub directory /sitecore modules/Shell\Web Forms for Marketers/Themes/ a file would be generated for properties specifically required for WFFM (mainly flexbox properties).

.scfForm .scfDropListLabel,
.scfForm .scfEmailLabel,
.scfForm .scfMultipleLineTextLabel,
.scfForm .scfSingleLineTextLabel,
.scfForm .scfPasswordLabel,
.scfForm .scfNumberLabel,
.scfForm .scfDatePickerLabel,
.scfForm .scfDateLabel,
.scfForm .scfRadioButtonListLabel,
.scfForm .scfListBoxLabel,
.scfForm .scfFileUploadLabel,
.scfForm .scfDateSelectorLabel,
.scfForm .scfCreditCardLabel,
.scfForm .scfConfirmPasswordLabel,
.scfForm .scfCaptchaLabel,
.scfForm .scfTelephoneLabel,
.scfForm .scfSmsTelephoneLabel {
-webkit-box-ordinal-group: 1;
-webkit-order: 0;
-ms-flex-order: 0;
order: 0;
}
view raw example-wffm.css hosted with ❤ by GitHub

This would reduce the amount of repeated styles as all WFFM classes would be extended to my base form stylesheet which my look like a lot of selectors however that performs better than having repeated styles using the mixin LESS function.

Reduce the Web Forms for Marketers CSS Output File Size

When you first install WFFM in Sitecore, WFFM bundles you with a few CSS files to get you up and running with WFFM. For me these stylesheets should never be used in production and should be treated as a guide for all the CSS selectors that are in WFFM.

The main file of concern for this topic is default.css. This file is around 21kb without minification, you also have to add into consideration the left, right or top orientation styles which are roughly 8kb giving us a total size of 29kb.

That’s 29kb un-minified (minified is around 12kb) of just WFFM styles not including any base form styling you may have to do in a project. I may be being picky but that is an extra 29kb of repeated styles I’ve already declared in my base forms LESS partial why repeat this code.

After achieving goals 1 – 3 on my checklist, goal 4 fell into place. After extending to my WFFM styles to my base form styles and only applying styles which I needed specifically for WFFM ended up being 9kb unminified.

That’s a 68.97% reduction in file size which in web performance is a massive saving, once you apply minification and GZipping you would be looking at around 4 to 5 kb for a production ready file.

I hope reading this blog post helped you with your web forms for marketers workflow. Happy coding; may the forms be with you!

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