Loading...
Area: Episerver Add-ons
Applies to versions: 4.3 and higher

Rendering a multi-column form container block

Recommendations [hide]

Note: Episerver Forms is only supported by MVC-based websites and HTML5-compliant browsers.

The form container block render elements line-by-line by default. You can customize the rendering proccess to display the form in a multi-column layout using a grid system like Bootstrap.

The following steps show how to create your own form container block template and customize the rendering process to arrange elements into rows and columns using EPiServer Display options and Bootstrap. To get the best experience, the Bootstrap version should be version 3 or higher.

  1. Add predefined breakpoints for the bootstrap grid.
    public static class FormDisplayOtionTags
      {
        public static readonly string[] FullWidth = new string[] { "span12", "col-xs-12", "col-sm-12", "col-md-12", "col-lg-12" };
        public static readonly string[] ThirdQuaterWidth = new string[] { "span9", "col-xs-9", "col-sm-9", "col-md-9", "col-lg-9" };
        public static readonly string[] TwoThirdsWidth = new string[] { "span8", "col-xs-8", "col-sm-8", "col-md-8", "col-lg-8" };
        public static readonly string[] HalfWidth = new string[] { "span6", "col-xs-6", "col-sm-6", "col-md-6", "col-lg-6" };
        public static readonly string[] OneThirdWidth = new string[] { "span4", "col-xs-4", "col-sm-4", "col-md-4", "col-lg-4" };
        public static readonly string[] OneQuaterWidth = new string[] { "span3", "col-xs-3", "col-sm-3", "col-md-3", "col-lg-3" };
      }
    
  2. Optionally, add more display options corresponding to these breakpoints:
    var options = ServiceLocator.Current.GetInstance();
    options.Add("full", "/displayoptions/full", Global.ContentAreaTags.FullWidth, "", "epi-icon__layout--full")
           .Add("wide", "/displayoptions/wide", Global.ContentAreaTags.TwoThirdsWidth, "", "epi-icon__layout--two-thirds")
           .Add("narrow", "/displayoptions/narrow", Global.ContentAreaTags.OneThirdWidth, "", "epi-icon__layout--one-third")
           ...;
    
  3. Create the FormContentAreaRenderer class to get content area items style and calculate the item width necessary for the layout.
    [ServiceConfiguration(ServiceType = typeof(FormContentAreaRender), Lifecycle = ServiceInstanceScope.Singleton)]
    public class FormContentAreaRender: ContentAreaRenderer
      {
        private IContent _currentContent;
    
        /// Get css of a content area item
        public string GetItemCssClass(HtmlHelper html, ContentAreaItem areaItem)
          {
            var tag = GetContentAreaItemTemplateTag(html, areaItem);
            var baseClasses = base.GetContentAreaItemCssClass(html, areaItem);
            return $"block {GetTypeSpecificCssClasses(areaItem)} {tag} {baseClasses}";
          }
    
        /// Get layout width of a content area item
        public int GetColumnWidth(HtmlHelper html, ContentAreaItem item)
          {
            var tag = GetContentAreaItemTemplateTag(html, item);
            return GetColumnWidth(tag);
          }
    
        private string GetTypeSpecificCssClasses(ContentAreaItem contentAreaItem)
          {
            var content = GetCurrentContent(contentAreaItem);
            var cssClass = content?.GetOriginalType().Name.ToLowerInvariant() ?? string.Empty;
            var customClassContent = content as ICustomCssInContentArea;
            if (customClassContent != null && !string.IsNullOrWhiteSpace(customClassContent.ContentAreaCssClass))
              {
                cssClass += $" {customClassContent.ContentAreaCssClass}";
              }
            return cssClass;
          }
    
        private IContent GetCurrentContent(ContentAreaItem contentAreaItem)
          {
            if (_currentContent == null || !_currentContent.ContentLink.CompareToIgnoreWorkID(contentAreaItem.ContentLink))
              {
                _currentContent = contentAreaItem.GetContent();
              }
            return _currentContent;
          }
    
        /// Get the width of a css tag (bootstrap column width)
        private int GetColumnWidth(string tag)
          {
            if (Global.FormDisplayOtionTags.FullWidth.Contains(tag))
              {
                return 12;
              }
            if (Global.FormDisplayOtionTags.ThirdQuaterWidth.Contains(tag))
              {
                return 9;
              }
            if (Global.FormDisplayOtionTags.TwoThirdsWidth.Contains(tag))
              {
                return 8;
              }
            if (Global.FormDisplayOtionTags.HalfWidth.Contains(tag))
              {
                return 6;
              }
            if (Global.FormDisplayOtionTags.OneThirdWidth.Contains(tag))
              {
                return 4;
              }
            if (Global.FormDisplayOtionTags.OneQuaterWidth.Contains(tag))
              {
                return 3;
              }
            return 12;
          }
      }
    
  4. Define RenderFormElements extension method for HtmlHelper.
    static Injected _formContenAreaRender;
    
    ///
    /// Renders form elements
    ///
    ///Instance of HtmlHelper class
    ///Form element collection
    public static void RenderFormElements(this HtmlHelper html, int currentStepIndex, IEnumerable elements)
      {
        FormContainerBlock model = (FormContainerBlock)html.ViewData.Model;
        if (model == null) 
          {
            return;
          }
        /// TODO: calculate element width and group elements into rows
        /// We use rows to keep the layout unbroken since elements have different heights
        int rowWidthState = 0;
        var elementsInfos = elements.Select(element =>
          {
            var areaItem = model.ElementsArea.Items.FirstOrDefault(i => i.ContentLink == element.SourceContent.ContentLink);
            var columnWidth = _formContenAreaRender.Service.GetColumnWidth(html, areaItem);
            rowWidthState += columnWidth;
            return new
              {
                ContentAreaItem = areaItem,
                RowNumber = rowWidthState % 12 == 0 ? rowWidthState / 12 - 1 : rowWidthState / 12
              };
        });
    
        var rows = elementsInfos.GroupBy(a => a.RowNumber, a => a.ContentAreaItem);
        foreach (var row in rows)
          {
            // start of new row
            html.ViewContext.Writer.Write("<div class=\"row row-" + row.Key + "\">");
            foreach (var item in row)
              {
                IContent content = item.GetContent();
                if (content == null || content.IsDeleted)
                  {
                    continue;
                  }
                var cssClasses = _formContenAreaRender.Service.GetItemCssClass(html, item);
                // start of conten area item
                html.ViewContext.Writer.Write($"<div class=\"{cssClasses}\">");
    
                if (content is ISubmissionAwareElement)
                  {
                    var submissionAwareElement = (content as IReadOnly).CreateWritableClone() as IContent;
                    (submissionAwareElement as ISubmissionAwareElement).FormSubmissionId = html.ViewBag.FormSubmissionId;
                    html.RenderContentData(submissionAwareElement, false);
                  }
                else
                  {
                    html.RenderContentData(content, false);
                  }
                // end of content area item
                html.ViewContext.Writer.Write("</div>");
              }
            // end of row
            html.ViewContext.Writer.Write("</div>");
        }
      }
    
  5. Edit the view of FormContainerBlock (FormContainerBlock.ascx) to use the new render method.
    Replace:
    <%@ Import Namespace="EPiServer.Forms.EditView.Internal" %>
    with:
    <%@ Import Namespace="EpiserverSite2.Helpers" %>
  6. ICustomCssInContentArea interface for adding a custom CSS for form element.
    public interface ICustomCssInContentArea
      {
        string ContentAreaCssClass { get; }
      }

The following shows a form rendered in view mode. 

Note: A form can appear differently between edit view and view mode because in view mode, form elements are grouped into rows and columns to keep the layout unbroken. If you want a consistent look between edit view and view mode, create a custom content area renderer and apply it for the whole site.

Limitation: Currently, for simplicity, breakpoints are fixed for bootstrap grid, so 1/3 is 1/3 across screens. You can specify that 1/3 should be on the desktop and 12/12 should be on smaller screens. However, modifying the form container block template (FormContainerBlock.ascx) can be little tricky.

Blog posts

Do you find this information helpful? Please log in to provide feedback.

Last updated: Jan 23, 2017

Recommendations [hide]