New to Telerik UI for BlazorStart a free 30-day trial

In-Place Editor Component

Environment

ProductUI for Blazor

Description

This KB article demonstrates and describes how to create a custom InPlaceEditor component. The article also answers the following questions:

  • How to create an in-place editor, which looks like text when in read mode and switches to an input component when editable?
  • How to toggle between text content and an editor to allow users to edit something in place?

Solution

The sample below uses an algorithm which toggles between read-only UI and an editable component on user click and blur.

How It Works

  • InPlaceEditor is a generic component. It supports strings and most value types, including nullable types.
  • Initially, the component renders a clickable Button with Clear FillMode that shows the current Value.
  • The component detects the type of its Value and renders the appropriate Telerik editor:
  • If the Width parameter is not set, the In-Place Editor approximately matches the width of its editor components to the current Value length. The component uses a monospace font-family to make this easier.
  • The component features a ReadOnly mode that controls the editability, for example, depending on user permissions.
  • The DisplayFormat parameter affects the Value consistently in both read mode and edit mode.
  • The Placeholder parameter provides a helper label that will show when the Value is null or empty.
  • The ShowIcons parameter controls the visibility of optional SVG Icons. The icons hint users about the ability to edit the component Value or provide clickable Save and Cancel commands in edit mode. The parameter is of type InPlaceEditorShowIcons, which is a custom enum and must be imported in both InPlaceEditor.razor and all .razor files that use InPlaceEditor.
  • The Class parameter allows you to apply custom styles.
  • The Title parameter allows you to show a tooltip hint on read mode.
  • To see invalid state styling and validation messages in Forms, pass the respective ValueExpression values to the InPlaceEditor component.
  • InPlaceEditor.razor.css is a CSS isolation file. It depends on a YourAppName.styles.css file in App.razor to load.

Example

The features and business logic below can be subject to additional customizations and enhancements.

To run the code successfully:

  • Replace YourAppName with the actual root namespace of your app.
  • Make sure your app supports CSS isolation and loads a YourAppName.styles.css file. Browser caching of this file can prevent the InPlaceEditor styles from showing.
RAZOR
@* import InPlaceEditorType enum *@
@using YourAppName.Models

@using System.ComponentModel.DataAnnotations

<h1>InPlaceEditor Component</h1>

<p>
    This in-place editor component works with strings and value types, including nullables, for example:

    <InPlaceEditor @bind-Value="@NumericValue"
                   DisplayFormat="C2"
                   Placeholder="Enter Number..." />

    The component supports custom styles and responsive textbox width that depends on the value:

    <InPlaceEditor @bind-Value="@StringValue"
                   Class="primary-color"
                   ShowIcons="@InPlaceEditorShowIcons.Hover" />

    The icon can be visible only on hover:

    <InPlaceEditor @bind-Value="@DateValue"
                   Class="primary-color"
                   DisplayFormat="d"
                   ShowIcons="@InPlaceEditorShowIcons.Hover" />

    (unless the value is empty) or never:

    <InPlaceEditor @bind-Value="@TimeValue"
                   Class="primary-color"
                   DisplayFormat="HH:mm"
                   ShowIcons="@InPlaceEditorShowIcons.Never" />

    You can even edit booleans:

    <InPlaceEditor @bind-Value="@BoolValue"
                   Class="primary-color" />
</p>

<h2>Configuration</h2>

<ul>
    <li>
        <label for="editor-placeholder">Placeholder: </label>
        <TelerikTextBox @bind-Value="@InPlaceEditorPlaceholder"
                        Id="editor-placeholder"
                        ShowClearButton="true"
                        Width="180px" />
    </li>
    <li><label><TelerikCheckBox @bind-Value="@InPlaceEditorReadOnly" />  Read Only</label></li>
    <li>
        <span>Show Icon: </span>
        <TelerikButtonGroup SelectionMode="@ButtonGroupSelectionMode.Single">
            <ButtonGroupToggleButton Selected="@( InPlaceEditorShowIcons == InPlaceEditorShowIcons.Always )"
                                     OnClick="@( () => InPlaceEditorShowIcons = InPlaceEditorShowIcons.Always )">
                Always
            </ButtonGroupToggleButton>
            <ButtonGroupToggleButton Selected="@( InPlaceEditorShowIcons == InPlaceEditorShowIcons.Hover )"
                                     OnClick="@( () => InPlaceEditorShowIcons = InPlaceEditorShowIcons.Hover )">
                Hover
            </ButtonGroupToggleButton>
            <ButtonGroupToggleButton Selected="@( InPlaceEditorShowIcons == InPlaceEditorShowIcons.Never )"
                                     OnClick="@( () => InPlaceEditorShowIcons = InPlaceEditorShowIcons.Never )">
                Never
            </ButtonGroupToggleButton>
        </TelerikButtonGroup>
    </li>
    <li>
        <label for="editor-title">Title: </label>
        <TelerikTextBox @bind-Value="@InPlaceEditorTitle"
                        Id="editor-title"
                        ShowClearButton="true"
                        Width="180px" />
    </li>
    <li>
        <label for="editor-width">Editor Width: </label>
        <TelerikNumericTextBox @bind-Value="@InPlaceEditorWidth"
                               Format="# px"
                               Id="editor-width"
                               Width="120px" />
    </li>
</ul>

<p>
    In Place Editor:
    <InPlaceEditor @bind-Value="@InPlaceEditorValue"
                   Class="primary-color"
                   Placeholder="@InPlaceEditorPlaceholder"
                   ReadOnly="@InPlaceEditorReadOnly"
                   ShowIcons="@InPlaceEditorShowIcons"
                   Title="@InPlaceEditorTitle"
                   Width="@( InPlaceEditorWidth.HasValue ? $"{InPlaceEditorWidth}px" : null )" />
</p>

<h2>Form Validation</h2>

<TelerikForm Model="@Employee">
    <FormValidation>
        <DataAnnotationsValidator />
    </FormValidation>
    <FormItems>
        <FormItem Field="@nameof(Person.Name)">
            <Template>
                Name:
                <InPlaceEditor Value="@Employee.Name"
                               ValueChanged="@( (string newValue) => Employee.Name = newValue )"
                               ValueExpression="@( () => Employee.Name )"
                               Placeholder="Enter Name..." />
                <TelerikValidationMessage For="@( () => Employee.Name )" />
            </Template>
        </FormItem>
        <FormItem Field="@nameof(Person.BirthDate)">
            <Template>
                Hire Date:
                <InPlaceEditor Value="@Employee.BirthDate"
                               ValueChanged="@( (DateTime? newValue) => Employee.BirthDate = newValue )"
                               ValueExpression="@( () => Employee.BirthDate )"
                               DisplayFormat="d"
                               Placeholder="Enter Date..."
                               T="@(DateTime?)" />
                <TelerikValidationMessage For="@( () => Employee.BirthDate )" />
            </Template>
        </FormItem>
    </FormItems>
</TelerikForm>

<style>
    h1 {
        font-size: 1.5rem;
    }

    h2 {
        font-size: 1.2rem;
    }

    .primary-color {
        color: var(--kendo-color-primary);
    }
</style>

@code {
    private bool BoolValue { get; set; }
    private DateTime? DateValue { get; set; } = DateTime.Now;
    private decimal? NumericValue { get; set; } = 1.23m;
    private string StringValue { get; set; } = "foo bar";
    private TimeOnly TimeValue { get; set; } = TimeOnly.FromDateTime(DateTime.Now);

    private string InPlaceEditorPlaceholder { get; set; } = "Enter Value...";
    private bool InPlaceEditorReadOnly { get; set; }
    private InPlaceEditorShowIcons InPlaceEditorShowIcons { get; set; } = InPlaceEditorShowIcons.Always;
    private string InPlaceEditorTitle { get; set; } = "Edit Sample Value";
    private string InPlaceEditorValue { get; set; } = "foo bar";
    private int? InPlaceEditorWidth { get; set; } = 120;

    private Person Employee { get; set; } = new();

    public class Person
    {
        [Required]
        public string? Name { get; set; } = string.Empty;
        [Required]
        public DateTime? BirthDate { get; set; }
    }
}

See Also