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

Dropdown Search in Multiple Fields

Environment

Product AutoComplete for Blazor,
ComboBox for Blazor,
DropDownList for Blazor,
MultiSelect for Blazor

Description

My dropdown data model has one numeric property in ValueField and two string properties (one of them is the TextField). I want to search (filter) for text from both string fields and get filtered results. After the user makes a selection, the component value should be the value from ValueField.

Solution

  • Use manual data binding for the component with the OnRead event. This will allow programmatic changes to the data request filter.
  • Create a new DataSourceRequest object in the OnRead handler. Set its Filters collection to include one CompositeFilterDescriptor instead of the default FilterDescriptor (see note below).
  • The CompositeFilterDescriptor object should have its LogicalOperator set to FilterCompositionLogicalOperator.Or (unless you want all searchable fields to contain the search string).
  • The CompositeFilterDescriptor object should have its FilterDescriptors collection contain one FilterDescriptor for each searchable field in the data.
  • (optional) The new DataSourceRequest should copy the PageSize and Skip property values of the original OnRead event argument. This applies to virtual scrolling scenarios.
  • The FilterOperator of the component should be the same as the one in the custom filter descriptors.
  • Configure the component ValueField, according to the application and business requirements. The dropdown items can display any string field via TextField. The TextField should be a string property, because the built-in filtering uses string filter operators. (Note that the AutoComplete does not have a TextField parameter and its ValueField should be a string.)
  • (optional) Use an ItemTemplate to display multiple fields in the dropdown, including non-string fields.

Each FilterDescriptor defines one filtering criterion for one data field (Member). The CompositeFilterDescriptor contains a collection of FilterDescriptors, which can target the same field or different fields. All descriptors in the collection are applied with an AND or an OR LogicalOperator.

The OnRead handler below uses a ReadEventArgs event argument type. This is to reuse the OnRead handler for all components and prevent code duplication. Use the correct event argument type in the production application, depending on the component (AutoCompleteReadEventArgs, ComboBoxReadEventArgs, etc.).

Search in multiple data fields - custom filtering

@using Telerik.DataSource
@using Telerik.DataSource.Extensions

<p>Search (filter) by typing numbers (0 - 99) or pairs of letters (aa - zz)</p>

<p>AutoComplete value: @AutoCompleteValue</p>

<TelerikAutoComplete TItem="@Product" OnRead="@GetItems"
                     @bind-Value="@AutoCompleteValue"
                     ValueField="@nameof(Product.Name)"
                     Filterable="true"
                     FilterOperator="@((StringFilterOperator)ReusableFilterOperator)"
                     Width="300px">
    <ItemTemplate>
        @context.Code - @context.Name
    </ItemTemplate>
</TelerikAutoComplete>

<p>ComboBox value: @ComboBoxValue</p>

<TelerikComboBox TItem="@Product" TValue="@(int?)" OnRead="@GetItems"
                 @bind-Value="@ComboBoxValue"
                 ValueField="@nameof(Product.Id)"
                 TextField="@nameof(Product.Name)"
                 Filterable="true"
                 FilterOperator="@((StringFilterOperator)ReusableFilterOperator)"
                 Width="300px">
    <ItemTemplate>
        @context.Code - @context.Name
    </ItemTemplate>
</TelerikComboBox>

<p>DropDownList value: @DropDownListValue</p>

<TelerikDropDownList TItem="@Product" TValue="@(int?)" OnRead="@GetItems"
                     @bind-Value="@DropDownListValue"
                     ValueField="@nameof(Product.Id)"
                     TextField="@nameof(Product.Name)"
                     Filterable="true"
                     FilterOperator="@((StringFilterOperator)ReusableFilterOperator)"
                     Width="300px">
    <ItemTemplate>
        @context.Code - @context.Name
    </ItemTemplate>
</TelerikDropDownList>

<p>MultiSelect selected items: @MultiSelectValues.Count</p>

<TelerikMultiSelect TItem="@Product" TValue="@(int)" OnRead="@GetItems"
                    @bind-Value="@MultiSelectValues"
                    ValueField="@nameof(Product.Id)"
                    TextField="@nameof(Product.Name)"
                    Filterable="true"
                    FilterOperator="@((StringFilterOperator)ReusableFilterOperator)"
                    Width="300px">
    <ItemTemplate>
        @context.Code - @context.Name
    </ItemTemplate>
</TelerikMultiSelect>

@code {
    string AutoCompleteValue { get; set; }
    int? ComboBoxValue { get; set; }
    int? DropDownListValue { get; set; }
    List<int> MultiSelectValues { get; set; } = new List<int>();

    List<Product> Products { get; set; }

    FilterOperator ReusableFilterOperator { get; set; } = FilterOperator.Contains;

    // !!! use the correct OnRead event argument type in your app !!!
    // AutoCompleteReadEventArgs
    // ComboBoxReadEventArgs
    // DropDownListReadEventArgs
    // MultiSelectReadEventArgs

    // ReadEventArgs here prevents handler duplication
    async Task GetItems(ReadEventArgs args)
    {
        DataSourceRequest newRequest = GenerateCustomFilterRequest(args.Request);

        // simulate network delay
        await Task.Delay(200);

        var result = Products.ToDataSourceResult(newRequest);

        args.Data = result.Data;
        // args.Total is required for virtual scrolling
        //args.Total = result.Total;
    }

    DataSourceRequest GenerateCustomFilterRequest(DataSourceRequest oldRequest)
    {
        var filter = (FilterDescriptor)oldRequest.Filters.FirstOrDefault();
        string searchString = "";

        if (filter != null)
        {
            // extract the search string for the custom FilterDescriptor
            searchString = filter.Value.ToString();
        }
        else
        {
            // no search string, no need to create a FilterDescriptor
            return oldRequest;
        }

        var newRequest = new DataSourceRequest()
        {
            // PageSize and Skip are needed for virtual scrolling
            //PageSize = oldRequest.PageSize,
            //Skip = oldRequest.Skip,
            Filters = new List<IFilterDescriptor>()
        };

        newRequest.Filters.Add(new CompositeFilterDescriptor()
        {
            LogicalOperator = FilterCompositionLogicalOperator.Or,
            FilterDescriptors = new FilterDescriptorCollection() {
                new FilterDescriptor() {
                    Member = nameof(Product.Code),
                    Operator = ReusableFilterOperator,
                    Value = searchString
                },
                new FilterDescriptor() {
                    Member = nameof(Product.Name),
                    Operator = ReusableFilterOperator,
                    Value = searchString
                }
            }
        });

        return newRequest;
    }

    protected override void OnInitialized()
    {
        Products = new List<Product>();
        for (int i = 1; i <= 99; i++)
        {
            Products.Add(new Product()
            {
                Id = i,
                Code = i.ToString("00"),
                Name = $"Product {((char)(i % 27 + 64)).ToString()}{((char)(i % 27 + 64)).ToString()}"
            });
        }

        base.OnInitialized();
    }

    public class Product
    {
        public int Id { get; set; }
        public string Code { get; set; }
        public string Name { get; set; }
    }
}
In this article