Dropdown Search in Multiple Fields
Environment
| Product |
AutoComplete for Blazor, ComboBox for Blazor, DropDownList for Blazor, MultiColumnComboBox 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
- Bind the component with the
OnReadevent. This will allow programmatic changes to the data request filter. - Create a new
DataSourceRequestobject in theOnReadhandler. Set itsFilterscollection to include oneCompositeFilterDescriptorinstead of the defaultFilterDescriptor(see note below). - The
CompositeFilterDescriptorobject should have itsLogicalOperatorset toFilterCompositionLogicalOperator.Or(unless you want all searchable fields to contain the search string). - The
CompositeFilterDescriptorobject should have itsFilterDescriptorscollection contain oneFilterDescriptorfor each searchable field in the data. - (optional) The new
DataSourceRequestshould copy thePageSizeandSkipproperty values of the originalOnReadevent argument. This applies to virtual scrolling scenarios. - The
FilterOperatorof 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 anystringfield viaTextField. TheTextFieldshould be astringproperty, because the built-in filtering uses string filter operators. (Note that the AutoComplete does not have aTextFieldparameter and itsValueFieldshould be astring.) - (optional) Use an
ItemTemplateto display multiple fields in the dropdown, including non-string fields.
Each
FilterDescriptordefines one filtering criterion for one data field (Member). TheCompositeFilterDescriptorcontains a collection ofFilterDescriptors, which can target the same field or different fields. All descriptors in the collection are applied with an AND or an ORLogicalOperator.
The
OnReadhandler below uses aReadEventArgsevent argument type. This is to reuse theOnReadhandler 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; }
}
}