Render multi-column form container block
Shows how to create your own form container block template and customize the rendering process to arrange elements into rows and columns using Display options and Bootstrap. To get the best experience, the Bootstrap version should be version 3 or higher.
Note
Optimizely Forms is only supported by MVC-based websites and HTML5-compliant browsers.
The form container block renders elements line-by-line by default. Using a grid system like Bootstrap, you can customize the rendering process to display the form in a multi-column layout.
-
Add predefined breakpoints for the bootstrap grid.
public static class FormDisplayOptionTags { 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" }; }
-
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") ...;
-
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; } }
-
Define
RenderFormElements
extension method forHtmlHelper
.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>"); } }
-
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" %>
-
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 a little tricky.
See also: blog post, Create custom Forms container
Updated 8 months ago