CustomFiltersComponent.razor
Razor
Copy Code
<style>
    input[type=radio] + label {
        font-weight: normal;
        padding: 2px;
    }

    input[type=radio]:not(:disabled):hover + label {
        color: dimgray;
    }

    input[type=radio]:disabled + label {
        color: lightslategray;
    }

    input[type=radio]:not(:checked) + label {
        color: deepskyblue;
        text-decoration: underline;
    }
</style>

<div style="padding: 8px">
    <div style="padding: 8px">
        @foreach (var option in options)
        {
            <input id="@option.Key"
                   @key="@option"
                   type="radio"
                   style="display: none"
                   name="mode"
                   disabled="@option.Disabled"
                   checked="@option.Selected"
                   @onchange="@(() => SwitchOption(option))" />
            <label for="@option.Key"> @option.Label </label>
        }
    </div>

    <C1DataFilter AutoGenerateFilters="false"
                  AutoApply="false"
                  Style="@(new C1Style("width: 400px;"))"
                  HeaderStyle="@(new C1Style(" display: none"))"
                  ItemStyle="@(new C1Style())"
                  DataFilters="@FiltersFragment"
                  @ref="dataFilter" />
</div>

@code {
    C1DataFilter dataFilter;
    List<Option> options { get; set; }
    Option selectedOption;
    C1FilterDataCollection<object> filtersSource;
    TaskCompletionSource rendering;
    RenderFragment FiltersFragment => selectedOption?.FilterFragment;

    [Parameter]
    public int NonExistentKey { get; set; } = -1;

    [Parameter]
    public string PropertyName { get; set; }

    [Parameter]
    public KeyValuePair<int, string>[] FiltersValues { get; set; }

    [Parameter]
    public IDataCollection<object> Source { get; set; }
    
    protected override void OnInitialized()
    { 
        filtersSource = new C1FilterDataCollection<object>(FiltersValues);
        options = new List<Option>
        {
            new Option {
                Key = "conditionalRatio",
                Label = "FilterByCondition",
                FilterFragment = CreateConditionalFilterOptionFragment(),
                Selected = true,
                ExpressionConverter = new ExpressionConverter{ FiltersValues = FiltersValues}
            },

            new Option {
                Key = "checklistRatio",
                Label = "FilterByValue",
                FilterFragment = CreateChecklistFilterOptionFragment(),
                ExpressionConverter = new CheckListFilterExpressionConverter{ FiltersValues = FiltersValues}
            }
        };

        RenderFragment CreateConditionalFilterOptionFragment() =>
            b =>
            {
                b.OpenComponent<TextFilter>(0);
                b.AddAttribute(1, nameof(TextFilter.PropertyName), "Value");
                b.AddComponentReferenceCapture(2, r => _ = OnOptionFilterAdded());
                b.CloseComponent();
            };

        RenderFragment CreateChecklistFilterOptionFragment() =>
            b =>
            {
                b.OpenComponent<ChecklistFilter>(0);
                b.AddAttribute(1, nameof(ChecklistFilter.PropertyName), "Value");
                b.AddAttribute(2, nameof(ChecklistFilter.ValueMemberPath), "Value");
                b.AddAttribute(3, nameof(ChecklistFilter.DisplayMemberPath), "Value");
                b.AddAttribute(4, nameof(ChecklistFilter.ItemsSource), FiltersValues);
                b.AddAttribute(5, nameof(ChecklistFilter.ShowSearchBox), true);
                b.AddComponentReferenceCapture(6, r => _ = OnOptionFilterAdded());
                b.CloseComponent();
            };            
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            SwitchOption(options.First(o => o.Selected));
        }

        rendering?.TrySetResult();
        await base.OnAfterRenderAsync(firstRender);
    }

    void SwitchOption(Option option)
    {
        selectedOption = option;
        foreach (var opt in options)
        {
            opt.Disabled = opt.Selected = (opt.Key == option.Key);
        }

        // Disable/reset DataFilter before introducing a new filter.
        dataFilter.ItemsSource = null;
        dataFilter.FilterValueChanged -= OnFilterValueChanged;
        StateHasChanged();
    }

    async Task OnOptionFilterAdded()
    {
        // Initiate the DataFilter.
        await InvalidateFilterExpression(selectedOption.ExpressionConverter);
        dataFilter.ItemsSource = filtersSource;

        // Defers handling of the FilterValueChanged event to avoid processing events fired during the enabling of the DataFilter.
        rendering = new TaskCompletionSource();
        StateHasChanged();
        await rendering.Task;

        dataFilter.FilterValueChanged += OnFilterValueChanged;
    }

    async Task InvalidateFilterExpression(ExpressionConverter converter)
    {
        var sourceExpression = Source.GetFilterExpression().GetExpresssionInScope(PropertyName);
        FilterExpression exp = converter.Convert(sourceExpression);
        await filtersSource.FilterAsync(exp);
    }

    async void OnFilterValueChanged(object s, FilterChangedEventArgs a)
    {
        await dataFilter.ApplyFilterAsync();
        var expression = GenerateFilterExpression();

        var currentExpression = Source.GetFilterExpression();
        var filterExpression = currentExpression;
        filterExpression = filterExpression.ReplaceExpressionInScope(PropertyName, expression.GetExpression());

        await Source.FilterAsync(filterExpression);

        CustomCombinationExpressions GenerateFilterExpression()
        {
            var result = new CustomCombinationExpressions() { FilterCombination = FilterCombination.Or };
            foreach (var item in dataFilter.DataCollection)
            {
                var key = ((KeyValuePair<int, string>)item).Key;
                result.Expressions.Add(new OperationExpression() { Value = key, FilterOperation = FilterOperation.Equal, PropertyName = PropertyName });
            }
            if (result.Expressions.Count == 0)
            {
                result.Expressions.Add(new OperationExpression() { Value = NonExistentKey, FilterOperation = FilterOperation.Equal, PropertyName = PropertyName });
            }
            return result;
        }
    }

    class Option
    {
        public string Key { get; set; }
        public string Label { get; set; }
        public bool Selected { get; set; }
        public bool Disabled { get; set; }
        public ExpressionConverter ExpressionConverter { get; set; }
        public RenderFragment FilterFragment { get; set; }
    }

    class CustomCombinationExpressions : CombinationExpression
    {
        public CustomCombinationExpressions() : base() { }
        public CustomCombinationExpressions(FilterCombination filterCombination, IEnumerable<Expression> expressions) : base(filterCombination, expressions) { }

        public FilterExpression GetExpression()
        {
            return this.GetFilterExpression();
        }
    }

    class CustomOperationExpressions : OperationExpression
    {
        public FilterExpression GetExpression()
        {
            return this.GetFilterExpression();
        }
    }

    class CheckListFilterExpressionConverter : ExpressionConverter
    {
        protected override FilterExpression Convert(FilterOperationExpression expression) =>
            new CustomOperationExpressions
                {
                    PropertyName = "Value",
                    FilterOperation = FilterOperation.IsOneOf,
                    Value = FiltersValues.Where(v => v.Key == (int)expression.Value).Select(v => v.Value).ToList()
                }
            .GetExpression();
    }

    class ExpressionConverter
    {
        public KeyValuePair<int, string>[] FiltersValues { get; set; }

        protected virtual FilterExpression Convert(FilterOperationExpression expression) =>
            new CustomCombinationExpressions(
                FilterCombination.Or,
                FiltersValues.Where(v => v.Key == (int)expression.Value)
                            .Select(v => new OperationExpression { PropertyName = "Value", Value = v.Value })
                            .ToList())
            .GetExpression();

        protected virtual FilterExpression Convert(FilterNaryExpression expression) =>
            new CustomCombinationExpressions(
                FilterCombination.Or,
                expression.Expressions.Count == FiltersValues.Length ? null :
                expression.Expressions.Select(oe =>
                                                new OperationExpression
                                                    {
                                                        FilterOperation = FilterOperation.Equal,
                                                        PropertyName = "Value",
                                                        Value = FiltersValues.FirstOrDefault(f => f.Key == (int)((FilterOperationExpression)oe).Value).Value
                                                    }))
            .GetExpression();

        public FilterExpression Convert(FilterExpression expression)
        {
            if (expression is FilterOperationExpression foe)
            {
                return Convert(foe);
            }

            if (expression is FilterNaryExpression fne)
            {
                return Convert(fne);
            }
            return null;
        }
    }
}