Friday 3 July 2015

Customising wrappers

Introduction


When trying to build flexible, generic components that are highly configurable by content editors or designers, sometimes the issue of configurable wrapper element comes up.  For example I might have a text or image field, and I might want to be able to specify if it is wrapped in an h1 tag, or I might want it in an h2 tag, a div with a certain class, or maybe just a p tag will do.  Wrapping elements isn't a problem if your field is defined as a rich text field and you're selecting the styles from the toolbar; this solution is intended for fields that aren't.

Create the wrapper templates

I'm going to define how my wrappers are represented in Sitecore using a template called Wrapper.  The template will contain the tag name (div, h1 etc) and any class I might want to add to the tag.


Create the wrapper items

Create a folder somewhere suitable in your content tree and create your Wrapper items.  The "Main title" wrapper is defined as using an h1 tag with no extra classes.



The Quote tag is a blockquote with a class of italic.



The sub heading tag is an "h2", the Paragraph is "p" and so on, simply create whatever wrappers you are going to need.

Define the content template

In order to show the concept working I'm going to create some fields in a template item that I can use on a page item, or as a data source to a rendering.  These fields are going to hold our data and also a reference to how we want the data to be displayed.



The source for the droplink fields is

query:/sitecore/content/Wrappers/*[@@templatename='Wrapper']

The query references the path we are storing our Wrapper items, and the templatename parameter ensures only our Wrapper items are shown.
Here is an item with some sample data.



The Title format and Tagline format items are dropdowns whose values are taken from the Wrapper folder.

Using the code


The wrapper itself is implemented as an html helper function so that we can use the same Razor syntax as BeginForm etc.  Before I show you the code for the helper I'll show you the usage and also the results so that you get a better understanding for what the code is doing.
Our page is going to have a rendering on it with the following code

@using(Html.BeginWrapper("Title format"))
{
    @Html.Sitecore().Field("Title")
}
@using (Html.BeginWrapper("Tagline format"))
{
    @Html.Sitecore().Field("Tagline")
}


The parameter we supply to the BeginWrapper method is the name of the field that references our Wrapper item.  In our case that is "Title format" and "Tagline format".  The code inside the BeginWrapper block is the standard Sitecore field helper that you should be familiar with.  This is the resulting html;
<h1>Hello world</h1>
<blockquote class="italic">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi posuere blandit tortor, eu convallis purus euismod mattis. In quis laoreet diam.</blockquote>
As you can see the title was wrapped in an h1 block and the tagline in a blockquote with a class of italic, as per our Wrapper item configuration in Sitecore.

The Wrapper class and helper

This is the code that generates the opening and closing tags.  I have put both the wrapper class and the HtmlHelper extension class in the same file for simplicity, you will probably want them in their own cs files.
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Mvc.Presentation;
using System;
using System.IO;
using System.Web.Mvc;
namespace MyNamespace.Wrapper
{
    /// <summary>
    /// A class that handles the writing of the opening and closing tag
    /// </summary>
    public class Wrapper : IDisposable
    {
        private TextWriter writer;
        private string tag;
        /// <summary>
        /// Constrcutor that gets called when the item is created.
        /// </summary>
        /// <remarks>
        /// The constrcutor writes the correct tag to the output stream via the TextWriter
        /// </remarks>
        /// <param name="writer">A TextWriter that gives access to the output stream</param>
        /// <param name="tag">The tag the Wrapper is responsible for.  This will be h1, h2, div etc</param>
        /// <param name="cssClass">An optional class to be added to the tag</param>
        public Wrapper(TextWriter writer, string tag, string cssClass)
        {
            this.writer = writer;
            this.tag = tag;
            if (!string.IsNullOrWhiteSpace(this.tag))
            {
                string attributes;
                // if a class has been supplied construct the appropriate markup
                if (!string.IsNullOrWhiteSpace(cssClass))
                {
                    attributes = string.Format(" class=\"{0}\"", cssClass);
                }
                else
                {
                    attributes = string.Empty;
                }
                
                // write the opening tag
                this.writer.Write(string.Format("<{0}{1}>", this.tag, attributes));
            }
        }
        public void Dispose()
        {
            if (!string.IsNullOrWhiteSpace(this.tag))
            {
                // write the closing tag
                this.writer.Write(string.Format("</{0}>", this.tag));
            }
        }
    }
    /// <summary>
    /// Extension methods that allow us to extend the HtmlHelper
    /// </summary>
    public static class Extensions
    {
        public static Wrapper BeginWrapper(this HtmlHelper helper, string fieldName)
        {
            // we haven't been passed an explicit item so use the rendering item
            // this is the data source item if supplied, or the current item if not
            return BeginWrapper(helper, fieldName, RenderingContext.Current.Rendering.Item);
        }
        public static Wrapper BeginWrapper(this HtmlHelper helper, string fieldName, Item item)
        {
            string tag = string.Empty;
            string cssClass = string.Empty;
            if (item != null)
            {
                // get the field that points to our wrapper item
                ReferenceField wrapperField = item.Fields[fieldName];
                if (wrapperField != null && wrapperField.TargetItem != null)
                {
                    // read the desired tag and css class
                    tag = wrapperField.TargetItem["Tag"];
                    cssClass = wrapperField.TargetItem["CSS Class"];
                }
            }
            // Return a Wrapper object for the correct tag and class
            return new Wrapper(helper.ViewContext.Writer, tag, cssClass);
        }
    }
}

Notes on using the helper

The custom BeginWrapper helper won't be available in your views until you specify what namespace the helper is in.  You can do this in two ways.  One is to add the relevant @using directive to the top of your view

@using MyNamespace.Wrapper

The other is to configure the namespace in the web.config that is in your View folder.  NOTE I am talking about the config file at

\Views\web.config

and not the web.config in the root folder of your application.  Add the namespace here
<configuration>
  <system.web.webPages.razor>
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="MyNamespace.Wrapper"/>

There will already be some namespaces registered, just add yours to the list.  Note that you might also need to restart Visual Studio for this change to take effect.

No comments:

Post a Comment