Havit.Blazor icon indicating copy to clipboard operation
Havit.Blazor copied to clipboard

[HxGrid] A way to persist grid state (sorting, current page) ?

Open Apskaita5 opened this issue 3 years ago • 12 comments

Is there a way to persist (e.g. in a cookie) the current state of the grid? It would be nice if the grid is initialized/restored to its last state after a user navigates back. I understand that the grid state is not enough by itself, but it's current page and sorting are important elements for that purpose.

Apskaita5 avatar Sep 06 '22 13:09 Apskaita5

You can use the CurrentUserState parameter and bind it to persisted field/property of your choice. E.g. serialize it to localStorage, cookie or just to any application-scoped storage (static field, singleton, ...).

hakenr avatar Sep 06 '22 13:09 hakenr

Got a warning Component parameter 'CurrentUserState' should not be set outside of its component. Is it (CurrentUserState) json serializable?

Apskaita5 avatar Sep 06 '22 13:09 Apskaita5

You have to use the @bind-CurrentUserState="myField" to bind to the parameter safely. It should be serializable (let me know if you find the opposite).

hakenr avatar Sep 06 '22 16:09 hakenr

Not serializable.

When using Default newtonsoft json serialization settings, exception: "Self referencing loop detected for property 'ManifestModule' with type 'System.Reflection.RuntimeModule'. Path 'Sorting[0].SortKeySelector.Body.Operand.Member.Module.Assembly'."

When using ReferenceLoopHandling.Serialize, program goes into infinite recursion.

When using ReferenceLoopHandling.Ignore, serialization takes suspiciously long time and ends with an exception: "Unable to find a constructor to use for type System.Linq.Expressions.Expression1[System.Func2[A5Soft.ClaimManager.Domain.Claims.Queries.FullCaseStateQueryResult,System.IComparable]]. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'Sorting[0].SortKeySelector.Type', line 1, position 71."

Most likely cause - SortKeySelector property of Expression type.

P.S. it only happens if the grid is sorted by some column. P.P.S. You could actually serialize Expression (e.g. https://github.com/6bee/Remote.Linq), but that would open a huge security hole...

Apskaita5 avatar Sep 07 '22 08:09 Apskaita5

Thanks for opening this issue and testing the serializability. We will prepare some guidance and/or adjustments to enable this use-case (preserving the grid state).

Just to be sure. Do you need to preserve the state over application restart (i.e. next user visit) or just within single application run (i.e. user travels across the app and returns to the screen with grid)? For the second, you can just save the state in app/circuit/scoped service.

cc @jirikanda

hakenr avatar Sep 07 '22 10:09 hakenr

I thought about standard user options persistence over application restart but the idea with a scoped service will do for starters.

Apskaita5 avatar Sep 07 '22 10:09 Apskaita5

We had a short chat (ad hoc design meeting :-D) with @jirikanda. Recording major thoughts for future:

  1. It should be possible to replace the SortItem usage in GridUserState with some sort of alternative SortItemDto where the Expression SortKeySelector (and possibly SortString) will be replaced with some sort of serializable reference to the originating GridColumn (such as ColumnId) => allowing serialization of GridUserState
  2. The HxGrid itself would have to interpret such reference when building GridDataProviderRequest and pickup the Expression ColumnBase.SortKeySelector from the corresponding columns.

Unfortunately, this is not on top of our priority list, as we do not feel any major demand for the related use-case. Pull-request will be welcomed, sponsorship might also help to prioritize this on our side. :-D

hakenr avatar Sep 08 '22 13:09 hakenr

Not a priority for me as well ;) It turned out that my users themselves are not sure if they want to save these specific GUI settings between sessions at all. They are quite happy about single session solution.

I think the feature could be implemented by extension methods without touching HxGrid code. The SortItemDto <-> Dto conversions could be done externally if Expression SortKeySelector could be semantically simplified to a property name (string). Does it always point to a property?

Apskaita5 avatar Sep 08 '22 14:09 Apskaita5

I think the feature could be implemented by extension methods without touching HxGrid code. The SortItemDto <-> Dto conversions could be done externally if Expression SortKeySelector could be semantically simplified to a property name (string). Does it always point to a property?

Unfortunately not. The SortKeySelector can be any expression and in some scenarios it is being used in more complicated way. For the simple sorting "markers" (e.g. for API calls), use SortString where the interpretation of what is the meaning od the SortString value is up to the final data provider.

hakenr avatar Sep 08 '22 14:09 hakenr

Took a quick look at your code. I think if you implement HxGrid method

public IEnumerable<Expression<Func<TItem, IComparable>>> GetAvailableSortings()
=> columnsList.Where(c => null != c.SortKeySelector).Select(c => c.SortKeySelector);

the serialization could be simplified by trivial ToString. I.e. you persist SortKeySelector.ToString() and after deserialization just pick one of the existing ones.

Apskaita5 avatar Sep 08 '22 15:09 Apskaita5

the serialization could be simplified by trivial ToString. I.e. you persist SortKeySelector.ToString() and after deserialization just pick one of the existing ones

👍 Also one of the options to be able to rebuild the original state or pick the right expression for the sorting.

hakenr avatar Sep 08 '22 15:09 hakenr

...but still we have to take in account the scenarios where the CurrentUserState of the grid is not only saved and restored, but also can be manipulated (e.g. external controls for setting the sorting, etc.). It has to be intuitive for the developers, what the GridUserState holds and how to manipulate it (I admit, that the current implementation is not that friendly for such scenarios in combination with Expression SortKeySelector, we use it with SortString).

hakenr avatar Sep 08 '22 15:09 hakenr

...but still we have to take in account the scenarios where the CurrentUserState of the grid is not only saved and restored, but also can be manipulated (e.g. external controls for setting the sorting, etc.). It has to be intuitive for the developers, what the GridUserState holds and how to manipulate it (I admit, that the current implementation is not that friendly for such scenarios in combination with Expression SortKeySelector, we use it with SortString).

Yes, I am looking for functionality to manipulate sorting from the c# code. Tried it with the bind suggestions on CurrentUserState in this post, but it didn't help. I will wait until this feature is implemented.

jim-craane avatar Jan 31 '23 13:01 jim-craane

@jim-craane The functionality was already published in one of earlier releases and is part of the documentation (just forgot to close this issue). See https://havit.blazor.eu/components/HxGrid#persisting-state

On one of our projects, we do manipulate the CurrentUserState from outside. Fragments you might be interested in (this time we didn't use the column Id, but a enum-based SortString for every single column, which is then switch-ed on server side when preparing data for the grid):

private void HandleSortingChanged()
{
	var sorting = clientCardListFilterSortModel.ToSortingItems();
	currentUserState = currentUserState with { Sorting = sorting };
}
<HxGridColumn ... SortString="@nameof(ClientCardSort.LegalType)" />
public IReadOnlyList<SortingItem<ClientCardListItem>> ToSortingItems()
{
	var result = new List<SortingItem<ClientCardListItem>>();

	if (Sort.Value is not null)
	{
		result.Add(new SortingItem<ClientCardListItem>(
			Enum.GetName(Sort.Value.SortExpression),
			null, Sort.Value.SortDirection, null));
	}

	return result;
}
public enum ClientCardSort
{
	[Description("Podle příjmení")] LastName,
	[Description("Podle města")] City,
	[Description("Podle typu subjektu")] LegalType,
	[Description("Podle rodného čísla")] BirthNumber,
	[Description("Podle hodnocení")] Rating,
	[Description("Podle platnosti klientské karty")] CardValidity,
	[Description("Podle poslední aktivity")] LastActivity,
	[Description("Podle datumu vytvoření")] BtCreated,
	[Description("Podle splnění GDPR")] HasConsent
}

hakenr avatar Feb 03 '23 15:02 hakenr