Skip to main content

Back-End Development

SCORE 2.0 Component Assembly 101, Part 2: Form Components

Form@1x.jpg

In my first post in this series, we walked through the process of building a simple component. Let’s go a bit further now and build something very useful in Sitecore: a custom form. While a module like Web Forms for Marketers can definitely help you build forms, it can be heavy handed and sometimes doesn’t quite fit the bill. That’s where form components come in.

Luckily, SCORE has a few features and patterns that can help you out, and keep your content authors happy at the same time.

 

Form Component Requirements

Here’s our user story for this form:

As a Content Assembler, I would like the ability to supply a ‘Contact Us’ form on any page within our site.

This form should have:

  1. Name, Required, Must be Alpha.
  2. Email, Required, Must be valid format.
  3. Comments, Required, Must be under 500 characters.

I also want the ability to specify the error messages that are displayed to the user whenever validation fails on a form field and to select a custom style for the form.

 

Getting Started

Let’s start with our datasource template. First, I’ll create a template at /sitecore/templates/My Site/Forms/Contact Us Form. This template has no special inheritance; it’s just a template! My fields look something like this:

 

Datasource Template

 

Next I’ll add some custom rendering parameters so that I can give my content authors the ability to select a custom style for this form. This part may or may not be necessary depending on your requirements, but if you are working on an enterprise framework that will support multiple sites and multiple languages, you definitely want to complete this step.

I’ll start by creating a template which acts as a folder to hold form styles, and I’ll call this “Form Styles”. This template has no fields, and allows SCORE’s List Item (/sitecore/templates/Score/Base/List Item) to be inserted. I will add an instance of this template to my tenant’s selections, with a few default styles. Mine looks like this:

Form Styles Template

After this is created, I can add a custom set of rendering parameters that allow my content team to select any styles they need. I’ve created a template for my rendering parameters at /sitecore/templates/My Site/Forms/Contact Us Form Rendering Parameters. You should always inherit custom rendering parameters from /sitecore/templates/ScoreUI/Base/Rendering Parameters/Base Component. This allows for our form to use SCORE’s Show/Hide feature out of the box.

I’ve added one additional field to my Contact Us Form rendering parameters:

 

Rendering Parameters

 

Now, the query here is important. This query allows us to select out our styles from our tenant’s selections folder. My query looks like this:

query:#selections#*[@@templateid='{FB02DA7F-D050-4452-B008-AE2C46FB1319}']/*

What’s critical to note here is that my Contact Us Form Styles template has an ID of {FB02DA7F-D050-4452-B008-AE2C46FB1319}. The #selections# token in the query gets replaced dynamically by SCORE’s pre-scaffolded Mappable Queries rule (under Component Datasource Locations).

 

Some Code

Now that we have two templates, let’s add some code so that we can use the power of strong typing.

public class ContactDatasource : ScoreUIItem
{
// be sure this matches your template's ID
public static readonly ID Template = new ID("{5E1AFEBC-CB85-4A47-AB20-B53E47626D7A}");

public static class Fields
{
public const string NameRequiredError = "Name Required Error";
public const string NameFormatError = "Name Format Error";
public const string NameLabel = "Name Label";

public const string EmailRequiredError = "Email Required Error";
public const string EmailFormatError = "Email Format Error";
public const string EmailLabel = "Email Label";

public const string CommentsLengthError = "Comments Length Error";
public const string CommentsRequiredError = "Comments Required Error";
public const string CommentsLabel = "Comments Label";
}

public ContactDatasource(Item item) : base(item)
{
}

public static bool TryParse(Item item, out ContactDatasource datasource)
{
datasource = item == null || item.IsDerived(Template) == false ? null : new ContactDatasource(item);
return datasource != null;
}

#region implicit casting
public static implicit operator ContactDatasource(Item innerItem)
{
return innerItem != null && innerItem.IsDerived(Template) ? new ContactDatasource(innerItem) : null;
}

public static implicit operator Item(ContactDatasource customItem)
{
return customItem != null ? customItem.InnerItem : null;
}
#endregion
}

 

So, a few things to note…

  1. We inherit from ScoreUIItem. This class inherits from CustomItem and exposes a few helpers.
  2. We expose our fields as constants to avoid magic strings elsewhere.
  3. We expose our template’s ID.
  4. We allow casting to and from this item from a generic Sitecore Item.

At a minimum, you should always wrap your custom datasources in classes and provide functionality similar to what you see here.

Now, for some rendering parameters:

public class ContactRenderingParameters : BaseComponentParameters
{
public static class Fields
{
public static string ContactFormStyle = "Contact Form Style";
}

public override void LoadRenderingParameters(Rendering rendering)
{
base.LoadRenderingParameters(rendering);
this.ClassSelection = rendering.Parameters.GetUserFriendlyValue(Fields.ContactFormStyle);
}
}

 

Remember when we made our rendering parameter’s template inherit from SCORE’s Base Component Parameters? We do the same here for our custom class. This will enable show/hide on our component. To pull out our Content Assembler’s custom Form Style selection, we utilize SCORE’s “GetUserFriendlyValue” extension when overriding the LoadRenderingParameters function. Class selection, Show/Hide selections and the rendering model’s default class will all get aggregated into @Model.Classes for usage in our HTML.

I stick both of these classes in the Data project of my default SCORE solution. You can stick them wherever it makes sense for you!

 

The Web Layer

Now, in our web layer we need a few things.

First off, we’re creating a Form, so we’ll need a controller to submit to. Mine looks like this:

 

public class ContactController : SitecoreController
{
private IContactService contactService;

public ContactController(IContactService contactService)
{
this.contactService = contactService;
}

[HttpPost]
public ActionResult ContactFormSubmit(ContactRenderingModel model)
{
if (!ModelState.IsValid)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
Response.TrySkipIisCustomErrors = true;

var errors =
ModelState.Where(v => v.Value.Errors.Count > 0)
.Select(x => new {Name = x.Key, Message = x.Value.Errors[0].ErrorMessage});

// grab your form errors and send them to JS here
return new JsonResult() { Data = errors };
}
else
{
Response.StatusCode = (int)HttpStatusCode.OK;
contactService.ProcessSubmission(model.Email, model.Name, model.Comments);

// return anything you need to your JS here.
return new JsonResult() {};
}
}
}

 

Notice that I’m using Dependency Injection to inject an instance of IContactService into my controller. This is not required, but it’s something I always set up and use. You can follow along with how to do that yourself at https://jockstothecore.com/adventures-in-dependency-injection/.

It’s also worth noting that we have a POST method. If you want a corresponding GET, you can set up your component as a Sitecore Controller Rendering, but I’ll probably just use a View Rendering.

My service is simple; at this point it’s just C#:

 

public interface IContactService
{
void ProcessSubmission(string email, string name, string comment);
}

public class ContactService : IContactService
{
public void ProcessSubmission(string email, string name, string comment)
{
// process the form submission here. from here on out it's just C#.NET, right? :)

return;
}
}

 

Now this is where it gets a little bit interesting. We need to use a View Model to hold our Form inputs for posting to the server, and this View Model needs to be able to validate itself and expose error messages according to what our content authors provide. In Sitecore, we like to call these “Rendering Models.” Mine looks like this:

 

public class ContactRenderingModel : RenderingModelBase<ContactRenderingParameters, ContactDatasource>
{
public ContactRenderingModel() : base("contact-form") { }

[SitecoreRequired(ContactDatasource.Fields.NameRequiredError)]
[SitecoreRegularExpression("[a-zA-Z ,.'-]*", ContactDatasource.Fields.NameFormatError)]
[SitecoreDisplayName(Key = ContactDatasource.Fields.NameLabel)]
public string Name { get; set; }

[SitecoreRequired(ContactDatasource.Fields.EmailRequiredError)]
[SitecoreRegularExpression(@".+\@.+\..+", ContactDatasource.Fields.EmailFormatError)]
[SitecoreDisplayName(Key = ContactDatasource.Fields.EmailLabel)]
public string Email { get; set; }

[SitecoreRequired(ContactDatasource.Fields.CommentsRequiredError)]
[SitecoreStringLength(500, ContactDatasource.Fields.CommentsLengthError)]
[SitecoreDisplayName(Key = ContactDatasource.Fields.CommentsLabel)]
public string Comments { get; set; }

protected override ContactDatasource InitializeDatasource(Item item)
{
ContactDatasource datasource;
if (ContactDatasource.TryParse(item, out datasource))
{
return datasource;
}
return null;
}
}

 

There’s a few things going on here that you should pay attention to:

  1. We’re inheriting from RenderingModelBase. This is the mechanism we use to tie our custom Rendering Parameters and Datasource to our Rendering Model.
  2. We call our Base Constructor and pass in a string. This string will expose itself in @Model.Classes in our view.
  3. We use SitecoreRequired, SitecoreStringLength, SitecoreDisplayName, etc. All of these wrap up normal ASP.NET MVC annotations that you would use in a vanilla MVC site. If you dig around in the SCORE documentation you will find a whole slew of available options.
  4. During initialization of the Datasource, we ensure that it’s a Contact item. If it’s not, bad things may happen (like unhandled exceptions). You should handle these scenarios appropriately.

Next, we need a View. I’ve set mine up like this:

 

@model Namespace.To.Your.Tenants.ContactRenderingModel

@{
var formId = Guid.NewGuid().ToString("N");
}

@using (Html.BeginUXModule("mysite/Components/ContactForm",
new
{
IsEditing = Sitecore.Context.PageMode.IsExperienceEditorEditing,
FormId = formId
},
new {@class = Model.Classes}))
{
Html.EnableClientValidation();
Html.EnableUnobtrusiveJavaScript();

using (Ajax.BeginRouteForm("ContactFormSubmit",
new
{
scItemPath = Model.Datasource.ID
},
new AjaxOptions()
{
HttpMethod = "POST"
},
new {id = formId}))
{
<div class="custom-form-control-group custom-form-nickname">
<div class="custom-form-label">
@Html.EditableLabelFor(m => m.Name)
</div>
<div class="custom-form-control">
@Html.TextBoxFor(m => m.Name)
</div>
@Html.EditableValidationMessageFor(m => m.Name)
</div>
<div class="custom-form-control-group custom-form-email">
<div class="custom-form-label">
@Html.EditableLabelFor(m => m.Email)
</div>
<div class="custom-form-control">
@Html.TextBoxFor(m => m.Email)
</div>
@Html.EditableValidationMessageFor(m => m.Email)
</div>
<div class="custom-form-control-group custom-form-comments">
<div class="custom-form-label">
@Html.EditableLabelFor(m => m.Comments)
</div>
<div class="custom-form-control">
@Html.TextAreaFor(m => m.Comments)
</div>
@Html.EditableValidationMessageFor(m => m.Comments)
</div>
<button type="button" class="contact-form-button">Go!</button>
}
}

 

So, there’s a lot going on here, some of which may be new to you. First off, Html.BeginUXModule is a SCORE helper that ties your component to a JavaScript module. In SCORE, JavaScript modules are organized via Require.JS. This method supports three very important arguments:

  1. The Require.JS path of the JavaScript module to load into the DOM.
  2. Any arguments you want to path to that Module. Here I am passing two: IsExperienceEditorEditing, and the ID that I’ll use for my form. You should probably always pass IsExperienceEditorEditing because you often times don’t want rogue JavaScript executing from Experience Editor.
  3. Any attributes you want to pass to your wrapping DIV. BeginUXModule creates a DIV in the DOM, and anything specified in the third argument will be an attribute there.

Now, within my UXModule I have some divs that wrap up my fields. Notice how I stick unique CSS classes on almost everything? It’s because I love my UI developers. If you don’t do this, they’ll have a hard time styling your components, and will generally loathe you. Did I overkill it here? Probably, but burnt chicken is better than raw chicken.

For my Form, I’m using an Ajax Form. To know where to submit to, it’s going to look in my route table for a route named “ContactFormSubmit” and it’s going to be passing something called scItemPath. We’ll set this up in a second, as these are two very crucial pieces that make any form work.

The only other magic in here are the custom HTML extensions provided by SCORE: @Html.EditableValidationMessageFor(m => m.Name), and @Html.EditableLabelFor(m => m.Name). These behave JUST like the native MVC methods, except they work with the attributes from our Rendering Model to allow the content author to edit the text in experience editor. Nifty, right?

In order to resolve that Require.JS JavaScript path in the @Html.BeginUXModule, we need to navigate to our tenant’s require.config.js and add in this setting:


window.require.paths.mysite = "/Areas/MySite/js";

 

Speaking of JavaScript and Require.JS, here’s what I’ve got:

 

define([
"jquery",
"underscore",
"jqueryValidate",
"jqueryUnobtrusiveAjax"
],
function ($, _) {
function ContactForm(scope, formId) {
var $form = $("#" + formId, scope);
var validator = $form.validate();

function submitForm() {
var action = $form.attr("action");
var type = $form.data("ajax-method");
var formData = $form.serialize();

$.ajax({
url: action,
type: type,
data: formData
}).fail(function(response) {
if (response.status === 400) {
var data = response.responseJSON;
var errors = {};
for (var i = 0; i < data.length; i++) {
errors[data[i].Name] = data[i].Message;
}

$form.data('validator').showErrors(errors);
}
});
}

$(".contact-form-button").on('click', function (event) {
event.preventDefault();
submitForm();
});
}

return function init(args) {
return args.IsEditing === true ? null : new ContactForm(args.scope, args.FormId);
}
})

 

This should be pretty straightforward if you’re familiar with JavaScript and Require.JS. If not, I highly recommend taking a crash course in Require.JS, as it’s used pretty heavily in SCORE UI. What’s happening here is that the SCORE Module Loader is responsible for initializing any component that uses @HTML.BeginUXModule. The Module Loader will then find the required JavaScript module, call the function returned from the module and pass in an argument object. This argument object will have any custom arguments that you passed to your module (in our case, IsEditing and FormId) as well as a custom “scope” item. This scope item can be used in jQuery selections so that your jQuery DOM selections are scoped to your component. Notice how I initialize the module if the page is in normal mode, but I do not if it’s in editing mode.

Also, in order to be able to submit to “ContactFormSubmit”, we need to go into our Area configuration and set it up as a route:

 

public class MySiteAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "MySite";
}
}

public override void RegisterArea(AreaRegistrationContext context)
{
// Register your MVC routes in here

context.MapRoute("ContactFormSubmit", "contactformsubmit/{*scItemPath}", new
{
controller = "Contact",
action = "ContactFormSubmit",
scItemPath = "{*scItemPath}"
});
}
}

 

So what’s this scItemPath thing? You shouldn’t have to worry about it too much, but I like to think of it as how Sitecore knows what the context is during a Post. You can read more about it here: https://jockstothecore.com/to-the-controller-and-back-part-1-routing/

The last piece we need to make this form component work is a Rendering. Keep in mind that I set up Dependency Injection in my solutions (you probably should, too!), so if you use a controller rendering + dependency injection, you would call out your controllers in a slightly different way (via Convention rather than fully qualified). For this post, however, I’m just going to use a View Rendering. It’s the same method as in our last post, except I’m calling out our custom rendering parameters and custom datasource in the appropriate fields. View Renderings also require a custom Sitecore Model to go with them. This Sitecore model should point back to your ContactRenderingModel.

Of course, we will allow this Rendering to be placed in any Nested Content Area for our tenant. You remember how to do that from the previous post, right? 😉

So now, when we insert this form into the page, it should look like this:

 

View Rendering for Form Component

Notice how our labels and error messages are editable by the content author?! Normally, I would make the button editable, too, but I didn’t bother because we’re going to remove the button in the next post. 🙂

 

Ta-da: Our Form Component!

 

Here’s the final product in action!
Form Component

 

Ready to try your hand at building form components? Let me know how it goes!

Thoughts on “SCORE 2.0 Component Assembly 101, Part 2: Form Components”

  1. Dylan,
    What version of SCORE are you using? We are still using version 2.0. From your file Forms/ContactRenderingParameters.cs, my version does not have library Score.UI.Data.RenderingParameters.

  2. Dylan McCurry Post author

    Hi Jim,

    I believe this was done using SCORE 2.1. However, the Score.UI.Data.RenderingParameters namespace should be in 2.0 as well. Did you add a reference to Score.UI.Data in your C# project?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Dylan McCurry, Solutions Architect

I am a certified Sitecore developer, code monkey, and general nerd. I hopped into the .NET space 10 years ago to work on enterprise-class applications and never looked back. I love building things—everything from from Legos to software that solves real problems. Did I mention I love video games?

More from this Author

Follow Us