EPiServer Forms: Notes on creating custom form fields

I’ve been doing a lot of work with EPiServer Forms for two of my clients recently. They both have more or less complex special form fields they need for their business; ranging from the addition of simple character counters to allowing the web editors to connect EPiServer Forms elements to fields in a SOAP API.

This article is just to cover basics in regards to creating own custom EPiServer Forms fields by extending EPiServer‘s existing ones.

Basics on adding custom EPiServer Forms fields

The first thing we need for a custom EPiServer Forms field is somewhere to put our additional properties, so a block model extending a suitable one from EPiServer’s existing collection is a good place to start.

LimitedTextboxElementBlock.cs

[ContentType(
  GUID = "1FB2DF44-9F64-4133-B575-D8E9253D6953",
  DisplayName = "Limited Textbox",
  Description = "A textbox with a character limit.",
  Order = 100)]
public class LimitedTextboxElementBlock : TextboxElementBlock
{
  [Display(
    Name = "Max length",
    Description = "The maximum number of characters.",
    Order = 2999)]
  public virtual int? MaxLength { get; set; }
}

As usual, this is enough to make our block type appear in EPiServer’s edit mode.

Our next step should be the view. Should you want to keep them in another directory than the default one, please see EPiServer Forms: Moving your EPiSever Forms views to another location. If you need inspiration for the view code, open up the EPiServer Forms ZIP archive \modules\_protected\EPiServer.Forms\EPiServer.Forms.zip and have a look in the \Views\ElementBlocks\ directory.

An idea is to copy the code from the user control file (the ascx file) corresponding to the block that you’re extending, and then translating the code to fit your Razor view.

EPiServer’s TextboxElementBlock.ascx file

<%@ import namespace="System.Web.Mvc" %>
<%@ import namespace="EPiServer.Forms.Helpers.Internal" %>
<%@ import namespace="EPiServer.Forms.Implementation.Elements" %>
<%@ control language="C#" inherits="ViewUserControl<TextboxElementBlock>" %>

<%
  var formElement = Model.FormElement; 
  var labelText = Model.Label;
  var errorMessage = Model.GetErrorMessage();
%>

<div class="Form__Element FormTextbox <%: Model.GetValidationCssClasses() %>" data-epiforms-element-name="<%: formElement.ElementName %>">
  <label for="<%: formElement.Guid %>" class="Form__Element__Caption"><%: labelText %></label>
  <input name="<%: formElement.ElementName %>" id="<%: formElement.Guid %>" type="text" class="FormTextbox__Input"
        placeholder="<%: Model.PlaceHolder %>" value="<%: Model.GetDefaultValue() %>" <%: Html.Raw(Model.AttributesString) %> />

  <span data-epiforms-linked-name="<%: formElement.ElementName %>" class="Form__Element__ValidationError" style="<%: string.IsNullOrEmpty(errorMessage) ? "display:none" : "" %>;"><%: errorMessage %></span>
  <%= Model.RenderDataList() %>
</div>

So translating this, and adding a small feature for our maximum lengh property value, we would end up with something like this. The model should also of course be changed to be our extended block model rather than EPiServer’s own.

LimitedTextboxElementBlock.cshtml

@using EPiServer.Forms.EditView.Internal
@using EPiServer.ServiceLocation
@model MyProject.Core.Models.Forms.Blocks.LimitedTextboxElementBlock

@{
  var errorMessage = Model.GetErrorMessage();
  var errorStyle = string.IsNullOrEmpty(errorMessage) ? " display:none" : string.Empty;
  var validationService = ServiceLocator.Current.GetInstance<ValidationService>();
  var maxLength = (Model?.MaxLength != null) ? $"maxlength={Model.MaxLength}" : string.Empty;
}

<div class="Form__Element FormTextbox @validationService.GetValidationCssClasses(Model)"
     data-f-element-name="@Model.FormElement.ElementName" data_f_type="textbox">

  <label for="@Model.FormElement.Guid"
         class="Form__Element__Caption">
    @Model.Label
  </label>

  <input name="@Model.FormElement.ElementName"
         id="@Model.FormElement.Guid"
         type="text"
         class="FormTextbox FormTextbox__Input @Model.InputElementSize"
         placeholder="@Model.PlaceHolder"
         value="@Model.GetDefaultValue()"
         @Html.Raw(maxLength)
         @Html.Raw(Model.AttributesString) />

  <span data-epiforms-linked-name="@Model.FormElement.ElementName"
        class="Form__Element__ValidationError"
        style="@errorStyle">@errorMessage</span>

  @Model.RenderDataList()
</div>

Be careful when you start changing the names of EPiServer’s CSS classes; they are sometimes used by the system to know what to do with your data. If you for instance remove the FormTextbox class from the outer div element, the submitted data won’t be saved when submitting your form.