How To Dynamically Insert Row Divs When Using Episerver Content Areas With Bootstrap

One of the hardest challengers for devlopers new to Episerver is designing the pagetypes and blocks in a way that makes the Episerver backend easy for content editors to use.  Trust me, if you design an EPiserver backend in a way that takes content editors ages to create pages, you won't hear the end of it!  

When Episerver moved to a block architecture, it provided a new challenge for designers.  The HTML that Episerver produces can vary based on the decisions of content editors.  How content editors drag blocks into content areas and re-size them, will define the HTML that Episerver will produce.  When I say content editors can set the size of a block, in Episerver we can configure the system to allow content editors to set the bootstrap class that will be rendered on the page by something called a DisplayOption:

A display option is a way that developers can configure Episerver to allow content editors to decide how a block will be rendered.  For example, you may want a Call To Action block to display for the whole width of a page on a landing page, but, only display for a quarter of the width in a sidebar on a blog post page.  This difference in sizing is based on the display option associated with a block.  As seen above, content editors can select a display option in the Episerver editor by clicking on the one they want.  If you are new to Episerver Dislay Options, I suggest you have a look at this project first DisplayOption to get a basic understanding about how to configure Episerver as a developer in order to take advantage of this feature.  

If you're designing a responsive website (and in this day and age who isn't?) then it's highly likely that you will want to use Bootstrap.  This brings us to an interesting challange, if content editors can drag as many blocks as they want into a content area AND they can also set each blocks width, they will break the 12 columns per row requirement by bootstrap.  When this happens, what will the page look like?  If you're new to Episerver and you're reading this and scratching your head, then I suggest you read this post first, HTML Considerations You Need To Make When Working WIth Episerver.

One way of solving this issue is to configure EPiserver to automically insert rows for content editors, so they can use EPisever without needing to know anything about responsive grids.  We can do this by automatically inserting the row div when ever the column width of the blocks within a content area are greater than 12.  In today's tutorial, I'm going to cover all the code that you will need to do this.

Boilerplate Code

Before I get into the meat and veg of the code, you'll need to add a lot of boiler plate objects, enums, extensions etc.. into your project in oder to get the code to compile.  This section will cover all the code you should need: 

BootstrapClass Attribute

 public class BootstrapClassAttribute : Attribute
    {
        public string Name { get; set; }
    }

Display Options Enum

  public enum DisplayOptionEnum
    {
        Unknown,

        [BootstrapClass(Name = "col-md-12")]
        Full = 12,

        [BootstrapClass(Name = "col-md-9")]
        ThreeQuaters = 9,

        [BootstrapClass(Name = "col-md-6")]
        Half = 6,

        [BootstrapClass(Name = "col-md-3")]
        Quater = 3,
    }
    

Configuring Episerver To Use Display Options

     [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
     public class DisplayOptionsConfig : IInitializableModule
     {

        public void Initialize(InitializationEngine context)
        {
            var options = ServiceLocator.Current.GetInstance();
            foreach (var optionId in options.Select(x => x.Id).ToArray())
            {
                options.Remove(optionId);
            }

            options
                .Add("full", DisplayOptionEnum.Full.ToString(), DisplayOptionEnum.Full.ToString(), string.Empty, "epi-icon__layout--full")
                .Add("threeQuaters", DisplayOptionEnum.ThreeQuaters.ToString(), DisplayOptionEnum.ThreeQuaters.ToString(), string.Empty, "epi-icon__layout--two-thirds")
                .Add("half", DisplayOptionEnum.Half.ToString(), DisplayOptionEnum.Half.ToString(), string.Empty, "epi-icon__layout--half")
                .Add("quarter", DisplayOptionEnum.Quater.ToString(), DisplayOptionEnum.Quater.ToString(), string.Empty, "epi-icon__layout--one-fourth");
         }
 
         public static void RegisterRoutes(RouteCollection routes)
         {
         }
 
         public void Uninitialize(InitializationEngine context)
         {
         }
 
         public void Preload(string[] parameters)
         {
         }
     }

Custom Class Used For Rendering Blocks With A Row

    public class ContentItemRenderData
    {
        public int RowNumber { get; set; }
        public ContentAreaItem Items { get; set; }
    }
    

Convert A String To Enum

    public static class DisplayOptionsHelper
    {
        public static DisplayOptionEnum GetDisplayOptionTag(string tag)
        {
            DisplayOptionEnum displayOptionEnum;
            Enum.TryParse(tag, out displayOptionEnum);

            return displayOptionEnum;
        }
    }

Custom Content Area

This is the actual class that will do the magic of keeping track of the current column width of the blocks and adding the row div when needed.  This class defines a custom HTML helper that you can call within your Razo views.  I've done it this way, rather than force every content area to use this code, so you can pick and choose in your code, what content areas will automatically add the row divs and which ones won't.  If you use content areas to create the primary nav for example, it is likely you won't want to wrap your menu items in row divs!

    [ServiceConfiguration(typeof(CustomContentAreaRenderer), Lifecycle = ServiceInstanceScope.Unique)]
    public class CustomContentAreaRenderer : ContentAreaRenderer
    {
        protected override void RenderContentAreaItems(HtmlHelper htmlHelper, IEnumerable contentAreaItems)
        {
            if (contentAreaItems == null || !contentAreaItems.Any())
                return;

            var currentRowNumber = 0;
            var rowCount = 0;

            var itemsToRender = new List();

            foreach (var contentAreaIem in contentAreaItems)
            {
                var displayOption = contentAreaIem.LoadDisplayOption();

                if (displayOption == null)
                    continue;

                var displayOptionEnum = DisplayOptionsHelper.GetDisplayOptionTag(displayOption.Tag);
                rowCount = rowCount + (int)displayOptionEnum;

                if (rowCount >= 12)
                {
                    currentRowNumber = currentRowNumber + 1;
                    rowCount = 0;
                }

                var item = new ContentItemRenderData
                {
                    Items = contentAreaIem,
                    RowNumber = currentRowNumber
                };

                itemsToRender.Add(item);

            }

            var tagBuilder = new TagBuilder("div");
            tagBuilder.AddCssClass("row1");

            var rows = itemsToRender.GroupBy(a => a.RowNumber, a => a.Items);
            foreach (var row in rows)
            {
                htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag));
                base.RenderContentAreaItems(htmlHelper, row);
                htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.EndTag));
            }
        }
    }
    
To use the custom content area you can create a custom HTML helper:

 public static class ContentAreaExtensions
    {
        internal static Injected _customContentAreaRenderer;

        public static void RenderCustomContentArea(this HtmlHelper htmlHelper, ContentArea contentArea)
        {
            _customContentAreaRenderer.Service.Render(htmlHelper, contentArea);
        }
    }


You could then render it out like so:

              @{
                    Html.RenderCustomContentArea(Model.CurrentPage.MainContentArea);
                }

Sample Code

If you want a working sample site to demo this concept then have a look at my Github project, Episerver Display Options.

Content Area Conclusion

In this tutorial, I've talked about the HTML challenges a flexible block architecture brings to designers and developers who wish to make a high-quality easy to use product.  The more power and flexibility that you give to content editors, the more you will need to consider how you will ensure that the HTML Episerver produces to generate a page contains all the divs, classes and columns widths needed to make the page look good.

As with most development tasks, I wouldn't say there's one 'right' way of dealing with this and there are several techniques that you could use.  To make the content lives as painfree as possible, automatically inserting the Bootstrap row grid when it's needed does make the backend easier to use.  If you want to do this, you will need to create a custom HTML helper and you will also need to extend the default Content Area to render out the row HTML. Extending it is pretty easy when you get your head around it.  You can always use Dotpeek to go through the default EPiserver content area to help you understad how Episerver works out the box.
 

submit to reddit

Jon D Jones

Software Architect, Programmer and Technologist Jon Jones is founder and CEO of London-based tech firm Digital Prompt. He has been working in the field for nearly a decade, specializing in new technologies and technical solution research in the web business. A passionate blogger by heart , speaker & consultant from England.. always on the hunt for the next challenge

Back to top