Monday 9 March 2015

Giving content editors control over styles

Introduction

When building a Sitecore solution one of the largest mindset decisions is who the client of your solution is going to be.  Is your site going to be managed by developers?  Or is it going to be managed by the business?  One of the advantages of a CMS is that it gives the business the ability to manage the site themselves, ie your marketing people, your product people and so on, who can run the site without needing help from a developer.  This concept of an end product that can be managed without developer input is of benefit to everyone; the business can instantly do the things they want to do, and developers only need to spend their time developing and not changing META data or page titles.  However many of the Sitecore solutions I get asked to consult on can only realistically be maintained by developers.

There are a few common practices that make for a "developer only" Sitecore solution, but the one I am going to discuss in this blog revolves around configuring styles.  This article assumes you already understand the basics of creating renderings, it's not going to be a complete tutorial on creating Sitecore components.

Examples

Let's say you have a "call to action" (CTA) box that can contain text and looks something like this


In the above example the background is blue, the text is black, maybe the text has a certain size.  You might want the ability to configure these aspects, so that you could end up with a CTA that looks like this with larger white text, red background etc.


The solution is easy, right?  Have a "Class" field and put the relevant classes in it, so for the first CTA;


 and the second


That certainly solves the problem.  You just need to have the relevant classes in your css files and you can add them to your html as needed via the "Class" field of the rendering's data source.  This is great for developers with knowledge of CSS and access to the source files, but if your marketing department wants to create their own CTA, then how are they going to use this?  They don't know what CSS classes have been defined or what CSS classes are compatible with each other.  Also it is easy for coders to look at these types of fields and think "it's obvious, isn't it?" - but if you have zero technical training, ability or confidence then this kind of thing can be very daunting and the first thing the marketing department is going to do is send an email to development asking; "We need a CTA with a green background and italic text, can you create one for us?"

Configuring styles via Sitecore

I'm now going to show you an alternative way to configure styles using Sitecore that will be easier and less daunting for your business people to use.  This is just a suggestion, but it is something that has worked for me.

The goal here is to abstract the actual css classes behind plain-English terms that need no technical background to understand.  The first thing I'm going to do is create a template called "Css Class" that will hold the css class to be added to a given element.



I'm going to group my classes into logical folders and create items for the possible configurations I can have for each style.


So I have blue and red as possible background styles, and bold, italics and normal for possible text styles.  The "Class" field for each item contains the css class to add to the html (so if Blue is selected, I add the cat-blue class).  Next I'm going to create a template that is going to be the data source for my Call to Action rendering.


As well as fields for the title and sub title, we have a droplink for the background style and one for the main title style.  You'd probably have one for the sub-title style as well, but this is just an example.  So lets create the items that are going to be our CTA's data source.



Ok, so now we have some basic configuration, next we're going to make our component.

Create the rendering

I'm using MVC in this example, but the concepts still work with web forms if that is what you are using.  So I've created a simple View Rendering that uses my "CallToAction.cshtml" view and added it to a page.  I've also set the rendering's data source to my "New product" item (these steps have been omitted for brevity).  Here is the view

@model Sitecore.Mvc.Presentation.RenderingModel
<div>
    <div>@Html.Sitecore().Field("Main title")</div>
    <div>@Html.Sitecore().Field("Sub title")</div>
</div>

And the resulting html

<div>
   <div>New product</div>
   <div>Be the first to try Widget X</div>
</div>

In order to add the classes to the relevant divs I have written a razor helper, but if you're using web forms you can write an extension method or your own helper.

The helper methods

I'll jump right in and show you one of the helper methods we'll be creating.  This is going to extend the SitecoreHelper class.  The first thing I'm going to do is write a function that will return me the value of the "Class" field that I reference from an item.  For example, if I have the "New product" item (the data source for my CTA), and I want the css for the "Background style" field, then I call this function passing in the item and "Background style" and it will return "cta-blue".

private static string CssClass(Item item, string fieldName)
{
    // if the item is null return an empty string
    if (item == null)
    {
        return string.Empty;
    }

    // read the field from the item as a multilist field
    MultilistField mlf = item.Fields[fieldName];
    if (mlf != null)
    {
        StringBuilder sb = new StringBuilder();
        // get the list of items to process
        var targetItems = mlf.GetItems();
        foreach (var targetItem in targetItems)
        {
            // for each item read the value of the "Class" field and append
            // it to the StringBuilder
            sb.Append(targetItem["Class"]);
            sb.Append(" ");
        }
        // return the list of classes
        return sb.ToString().Trim();
    }
    return string.Empty;
}

One thing you might notice is that our CTA items are using a droplink to reference a Css Class item, but the code expects a MultilistField.  When you access a single field item like a droplink as a multilist field then it is simply read as a multilist field with only a single item.  The reason we use multilistfield is so that our items can supply multiple classes in a single field if they want to, and we'll get onto an example of that later in the article.

Now we have our helper class in place we can create our SitecoreHelper extension methods, so create an "Extensions.cs" class

namespace MyNamespace.Extensions
{
   public static class SitecoreHelperExtensions
   {
   /// <summary>
   /// Render the CSS for the field on the current item
   /// </summary>
   public static HtmlString CssClass(this SitecoreHelper helper, string fieldName, string defaultCss = "")
   {
       // the method was called without specifying an item so make the current item the target item
       return CssClass(helper, helper.CurrentItem, fieldName, defaultCss);
   }

   /// <summary>
   /// Render the CSS for the field on the given item
   /// </summary>
   public static HtmlString CssClass(this SitecoreHelper helper, Item item, string fieldName, string defaultCss = "")
   {
       // get the css class from the item
       string css = CssClass(item, fieldName);
       // if it is empty use the default css
       // note the default css can also be empty
       if (string.IsNullOrWhiteSpace(css))
       {
           css = defaultCss;
       }
       // return the css as an HtmlString
       return new HtmlString(css);
   }

We use this helper like so (in these examples I have added the namespace to my extension class in the web.config so I don't need to add the @using at the top of my view.  You will have to do the same, or just add the @using)

@model Sitecore.Mvc.Presentation.RenderingModel

<div class="@Html.Sitecore().CssClass("Background style")">
    <div class="@Html.Sitecore().CssClass("Main title style")">@Html.Sitecore().Field("Main title")</div>
    <div>@Html.Sitecore().Field("Sub title")</div>
</div>

And now the html looks like this

<div class="cta-blue">
    <div class="">New product</div>
    <div>Be the first to try Widget X</div>
</div>

The only style we had for the title text was "Normal" which was an item where I left the "Class" value empty.  Rather than select an empty option like "Normal" you can just leave the field empty, but a "Normal" option makes it clear to content editors that the text is going to be left unaltered, even though that option actually has no effect on the markup.

Adding multiple classes

Now we're going to try adding multiple classes to an element rather than just one.  So let's add some Css Class items for text sizes


and change the template for the Call to Action's "Main title style" field to a TreelistEx


This will let us select multiple styles for our title text


As our helper already supports multiple fields we don't have to change our code, the html now looks like this

<div class="cta-blue">
    <div class="text-large text-em">New product</div>
    <div>Be the first to try Widget X</div>
</div>

The div for our title now has the classes that make it large and also italic.  Note that rather than use things like point sizes, em values etc in the name of our text sizes, we kept them in generic English terms that are easy for anyone to understand.  Everyone knows that Large is bigger than Medium, but not everyone is going to know that "Font size 1.5em" is larger than "Font size 1.0em".

Multiple fields for a single element

So far we have had a single field that defines the classes for a single element, but we can also have multiple fields define the classes for a single element.  Let's say we want to add an effect to the background such as transparency.  We can define the classes as normal


And add the relevant field to the Call to Action template



Now we want to create a new helper to let us read classes from multiple fields.  First we create a new helper function that uses our existing helper function to read from a list of field names.

private static string CssClasses(Item item, params string[] fieldNames)
{
    StringBuilder css = new StringBuilder();
    foreach (string fieldName in fieldNames)
    {
        string targetCss = CssClass(item, fieldName);
        if (!string.IsNullOrWhiteSpace(targetCss))
        {
            css.Append(targetCss);
            css.Append(" ");
        }
    }

    return css.ToString().Trim();
}

And now we create the SitecoreHelper extensions

public static HtmlString CssClasses(this SitecoreHelper helper, params string[] fieldNames)
{
    return CssClasses(helper, helper.CurrentItem, fieldNames);
}

public static HtmlString CssClasses(this SitecoreHelper helper, Item item, params string[] fieldNames)
{
    return new HtmlString(CssClasses(item, fieldNames));
}

And we use them like so

@model Sitecore.Mvc.Presentation.RenderingModel

<div class="@Html.Sitecore().CssClasses("Background style", "Background effect")">
    <div class="@Html.Sitecore().CssClass("Main title style")">@Html.Sitecore().Field("Main title")</div>
    <div>@Html.Sitecore().Field("Sub title")</div>
</div>

And the html

<div class="cta-blue bg-transparent">
    <div class="text-large text-em">New product</div>
    <div>Be the first to try Widget X</div>
</div>

Use items other than the current rendering item

In the above examples we have left the SitecoreHelper methods to use the current rendering item to read the class fields from, but our method have an overload should you want to read the css from a different item.  This works the same way that the "Html.Sitecore.Field" helper does.

Further amendments

There are further ways you could extend this, such as making sure any class is only added to the list of classes once, and another amendment I have made is the ability to read css classes from a rendering parameters as well as from the data source which gives you the ability to provide per-instance classes or to apply classes to items that you don't want to create data source items for.

Summary

To sum up, this is a way of allowing non-technical content editors to visually configure certain items without them needing to know any technical ins and outs of the solution, or to know any css or html.  The developers configure all of the possible options and that allows content editors distinct lists of options of choose from, with names that make it easy to understand exactly what that option does.


No comments:

Post a Comment