Megabit / Blazorise

Blazorise is a component library built on top of Blazor with support for CSS frameworks like Bootstrap, Tailwind, Bulma, AntDesign, and Material.
https://blazorise.com/
Other
3.25k stars 526 forks source link

Datagrid: SortField Feature #625

Closed MitchellNZ closed 2 years ago

MitchellNZ commented 4 years ago

Firstly, sorry for opening so many issues lately, I'm just starting to use this quite heavily! This is more of a question, possibly feature request.

Question: With the DataGrid, is it possible to configure the columns Field attribute to handle a List.Count? If not, is it possible to override with a custom sort function to enable ability to sort by a lists count?

Currently I am doing the following to display a list count in a column:

<DataGridNumericColumn TItem="RealmDto" Field="@nameof(RealmDto.Guilds)" Caption="Guilds" Sortable="true">
    <DisplayTemplate Context="realm">
        @realm.Guilds.Count
    </DisplayTemplate>
</DataGridNumericColumn>

Thanks again!

MitchellNZ commented 4 years ago

@stsrki I ended up creating my own example of this here: https://github.com/MitchellNZ/Blazorise/commit/150c1152326a470e26315426d6f604e33d0435a8

This just allows me to write something like <DataGridNumericColumn ... SortValue="(x) => x.Guilds.Count">

I haven't made a pull request, as I'm not sure if you can already do this some other way - or you had a different preferred option. Please let me know if you have other suggestions.

stsrki commented 4 years ago

I would probably go with the same route as current Field property and create a new SortField. The reason is that this new field also have to have precompiled getter so there is no bottlenecks. The usage in code-behind would be similar to the one you already started.

MitchellNZ commented 4 years ago

@stsrki thanks for the reply.

With going the same route as the Field attribute being a precompiled getter, would it be possible to still do something like I’ve done with the list.Count? Or would you recommend adding the count as a int to my model?

stsrki commented 4 years ago

Doing it with an expression predicate like in your example is easier at first. But the problem is it won't work with the dynamic ReadData event when it's connected to the outside data API.

MitchellNZ commented 4 years ago

Oh I see. Thanks for the explanation. I haven't looked into ReadData much yet. But I will be needing large data reading functionality soon, so I will take a look!

David-Moreira commented 2 years ago

I am starting work on this... I think @MitchellNZ hit the spot. Just introduce a SortExpression which is handled by Blazorise for static data. Otherwise, for ReadData we already make the user have to handle the sorting.

So the info about the SortExpression is passed on to the User, he should then handle it accordingly like it already happens with regular sorting. There's not much more we can do about it I guess. Right?

stsrki commented 2 years ago

@David-Moreira Doing it with the expression would make it impossible with large data and a ReadData callback. How to pass an expression to the backend API?

The best way would be to add SortField. It would behave the same as Field. If defined it would override it when sorting. That's it.

David-Moreira commented 2 years ago

I don't like that at all... A string? While a property accessor is kinda straightforward an expression of a Func<TItem, Object> is not... I doubt people would be enticed to pass an expression string to their api... I personally would probably cheat and send an identifier that tells me which type of sort to apply.

Even for normal use in static data that seems ugly?

In my opinion I'd like to give the users the intelisense from expression.

David-Moreira commented 2 years ago

Ahh I was thinking...

Now I understand what functionality for sorting you were thinking.. Just straight up property / field accessor... OK, I wouldn't mind a string there.

I was thinking in more flexibility sorting by proving a Func with the TItem, the user could do a lot more. But the first one is actually better for compatibility with both static and readdata I guess...

stsrki commented 2 years ago

Now I understand what functionality for sorting you were thinking.. Just straight up property / field accessor... OK, I wouldn't mind a string there.

Yes, and internally it would still compile through the same getter expression builder.


We could add expressions with the signature Func<TItem, Object> for Field and SortField. But then there is another problem that I realized. We would have to also have the setters. Remember that we have an edit mode that needs to properly update TItem. In the end, we would have 6 APIs. Not sure if it's worth it.

David-Moreira commented 2 years ago

Intelisense would be pretty... but nAAAAAAh not worth it, haha unless heavily requested. Going with the first option.

VadimLL commented 3 months ago

What SortField i should specify for example for the following case:

<DataGrid TItem="OrderItem" Data="orderItems" Sortable ShowCaptions>
  <DataGridColumns>
    <DataGridColumn Field="@nameof(OrderItem.Product)" Caption="Name">
      <DisplayTemplate>
        @context.Product.Name
      </DisplayTemplate>
    </DataGridColumn>
    <DataGridNumericColumn Field="@nameof(OrderItem.Quantity)" Caption="Count">
      <DisplayTemplate>
        @context.Quantity
      </DisplayTemplate>
    </DataGridNumericColumn>
  </DataGridColumns>
</DataGrid>

In this case at sort by the 'Name' column i get the error:

System.InvalidOperationException
Inner Exception: ArgumentException: At least one object must implement IComparable.

(sure, because the 'Product' class don't have a comparer)

Where 'OrderItem' is DB entity and i don't want define something like a ViewModel for one.

The mentioned 'SortExpression' would solve this issue.

David-Moreira commented 3 months ago

Field="@nameof(OrderItem.Product)"

Isn't Product a class or record? Why are you not binding the name? Field="Product.Name"

VadimLL commented 3 months ago

David-Moreira, How to binding? SortField="@nameof(OrderItem.Product.Name)" ?

But the 'Name' is not property of 'OrderItem' where we have: <DataGrid TItem="OrderItem" Data="orderItems">

I used well known classes, but here are their cropped definitions for more clarity:

class OrderItem
{
  public Product {get; set;}
  public int Quantity {get; set;}
}

class Product
{
  public string Name {get; set;}
}
David-Moreira commented 3 months ago

Hello, nameof() extracts the name of whatever field or property is indicated. So it will yield "Name" in your case. Which is not the correct path to get to the Product Name. Therefore the error : "But the 'Name' is not property of 'OrderItem'" as it's trying to look that field up directly in OrderItem https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/nameof

Since you have to "traverse a path" to get yo your path name you need to do something like : SortField="Product.Name"

Please let us know if this helped you.

VadimLL commented 3 months ago

David-Moreira, Sorry for delay.

Of course, I understand all this initially.

That's why early I mentioned the proposed 'SortExpression' field that can solve this issue (as a variant).

But I was hoping that you have some alternative way for such cases (wrapping by something like a ViewModel is bad variant for me, my real case much more complicated).

So, as far as I understand, the Blazorise does not have a direct solution to this issue. And may be this can be a feature request?

Indeed the Blazorise DataGrid have custom filtration, have custom calculated column (<DisplayTemplate>), but haven't custom sorting opportunities (for the same custom calculated column).

David-Moreira commented 3 months ago

What do you mean does not have a direct solution? SortField="Product.Name" should work and properly sort your data. Or am I miss understanding your requirement?

VadimLL commented 3 months ago

I'll give the whole code (simplified of course):

class OrderItem
{
  public Product {get; set;}
  public int Quantity {get; set;}
}

class Product
{
  public string Name {get; set;}
}
<DataGrid TItem="OrderItem" Data="orderItems" Sortable ShowCaptions>
  <DataGridColumns>
    <DataGridColumn Field="@nameof(OrderItem.Product)" SortField="@nameof(Product.Name)" Caption="Name">
      <DisplayTemplate>
        @context.Product.Name
      </DisplayTemplate>
    </DataGridColumn>
    <DataGridNumericColumn Field="@nameof(OrderItem.Quantity)" Caption="Count">
      <DisplayTemplate>
        @context.Quantity
      </DisplayTemplate>
    </DataGridNumericColumn>
  </DataGridColumns>
</DataGrid>

When I try to sort by the "Name" column I get the exception (see the screenshot):

sshot-2

As far as understand it's because the "Name" is not a member of the "OrderItem" class.

VadimLL commented 3 months ago

additional info (if it matters): used Blazorise version - 1.5.0 my project - net8 sorting by the "Quantity"("Count") column - ok

the <DataGrid TItem="OrderItem"..> (OrderItems.razor component) placed as <DetailRowTemplate> into another DataGrid as:

<DataGrid TItem="Order" Data="@ordersList"..>
  ..
  <DetailRowTemplate>
    <OrderItems OrderItemsCollection="context.Items" />
  </DetailRowTemplate>
  ..
</DataGrid>

where:

class Order
{
  ..
  public ICollection<OrderItem> Items {get; set;}
}
David-Moreira commented 3 months ago

Field="@nameof(OrderItem.Product)" SortField="@nameof(Product.Name)"

I'm sorry but I just told you, that you are missusing nameof in this case. This snippet is incorrect given your model: Field="@nameof(OrderItem.Product)" SortField="@nameof(Product.Name)" You need to tell the DataGrid the correct path.

Again, please try this. Field="Product.Name" SortField="Product.Name"

VadimLL commented 3 months ago

Mea Culpa! Sorry, Sorry, Sorry! I was inattentive. I thought that the "Field/SortField" is just a field name, but not a full path to field. And thank you very much for your patience and help.

David-Moreira commented 3 months ago

Mea Culpa! Sorry, Sorry, Sorry! I was inattentive. I thought that the "Field/SortField" is just a field name, but not a full path to field. And thank you very much for your patience and help.

Haha no problem. It's ok. Glad you got it working!