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

Search Grid in numeric and date fields

Environment

Product Grid for Blazor

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:

Steps

Here is an overview of the major steps in the example:

  1. Add a search textbox to the Grid toolbar or outside the Grid. Do not use the default GridSearchBox component, as it searches inside string fields only.
  2. Handle the oninput event of the search textbox. Alternatively, use a search button with an onclick handler.
  3. 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.
  4. 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.

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

@* 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
    }
}
@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

In this article