Render multi-column form container block
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 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>");
}
}- Edit the view of FormContainerBlock (FormContainerBlock.cshtml) 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.cshtml) can be a little tricky.
See also: blog post, Create custom Forms container