RadzenDataFilter - Sort filters

Hi,

I start a new topic here to try discuss again in a new way to help understand problem.

I have a RadzenDataFilter with a lot of filters. I have created custom RadzenDataFilterProperty to handle some of them. For example, I have a bool custom filter to set the template as a drop down list Yes/No instead of a checkbox.

If you try adding them like that :

<RadzenDataFilterProperty Property="@nameof(VmUser.LastName)" Title="Last Name"></RadzenDataFilterProperty>
            <RadzenDataFilterProperty Property="@nameof(VmUser.FirstName)"
                                      Title="First Name"></RadzenDataFilterProperty>
            <RadzenDataFilterProperty Property="@nameof(VmUser.Email)" Title="Email"></RadzenDataFilterProperty>
            <RadzenDataFilterBoolProperty Property="@nameof(VmUser.IsActive)"
                                          Title="Is Active"></RadzenDataFilterBoolProperty>
            <RadzenDataFilterBoolProperty Property="@nameof(VmUser.IsAdmin)"
                                          Title="Is Admin"></RadzenDataFilterBoolProperty>
            <RadzenDataFilterProperty Property="@nameof(VmUser.UserName)" Title="User Name"></RadzenDataFilterProperty>

The result will be

Even if you try with a RenderFragment, the order will not change in this case

I have created a repro solution if you want to see it :
RadzenSample

If you're agree, I can take the previous PR opened for this topic and improve it a little bit, I think we can set the option to use sort on the RadzenDataFilter to not use them if not required

I'll repeat what I've already posted:
You can always use for loop over some ordered enumerable to specify desired order. For example:

    <RadzenDataFilter Data="@orders">
        <Properties>
            @for(var item in items.OrderBy(i => i.MyProperty))
            {
                 <RadzenDataFilterProperty Property="@item.Property" Title="@item.Title" />
            }            
        </Properties>
    </RadzenDataFilter>

Furthermore if you have additional conditional items you can use the same technique demonstrated on this demo with DataGrid columns - will work in the same way with DataFilter and RadzenDataFilterProperty.

look at the sample, I set the sample with direct fragment and like your demo the RenderFragment way.
It's not working if you set the RadzenDataProperty razor part in the sub component.
And if you can't loop in anything because you have properties to filter and some other to not filter, with a complex view model with multiple nested view model in it, you can't.

This is what I see in your app:

and if you look at the order, User Name should be the last filter. but as my bool custom filter is a sub component, the 2 bools are generated at the end so the drop down show them at last.

Imagine now in a more complex use case, where I have 150-200 filters, some of them are easy (string prop), some of them are like the boolean in the sample, others are custom enum filters, etc... I can't predict the way they will be render and so can't set the order in any way

Here is an example of the approach I'm describing for third time:

private RenderFragment RenderFilters()
{
    IEnumerable<string> filters = new string[] 
    { 
        nameof(VmUser.LastName),
        nameof(VmUser.FirstName),
        nameof(VmUser.Email),
        nameof(VmUser.IsActive),
        nameof(VmUser.IsAdmin),
        nameof(VmUser.UserName)
    };

    return __builder =>
    {

        <text>
            @foreach (var item in filters)
            {
                if (item == nameof(VmUser.IsActive) || item == nameof(VmUser.IsAdmin))
                {
                    <RadzenDataFilterBoolProperty TItem="VmUser" Property="@item" Title="@item" />
                }
                else
                {
                    <RadzenDataFilterProperty TItem="VmUser" Property="@item" Title="@item" />
                }
            }

        </text>
    };
}

Instead of declaring 200 filters by hand you can create a collection with desired filter options like name, title, type, whatever is needed, order the collection in the way you need and use it when needed.

in your screenshot, I can see that UserName is after Email and IsActive / IsAdmin are at the end (not shown in your screen), so the order is not working more with your IEnumerable properties

You added well UserName at the end, and it's not the last filter

My example was just an example, here is the complete code with name, title, type and order:

private RenderFragment RenderFilters()
{
    IEnumerable<Tuple<string, string, Type, int>> filters = new List<Tuple<string, string, Type, int>>()
    { 
        new Tuple<string, string, Type, int>(nameof(VmUser.FirstName), nameof(VmUser.FirstName), typeof(string), 0),
        new Tuple<string, string, Type, int>(nameof(VmUser.LastName), nameof(VmUser.LastName), typeof(string), 1),
        new Tuple<string, string, Type, int>(nameof(VmUser.Email), nameof(VmUser.Email), typeof(string), 2),
        new Tuple<string, string, Type, int>(nameof(VmUser.UserName), nameof(VmUser.UserName), typeof(string), 3),
        new Tuple<string, string, Type, int>(nameof(VmUser.IsActive), nameof(VmUser.IsActive), typeof(bool), 5),
        new Tuple<string, string, Type, int>(nameof(VmUser.IsAdmin), nameof(VmUser.IsAdmin), typeof(bool), 6),
    };

    return __builder =>
    {

        <text>
            @foreach (var item in filters.OrderBy(i => i.Item4))
            {
                if (item.Item3 == typeof(bool))
                {
                    <RadzenDataFilterBoolProperty TItem="VmUser" Property="@item.Item1" Title="@item.Item2" />
                }
                else
                {
                    <RadzenDataFilterProperty TItem="VmUser" Property="@item.Item1" Title="@item.Item2" />
                }
            }

        </text>
    };
}


The goal is to have IsActive/IsAdmin before UserName

So I reorder your tuple sample :

private RenderFragment RenderFilters()
    {
        IEnumerable<Tuple<string, string, Type, int>> filters = new List<Tuple<string, string, Type, int>>()
        {
            new Tuple<string, string, Type, int>(nameof(VmUser.FirstName), nameof(VmUser.FirstName), typeof(string), 0),
            new Tuple<string, string, Type, int>(nameof(VmUser.LastName), nameof(VmUser.LastName), typeof(string), 1),
            new Tuple<string, string, Type, int>(nameof(VmUser.Email), nameof(VmUser.Email), typeof(string), 2),
            new Tuple<string, string, Type, int>(nameof(VmUser.UserName), nameof(VmUser.UserName), typeof(string), 6),
            new Tuple<string, string, Type, int>(nameof(VmUser.IsActive), nameof(VmUser.IsActive), typeof(bool), 3),
            new Tuple<string, string, Type, int>(nameof(VmUser.IsAdmin), nameof(VmUser.IsAdmin), typeof(bool), 4),
        };

        return __builder =>
        {
            <text>
                @foreach (var item in filters.OrderBy(i => i.Item4))
                {
                    if (item.Item3 == typeof(bool))
                    {
                        <RadzenDataFilterBoolProperty TItem="VmUser" Property="@item.Item1" Title="@item.Item2"/>
                    }
                    else
                    {
                        <RadzenDataFilterProperty TItem="VmUser" Property="@item.Item1" Title="@item.Item2"/>
                    }
                }

            </text>
        };
    }

And... not working :

I see what you are after however Blazor rendering is optimized by component type and to have full control of the render order of the components you need to use different composition for custom RadzenDataFilterProperty components - i.e. instead of different component types for each property type you can have single custom component type with different rendering depending on property type. For example:

MyRadzenDataFilterProperty.razor

@using Radzen
@typeparam TItem

@if (Type == typeof(bool))
{
<RadzenDataFilterProperty
    TItem="TItem"
    Property="@Property"
    Title="@Title"
    FilterOperator="FilterOperator.Equals"
    Type="@typeof(bool)">
    <FilterTemplate>
        <RadzenDropDown Style="width:100%;"
                        AllowFiltering="false"
                        FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive"
                        Change="@(args => OnChange(args))"
                        @bind-Value="@context.FilterValue"
                        Data="@(new[] { true, false })"
                        AllowClear="true"
                        Multiple="false">
            <Template Context="filterValue">
                @{
                    var filterValueCasted = (bool)filterValue;
                }
                @if (filterValueCasted)
                {
                    <span>Yes</span>
                }
                else
                {
                    <span>No</span>
                }
            </Template>
            <ValueTemplate Context="filterValue">
                @{
                    var filterValueCasted = (bool)filterValue;
                }
                @if (filterValueCasted)
                {
                    <span>Yes</span>
                }
                else
                {
                    <span>No</span>
                }
            </ValueTemplate>
        </RadzenDropDown>
    </FilterTemplate>
</RadzenDataFilterProperty>
}
else
{
    <RadzenDataFilterProperty TItem="TItem" Property="@Property" Title="@Title"/>
}

@code {
    [Parameter]
    public Type Type { get; set; }

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

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

    [Parameter]
    public EventCallback<bool> ValueChanged { get; set; }
    
    private void OnChange(object value)
    {
        if (value is bool boolValue)
        {
            ValueChanged.InvokeAsync(boolValue);
        }
    }
}

Home.razor

@page "/"
@using Microsoft.AspNetCore.Components.Rendering
@using RadzenDataFilterSort.Model

<PageTitle>Home</PageTitle>

<RadzenCard>
    <RadzenText TextStyle="TextStyle.Subtitle2" TagName="TagName.H3">Render Fragment</RadzenText>
    <RadzenDataFilter Auto="true"
    TItem="VmUser"
    Data="users"
    AllowColumnFiltering="@true">
        <Properties>
            @RenderFilters()
        </Properties>
    </RadzenDataFilter>
</RadzenCard>

@code{

    private List<VmUser> users = new List<VmUser>();

    private RenderFragment RenderFilters()
    {
        IEnumerable<Tuple<string, string, Type, int>> filters = new List<Tuple<string, string, Type, int>>()
        {
            new Tuple<string, string, Type, int>(nameof(VmUser.FirstName), nameof(VmUser.FirstName), typeof(string), 0),
            new Tuple<string, string, Type, int>(nameof(VmUser.LastName), nameof(VmUser.LastName), typeof(string), 1),
            new Tuple<string, string, Type, int>(nameof(VmUser.Email), nameof(VmUser.Email), typeof(string), 2),
            new Tuple<string, string, Type, int>(nameof(VmUser.UserName), nameof(VmUser.UserName), typeof(string), 6),
            new Tuple<string, string, Type, int>(nameof(VmUser.IsActive), nameof(VmUser.IsActive), typeof(bool), 3),
            new Tuple<string, string, Type, int>(nameof(VmUser.IsAdmin), nameof(VmUser.IsAdmin), typeof(bool), 4),
        };

        return __builder =>
        {
            <text>
                @foreach (var item in filters.OrderBy(i => i.Item4))
                {
                    <MyRadzenDataFilterProperty TItem="VmUser" Type="@item.Item3" 
                        Property="@item.Item1" Title="@item.Item2"/>
                }
            </text>
        };
    }
}

The result:

Using such approach you have full flexibility what and where to render and again it's far more flexible compared to hardcoding order index values in 200+ component declarations. Next day you can decide to order the filters in descending alphabet order and you will be able to do it with the approach I've demonstrated.

you're suggestion can be a good idea, but it can be hard to manipulate in my case. I have condition to generate some filters or not, have same type but not the same template (boolean in a format like in the sample and in another format).

Ordering by index, define them on all properties by step (first at 1, second at 10, etc...) to be able to include some additional property between sounds easy for me.

But ok, let's move forward, I will think about my 2 options : find a way to work like you want, or fork the library