From 854259896b53b25c94a43ec69a1ef0b256f23dc0 Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Fri, 3 May 2024 12:50:25 +0530 Subject: [PATCH] Grid: Filters - Enum support #485 --- .../Pages/Grid/GridDocumentation.razor | 3 + .../Grid/Grid_Demo_35_Enum_Filters.razor | 71 +++++++++++++++++++ blazorbootstrap/Components/Grid/Grid.razor | 1 + .../Components/Grid/GridColumn.razor.cs | 7 ++ .../Components/Grid/GridColumnFilter.razor | 46 +++++++++--- .../Components/Grid/GridColumnFilter.razor.cs | 20 ++++++ .../Extensions/ExpressionExtensions.cs | 59 +++++++++++++++ blazorbootstrap/Extensions/TypeExtensions.cs | 36 +++++++++- blazorbootstrap/Models/Constants.cs | 1 + blazorbootstrap/Models/FilterItem.cs | 26 ++++++- .../Grid => Models}/GridSettings.cs | 0 .../FilterOperatorHelper.cs | 14 ++++ 12 files changed, 271 insertions(+), 13 deletions(-) create mode 100644 BlazorBootstrap.Demo.RCL/Components/Pages/Grid/Grid_Demo_35_Enum_Filters.razor rename blazorbootstrap/{Components/Grid => Models}/GridSettings.cs (100%) rename blazorbootstrap/{Components/Grid => Utilities}/FilterOperatorHelper.cs (89%) diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/GridDocumentation.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/GridDocumentation.razor index 14b29f327..db5251874 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/GridDocumentation.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/GridDocumentation.razor @@ -206,6 +206,9 @@ + + + @code { private string pageUrl = "/grid"; private string title = "Blazor Grid Component"; diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/Grid_Demo_35_Enum_Filters.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/Grid_Demo_35_Enum_Filters.razor new file mode 100644 index 000000000..50ca02a0f --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/Grid_Demo_35_Enum_Filters.razor @@ -0,0 +1,71 @@ + + + + @context.Id + + + @context.Name + + + @context.DOB + + + @context.Status + + + + +@code { + BlazorBootstrap.Grid grid = default!; + private IEnumerable users = default!; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + } + + private async Task> UsersDataProvider(GridDataProviderRequest request) + { + if (users is null) // pull employees only one time for client-side filtering, sorting, and paging + users = GetUsers(); // call a service or an API to pull the employees + + return await Task.FromResult(request.ApplyTo(users)); + } + + private IEnumerable GetUsers() + { + return new List + { + new User { Id = 107, Name = "Alice", DOB = new DateOnly(1998, 11, 17), Status = UserStatus.Registered }, + new User { Id = null, Name = "Bob", DOB = new DateOnly(1985, 1, 5), Status = UserStatus.Verified }, + new User { Id = 106, Name = "John", DOB = new DateOnly(1995, 4, 17), Status = UserStatus.Registered }, + new User { Id = 104, Name = "Pop", DOB = new DateOnly(1985, 6, 8), Status = UserStatus.Registered }, + new User { Id = 105, Name = "Ronald", DOB = new DateOnly(1991, 8, 23), Status = UserStatus.VerificationPending }, + new User { Id = 102, Name = "Line", DOB = new DateOnly(1977, 1, 12), Status = UserStatus.VerificationPending }, + new User { Id = 101, Name = "Daniel", DOB = new DateOnly(1977, 1, 12), Status = UserStatus.Registered }, + new User { Id = 108, Name = "Zayne", DOB = new DateOnly(1991, 1, 1), Status = UserStatus.Verified }, + new User { Id = 109, Name = "Isha", DOB = null, Status = UserStatus.Verified }, + new User { Id = 110, Name = "Vijay", DOB = new DateOnly(1990, 7, 1), Status = UserStatus.Verified }, + }; + } + + public record class User + { + public int? Id { get; set; } + public string? Name { get; set; } + public DateOnly? DOB { get; set; } + public UserStatus Status { get; set; } + } + + public enum UserStatus + { + Registered, + VerificationPending, + Verified + } +} \ No newline at end of file diff --git a/blazorbootstrap/Components/Grid/Grid.razor b/blazorbootstrap/Components/Grid/Grid.razor index cc0af7639..09eb6fd94 100644 --- a/blazorbootstrap/Components/Grid/Grid.razor +++ b/blazorbootstrap/Components/Grid/Grid.razor @@ -56,6 +56,7 @@ FiltersTranslationProvider="GridFiltersTranslationProviderAsync" FixedHeader="@FixedHeader" GridColumnFilterChanged="async args => await column.OnFilterChangedAsync(args, column)" + PropertyType="@column.GetPropertyType()" PropertyTypeName="@column.GetPropertyTypeName()" Unit="@Unit" /> } diff --git a/blazorbootstrap/Components/Grid/GridColumn.razor.cs b/blazorbootstrap/Components/Grid/GridColumn.razor.cs index efd05e396..d228c3abd 100644 --- a/blazorbootstrap/Components/Grid/GridColumn.razor.cs +++ b/blazorbootstrap/Components/Grid/GridColumn.razor.cs @@ -46,6 +46,8 @@ protected override async Task OnInitializedAsync() internal string GetFilterValue() => filterValue; + internal Type GetPropertyType() => typeof(TItem).GetPropertyType(PropertyName)!; + internal string GetPropertyTypeName() => typeof(TItem).GetPropertyTypeName(PropertyName); internal IEnumerable> GetSorting() @@ -97,6 +99,11 @@ or StringConstants.PropertyTypeNameDecimal if (filterOperator == FilterOperator.None) FilterOperator = filterOperator = FilterOperator.Equals; } + else if (propertyTypeName == StringConstants.PropertyTypeNameEnum) + { + if (filterOperator == FilterOperator.None) + FilterOperator = filterOperator = FilterOperator.Equals; + } } internal void SetFilterOperator(FilterOperator filterOperator) => FilterOperator = this.filterOperator = filterOperator; diff --git a/blazorbootstrap/Components/Grid/GridColumnFilter.razor b/blazorbootstrap/Components/Grid/GridColumnFilter.razor index 06e628cea..d71bba595 100644 --- a/blazorbootstrap/Components/Grid/GridColumnFilter.razor +++ b/blazorbootstrap/Components/Grid/GridColumnFilter.razor @@ -6,13 +6,13 @@ @if (string.IsNullOrWhiteSpace(filterValue)) { - + } else { - + } @selectedFilterSymbol @@ -32,28 +32,52 @@ @if (PropertyTypeName == StringConstants.PropertyTypeNameInt16 - || PropertyTypeName == StringConstants.PropertyTypeNameInt32 - || PropertyTypeName == StringConstants.PropertyTypeNameInt64 - || PropertyTypeName == StringConstants.PropertyTypeNameSingle // float - || PropertyTypeName == StringConstants.PropertyTypeNameDecimal - || PropertyTypeName == StringConstants.PropertyTypeNameDouble) + || PropertyTypeName == StringConstants.PropertyTypeNameInt32 + || PropertyTypeName == StringConstants.PropertyTypeNameInt64 + || PropertyTypeName == StringConstants.PropertyTypeNameSingle // float + || PropertyTypeName == StringConstants.PropertyTypeNameDecimal + || PropertyTypeName == StringConstants.PropertyTypeNameDouble) { } else if (PropertyTypeName == StringConstants.PropertyTypeNameDateOnly) { - + } else if (PropertyTypeName == StringConstants.PropertyTypeNameDateTime) { - + } else if (PropertyTypeName == StringConstants.PropertyTypeNameBoolean) { - + + } + else if (PropertyTypeName == StringConstants.PropertyTypeNameEnum) + { + + + @if (string.IsNullOrWhiteSpace(filterValue)) + { + Select + } + else + { + @filterValue + } + + + @if (PropertyType is not null) + { + @foreach (var item in Enum.GetValues(PropertyType!)) + { + @item + } + } + + } else // string { - + } diff --git a/blazorbootstrap/Components/Grid/GridColumnFilter.razor.cs b/blazorbootstrap/Components/Grid/GridColumnFilter.razor.cs index 05b834385..8230ff615 100644 --- a/blazorbootstrap/Components/Grid/GridColumnFilter.razor.cs +++ b/blazorbootstrap/Components/Grid/GridColumnFilter.razor.cs @@ -60,6 +60,11 @@ or StringConstants.PropertyTypeNameDecimal if (filterOperator is FilterOperator.None or FilterOperator.Clear) filterOperator = FilterOperator.Equals; } + else if (PropertyTypeName == StringConstants.PropertyTypeNameEnum) + { + if (filterOperator is FilterOperator.None or FilterOperator.Clear) + filterOperator = FilterOperator.Equals; + } } private async Task> GetFilterOperatorsAsync(string propertyTypeName) @@ -97,6 +102,14 @@ private async Task OnFilterOperatorChangedAsync(FilterOperatorInfo filterOperato await GridColumnFilterChanged.InvokeAsync(new FilterEventArgs(filterValue!, filterOperator)); } + private async Task OnEnumFilterValueChangedAsync(object enumValue) + { + filterValue = enumValue?.ToString(); + + if (GridColumnFilterChanged.HasDelegate) + await GridColumnFilterChanged.InvokeAsync(new FilterEventArgs(filterValue!, filterOperator)); + } + private async Task OnFilterValueChangedAsync(ChangeEventArgs args) { filterValue = args?.Value?.ToString(); @@ -121,6 +134,7 @@ or StringConstants.PropertyTypeNameDecimal or StringConstants.PropertyTypeNameDateTime) selectedFilterSymbol = filterOperators?.FirstOrDefault(x => x.FilterOperator == filterOperator)?.Symbol; else if (PropertyTypeName == StringConstants.PropertyTypeNameBoolean) selectedFilterSymbol = filterOperators?.FirstOrDefault(x => x.FilterOperator == filterOperator)?.Symbol; + else if (PropertyTypeName == StringConstants.PropertyTypeNameEnum) selectedFilterSymbol = filterOperators?.FirstOrDefault(x => x.FilterOperator == filterOperator)?.Symbol; } #endregion @@ -168,6 +182,12 @@ or StringConstants.PropertyTypeNameDecimal [Parameter] public string? PropertyTypeName { get; set; } + /// + /// Gets or sets the filter property name. + /// + [Parameter] + public Type? PropertyType { get; set; } + /// /// Gets or sets the units. /// diff --git a/blazorbootstrap/Extensions/ExpressionExtensions.cs b/blazorbootstrap/Extensions/ExpressionExtensions.cs index 476b5f20f..de95b11dd 100644 --- a/blazorbootstrap/Extensions/ExpressionExtensions.cs +++ b/blazorbootstrap/Extensions/ExpressionExtensions.cs @@ -16,6 +16,8 @@ public static Expression> And(this Expression>(body, parameterExpression); } + #region Boolean + public static ConstantExpression GetBooleanConstantExpression(FilterItem filterItem, string propertyTypeName) { ConstantExpression? value = null; @@ -45,6 +47,10 @@ public static Expression> GetBooleanNotEqualExpressionDelegate return Expression.Lambda>(expression, parameterExpression); } + #endregion Boolean + + #region Date + public static ConstantExpression GetDateConstantExpression(FilterItem filterItem, string propertyTypeName) { if (filterItem.Value == null) @@ -294,8 +300,44 @@ public static Expression> GetDateNotEqualExpressionDelegate>(nonNullComparisonExpression, parameterExpression); } + #endregion Date + + #region Enum + + public static ConstantExpression GetEnumConstantExpression(FilterItem filterItem, Type propertyType, string propertyTypeName) + { + ConstantExpression? value = null; + + if(propertyType is not null && propertyType.IsEnum) + { + _ = Enum.TryParse(propertyType, filterItem.Value, out object filterValue); + value = Expression.Constant(filterValue); + } + + return value!; + } + + public static Expression> GetEnumEqualExpressionDelegate(ParameterExpression parameterExpression, FilterItem filterItem, Type propertyType, string propertyTypeName) + { + var property = Expression.Property(parameterExpression, filterItem.PropertyName); + var expression = Expression.Equal(property, GetEnumConstantExpression(filterItem, propertyType, propertyTypeName)); + + return Expression.Lambda>(expression, parameterExpression); + } + + public static Expression> GetEnumNotEqualExpressionDelegate(ParameterExpression parameterExpression, FilterItem filterItem, Type propertyType, string propertyTypeName) + { + var property = Expression.Property(parameterExpression, filterItem.PropertyName); + var expression = Expression.NotEqual(property, GetEnumConstantExpression(filterItem, propertyType, propertyTypeName)); + + return Expression.Lambda>(expression, parameterExpression); + } + + #endregion Enum + public static Expression>? GetExpressionDelegate(ParameterExpression parameterExpression, FilterItem filterItem) { + var propertyType = typeof(TItem).GetPropertyType(filterItem.PropertyName); var propertyTypeName = typeof(TItem).GetPropertyTypeName(filterItem.PropertyName); if (propertyTypeName is StringConstants.PropertyTypeNameInt16 @@ -348,9 +390,20 @@ or StringConstants.PropertyTypeNameDecimal _ => GetBooleanEqualExpressionDelegate(parameterExpression, filterItem, propertyTypeName) }; + // Enum + if (propertyTypeName == StringConstants.PropertyTypeNameEnum) + return filterItem.Operator switch + { + FilterOperator.Equals => GetEnumEqualExpressionDelegate(parameterExpression, filterItem, propertyType, propertyTypeName), + FilterOperator.NotEquals => GetEnumNotEqualExpressionDelegate(parameterExpression, filterItem, propertyType, propertyTypeName), + _ => GetEnumEqualExpressionDelegate(parameterExpression, filterItem, propertyType, propertyTypeName) + }; + return null; } + #region Number + public static ConstantExpression GetNumberConstantExpression(FilterItem filterItem, string propertyTypeName) { if (filterItem.Value is null) @@ -560,6 +613,10 @@ public static Expression> GetNumberNotEqualExpressionDelegate< return Expression.Lambda>(finalExpression, parameterExpression); } + #endregion Number + + #region String + public static Expression> GetStringContainsExpressionDelegate(ParameterExpression parameterExpression, FilterItem filterItem) { var propertyExp = Expression.Property(parameterExpression, filterItem.PropertyName); @@ -656,6 +713,8 @@ public static Expression> GetStringStartsWithExpressionDelegat return Expression.Lambda>(finalExpression, parameterExpression); } + #endregion String + public static bool IsNullableType(this Type type) => Nullable.GetUnderlyingType(type) != null; public static Expression> Or(this Expression> leftExpression, Expression> rightExpression) diff --git a/blazorbootstrap/Extensions/TypeExtensions.cs b/blazorbootstrap/Extensions/TypeExtensions.cs index 6e03091e0..28a944af5 100644 --- a/blazorbootstrap/Extensions/TypeExtensions.cs +++ b/blazorbootstrap/Extensions/TypeExtensions.cs @@ -18,7 +18,24 @@ public static string GetPropertyTypeName(this Type type, string propertyName) if (type is null || string.IsNullOrWhiteSpace(propertyName)) return string.Empty; - var propertyTypeName = type.GetProperty(propertyName)?.PropertyType?.ToString(); + var propertyType = type.GetProperty(propertyName)?.PropertyType; + if (propertyType is null) + return string.Empty; + + var propertyTypeName = propertyType?.ToString(); + + //if(propertyTypeName is null) // Nullable type scenario + //{ + // var props = type.GetProperties(); + // if(props.Length > 0) + // { + // var propertyInfo = props?.FirstOrDefault(x=>x.Name == propertyName); + // if (propertyInfo is null) + // throw new InvalidDataException($"PropertyName `{propertyName}` is invalid."); + + // propertyTypeName = Nullable.GetUnderlyingType(propertyInfo.PropertyType)?.FullName; + // } + //} if (string.IsNullOrWhiteSpace(propertyTypeName)) return string.Empty; @@ -56,8 +73,25 @@ public static string GetPropertyTypeName(this Type type, string propertyName) if (propertyTypeName.Contains(StringConstants.PropertyTypeNameBoolean, StringComparison.InvariantCulture)) return StringConstants.PropertyTypeNameBoolean; + if (propertyType!.IsEnum) + return StringConstants.PropertyTypeNameEnum; + return string.Empty; } + /// + /// Get property type. + /// + /// + /// + /// Type? + public static Type? GetPropertyType(this Type type, string propertyName) + { + if (type is null || string.IsNullOrWhiteSpace(propertyName)) + return null; + + return type.GetProperty(propertyName)?.PropertyType; + } + #endregion } diff --git a/blazorbootstrap/Models/Constants.cs b/blazorbootstrap/Models/Constants.cs index c20077625..a9803270d 100644 --- a/blazorbootstrap/Models/Constants.cs +++ b/blazorbootstrap/Models/Constants.cs @@ -15,6 +15,7 @@ internal class StringConstants public const string PropertyTypeNameDateOnly = "DateOnly"; public const string PropertyTypeNameDateTime = "DateTime"; public const string PropertyTypeNameBoolean = "Boolean"; + public const string PropertyTypeNameEnum = "Enum"; public const string DataBootstrapToggle = "data-bs-toggle"; diff --git a/blazorbootstrap/Models/FilterItem.cs b/blazorbootstrap/Models/FilterItem.cs index 863989466..8f8418fdd 100644 --- a/blazorbootstrap/Models/FilterItem.cs +++ b/blazorbootstrap/Models/FilterItem.cs @@ -1,3 +1,27 @@ namespace BlazorBootstrap; -public sealed record class FilterItem(string PropertyName, string Value, FilterOperator Operator, StringComparison StringComparison); +public sealed record class FilterItem +{ + public FilterItem(string propertyName, string value, FilterOperator @operator, StringComparison stringComparison) + { + PropertyName = propertyName; + Value = value; + Operator = @operator; + StringComparison = stringComparison; + } + + FilterItem(Type propertType, string propertyName, string value, FilterOperator @operator, StringComparison stringComparison) + { + PropertType = propertType; + PropertyName = propertyName; + Value = value; + Operator = @operator; + StringComparison = stringComparison; + } + + public Type PropertType { get; private set; } + public string PropertyName { get; private set; } + public string Value { get; private set; } + public FilterOperator Operator { get; private set; } + public StringComparison StringComparison { get; private set; } +} diff --git a/blazorbootstrap/Components/Grid/GridSettings.cs b/blazorbootstrap/Models/GridSettings.cs similarity index 100% rename from blazorbootstrap/Components/Grid/GridSettings.cs rename to blazorbootstrap/Models/GridSettings.cs diff --git a/blazorbootstrap/Components/Grid/FilterOperatorHelper.cs b/blazorbootstrap/Utilities/FilterOperatorHelper.cs similarity index 89% rename from blazorbootstrap/Components/Grid/FilterOperatorHelper.cs rename to blazorbootstrap/Utilities/FilterOperatorHelper.cs index 692d01c40..02ca58272 100644 --- a/blazorbootstrap/Components/Grid/FilterOperatorHelper.cs +++ b/blazorbootstrap/Utilities/FilterOperatorHelper.cs @@ -27,6 +27,18 @@ public static IEnumerable GetDateFilterOperators() return result; } + public static IEnumerable GetEnumFilterOperators() + { + List result = new() + { + new FilterOperatorInfo("=", "Equals", FilterOperator.Equals), + new FilterOperatorInfo("!=", "Not equals", FilterOperator.NotEquals), + new FilterOperatorInfo("x", "Clear", FilterOperator.Clear) + }; + + return result; + } + public static IEnumerable GetFilterOperators(string propertyTypeName, IEnumerable filtersTranslations) { if (filtersTranslations is null || !filtersTranslations.Any()) @@ -68,6 +80,8 @@ or StringConstants.PropertyTypeNameDecimal if (propertyTypeName == StringConstants.PropertyTypeNameBoolean) return GetBooleanFilterOperators(); + if (propertyTypeName == StringConstants.PropertyTypeNameEnum) return GetEnumFilterOperators(); + return new List(); }