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

Add the Grid Built-In Functions When Using Grid Row Template

Environment

Product Grid for Blazor

Description

This KB article answers the following questions:

  • How to select rows in the Grid when using a Row Template?
  • How to add a checkbox column in the Grid when using a Row Template? I want to be able to select the row through its checkbox, but also to have the functionality to select all rows from the header of the checkbox column.
  • Do the built-in keyboard options to select a range of rows by clicking the Shift or Ctrl key work when using a Row Template? How to check the checkbox of the row when I select a row by clicking the row?
  • How to add a command column in the Grid when using a Row Template?
  • When using a Row Template, how to prevent the selection of Grid rows when clicking on command buttons?
  • How to implement Grid column resizing, auto-fitting, visibility, locking, and reordering when using a Row Template?

Solution

By default, using the Row Template disables most built-in functionalities of the Grid because the Grid no longer controls its own rendering. This lets you add custom implementations for these features. The example below shows one way to implement functionalities such as row selection (both by clicking on a row and through a checkbox column), column resizing and visibility, editing through command buttons, sorting, and filtering.

Selection

To implement custom selection functionality:

Editing, Sorting, Filtering

The built-in editing, sorting, and filtering will work if the Row Template structure is similar to an actual table and only for the first Grid data model property included in the <td> element, if any.

Command Column

To implement a custom command column:

Column Resizing, Auto-Fitting, Visibility, Locking, Reordering

  • Column resizing and auto-fitting will work if the Row Template structure resembles an actual table row, with a corresponding number of cells matching the Grid columns.
  • Column visibility depends on including a <td> element for the column in the Row Template.
  • To implement column locking, add the k-grid-content-sticky class to the <td> element of the columns that you want locked, and calculate and set the correct left and right CSS properties, as the content inside the Row Template can be any valid HTML.
  • For column reordering, manage the left and right CSS properties on the <td> elements within the Row Template.

Example

Row selection, Column resizing and visibility, Editing, Sorting, and Filtering when using Row Template

<TelerikGrid @ref="@GridRef"
             Data=@GridData
             Pageable="true"
             PageSize="15"
             Sortable="true"
             Resizable="true"
             FilterMode="@GridFilterMode.FilterMenu"
             FilterMenuType="@FilterMenuType.CheckBoxList"
             EditMode="@GridEditMode.Inline"
             SelectionMode="@GridSelectionMode.Multiple"
             SelectedItems="@SelectedItems"
             SelectedItemsChanged="@( (IEnumerable<ArticleDto> newSelected) => SelectedItemsChangedHandler(newSelected) )"
             OnCreate="@OnCreateHandler"
             OnUpdate="@OnUpdateHandler">
    <GridToolBarTemplate>
        <GridCommandButton Command="Add">Add</GridCommandButton>
    </GridToolBarTemplate>
    <RowTemplate Context="article">
        <td>
            <TelerikCheckBox @bind-Value="@article.IsSelected" OnChange="@(() => OnCheckBoxChangeHandler(article.Id))" />
        </td>
        <td>
            <img src="@article.ImageUrl" width="55" height="35" />
        </td>
        <td>
            @{
                if(article.IsSelected)
                {
                    <h2>Selected</h2>
                }
                else
                {
                    <h2>Not Selected</h2>
                }
            }
            @article.Title
        </td>
        <td @onclick:stopPropagation="true">
            <TelerikButton Icon="@SvgIcon.Pencil" OnClick="@(() => OnProgrammaticEditHandler(article.Id))">Programmatic Edit</TelerikButton>
            <TelerikButton Icon="@SvgIcon.Trash" OnClick="@(() => OnProgrammaticDeleteHandler(article.Id))">Programmatic Delete</TelerikButton>
        </td>
    </RowTemplate>
    <GridColumns>
        <GridCheckboxColumn>
            <HeaderTemplate>
                <TelerikCheckBox Value="@SelectAll" ValueChanged="@((bool value) => SelectAllHandler(value))" />
            </HeaderTemplate>
        </GridCheckboxColumn>
        <GridColumn Field=@nameof(ArticleDto.Id) Visible="false" />
        <GridColumn Field=@nameof(ArticleDto.ImageUrl) Title="Image" Editable="false" Resizable="true" />
        <GridColumn Field=@nameof(ArticleDto.Title) Title="Article Title" Resizable="true" />
        <GridCommandColumn>
            <GridCommandButton Icon="SvgIcon.Save" Command="Save" ShowInEdit="true">Update</GridCommandButton>
            <GridCommandButton Icon="SvgIcon.Cancel" Command="Cancel" ShowInEdit="true">Cancel</GridCommandButton>
        </GridCommandColumn>
    </GridColumns>
</TelerikGrid>

@code {
    private TelerikGrid<ArticleDto>? GridRef { get; set; }

    private List<ArticleDto> GridData { get; set; } = new();

    private IEnumerable<ArticleDto> SelectedItems { get; set; } = Enumerable.Empty<ArticleDto>();
    private List<ArticleDto> TempSelectedItemsCollection { get; set; } = new();
    private bool SelectAll { get; set; }

    #region Selection

    private void SelectAllHandler(bool newValue)
    {
        SelectAll = newValue;

        foreach (var item in GridData)
        {
            item.IsSelected = SelectAll;
        }

        // If SelectAll is true, assign all items to SelectedItems, 
        // else set it to an empty list.
        SelectedItems = SelectAll ? new List<ArticleDto>(GridData) : new List<ArticleDto>();
        TempSelectedItemsCollection = SelectAll ? new List<ArticleDto>(GridData) : new List<ArticleDto>();
    }

    protected void SelectedItemsChangedHandler(IEnumerable<ArticleDto> selectedItems)
    {
        foreach (var item in GridData)
        {
            item.IsSelected = false;
        }

        // Use temporary collection to be able to persist the
        // selected items when multiselecting with checkboxes.
        TempSelectedItemsCollection = SelectedItems.ToList();
        SelectedItems = selectedItems;

        foreach (var item in selectedItems)
        {
            item.IsSelected = true;
        }
    }

    private void OnCheckBoxChangeHandler(Guid itemId)
    {
        ArticleDto? currentItem = GridData.FirstOrDefault(a => a.Id == itemId);

        if (currentItem != null)
        {
            if (currentItem.IsSelected)
            {
                TempSelectedItemsCollection.Add(currentItem);
            }
            else
            {
                TempSelectedItemsCollection.Remove(currentItem);
            }
        }

        // The OnChange event fires after the SelectedItemsChanged
        // thus we need to update the SelectedItems collection.
        SelectedItems = TempSelectedItemsCollection;

        foreach (var item in SelectedItems)
        {
            item.IsSelected = true;
        }
    }

    #endregion Selection

    #region Edit

    private void OnCreateHandler(GridCommandEventArgs args)
    {
        var createdItem = (ArticleDto)args.Item;
        createdItem.Id = Guid.NewGuid();
        var rnd = new Random();
        createdItem.ImageUrl = $"https://demos.telerik.com/blazor-ui/images/photos/{rnd.Next(1, 30) % 7 + 1}.jpg";
        GridData.Insert(0, createdItem);
    }

    private async Task OnProgrammaticEditHandler(Guid itemId)
    {
        if (GridData.Any() && GridRef != null)
        {
            var gridState = GridRef.GetState();

            gridState.InsertedItem = null;
            gridState.OriginalEditItem = GridData.Where(x => x.Id == itemId).First();
            gridState.EditItem = GridData.Where(x => x.Id == itemId).First().Clone();
            var rnd = new Random();
            gridState.EditItem.ImageUrl = $"https://demos.telerik.com/blazor-ui/images/photos/{rnd.Next(1, 30) % 7 + 1}.jpg";
            await GridRef.SetStateAsync(gridState);
        }
    }

    private void OnUpdateHandler(GridCommandEventArgs args)
    {
        var updatedItem = (ArticleDto)args.Item;
        var index = GridData.FindIndex(i => i.Id == updatedItem.Id);
        if (index != -1)
        {
            GridData[index] = updatedItem;
        }
    }

    private void OnProgrammaticDeleteHandler(Guid itemId)
    {
        if (GridData.Any() && GridRef != null)
        {
            var itemToDelete = GridData.Where(x => x.Id == itemId).First();
            GridData.Remove(itemToDelete);

            // Remove from SelectedItems collection
            TempSelectedItemsCollection = SelectedItems.ToList();
            TempSelectedItemsCollection.Remove(itemToDelete);
            SelectedItems = TempSelectedItemsCollection;

            GridRef.Rebind();
        }
    }

    #endregion Edit

    #region Data Generation

    private void GetGridData()
    {
        GridData = new List<ArticleDto>();

        for (int i = 1; i <= 30; i++)
        {
            GridData.Add(new ArticleDto
                {
                    Id = Guid.NewGuid(),
                    Title = "Article title " + i,
                    ImageUrl = $"https://demos.telerik.com/blazor-ui/images/photos/{i % 7 + 1}.jpg"
                });
        }
    }

    protected override void OnInitialized()
    {
        GetGridData();
    }

    public class ArticleDto
    {
        public Guid Id { get; set; }
        public string Title { get; set; }
        public string ImageUrl { get; set; }
        public bool IsSelected { get; set; }

        public ArticleDto Clone()
        {
            return new ArticleDto()
                {
                    Id = Id,
                    Title = Title,
                    ImageUrl = ImageUrl
                };
        }
    }

    #endregion DataGeneration

}

See Also

In this article