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
OnRead
event. This will allow programmatic changes to the data request filter. - Create a new
DataSourceRequest
object in theOnRead
handler. Set itsFilters
collection to include oneCompositeFilterDescriptor
instead of the defaultFilterDescriptor
(see note below). - The
CompositeFilterDescriptor
object should have itsLogicalOperator
set toFilterCompositionLogicalOperator.Or
(unless you want all searchable fields to contain the search string). - The
CompositeFilterDescriptor
object should have itsFilterDescriptors
collection contain oneFilterDescriptor
for each searchable field in the data. - (optional) The new
DataSourceRequest
should copy thePageSize
andSkip
property values of the originalOnRead
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 anystring
field viaTextField
. TheTextField
should be astring
property, because the built-in filtering uses string filter operators. (Note that the AutoComplete does not have aTextField
parameter and itsValueField
should be astring
.) - (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
). TheCompositeFilterDescriptor
contains a collection ofFilterDescriptor
s, which can target the same field or different fields. All descriptors in the collection are applied with an AND or an ORLogicalOperator
.
The
OnRead
handler below uses aReadEventArgs
event argument type. This is to reuse theOnRead
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.).
@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; }
}
}