Form Template for All Items
This article describes how to create custom Form layouts with components and HTML markup between or around the Form items. To define a custom editor in a specific Form item, see article Form Item Template.
The content on this page applies to Telerik UI for Blazor version 4.2.0 and above.
Background
The <FormItems>
tag is a standard Blazor RenderFragment
, which expects only Form groups and Form items. The Blazor framework does not support tag restrictions inside RenderFragment
s. As a result, <FormItems>
allows non-Form child components and arbitrary markup. However, such setup with custom markup was never officially supported before version 4.2.0. Here is the relevant Form behavior timeline:
- Until version 4.0.1 Form items and groups could be mixed with arbitrary content. This worked without explicit intent or support.
-
Version 4.1.0 added the ability to combine auto-generated and declared Form items. The feature required changes in the Form implementation. As a result, custom components inside
<FormItems>
caused unexpected Form item reordering. - Version 4.2.0 added official support for custom markup and components inside the Form.
Using Template for All Form Items
Telerik UI for Blazor version 4.2.0 introduced one new Form parameter and two new components that allow mixing Form groups and items with other content, such as HTML markup or Razor components:
Feature | Description |
---|---|
FormItemsTemplate |
A RenderFragment parameter of the TelerikForm component, so it's usually used as a child tag. |
<TelerikFormGroupRenderer> |
A component that renders a specific Form group. It can occur anywhere inside <FormItemsTemplate> . |
<TelerikFormItemRenderer> |
A component that renders a specific Form item. It can occur inside <FormItemsTemplate> or the Template of a <TelerikFormGroupRenderer> . |
The Form setup uses the following architecture:
-
<FormItems>
defines the configuration of all Form groups and items, including the<Template>
s of the form items. -
<FormItemsTemplate>
is the optional rendering container of all groups and items. -
<TelerikFormGroupRenderer>
defines the rendering place of a specific form group. -
<TelerikFormItemRenderer>
defines the rendering place of a specific form item.
As any other template,
FormItemsTemplate
overrides the built-in rendering of Form groups and items. When the<FormItemsTemplate>
tag is used, the Form component will render only groups and items that have their own renderer components. The HTML output inside each form item is unrelated to this feature and is not affected. FormColumns
and FormItemColSpan
may work differently or stop working, depending on the exact template markup. In such cases, stop using the built-in Form columns and achieve the desired layout with custom HTML and CSS.
FormItemsTemplate
The FormItemsTemplate
RenderFragment
provides a context of type FormItemsTemplateContext
. This object exposes an Items
property, which is a generic List<IFormItemBase>
and can contain form groups (IFormGroup
) and form items (IFormItem
).
Be aware of nested form contexts when using TelerikFormGroupRenderer
.
Form Group Renderer
The TelerikFormGroupRenderer
component defines the rendering place of a Form group inside the FormItemsTemplate
. The component has the following parameters:
Parameter | Type | Description |
---|---|---|
Group |
IFormGroup |
A Form group, which is defined in <FormItems> . The IFormGroup interface provides all properties of a <FormGroup> component, so you can access and reuse the group configuration, which is defined in <FormItems> . IFormGroup also implements an Items property, which is a List<IFormItemBase> and provides access to nested Form items. |
Template |
RenderFragment |
The container for the Form group content. The fragment exposes a context , which is an IFormGroup . |
Nested Form Contexts
The TelerikFormGroupRenderer
Template
is nested inside another template - FormItemsTemplate
. Both templates expose a context
, so at least one of these contexts must have a custom name, which is set via the template Context
parameter. Otherwise you will get an error about child content using the same parameter name. Check the the snippet below and the Render Form items in a loop example.
<TelerikForm>
<FormItemsTemplate Context="formContext">
...
<TelerikFormGroupRenderer>
<Template Context="groupContext">
...
</Template>
</TelerikFormGroupRenderer>
...
</FormItemsTemplate>
<TelerikForm>
Form Item Renderer
The TelerikFormItemRenderer
component defines the rendering place of a Form item inside the FormItemsTemplate
. The component has the following parameters:
Parameter | Type | Description |
---|---|---|
Item |
IFormItem |
A Form item, which is auto-generated or defined in <FormItems> . The IFormItem interface provides all properties of a <FormItem> component, so you can access and reuse the item configuration, which is defined in <FormItems> . |
TelerikFormItemRenderer
is unrelated to the template of a single<FormItem>
. The two features are used separately and independently.
Upgrading from Older Versions
Here are the steps to migrate custom content inside the Form from version 4.0.1- to 4.2.0+.
- Move all custom components and markup from
<FormItems>
to<FormItemsTemplate>
. The<FormItems>
tag should hold only<FormItem>
and<FormGroup>
tags. - Inside
<FormItemsTemplate>
, define a<TelerikFormGroupRenderer>
and<TelerikFormItemRenderer>
for each group and item that exists in<FormItems>
. Place each Renderer tag at the desired place. This steps also applies to<FormAutoGeneratedItems />
, which need Renderers too. - If using conditional statements to render
<FormItem>
tags, then define these tags unconditionally. Instead, use conditional logic for<TelerikFormItemRenderer>
tags inside<FormItemsTemplate>
. - If using
<FormItem>
instances inside a nested Razor component, then see the linked KB article and:- Move these
<FormItem>
tags inside the Form<FormItems>
tag. - Pass the
FormItemsTemplate
context
or a specificIFormItem
as a new parameter of the nested component. - Use
<TelerikFormItemRenderer>
inside the nested component. Consume the newIFormItem
parameter in theItem
parameter of the Renderer.
- Move these
Up to 4.0.1 | From 4.2.0 |
---|---|
Index.razor
|
Index.razor
|
ChildComponent.razor
|
ChildComponent.razor
|
Examples
The samples below demonstrate a few different use cases:
- Render Form groups and items in a loop
- Render Form items one by one or conditionally
- Combine autogenerated and defined Form items
Also check the following examples on other pages:
Render Form Groups and Items in a Loop
This approach is suitable for scenarios where the custom Form layout can accommodate a random number of groups or items.
If the Form has no groups, then the
formContext.Items
List
will contains onlyIFormItem
instances. In this case, there is no need to check the type of eachList
member, but you will still need to cast the member fromIFormItemBase
toIFormItem
.
@using System.ComponentModel.DataAnnotations
<TelerikForm Model="@Employee"
Width="600px">
<FormValidation>
<DataAnnotationsValidator></DataAnnotationsValidator>
<TelerikValidationSummary />
</FormValidation>
<FormItems>
<FormGroup LabelText="Disabled Section">
<FormItem Field="@nameof(Person.Id)" Enabled="false"></FormItem>
</FormGroup>
<FormGroup LabelText="Names">
<FormItem Field="@nameof(Person.FirstName)" LabelText="First Name"></FormItem>
<FormItem Field="@nameof(Person.LastName)" LabelText="Last Name"></FormItem>
</FormGroup>
<FormItem Field="@nameof(Person.BirthDate)" LabelText="Date of Birth"></FormItem>
</FormItems>
<FormItemsTemplate Context="formContext">
<p>Text before all form groups</p>
@foreach (IFormItemBase item in formContext.Items)
{
if (item is IFormGroup) // only if using FormGroups
{
var groupItem = (IFormGroup)item;
<TelerikFormGroupRenderer Group="@groupItem">
<Template Context="groupContext">
<div class="form-group-wrapper">
<h3>Group "@groupItem.LabelText"</h3>
@foreach (IFormItem singleItem in groupContext.Items)
{
<div class="form-item-wrapper">
<TelerikFormItemRenderer Item="@singleItem" />
</div>
}
</div>
</Template>
</TelerikFormGroupRenderer>
}
else
{
<div class="form-item-wrapper">
<TelerikFormItemRenderer Item="@(item as IFormItem)" />
</div>
}
}
<p>Text after all form groups</p>
</FormItemsTemplate>
</TelerikForm>
<style>
.form-group-wrapper {
border: 1px solid #000;
margin: .6em;
padding: .6em;
background: #ccf;
}
.form-item-wrapper {
border: 1px dashed #999;
margin: .4em;
padding: .4em;
background: #ffc;
}
.form-item-wrapper:nth-child(even) {
background: #feb;
}
</style>
@code {
private Person Employee = new Person();
protected override void OnInitialized()
{
Employee = new Person()
{
Id = 1,
FirstName = "John",
LastName = "Doe",
BirthDate = DateTime.Today.AddYears(-30)
};
base.OnInitialized();
}
public class Person
{
[Editable(false)]
public int Id { get; set; }
[Required]
[MaxLength(24)]
public string FirstName { get; set; }
[Required]
[MaxLength(24)]
public string LastName { get; set; }
[Required]
public DateTime BirthDate { get; set; }
}
}
Render Form Items One by One or Conditionally
This approach is suitable for scenarios where the application positions each Form item (or group) at a specific place in the FormItemsTemplate
.
FormItemsTemplateContext.Items
is a generic List<IFormItemBase>
, so there are multiple different ways to get each form item (or group) via standard methods. The example below shows a few alternatives - First()
, Skip()
, and index.
The sample also demonstrates how to display Form items conditionally (in this case, DriversLicense
), depending on the value of another Form field (BirthDate
). Note the usage of a Form item Template
for BirthDate
, which enables the Form to re-render and toggle the DriversLicense
checkbox. See UI rendering and performance optimization inside the Form for more ways to refresh the Form component.
@using System.ComponentModel.DataAnnotations
<TelerikForm Model="@Employee"
Width="600px">
<FormValidation>
<DataAnnotationsValidator></DataAnnotationsValidator>
<TelerikValidationSummary />
</FormValidation>
<FormItems>
<FormItem Field="@nameof(Person.Id)" Enabled="false" Id="IdItem"></FormItem>
<FormItem Field="@nameof(Person.FirstName)" LabelText="First Name"></FormItem>
<FormItem Field="@nameof(Person.LastName)" LabelText="Last Name" Id="last-name-item"></FormItem>
<FormItem Field="@nameof(Person.BirthDate)">
<Template>
<label for="birth-date" class="k-label k-form-label">
Date of Birth
(Age toggles the Drivers License checkbox)
</label>
<div class="k-form-field-wrap">
<TelerikDatePicker @bind-Value="@Employee.BirthDate"
Format="d"
Id="birth-date" />
</div>
</Template>
</FormItem>
<FormItem Field="@nameof(Person.DriversLicense)" LabelText="Driver's License"></FormItem>
</FormItems>
<FormItemsTemplate Context="formContext">
@{
var formItems = formContext.Items.Cast<IFormItem>().ToList();
}
<p>Text before all form items.</p>
<div class="form-item-wrapper">
@* Get Form item by Field *@
<TelerikFormItemRenderer Item="@( formItems.First(x => x.Field == nameof(Person.Id)) )" />
</div>
<div class="form-item-wrapper">
@* Get Form item by index *@
<TelerikFormItemRenderer Item="@( formItems[1] )" />
</div>
<div class="form-item-wrapper">
@* Get Form item by Id *@
<TelerikFormItemRenderer Item="@( formItems.First(x => x.Id == "last-name-item") )" />
</div>
<div class="form-item-wrapper">
<TelerikFormItemRenderer Item="@( formItems.Skip(3).First() )" />
</div>
@* Render Form item conditionally *@
@if (Employee.BirthDate < DateTime.Today.AddYears(-18))
{
<div class="form-item-wrapper">
<TelerikFormItemRenderer Item="@( formItems.First(x => x.Field == nameof(Person.DriversLicense)) )" />
</div>
}
</FormItemsTemplate>
</TelerikForm>
<style>
.form-group-wrapper {
border: 1px solid #000;
margin: .6em;
padding: .6em;
background: #ccf;
}
.form-item-wrapper {
border: 1px dashed #999;
margin: .4em;
padding: .4em;
background: #ffc;
}
.form-item-wrapper:nth-child(even) {
background: #feb;
}
</style>
@code {
private Person Employee = new Person();
protected override void OnInitialized()
{
Employee = new Person()
{
Id = 1,
FirstName = "John",
LastName = "Doe",
BirthDate = DateTime.Today.AddYears(-30)
};
base.OnInitialized();
}
public class Person
{
[Editable(false)]
public int Id { get; set; }
[Required]
[MaxLength(24)]
public string FirstName { get; set; } = string.Empty;
[Required]
[MaxLength(24)]
public string LastName { get; set; } = string.Empty;
[Required]
public DateTime BirthDate { get; set; } = DateTime.Today;
public bool DriversLicense { get; set; }
}
}
Combine Autogenerated and Defined Form Items
If used, the optional <FormAutoGeneratedItems />
tag belongs to <FormItems>
. Then, get and render the auto-generated form items inside <FormItemsTemplate>
in the same way as explicitly defined <FormItem>
instances.
When using only auto-generated Form items with a custom Form layout, you can remove the whole <FormItems>
tag and only use <FormItemsTemplate>
.
@using System.ComponentModel.DataAnnotations
<TelerikForm Model="@Employee"
Width="600px">
<FormValidation>
<DataAnnotationsValidator></DataAnnotationsValidator>
<TelerikValidationSummary />
</FormValidation>
<FormItems>
<FormItem Field="@nameof(Person.Id)" Enabled="false" Id="IdItem"></FormItem>
<FormAutoGeneratedItems />
</FormItems>
<FormItemsTemplate Context="formContext">
@{
var formItems = formContext.Items.Cast<IFormItem>().ToList();
foreach (IFormItem formItem in formItems)
{
<div class="form-item-wrapper">
<TelerikFormItemRenderer Item="@formItem" />
</div>
}
}
</FormItemsTemplate>
</TelerikForm>
<style>
.form-group-wrapper {
border: 1px solid #000;
margin: .6em;
padding: .6em;
background: #ccf;
}
.form-item-wrapper {
border: 1px dashed #999;
margin: .4em;
padding: .4em;
background: #ffc;
}
.form-item-wrapper:nth-child(even) {
background: #feb;
}
</style>
@code {
private Person Employee = new Person();
protected override void OnInitialized()
{
Employee = new Person()
{
Id = 1,
FirstName = "John",
LastName = "Doe",
BirthDate = DateTime.Today.AddYears(-30)
};
base.OnInitialized();
}
public class Person
{
[Editable(false)]
public int Id { get; set; }
[Required]
[MaxLength(24)]
public string FirstName { get; set; }
[Required]
[MaxLength(24)]
public string LastName { get; set; }
[Required]
public DateTime BirthDate { get; set; }
}
}