The Field is a single data collection unit; you create a Field by using the <field>
tag helper, calling the Field
method on a Section or otherwise instantiating and outputting to the page a Field<TModel>
.
You can also create a parent field that can have child fields nested within it by instantiating nesting things within a non-self-closing <field>
tag helper or creating a Field<TModel>
within a using
block (the start and end of the using
block will output the start and end HTML for the Field and the contents of the using
block will output the child Fields).
The Field<TModel>
class is defined as follows in the ChameleonForms.Component
namespace:
/// <summary>
/// Wraps the output of a single form field.
/// </summary>
/// <typeparam name="TModel">The view model type for the current view</typeparam>
public class Field<TModel> : FormComponent<TModel>
{
/// <summary>
/// Creates a form field.
/// </summary>
/// <param name="form">The form the field is being created in</param>
/// <param name="isParent">Whether or not the field has other fields nested within it</param>
/// <param name="fieldGenerator">A field HTML generator class</param>
/// <param name="config">The configuration values for the field</param>
public Field(IForm<TModel> form, bool isParent, IFieldGenerator fieldGenerator, IFieldConfiguration config)
: base(form, !isParent) {...}
...
}
The HTML for a Field is generated via the Field
method in the form template (or BeginField
and EndField
for the start and end HTML for a parent field).
A Field consists of 8 sub-components:
- Field Element - The HTML that makes up the control(s) to accept data from the user
- Field Label - Text that describes a Field Element to a user (and is linked to that Field Element)
- Field Validation HTML - Markup that acts as a placeholder to display any validation messages for a particular Field Element
- Field Configuration - The configuration for a particular Field, Field Element and/or Field Label
- Hint - Any hint text that is specified against the field
- Required designator - A visual designator to indicate that the field is required
- Prepended and appended HTML - Any prepended or appended HTML specified against the field to be added before / after the Field Element
- Field container - The containing element surrounding the Field Element and other relevant parts of the field
The form template determines how to lay out these sub-components.
If you want to define your own HTML for the Field Element, Field Label and Field Validation HTML then you can do so by using the manual
attribute on the <field>
tag helper and nest <manual-element>
, <manual-label>
and <manual-validation>
elements to specify the Field Element, Field Label and Field Validation HTML, e.g.:
<form-section heading="Title">
<field manual>
<manual-element><strong>Element</strong></manual-element>
<manual-label><strong>Label</strong></manual-label>
<manual-validation><strong>validation</strong></manual-validation>
</field>
Or, if you want to specify the optional model metadata, valid state and field configuration:
@inject ICompositeMetadataDetailsProvider MetadataDetailsProvider
...
<form-section heading="Title">
<field manual model-metadata="new DefaultModelMetadataProvider(MetadataDetailsProvider).GetMetadataForType(typeof(int))" is-valid="true" append="After Element" prepend="Before Element">
<manual-element><strong>Element</strong></manual-element>
<manual-label><strong>Label</strong></manual-label>
<manual-validation><strong>validation</strong></manual-validation>
</field>
</form-section>
Note: the way that the manual field is implemented allows for quite a bit of extensibility by defining custom tag helpers that define the various parts of your field. See the implementations for <manual-element>
etc. for an idea of how to do that.
If you want to define your own HTML for the Field Element, Field Label and Field Validation HTML then you can do so by using the Field
method on the Section, e.g.:
@using (var s = f.BeginSection("Title")) {
@s.Field(new HtmlString("label"), new HtmlString("element")).ChainFieldConfigurationMethodsHere()
@* Or, if you want to specify all the possible values: *@
@s.Field(new HtmlString("label"), new HtmlString("element"), new HtmlString("validation"), new ModelMetadata(...), isValid: false).ChainFieldConfigurationMethodsHere()
}
The Field
method on the Section looks like this:
/// <summary>
/// Outputs a field with passed in HTML.
/// </summary>
/// <param name="labelHtml">The HTML for the label part of the field</param>
/// <param name="elementHtml">The HTML for the field element part of the field</param>
/// <param name="validationHtml">The HTML for the validation markup part of the field</param>
/// <param name="metadata">Any field metadata</param>
/// <param name="isValid">Whether or not the field is valid</param>
/// <param name="fieldConfiguration">Optional field configuration</param>
/// <returns>A field configuration that can be used to output the field as well as configure it fluently</returns>
IFieldConfiguration Field(IHtmlContent labelHtml, IHtmlContent elementHtml, IHtmlContent validationHtml = null,
ModelMetadata metadata = null, bool isValid = true, IFieldConfiguration fieldConfiguration = null);
If you would like ChameleonForms to use a Field Generator to generate the HTML for the Field Element, Field Label and Field Validation HTML from a field on the model then you can use the <field>
tag helper, e.g.:
<form-section heading="Title">
<field for="FieldOnTheModel" />
@* and you can add field configuration: *@
<field for="FieldOnTheModel" placeholder="Placeholder text" ... />
<field for="FieldOnTheModel" fluent-config='c => c.ChainFieldConfigurationMethodsHere()' />
</form-section>
If you would like ChameleonForms to use a Field Generator to generate the HTML for the Field Element, Field Label and Field Validation HTML from a field on the model then you can use the FieldFor
extension method on the Section, e.g.:
@using (var s = f.BeginSection("Title")) {
@s.FieldFor(m => m.FieldOnTheModel).ChainFieldConfigurationMethodsHere()
}
The FieldFor
extension method looks like this:
/// <summary>
/// Creates a single form field as a child of a form section.
/// </summary>
/// <example>
/// @s.FieldFor(m => m.FirstName)
/// </example>
/// <typeparam name="TModel">The view model type for the current view</typeparam>
/// <typeparam name="T">The type of the field being generated</typeparam>
/// <param name="section">The section the field is being created in</param>
/// <param name="property">A lamdba expression to identify the field to render the field for</param>
/// <returns>A field configuration object that allows you to configure the field</returns>
public static IFieldConfiguration FieldFor<TModel, T>(this ISection<TModel> section, Expression<Func<TModel, T>> property)
{
var fc = new FieldConfiguration();
new Field<TModel>(section.Form, false, section.Form.GetFieldGenerator(property), fc);
return fc;
}
If you want to use a Field Generator and want to nest child Fields under a Field then you can use a non self-closing <field>
tag helper, e.g.:
<form-section heading="Title">
<field for="FieldOnTheModel">
@* Child Fields *@
</field>
</form-section>
If you want to use a Field Generator and want to nest child Fields under a Field then you can use the BeginFieldFor
extension method on the Section (optionally with a Field Configuration), e.g.:
@using (var s = f.BeginSection("Title")) {
using (var ff = s.BeginFieldFor(m => m.FieldOnTheModel, Field.Configure().ChainFieldConfigurationMethodsHere())) {
@* Child Fields *@
}
}
The BeginFieldFor
extension method looks like this:
/// <summary>
/// Creates a single form field as a child of a form section that can have other form fields nested within it.
/// </summary>
/// <example>
/// @using (var f = s.BeginFieldFor(m => m.Company)) {
/// @f.FieldFor(m => m.PositionTitle)
/// }
/// </example>
/// <typeparam name="TModel">The view model type for the current view</typeparam>
/// <typeparam name="T">The type of the field being generated</typeparam>
/// <param name="section">The section the field is being created in</param>
/// <param name="property">A lamdba expression to identify the field to render the field for</param>
/// <param name="config">Any configuration information for the field</param>
/// <returns>The form field</returns>
public static Field<TModel> BeginFieldFor<TModel, T>(this ISection<TModel> section, Expression<Func<TModel, T>> property, IFieldConfiguration config = null)
{
return new Field<TModel>(section.Form, true, section.Form.GetFieldGenerator(property), config);
}
If you want to use a Field Generator to create nested Fields under a parent Field then you can nest <field>
tag helpers within the parent <field>
, e.g.:
<field for="FieldOnTheModel">
<field for="ChildField" />
</field>
If you want to use a Field Generator to create nested Fields under a parent Field then you can use the BeginFieldFor
extension method on the Field (with an optional Field Configuration), e.g.:
@using (var ff = s.BeginFieldFor(m => m.FieldOnTheModel)) {
@ff.FieldFor(m => m.ChildField).ChainFieldConfigurationMethodsHere()
}
The FieldFor
extension method looks like this:
/// <summary>
/// Creates a single form field as a child of another form field.
/// </summary>
/// <example>
/// @using (var f = s.BeginFieldFor(m => m.Company)) {
/// @f.FieldFor(m => m.PositionTitle)
/// }
/// </example>
/// <typeparam name="TModel">The view model type for the current view</typeparam>
/// <typeparam name="T">The type of the field being generated</typeparam>
/// <param name="field">The parent field the field is being created in</param>
/// <param name="property">A lamdba expression to identify the field to render the field for</param>
/// <returns>A field configuration object that allows you to configure the field</returns>
public static IFieldConfiguration FieldFor<TModel, T>(this Field<TModel> field, Expression<Func<TModel, T>> property)
{
var config = new FieldConfiguration();
new Field<TModel>(field.Form, false, field.Form.GetFieldGenerator(property), config);
return config;
}
<dt>%labelHtml% %requiredDesignator%</dt>
<dd class="%fieldContainerClasses%">
%prependedHtml% %fieldElement% %appendedHtml% %hint% %validationHtml%
</dd>
The %requiredDesignator%
is shown if the field is required:
<em class="required">*</em>
If you want to override the required designator look at Creating custom form templates > Field.
The %hint%
is shown if a hint is specified in the Field Configuration:
<div class="hint" id="%fieldId%--Hint">%hint%</div>
If a hint is added then an aria-describedby="%fieldId%--Hint"
attribute value will automatically be added to the field element to improve accessibility.
<dt>%labelHtml% %requiredDesignator%</dt>
<dd class="%fieldContainerClasses%">
%prependedHtml% %fieldElement% %appendedHtml% %hint% %validationHtml%
<dl>
</dl>
</dd>
<div class="form-group%if !isValid% has-error%endif%%fieldContainerClasses%">
%if withoutLabel%<span class="control-label">%endif%
%labelHtml class="control-label"%
%if withoutLabel%</span>%endif%
%if isInputGroup or isRequired%
<div class="input-group">
%endif%
%prependedHtml%
%fieldElement class="form-control"%
%appendedHtml%
%if isRequired%
<div class="input-group-addon required">%requiredDesignator%</div>
%endif%
%if isInputGroup or isRequired%
</div>
%endif%
%hint%
%validationHtml class="help-block"%
</div>
<div class="checkbox%if !isValid% has-error%endif%%fieldContainerClasses%">
%prependedHtml%
%fieldElement%
%requiredDesignator%
%appendedHtml%
%hint%
%validationHtml class="help-block"%
</div>
<div class="form-group%if !isValid% has-error%endif%%fieldContainerClasses%">
<span class="control-label">
%labelHtml% %requiredDesignator%
</span>
%prependedHtml%
%fieldElement%
%appendedHtml%
%hint%
%validationHtml class="help-block"%
</div>
<div class="form-group%if !isValid% has-error%endif%%fieldContainerClasses%">
%if withoutLabel%<span class="control-label">%endif%
%labelHtml% %requiredDesignator%
%if withoutLabel%</span>%endif%
%prependedHtml%
%fieldElement%
%appendedHtml%
%hint%
%validationHtml class="help-block"%
</div>
The %requiredDesignator%
is shown if the field is required:
<em class="required" title="Required">∗</em>
The %hint%
is shown if a hint is specified in the Field Configuration:
<div class="help-block form-hint" id="%fieldId%--Hint">%hint</div>
If the Field Element is within an input group then the prepended and appended HTML will be wrapped in the following:
<div class="input-group-addon">%html%</div>
A field is in an input group if:
- The field is an input (except checkbox and file upload), textarea or select control and:
- The field is required (since the Form Template appends the required designator as an input group add-on); or
- You use the
AsInputGroup
extension method from theChameleonForms.Templates.TwitterBootstrap3
namespace on theIFieldConfiguration
In all other situations you will manually need to add wrapping HTML with the relevant classes (e.g. using Append
and Prepend
on the Field Configuration).
As an example of what you can do with the input group consider the following:
<field for="Dollars" fluent-config='c => c.AsInputGroup()' prepend="$" append=".00" />
@s.FieldFor(m => m.Dollars).AsInputGroup().Prepend("$").Append(".00")
This will render like this:
In order to be able to swap out the extension method usage across your application easily if you change your form template we recommend that rather than adding a using statement to ChameleonForms.Templates.TwitterBootstrap3
for each view that has a form using the extension method you instead add the namespace to your _ViewImports.cshtml
file.
Begin HTML
The HTML is the same as the Field HTML specified above, but the last </div>
is replaced with:
<div class="row nested-fields">
<div class="col-xs-1"></div>
<div class="col-xs-11">
End HTML
</div>
</div>
</div>