Creating custom form templates
To create a custom form template you need to have a class that derives from the IFormTemplate
interface:
/// <summary>
/// Allows the template the modify the field configuration for a particular field.
/// </summary>
/// <typeparam name="TModel">The type of model the form is being displayed for</typeparam>
/// <typeparam name="T">The type of the property the field is being generated against</typeparam>
/// <param name="fieldGenerator">The instance of the field generator that will be used to generate the field</param>
/// <param name="fieldGeneratorHandler">The instance of the field generator handler that will be used to generate the field element</param>
/// <param name="fieldConfiguration">The field configuration that is being used to configure the field</param>
/// <param name="fieldParent">The parent component of the field</param>
void PrepareFieldConfiguration<TModel, T>(IFieldGenerator<TModel, T> fieldGenerator, IFieldGeneratorHandler<TModel, T> fieldGeneratorHandler, IFieldConfiguration fieldConfiguration, FieldParent fieldParent);
/// <summary>
/// Creates the starting HTML for a form.
/// </summary>
/// <param name="action">The form action</param>
/// <param name="method">The form method</param>
/// <param name="htmlAttributes">Any HTML attributes the form should use; specified as an anonymous object</param>
/// <param name="enctype">The encoding type for the form</param>
/// <returns>The starting HTML for a form</returns>
IHtmlContent BeginForm(string action, FormMethod method, HtmlAttributes htmlAttributes, EncType? enctype);
/// <summary>
/// Creates the ending HTML for a form.
/// </summary>
/// <returns>The ending HTML for a form</returns>
IHtmlContent EndForm();
/// <summary>
/// Creates the beginning HTML for a section.
/// </summary>
/// <param name="heading">The heading of the section</param>
/// <param name="leadingHtml">Any HTML to output at the start of the section</param>
/// <param name="htmlAttributes">Any HTML attributes the section container should use; specified as an anonymous object</param>
/// <returns>The beginning HTML for a section</returns>
IHtmlContent BeginSection(IHtmlContent heading = null, IHtmlContent leadingHtml = null, HtmlAttributes htmlAttributes = null);
/// <summary>
/// Creates the ending HTML for a section.
/// </summary>
/// <returns>The ending HTML for a section</returns>
IHtmlContent EndSection();
/// <summary>
/// Creates the beginning HTML for a section that is nested within another section.
/// </summary>
/// <param name="heading">The heading of the nested section</param>
/// <param name="leadingHtml">Any HTML to output at the start of the nested section</param>
/// <param name="htmlAttributes">Any HTML attributes the nested section container should use; specified as an anaonymous object</param>
/// <returns>The beginning HTML for a nested section</returns>
IHtmlContent BeginNestedSection(IHtmlContent heading = null, IHtmlContent leadingHtml = null, HtmlAttributes htmlAttributes = null);
/// <summary>
/// Creates the ending HTML for a section that is nested within another section.
/// </summary>
/// <returns>The ending HTML for a nested section</returns>
IHtmlContent EndNestedSection();
/// <summary>
/// Creates the HTML for a single form field.
/// </summary>
/// <param name="labelHtml">The HTML that comprises the form label</param>
/// <param name="elementHtml">The HTML that comprieses the field itself</param>
/// <param name="validationHtml">The HTML that comprises the field's validation messages</param>
/// <param name="fieldMetadata">The metadata for the field being created</param>
/// <param name="fieldConfiguration">Configuration for the field</param>
/// <param name="isValid">Whether or not the field is valid</param>
/// <returns>The HTML for the field</returns>
IHtmlContent Field(IHtmlContent labelHtml, IHtmlContent elementHtml, IHtmlContent validationHtml, ModelMetadata fieldMetadata, IReadonlyFieldConfiguration fieldConfiguration, bool isValid);
/// <summary>
/// Creates the beginning HTML for a single form field that contains other fields nested within it.
/// </summary>
/// <param name="labelHtml">The HTML that comprises the form label</param>
/// <param name="elementHtml">The HTML that comprieses the field itself</param>
/// <param name="validationHtml">The HTML that comprises the field's validation messages</param>
/// <param name="fieldMetadata">The metadata for the field being created</param>
/// <param name="fieldConfiguration">Configuration for the field</param>
/// <param name="isValid">Whether or not the field is valid</param>
/// <returns>The beginning HTML for the parent field</returns>
IHtmlContent BeginField(IHtmlContent labelHtml, IHtmlContent elementHtml, IHtmlContent validationHtml, ModelMetadata fieldMetadata, IReadonlyFieldConfiguration fieldConfiguration, bool isValid);
/// <summary>
/// Creates the ending HTML for a single form field that contains other fields nested within it.
/// </summary>
/// <returns>The ending HTML for the parent field</returns>
IHtmlContent EndField();
/// <summary>
/// Creates the beginning HTML for a navigation section.
/// </summary>
/// <returns>The beginning HTML for a navigation section</returns>
IHtmlContent BeginNavigation();
/// <summary>
/// Creates the ending HTML for a navigation section.
/// </summary>
/// <returns>The ending HTML for a navigation section</returns>
IHtmlContent EndNavigation();
/// <summary>
/// Creates the beginning HTML for a message.
/// </summary>
/// <param name="messageType">The type of message being displayed</param>
/// <param name="heading">The heading for the message</param>
/// <returns>The beginning HTML for the message</returns>
IHtmlContent BeginMessage(MessageType messageType, IHtmlContent heading);
/// <summary>
/// Creates the ending HTML for a message.
/// </summary>
/// <returns>The ending HTML for the message</returns>
IHtmlContent EndMessage();
/// <summary>
/// Creates the HTML for a paragraph in a message.
/// </summary>
/// <param name="paragraph">The paragraph HTML</param>
/// <returns>The HTML for the message paragraph</returns>
IHtmlContent MessageParagraph(IHtmlContent paragraph);
/// <summary>
/// Creates the HTML for a button.
/// </summary>
/// <param name="content">The content for the user to see or null if the value should be used instead</param>
/// <param name="type">The type of button or null if a generic button should be used</param>
/// <param name="id">The name/id of the button or null if one shouldn't be set</param>
/// <param name="value">The value to submit if the button is clicked or null if one shouldn't be set</param>
/// <param name="htmlAttributes">Any HTML attributes to add to the button or null if there are none</param>
/// <returns>The HTML for the button</returns>
IHtmlContent Button(IHtmlContent content, string type, string id, string value, HtmlAttributes htmlAttributes);
/// <summary>
/// Creates the HTML for a list of radio buttons or checkboxes.
/// </summary>
/// <param name="list">The list of HTML items (one per radio/checkbox)</param>
/// <param name="isCheckbox">Whether the list is for checkboxes rather than radio buttons</param>
/// <returns>The HTML for the radio list</returns>
IHtmlContent RadioOrCheckboxList(IEnumerable<IHtmlContent> list, bool isCheckbox);
}
Recommendation
To make it easy for yourself we recommend that you start by extending the DefaultFormTemplate
class and override each method in turn that you want to change the HTML for. For an example of this approach see the Twitter Bootstrap 3 Form Template, which extends most, but not all methods.
MSBuildRazorCompiler
Rather than expressing all of the complex template HTML as a string we recommend that you instead use the MSBuildRazorCompiler library so that you can specify the HTML using Razor syntax in a .cshtml
file and have it statically compiled into .cs
files that you can reference. See the ChameleonForms.Template
library to see how it works and use as a base to get started.
PrepareFieldConfiguration
The PrepareFieldConfiguration
method allows you to make arbitrary changes to the Field Configuration of a field before it is rendered by the other template methods. For an example of the type of changes you can make, see the Twitter Bootstrap 3 form template.
This technique allows you to take advantage of the expressiveness and flexibility afforded by the Field Configuration to:
- Make easily unit testable changes
- Keep your templates terse and reduce the potential noise of lots of control statements
- Apply global configuration changes that are template specific without requiring users to specify those changes for each form/field
BeginSection / BeginNestedSection
The heading is not required and we recommend having logic like the following to conditionally display the heading:
@if (Model.Heading != null && !string.IsNullOrWhiteSpace(Model.Heading.ToString())) {
@* heading html here *@
}
Similarly, the leading HTML is optional and you might want to use similar conditional logic for that field (unless you don't wrap it in any HTML in which case you can simply output it e.g. @Model.LeadingHtml
).
You should always have a container element for your section which has the HtmlAttributes
object from that section applied to it.
Field
There are a lot of parameters passed to the field method - only use the ones that you need for your template (e.g. if you don't specify different HTML when a field is invalid just ignore the isValid
variable).
If you want the ability to specify the required designator separately within the template then make sure to call the RequiredDesignator
method from within the Field
method to get the HTML for the required designator. The templates we provide out-of-the-box do this and thus you can override just the required designator by extending the form template classes we provide and override the GetRequiredDesignator
method.
Given you will likely need to split up your HTML into multiple files with mismatching start and end tags it's recommended you make use of the @:
operator e.g.:
@{
@: <div>
}
If you want a fully-fledged Field HTML implementation then you need to account for:
- Outputting the Field Label HTML, Field Element HTML and Field Validation Message HTML
- Outputting the Field Container Classes from field configuration onto whatever the containing element is (e.g.
<div class="@Model.FieldConfiguration.FieldContainerClasses">
) - Outputting the required designator if the field is required (e.g.
@(new HtmlString(Model.FieldMetadata != null && Model.FieldMetadata.IsRequired ? requiredDesignator.ToHtmlString() : ""))
) - Outputting the prepended HTML before the field (e.g.
foreach (var html in Model.FieldConfiguration.PrependedHtml) {@html}
) - Outputting the appended HTML after the field (e.g.
foreach (var html in Model.FieldConfiguration.AppendedHtml) {@html}
) - Outputting the hint, including its ID (e.g.
if (Model.FieldConfigurationModel.FieldConfiguration.Hint != null) {<div class="hint" id="@Model.FieldConfiguration.HintId">@Model.FieldConfiguration.Hint</div>}
)
Obviously, if you never plan on using some of those then you can exclude them, but if you are creating a template that you want other people to use we recommend you include all of those points so people aren't surprised when they try to use the template and it doesn't behave how they expect.
Nested fields
If you don't want to support nested fields then we recommend you throw a NotSupportedException
from the BeginField
and EndField
methods (which are only used for supporting nested fields).
If you do want to support them then it's likely that you will reuse a lot of the same HTML for a normal field and a parent field. The Twitter Bootstrap 3 form template and the Default form template both use a single Field.cshtml
file, but passing in a FieldRenderMode
enum into the template model to switch the relevant output:
public enum FieldRenderMode
{
Field,
BeginField
}
Automated Testing
If you would like to have confidence in the HTML that your template generates then we recommend that you provide HTML Approval Test coverage of the HTML output of your template.
For an example of this see the tests for the built-in form templates.
Applying a global template change
You can often encounter the situation where the DefaultFormTemplate
is mostly sufficient for your needs, but you want to make slight tweaks for example adding classes to all buttons, or changing the required designator. In those instances you can easily create a custom form template to use in your application that simply extends the DefaultFormTemplate
class and overrides the relevant methods - in the above example, you would override the Button
or RequiredDesignator
methods respectively.
Even further to that, we can change the HtmlAttributes
object that is passed into the Button
method and then call base.Button(...)
to keep the original button generation code. For an example of this in action see the Button method
in the Twitter Bootstrap 3 template.
Using a custom form template
See the Using different form templates page.
Contributions
If you create a form template that you believe will be useful to other people then please feel free to send us a pull request and we will consider it for inclusion in the core package.