New to Telerik UI for Blazor? Download free 30-day trial

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 RenderFragments. 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:

  1. Until version 4.0.1 Form items and groups could be mixed with arbitrary content. This worked without explicit intent or support.
  2. 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 reodering.
  3. 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:

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. Form Columns and FormItem ColSpan 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.

Setting named contexts in the Form templates

<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+.

  1. Move all custom components and markup from <FormItems> to <FormItemsTemplate>. The <FormItems> tag should hold only <FormItem> and <FormGroup> tags.
  2. 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.
  3. If using conditional statements to render <FormItem> tags, then define these tags unconditionally. Instead, use conditional logic for <TelerikFormItemRenderer> tags inside <FormItemsTemplate>.
  4. If using <FormItem> instances inside a nested Razor component, then:
    • Move these <FormItem> tags inside the Form <FormItems> tag.
    • Pass the FormItemsTemplate context or a specific IFormItem as a new parameter of the nested component.
    • Use <TelerikFormItemRenderer> inside the nested component. Consume the new IFormItem parameter in the Item parameter of the Renderer.

Comparison between versions up to 4.0.1 and after 4.2.0.

Up to 4.0.1 From 4.2.0

Index.razor


<TelerikForm>
  <FormItems>
    <div>
      content around form items
      <FormItem Field="FieldName1" />
    </div>
    <div>content between items<div>
    <FormItem Field="FieldName2">
      <Template>
        ...
      </Template>
    </FormItem>
    @if (someCondition)
    {
        <FormItem Field="FieldName3" />
    }
    <ChildComponent />
  </FormItems>

























</TelerikForm>

Index.razor


<TelerikForm>
  <FormItems>


    <FormItem Field="FieldName1" />


    <FormItem Field="FieldName2">
      <Template>
        ...
      </Template>
    </FormItem>


    <FormItem Field="FieldName3" />

    <FormItem Field="FieldName4" />
  </FormItems>
  <FormItemsTemplate Context="formContext">
    @{
      var formItems = formContext.Items
        .Cast<IFormItem>().ToList();
    }
    <div>
      content around form items
      <TelerikFormItemRenderer
          Item="@( formItems.First(x =>
            x.Field == "FieldName1") )" />
    </div>
    <div>content between items<div>
    <TelerikFormItemRenderer
      Item="@( formItems.First(x =>
        x.Field == "FieldName2") )" />
    @if (someCondition)
    {
        <TelerikFormItemRenderer
        Item="@( formItems.First(x =>
            x.Field == "FieldName3") )" />
    }
    @{ var formItem4 = formItems.First(x =>
        x.Field == "FieldName4"); }
    <ChildComponent NestedFormItem="@formItem4" />
  </FormItemsTemplate>
</TelerikForm>

ChildComponent.razor



<FormItem Field="FieldName4" />






ChildComponent.razor



<TelerikFormItemRenderer Item="@NestedFormItem" />

@code {
    [Parameter]
    public IFormItem NestedFormItem { get; set; }
}

Examples

The samples below demonstrate a few different use cases:

Also check the Form Templates Online Demo.

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 only IFormItem instances. In this case, there is no need to check the type of each List member, but you will still need to cast the member from IFormItemBase to IFormItem.

Use a loop to render Form groups and items inside a FormItemsTemplate

@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; }
    }
}

Define Form Items One by One

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.

Render defined Form items inside a 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>
        <FormItem Field="@nameof(Person.FirstName)" LabelText="First Name"></FormItem>
        <FormItem Field="@nameof(Person.LastName)" LabelText="Last Name"></FormItem>
        <FormItem Field="@nameof(Person.BirthDate)" LabelText="Date of Birth"></FormItem>
    </FormItems>
    <FormItemsTemplate Context="formContext">
        @{
            var formItems = formContext.Items.Cast<IFormItem>().ToList();
        }

        <p>Text before all form items.</p>
        <div class="form-item-wrapper">
            <TelerikFormItemRenderer Item="@( formItems.First(x => x.Field == nameof(Person.Id)) )" />
        </div>
        <div class="form-item-wrapper">
            <TelerikFormItemRenderer Item="@( formItems[1] )" />
        </div>
        <div class="form-item-wrapper">
            <TelerikFormItemRenderer Item="@( formItems.Skip(2).First() )" />
        </div>
        <div class="form-item-wrapper">
            <TelerikFormItemRenderer Item="@( formItems.First(x => x.Field == nameof(Person.BirthDate)) )" />
        </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; }
    }
}

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>.

Render auto-generated and defined Form items inside a 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; }
    }
}

See Also

In this article