blazor-docs/knowledge-base/grid-search-numeric-fields.md

341 строка
14 KiB
Markdown

---
title: Search Grid in numeric and date fields
description: How to use the Grid SearchBox to search in integer, numeric, date and enum fields (data types).
type: how-to
page_title: Search Grid in numeric and date fields
slug: grid-kb-search-numeric-fields
position:
tags: grid, search, gridsearchbox
ticketid: 1485012, 1488627, 1440872, 1488627, 1551397, 1546489, 1520936, 1616608
res_type: kb
---
## Environment
<table>
<tbody>
<tr>
<td>Product</td>
<td>Grid for Blazor</td>
</tr>
</tbody>
</table>
## Description
How to allow users search the Blazor Grid in numeric, date and enum fields?
How to use the toolbar GridSearchBox to search values in number columns?
How to search integers or float numbers from the Grid toolbar?
How to make the Grid SearchBox search with multiple data types?
## Solution
The Blazor Grid search works with `string` data types, because it uses a `contains` string filter operator.
It is possible to search by other data types with some additional coding that will use non-string filter operators.
The following sections discuss how to implement the desired behavior, what are the limitations, and provide an example.
## Prerequisites
The solution below requires familiarity with:
* [How to use Grid filter descriptors]({%slug components/grid/filtering%}#filter-descriptors)
* [How to change the Grid state]({%slug grid-state%})
* [How to invoke Razor component events from a child component](https://docs.microsoft.com/en-us/aspnet/core/blazor/components/event-handling?view=aspnetcore-6.0#eventcallback).
## Steps
Here is an overview of the major steps in the example:
1. Add a search textbox to the [Grid toolbar]({%slug components/grid/features/toolbar%}) or outside the Grid. Do not use the default [`GridSearchBox` component]({%slug grid-searchbox%}), as it searches inside string fields only.
1. Handle the `oninput` event of the search textbox. Alternatively, use a search button with an `onclick` handler.
1. Use the event handler to:
* Create a new `CompositeFilterDescriptor`.
* Iterate all searchable fields of the Grid.
* For each searchable field, parse the search string and convert it the field's data type. Create a child `FilterDescriptor` object, depending on the data field type and the parsed search value.
1. Apply the filter descriptors to the `SearchFilter` property of the Grid state.
## How it works
* A search value `"8"` will return:
* *string* results that *contain* the string "8"
* *numeric* results that are *equal to* 8
* *enums* results that are *equal to* `int` 8
* Date search values *must fully match* date values in the Grid to return results. The example also shows how to search by year only.
* If the search value cannot be parsed to a specific data type (such as `"123 abc"`), then the code will not create additional non-string filters. This search value will not return any numeric, date and boolean results.
>tip It is possible to achieve different behavior with a different implementation of the filter descriptor creation.
> The `OnInput` handler in `GridUniversalSearchBox` uses a single `else if` block for similar data types for brevity, for example `float`, `float?`, `double` and `double?`. If the Grid data contains `null` values, then you must use a separate `else if` block for each type. For example, use one `else if` block for `float` and `float?`, and another block for `double` and `double?`. Otherwise you will get an exception *Nullable object must have a value*. The same requirement applies to all integer types too.
## Example
<div class="skip-repl"></div>
````Grid.razor
@* How to use a custom search textbox in the Grid, which supports multiple data types.
The example shows how to build FilterDescriptors programmatically for all searchable fields in the Grid model.
*@
@using Telerik.DataSource;
<TelerikGrid Data="@MyData" @ref="@Grid" Pageable="true" Height="400px">
<GridToolBarTemplate>
<GridUniversalSearchBox OnSearch="@OnSearch" T="SampleData" Fields="@SearchBoxFields" />
</GridToolBarTemplate>
<GridColumns>
<GridColumn Field="@(nameof(SampleData.Id))" Width="60px" />
<GridColumn Field="@(nameof(SampleData.Bool))" Width="80px" />
<GridColumn Field="@(nameof(SampleData.Byte))" Width="100px" />
<GridColumn Field="@(nameof(SampleData.Short))" Width="100px" />
<GridColumn Field="@(nameof(SampleData.Int))" Width="100px" />
<GridColumn Field="@(nameof(SampleData.Long))" Width="100px" />
<GridColumn Field="@(nameof(SampleData.Float))" Width="100px" />
<GridColumn Field="@(nameof(SampleData.Double))" Width="100px" />
<GridColumn Field="@(nameof(SampleData.Decimal))" Width="100px" />
<GridColumn Field="@(nameof(SampleData.DateTime))" Width="180px"/>
<GridColumn Field="@(nameof(SampleData.String))" Width="120px" />
<GridColumn Field="@(nameof(SampleData.Enum))" Width="120px" />
</GridColumns>
</TelerikGrid>
@code {
public TelerikGrid<SampleData> Grid { get; set; }
public string SearchBoxValue { get; set; }
public List<string> SearchBoxFields { get; set; } = new List<string> {
"Id", "Bool", "Byte", "Short", "Int", "Long", "Float", "Double", "Decimal", "DateTime", "String", "Enum"
};
public async Task SetGridSearchBoxFilters(CompositeFilterDescriptor searchDescriptors)
{
var state = Grid.GetState();
state.SearchFilter = searchDescriptors;
await Grid.SetStateAsync(state);
}
private async Task OnSearch(CompositeFilterDescriptor value)
{
await SetGridSearchBoxFilters(value);
}
public IEnumerable<SampleData> MyData = Enumerable.Range(1, 50).Select(x => new SampleData
{
Id = x,
Bool = x % 2 == 0,
Byte = (byte)(x * 2),
Short = (short)(x * 3),
Int = x * 5,
Long = (long)(x * 7),
Float = ((float)x) + .2f,
Double = ((double)x) + .3,
Decimal = x + .4m,
String = $"Name {x}",
DateTime = DateTime.Now.Date.AddMonths(-x),
Enum = (EnumType)(x % 3),
});
public class SampleData
{
public int Id { get; set; }
public bool Bool { get; set; }
public byte Byte { get; set; }
public short Short { get; set; }
public int Int { get; set; }
public long Long { get; set; }
public float Float { get; set; }
public double Double { get; set; }
public decimal Decimal { get; set; }
public string String { get; set; } = string.Empty;
public DateTime DateTime { get; set; }
public EnumType Enum { get; set; }
}
public enum EnumType
{
EnumValue1,
EnumValue2,
EnumValue3
}
}
````
````GridUniversalSearchBox.razor
@using System.ComponentModel.DataAnnotations
@using System.Globalization
@using System.Reflection
@using Telerik.DataSource
@typeparam T
<span class="k-grid-search k-textbox k-input k-input-solid k-rounded-md k-input-md">
<span class="k-icon k-input-icon k-i-search"></span>
<span onkeydown="event.stopPropagation()">
<input @oninput="@OnInput"
value="@SearchBoxValue"
type="text" class="k-input-inner" />
</span>
@if (!string.IsNullOrEmpty(SearchBoxValue))
{
<span @onclick="@ClearSearch"
class="k-clear-value"><span class="k-icon k-i-x"></span></span>
}
</span>
@code {
[Parameter]
public EventCallback<CompositeFilterDescriptor> OnSearch { get; set; }
[Parameter]
public List<string> Fields { get; set; }
public string SearchBoxValue { get; set; }
private protected async Task ClearSearch()
{
SearchBoxValue = string.Empty;
await OnSearch.InvokeAsync(new CompositeFilterDescriptor());
}
private async Task OnInput(ChangeEventArgs args)
{
var value = args.Value.ToString();
SearchBoxValue = value;
var newDescriptor = new CompositeFilterDescriptor() { LogicalOperator = FilterCompositionLogicalOperator.Or };
if (!string.IsNullOrEmpty(value))
{
foreach (var field in Fields)
{
var type = typeof(T).GetProperty(field).PropertyType;
// !!! If using nullable properties,
// then separate each type pair (foo and foo?)
// to its own else if block !!!
// Otherwise you will get 'Nullable object must have a value' exception during search.
if (type == typeof(int) || type == typeof(int?) ||
type == typeof(short) || type == typeof(short?) ||
type == typeof(byte) || type == typeof(byte?) ||
type == typeof(long) || type == typeof(long?))
{
// you can add the unsigned types too (uint, ulong...)
if (long.TryParse(value.ToString(), out var longValue))
{
var filter = CreateFilterDescriptor(field, FilterOperator.IsEqualTo, longValue);
newDescriptor.FilterDescriptors.Add(filter);
}
}
else if (type == typeof(float) || type == typeof(float?) ||
type == typeof(double) || type == typeof(double?))
{
if (double.TryParse(value.ToString(), out var doubleValue))
{
var filter = CreateFilterDescriptor(field, FilterOperator.IsEqualTo, doubleValue);
newDescriptor.FilterDescriptors.Add(filter);
}
}
else if (type == typeof(decimal) || type == typeof(decimal?))
{
if (decimal.TryParse(value.ToString(), out var decimalValue))
{
var filter = CreateFilterDescriptor(field, FilterOperator.IsEqualTo, decimalValue);
newDescriptor.FilterDescriptors.Add(filter);
}
}
else if (type.IsEnum)
{
if (int.TryParse(value.ToString(), out var intValue))
{
var filter = CreateFilterDescriptor<int>(field, FilterOperator.IsEqualTo, intValue);
newDescriptor.FilterDescriptors.Add(filter);
}
else
{
foreach (var item in Enum.GetValues(type))
{
var text = item.ToString();
var fieldInfo = type.GetField(text);
var displayNameAttribute = fieldInfo.GetCustomAttribute<DisplayAttribute>()?.GetName();
if ((displayNameAttribute?.Contains(value.ToString()) ?? false) || text.Contains(value.ToString()))
{
var filter = CreateFilterDescriptor<int>(field, FilterOperator.IsEqualTo, (int)item);
newDescriptor.FilterDescriptors.Add(filter);
}
}
}
}
else if (type == typeof(DateTime) || type == typeof(DateTime?))
{
// search by exact DateTime
var culture = CultureInfo.CurrentUICulture;
var styles = DateTimeStyles.None;
if (DateTime.TryParse(value.ToString(), culture, styles, out var dateValue))
{
var filter = CreateFilterDescriptor(field, FilterOperator.IsEqualTo, dateValue);
newDescriptor.FilterDescriptors.Add(filter);
}
// search by year only
if (short.TryParse(value.ToString(), out var yearValue))
{
if (yearValue > 1000 && yearValue < 2100)
{
var yearFilterDescriptor = new CompositeFilterDescriptor() { LogicalOperator = FilterCompositionLogicalOperator.And };
var filter1 = CreateFilterDescriptor(field, FilterOperator.IsGreaterThanOrEqualTo, new DateTime(yearValue, 1, 1));
yearFilterDescriptor.FilterDescriptors.Add(filter1);
var filter2 = CreateFilterDescriptor(field, FilterOperator.IsLessThan, new DateTime(yearValue + 1, 1, 1));
yearFilterDescriptor.FilterDescriptors.Add(filter2);
newDescriptor.FilterDescriptors.Add(yearFilterDescriptor);
}
}
}
else if (type == typeof(bool) || type == typeof(bool?))
{
if (bool.TryParse(value.ToString(), out var boolValue))
{
var filter = CreateFilterDescriptor(field, FilterOperator.IsEqualTo, boolValue);
newDescriptor.FilterDescriptors.Add(filter);
}
}
else if (type == typeof(string))
{
var filter = CreateFilterDescriptor(field, FilterOperator.Contains, value);
newDescriptor.FilterDescriptors.Add(filter);
}
}
}
await OnSearch.InvokeAsync(newDescriptor);
}
private FilterDescriptor CreateFilterDescriptor<TMember>(string member, FilterOperator filterOperator, TMember value)
{
var filter = new FilterDescriptor(member, filterOperator, value);
filter.MemberType = typeof(TMember);
return filter;
}
}
````
## See Also
* [Search the Grid on Button Click]({%slug grid-kb-search-button-click%})
* [Search the Grid in Hidden Fields]({%slug grid-kb-search-in-hidden-fields%})
* [Search the Grid with a `StartsWith` operator]({%slug grid-kb-search-startswith%})
* [Format or Bold Search Results in the Grid]({%slug grid-kb-search-highlight-results%})