Issue with RadzenDataGrid custom filters: no events to handle

Hello all.

Please, help me to achieve the following requested behavior.

Task: RadzenDataGrid has "Multiple" selection mode and different types of filters. When user changes filters, filtered out rows should disappear from the selection.

Issue 1: When custom filter value is changed, there is no way to raise Grid's "Filter" (or similar) event and process it (see "CompanyName 2" below).

Issue 2: Grid.Reset works differently for different types of filters, including raising of unhandled exception (see "CompanyName 2" and "Title Of Courtesy 2" below).

Proposed solution for Issue1: Define new or reuse "Filter" event callback on RadzenDataGrid which will be called AFTER ALL the filters (including custom) applied their logic to the data source.

Proposed solution for Issue2: Define new event callback on RadzenDataGrid which will be called AFTER Grid.Reset is called, so custom filters can be reset also.

Proposed feature request: Define new "FilterData" property on the column which can be used to define a source list of filter values and which (with existing FilterOperator) will be used to render a RadzenDropDown with different "Multiple" modes (e.g. Multiple=true when FilterOperator=Contains).

I've prepared a small example below to display the differences in behavior of different types of filters.

The base is taken from Blazor DataGrid custom filtering with small adjustments.

@page "/start"

<h1>DataGrid custom Column FilterTemplate</h1>

<p>This page demonstrates how to define custom DataGrid column filter template.</p>

    @if (employees == null)
    {
        <p><em>Loading...</em></p>
    }
    else
    {
        <h3>Custom filtering template with IQueryable binding</h3>
      <RadzenDataGrid @ref="grid" Data=@employees 
                      AllowRowSelectOnRowClick="false"
                      @bind-Value=@SelectedRows
                      SelectionMode="DataGridSelectionMode.Multiple"
                      Filter="@OnGridFilters"
                      FilterMode="FilterMode.Simple" AllowFiltering="true" AllowPaging="true" TItem="Employee" ColumnWidth="200px">
           <Columns>
             <RadzenDataGridColumn TItem="Employee" Width="40px" Filterable="false">
               <HeaderTemplate>
                 <RadzenCheckBox TriState="true" TValue="bool?"
                                 Value="@GetSelectedTriState()"
                                 Change="@SelectAllVisibleRows"/>
               </HeaderTemplate>
               <Template Context="row">
                 <RadzenCheckBox TriState="false" Value="@IsRowSelected(row)"
                                 TValue="bool" Change=@(_ => { grid.SelectRow(row); })/>
               </Template>
               <FooterTemplate>
                 @SelectedCount()
               </FooterTemplate>
             </RadzenDataGridColumn>
             <RadzenDataGridColumn TItem="Employee" Property="ID" Title="ID"/>
             <RadzenDataGridColumn TItem="Employee" Property="CompanyName" Title="CompanyName 1"/>
             <RadzenDataGridColumn TItem="Employee" Property="CompanyName" Title="CompanyName 2" Type="typeof(IEnumerable<string>)"
                                   FilterValue="@selectedCompanyNames" FilterOperator="FilterOperator.Contains" LogicalFilterOperator="LogicalFilterOperator.Or">
               <FilterTemplate>
                 <RadzenDropDown @bind-Value=@selectedCompanyNames Style="width: 100%;"
                                 Change=@OnSelectedCompanyNamesChange Data="@(companyNames)" AllowClear="true" Multiple="true"/>
               </FilterTemplate>
             </RadzenDataGridColumn>
             <RadzenDataGridColumn TItem="Employee" Property="TitleOfCourtesy" Title="Title Of Courtesy 1"/>
             <RadzenDataGridColumn TItem="Employee" Property="TitleOfCourtesy" Title="Title Of Courtesy 2"
                                   FilterValue="@currentTOC">
               <FilterTemplate>
                 <RadzenDropDown @bind-Value="@currentTOC" TextProperty="Text" ValueProperty="Value" Style="width: 100%;"
                                 Change=@OnSelectedTOCChange
                                 Data="@(Enum.GetValues(typeof(TitleOfCourtesy)).Cast<TitleOfCourtesy?>().Select(t => new { Text = $"{t}", Value = t == TitleOfCourtesy.All ? null : t }))"/>
               </FilterTemplate>
             </RadzenDataGridColumn>
           </Columns>
        </RadzenDataGrid>
    }

<RadzenButton Click="@Reset">Reset</RadzenButton>

@code {
    RadzenDataGrid<Employee> grid;

    TitleOfCourtesy? currentTOC;
    IEnumerable<string> selectedCompanyNames;

    List<string> companyNames = new List<string> {"Vins et alcools Chevalier", "Toms Spezialitäten", "Hanari Carnes", "Richter Supermarkt", "Wellington Importadora", "Centro comercial Moctezuma" };

    public enum TitleOfCourtesy
    {
        Ms,
        Mr,
        All = -1
    }

    public class Employee
    {
        public int ID { get; set; }
        public string CompanyName { get; set; }
        public TitleOfCourtesy TitleOfCourtesy { get; set; }

      protected bool Equals(Employee other)
      {
        return ID == other.ID;
      }

      public override bool Equals(object obj)
      {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((Employee)obj);
      }

      public override int GetHashCode()
      {
        return ID;
      }

      public static bool operator ==(Employee left, Employee right)
      {
        return Equals(left, right);
      }

      public static bool operator !=(Employee left, Employee right)
      {
        return !Equals(left, right);
      }
    }

    void OnSelectedCompanyNamesChange(object value)
    {
        if (selectedCompanyNames != null && !selectedCompanyNames.Any())
        {
            selectedCompanyNames = null;  
        }

      UpdateSelectedRows();
    }

    void OnSelectedTOCChange(object value)
    {
        if (currentTOC == TitleOfCourtesy.All)
        {
            currentTOC = null;
        }

      UpdateSelectedRows();
    }

  IEnumerable<Employee> employees; 

  protected override async Task OnInitializedAsync()
  {
    employees = await Task.FromResult(Enumerable.Range(0, 10).Select(i =>
      new Employee
      {
        ID = i,
        CompanyName = i < 4 ? companyNames[0] : companyNames[i - 4],
        TitleOfCourtesy = i < 5 ? TitleOfCourtesy.Mr : TitleOfCourtesy.Ms
      }).AsQueryable());
  }

  protected IList<Employee> SelectedRows;  

  protected bool? GetSelectedTriState()
  {
    if (SelectedRows == null || SelectedRows.Count == 0)
      return false;

    if (grid.View.Count() == SelectedRows.Count)
      return true;

    return null;
  }

  protected void SelectAllVisibleRows(bool? state)
  {
    if (!state.HasValue || state.GetValueOrDefault())
      SelectedRows = grid.View.ToList();
    else
      SelectedRows = null;
  }

  protected bool IsRowSelected(Employee rowData)
  {
    return SelectedRows != null && SelectedRows.Contains(rowData);
  }

  protected void OnGridFilters(DataGridColumnFilterEventArgs<Employee> item)
  {
    Console.WriteLine("OnGridFilters");
    UpdateSelectedRows();
  }

  protected void UpdateSelectedRows()
  {
    Console.WriteLine("UpdateSelectedRows. View has " + grid.View.Count());
    if (SelectedRows != null)
    {
      SelectedRows = SelectedRows.Intersect(grid.View).ToList();
    }
  }

  protected string SelectedCount()
  {
    if (SelectedRows != null)
      return SelectedRows.Count.ToString();

    return "0";
  }

  private void Reset()
  {
    grid.Reset(true, true);
  }
}

We have:
"CompanyName 1" - simple column declaration with "String" type.
"CompanyName 2" - column declaration with custom filter via RadzenDropDown with Multiple="true".
"Title Of Courtesy 1" - simple column declaration with "Enum" type.
"Title Of Courtesy 2" - column declaration with custom filter via RadzenDropDown with Multiple="false".

"CompanyName 1" works perfectly. Grid's "Filter" event is raised AFTER the new filter value is applied to the data source. Grid.Reset works good.

"CompanyName 2" doesn't work. Grid's "Filter" event is NOT raised, and RadzenDropDown.Change event is called BEFORE the new DropDown value is applied to the data source. Grid.Reset raises

Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Operator '=' incompatible with operand types 'String' and 'EnumerableQuery1' Operator '=' incompatible with operand types 'String' and 'EnumerableQuery1' (at index 12)

"Title Of Courtesy 1" works (kind of). It raises Grid's "Filter" event just fine, but Filter's DropDown component lists all the available Enum values which is not the best option, because often Enum may have "default"/"empty"/"unknown" values which basically mean "no filters". Grid.Reset works good.

Would be great to set something like "FilterData" property on the column, which can be used as a source for available filter values. This way it will be much easier to define columns with filters.

"Title Of Courtesy 2" works partially. Grid's "Filter" event is NOT raised, but at least RadzenDropDown.Change event is called AFTER the new DropDown value is applied to the data source. So, this RadzenDropDown.Change event allows to process the data. Grid.Reset doesn't clear the custom filter.

To sum up: In our application, we have all 4 types of filters which work differently, and do not allow us to remove rows from the selection after the filter is changed ("CompanyName 2"). Ideally, this logic should be in one place, e.g., in RadzenDataGrid's "Filter" event. Please, advise how to solve the described problem.

This is already reported and fixed in our master branch:

Hello Vladimir,
Thank you for a quick reply.
Would you have an input on all the other issues described in the topic, please?

Filter event can be raised only from default DataGrid UI for filtering, not from templates. When you use custom code in a template you know precisely when filtering is executed, you do not need any DataGrid event.

Help me please with "you know precisely when filtering is executed". What should be changed in the provided code to be able to handle this logic?