Skip to content

Commit

Permalink
Refactor DataPager to rely on Repeater, and not change its data context
Browse files Browse the repository at this point in the history
  • Loading branch information
exyi committed Oct 22, 2023
1 parent 8f9343a commit 8869876
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 215 deletions.
188 changes: 43 additions & 145 deletions src/Framework/Framework/Controls/DataPager.cs

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions src/Framework/Framework/Controls/DataPagerCommands.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
using System.Collections;
using System.Collections.Generic;
using DotVVM.Framework.Binding.Expressions;

namespace DotVVM.Framework.Controls
{
public class DataPagerCommands
public class DataPagerBindings
{
public ICommandBinding? GoToFirstPage { get; init; }
public ICommandBinding? GoToPreviousPage { get; init; }
public ICommandBinding? GoToNextPage { get; init; }
public ICommandBinding? GoToLastPage { get; init; }
public ICommandBinding? GoToPage { get; init; }

public IStaticValueBinding<bool>? IsFirstPage { get; init; }
public IStaticValueBinding<bool>? IsLastPage { get; init; }
public IStaticValueBinding<IEnumerable<int>>? PageNumbers { get; init; }
public IStaticValueBinding<bool>? IsActivePage { get; init; }
public IStaticValueBinding<string>? PageNumberText { get; init; }
}
}
}
81 changes: 60 additions & 21 deletions src/Framework/Framework/Controls/GridViewDataSetBindingProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,71 +10,110 @@
using DotVVM.Framework.Binding.Properties;
using DotVVM.Framework.Compilation;
using DotVVM.Framework.Compilation.ControlTree;
using DotVVM.Framework.Compilation.ControlTree.Resolved;

namespace DotVVM.Framework.Controls;

public class GridViewDataSetBindingProvider
{
private readonly BindingCompilationService service;

private readonly ConcurrentDictionary<(DataContextStack, GridViewDataSetCommandType), DataPagerCommands> dataPagerCommands = new();
private readonly ConcurrentDictionary<(DataContextStack dataContextStack, IValueBinding dataSetBinding, GridViewDataSetCommandType commandType), DataPagerBindings> dataPagerCommands = new();
private readonly ConcurrentDictionary<(DataContextStack, GridViewDataSetCommandType), GridViewCommands> gridViewCommands = new();

public GridViewDataSetBindingProvider(BindingCompilationService service)
{
this.service = service;
}

public DataPagerCommands GetDataPagerCommands(DataContextStack dataContextStack, GridViewDataSetCommandType commandType)
public DataPagerBindings GetDataPagerCommands(DataContextStack dataContextStack, IValueBinding dataSetBinding, GridViewDataSetCommandType commandType)
{
return dataPagerCommands.GetOrAdd((dataContextStack, commandType), _ => GetDataPagerCommandsCore(dataContextStack, commandType));
return dataPagerCommands.GetOrAdd((dataContextStack, dataSetBinding, commandType), x => GetDataPagerCommandsCore(x.dataContextStack, x.dataSetBinding, x.commandType));
}

public GridViewCommands GetGridViewCommands(DataContextStack dataContextStack, GridViewDataSetCommandType commandType)
{
return gridViewCommands.GetOrAdd((dataContextStack, commandType), _ => GetGridViewCommandsCore(dataContextStack, commandType));
}

private DataPagerCommands GetDataPagerCommandsCore(DataContextStack dataContextStack, GridViewDataSetCommandType commandType)
private DataPagerBindings GetDataPagerCommandsCore(DataContextStack dataContextStack, IValueBinding dataSetBinding, GridViewDataSetCommandType commandType)
{
ICommandBinding? GetCommandOrNull<T>(ParameterExpression dataSetParam, DataContextStack dataContextStack, string methodName, params Expression[] arguments)
var dataSetExpr = dataSetBinding.GetProperty<ParsedExpressionBindingProperty>().Expression;
ICommandBinding? GetCommandOrNull<T>(DataContextStack dataContextStack, string methodName, params Expression[] arguments)
{
return typeof(T).IsAssignableFrom(dataSetParam.Type)
? CreateCommandBinding<T>(commandType, dataSetParam, dataContextStack, methodName, arguments)
return typeof(T).IsAssignableFrom(dataSetExpr.Type)
? CreateCommandBinding<T>(commandType, dataSetExpr, dataContextStack, methodName, arguments)
: null;
}

IStaticValueBinding<TResult>? GetValueBindingOrNull<T, TResult>(Expression<Func<T, TResult>> expression)
{
if (typeof(T).IsAssignableFrom(dataSetExpr.Type))
{
return (IStaticValueBinding<TResult>)ValueOrBindingExtensions.SelectImpl(dataSetBinding, expression);
}
else
{
return null;
}
}

ParameterExpression CreateParameter(DataContextStack dataContextStack, string name = "_this")
{
return Expression.Parameter(dataContextStack.DataContextType, name).AddParameterAnnotation(new BindingParameterAnnotation(dataContextStack));
}

return new DataPagerCommands()
var pageIndexDataContext = DataContextStack.CreateCollectionElement(
typeof(int), dataContextStack
);

return new DataPagerBindings()
{
GoToFirstPage = GetCommandOrNull<IPageableGridViewDataSet<IPagingFirstPageCapability>>(
CreateParameter(dataContextStack),
dataContextStack,
nameof(IPagingFirstPageCapability.GoToFirstPage)),

GoToPreviousPage = GetCommandOrNull<IPageableGridViewDataSet<IPagingPreviousPageCapability>>(
CreateParameter(dataContextStack),
dataContextStack,
nameof(IPagingPreviousPageCapability.GoToPreviousPage)),

GoToNextPage = GetCommandOrNull<IPageableGridViewDataSet<IPagingNextPageCapability>>(
CreateParameter(dataContextStack),
dataContextStack,
nameof(IPagingNextPageCapability.GoToNextPage)),

GoToLastPage = GetCommandOrNull<IPageableGridViewDataSet<IPagingLastPageCapability>>(
CreateParameter(dataContextStack),
dataContextStack,
nameof(IPagingLastPageCapability.GoToLastPage)),

GoToPage = GetCommandOrNull<IPageableGridViewDataSet<IPagingPageIndexCapability>>(
CreateParameter(dataContextStack, "_parent"),
DataContextStack.Create(typeof(int), dataContextStack),
pageIndexDataContext,
nameof(IPagingPageIndexCapability.GoToPage),
CreateParameter(DataContextStack.Create(typeof(int), dataContextStack), "_thisIndex"))
CreateParameter(pageIndexDataContext, "_thisIndex")),

IsFirstPage =
GetValueBindingOrNull<IPageableGridViewDataSet<IPagingFirstPageCapability>, bool>(d => d.PagingOptions.IsFirstPage) ??
GetValueBindingOrNull<IPageableGridViewDataSet<IPagingPreviousPageCapability>, bool>(d => d.PagingOptions.IsFirstPage),

IsLastPage =
GetValueBindingOrNull<IPageableGridViewDataSet<IPagingLastPageCapability>, bool>(d => d.PagingOptions.IsLastPage) ??
GetValueBindingOrNull<IPageableGridViewDataSet<IPagingNextPageCapability>, bool>(d => d.PagingOptions.IsLastPage),

PageNumbers =
GetValueBindingOrNull<IPageableGridViewDataSet<IPagingPageIndexCapability>, IEnumerable<int>>(d => d.PagingOptions.NearPageIndexes),

IsActivePage = // _this == _parent.DataSet.PagingOptions.PageIndex
typeof(IPageableGridViewDataSet<IPagingPageIndexCapability>).IsAssignableFrom(dataSetExpr.Type)
? new ValueBindingExpression<bool>(service, new object[] {
pageIndexDataContext,
new ParsedExpressionBindingProperty(Expression.Equal(
CreateParameter(pageIndexDataContext, "_thisIndex"),
Expression.Property(Expression.Property(dataSetExpr, "PagingOptions"), "PageIndex")
)),
})
: null,

PageNumberText =
service.Cache.CreateValueBinding<string>("_this + 1", pageIndexDataContext)
};
}

Expand Down Expand Up @@ -103,27 +142,27 @@ ParameterExpression CreateParameter(DataContextStack dataContextStack)
};
}

private ICommandBinding CreateCommandBinding<TDataSetInterface>(GridViewDataSetCommandType commandType, ParameterExpression dataSetParam, DataContextStack dataContextStack, string methodName, Expression[] arguments, Func<Expression, Expression>? transformExpression = null)
private ICommandBinding CreateCommandBinding<TDataSetInterface>(GridViewDataSetCommandType commandType, Expression dataSet, DataContextStack dataContextStack, string methodName, Expression[] arguments, Func<Expression, Expression>? transformExpression = null)
{
var body = new List<Expression>();

// get concrete type from implementation of IXXXableGridViewDataSet<?>
var optionsConcreteType = GetOptionsConcreteType<TDataSetInterface>(dataSetParam.Type, out var optionsProperty);
var optionsConcreteType = GetOptionsConcreteType<TDataSetInterface>(dataSet.Type, out var optionsProperty);

// call dataSet.XXXOptions.Method(...);
var callMethodOnOptions = Expression.Call(
Expression.Convert(Expression.Property(dataSetParam, optionsProperty), optionsConcreteType),
Expression.Convert(Expression.Property(dataSet, optionsProperty), optionsConcreteType),
optionsConcreteType.GetMethod(methodName)!,
arguments);
body.Add(callMethodOnOptions);

if (commandType == GridViewDataSetCommandType.Command)
{
// if we are on a server, call the dataSet.RequestRefresh if supported
if (typeof(IRefreshableGridViewDataSet).IsAssignableFrom(dataSetParam.Type))
if (typeof(IRefreshableGridViewDataSet).IsAssignableFrom(dataSet.Type))
{
var callRequestRefresh = Expression.Call(
Expression.Convert(dataSetParam, typeof(IRefreshableGridViewDataSet)),
Expression.Convert(dataSet, typeof(IRefreshableGridViewDataSet)),
typeof(IRefreshableGridViewDataSet).GetMethod(nameof(IRefreshableGridViewDataSet.RequestRefresh))!
);
body.Add(callRequestRefresh);
Expand All @@ -146,7 +185,7 @@ private ICommandBinding CreateCommandBinding<TDataSetInterface>(GridViewDataSetC
else if (commandType == GridViewDataSetCommandType.StaticCommand)
{
// on the client, wrap the call into client-side loading procedure
body.Add(CallClientSideLoad(dataSetParam));
body.Add(CallClientSideLoad(dataSet));
Expression expression = Expression.Block(body);
if (transformExpression != null)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Tests/ControlTests/DataPagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public async Task CommandDataPager()
.Select(c => (c.control, c.command, str: c.command.GetProperty<ParsedExpressionBindingProperty>().Expression.ToCSharpString().Trim().TrimEnd(';')))
.OrderBy(c => c.str)
.ToArray();
check.CheckLines(commandExpressions.Select(c => c.str), checkName: "command-bindings", fileExtension: "txt");
check.CheckLines(commandExpressions.DistinctBy(c => c.command).Select(c => c.str), checkName: "command-bindings", fileExtension: "txt");

Check failure on line 41 in src/Tests/ControlTests/DataPagerTests.cs

View workflow job for this annotation

GitHub Actions / .NET unit tests (windows-2022)

'(DotvvmControl control, ICommandBinding command, string str)[]' does not contain a definition for 'DistinctBy' and no accessible extension method 'DistinctBy' accepting a first argument of type '(DotvvmControl control, ICommandBinding command, string str)[]' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 41 in src/Tests/ControlTests/DataPagerTests.cs

View workflow job for this annotation

GitHub Actions / .NET unit tests (windows-2022)

'(DotvvmControl control, ICommandBinding command, string str)[]' does not contain a definition for 'DistinctBy' and no accessible extension method 'DistinctBy' accepting a first argument of type '(DotvvmControl control, ICommandBinding command, string str)[]' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 41 in src/Tests/ControlTests/DataPagerTests.cs

View workflow job for this annotation

GitHub Actions / Build all projects without errors

'(DotvvmControl control, ICommandBinding command, string str)[]' does not contain a definition for 'DistinctBy' and no accessible extension method 'DistinctBy' accepting a first argument of type '(DotvvmControl control, ICommandBinding command, string str)[]' could be found (are you missing a using directive or an assembly reference?)

check.CheckString(r.FormattedHtml, fileExtension: "html");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
((PagingOptions)_parent.PagingOptions).GoToPage(_thisIndex);
((IRefreshableGridViewDataSet)_parent).RequestRefresh()
((PagingOptions)_this.PagingOptions).GoToFirstPage();
((IRefreshableGridViewDataSet)_this).RequestRefresh()
((PagingOptions)_this.PagingOptions).GoToLastPage();
((IRefreshableGridViewDataSet)_this).RequestRefresh()
((PagingOptions)_this.PagingOptions).GoToNextPage();
((IRefreshableGridViewDataSet)_this).RequestRefresh()
((PagingOptions)_this.PagingOptions).GoToPreviousPage();
((IRefreshableGridViewDataSet)_this).RequestRefresh()
((PagingOptions)_this.Customers.PagingOptions).GoToFirstPage();
((IRefreshableGridViewDataSet)_this.Customers).RequestRefresh()
((PagingOptions)_this.Customers.PagingOptions).GoToLastPage();
((IRefreshableGridViewDataSet)_this.Customers).RequestRefresh()
((PagingOptions)_this.Customers.PagingOptions).GoToNextPage();
((IRefreshableGridViewDataSet)_this.Customers).RequestRefresh()
((PagingOptions)_this.Customers.PagingOptions).GoToPage(_thisIndex);
((IRefreshableGridViewDataSet)_this.Customers).RequestRefresh()
((PagingOptions)_this.Customers.PagingOptions).GoToPreviousPage();
((IRefreshableGridViewDataSet)_this.Customers).RequestRefresh()
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
<html>
<head></head>
<body>
<ul data-bind="dotvvm-gridviewdataset: { dataSet: Customers() }, visible: (Customers()).PagingOptions().PagesCount() > 1, with: Customers">
<li data-bind="css: { 'disabled': $gridViewDataSetHelper.dataSet.PagingOptions().IsFirstPage() }">
<a href="javascript:;" onclick="dotvvm.postBack(this,[&quot;$parent.Customers&quot;],&quot;YNrHJz3wB1224tUc&quot;,&quot;&quot;,null,[],[],undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;">««</a>
<ul data-bind="dotvvm-gridviewdataset: { dataSet: Customers() }, visible: (Customers()).PagingOptions().PagesCount() > 1">
<li class="disabled" data-bind="css: { disabled: Customers()?.PagingOptions()?.IsFirstPage }">
<a href="javascript:;" onclick="dotvvm.postBack(this,[],&quot;XV5Ciz2t6C9bIsEQ&quot;,&quot;&quot;,null,[],[],undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;">««</a>
</li>
<li data-bind="css: { 'disabled': $gridViewDataSetHelper.dataSet.PagingOptions().IsFirstPage() }">
<a href="javascript:;" onclick="dotvvm.postBack(this,[&quot;$parent.Customers&quot;],&quot;YNrHJz3wB1224tUc&quot;,&quot;&quot;,null,[],[],undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;">«</a>
<li class="disabled" data-bind="css: { disabled: Customers()?.PagingOptions()?.IsFirstPage }">
<a href="javascript:;" onclick="dotvvm.postBack(this,[],&quot;PxPMW1CzRuBPCnTL&quot;,&quot;&quot;,null,[],[],undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;">«</a>
</li>
<!-- ko foreach: $gridViewDataSetHelper.dataSet.PagingOptions().NearPageIndexes -->
<li class="active" data-bind="visible: $data == $parent.PagingOptions().PageIndex()">
<span data-bind="text: 1+$data+&quot;&quot;"></span>
</li>
<li data-bind="visible: $data != $parent.PagingOptions().PageIndex()">
<a data-bind="text: 1+$data+&quot;&quot;" href="javascript:;" onclick="dotvvm.postBack(this,[&quot;$parent.Customers&quot;,&quot;PagingOptions.NearPageIndexes[$index]&quot;],&quot;ZwmBknlLAz5Evw5W&quot;,&quot;&quot;,null,[],[],undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;"></a>
<!-- ko foreach: { data: Customers()?.PagingOptions()?.NearPageIndexes } -->
<li data-bind="css: { active: $data == $parent.Customers()?.PagingOptions()?.PageIndex() }">
<a data-bind="text: $data + 1" href="javascript:;" onclick="dotvvm.postBack(this,[&quot;Customers()?.PagingOptions()?.NearPageIndexes/[$index]&quot;],&quot;3DH6e+4ylYjhk3T4&quot;,&quot;&quot;,null,[],[],undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;"></a>
</li>
<!-- /ko -->
<li data-bind="css: { 'disabled': PagingOptions().IsLastPage() }">
<a href="javascript:;" onclick="dotvvm.postBack(this,[&quot;$parent.Customers&quot;],&quot;YNrHJz3wB1224tUc&quot;,&quot;&quot;,null,[],[],undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;">»</a>
<li data-bind="css: { disabled: Customers()?.PagingOptions()?.IsLastPage }">
<a href="javascript:;" onclick="dotvvm.postBack(this,[],&quot;uY1Q3Oz/wIMX+SxN&quot;,&quot;&quot;,null,[],[],undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;">»</a>
</li>
<li data-bind="css: { 'disabled': PagingOptions().IsLastPage() }">
<a href="javascript:;" onclick="dotvvm.postBack(this,[&quot;$parent.Customers&quot;],&quot;YNrHJz3wB1224tUc&quot;,&quot;&quot;,null,[],[],undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;">»»</a>
<li data-bind="css: { disabled: Customers()?.PagingOptions()?.IsLastPage }">
<a href="javascript:;" onclick="dotvvm.postBack(this,[],&quot;aFLXuxCfzdFlmtnD&quot;,&quot;&quot;,null,[],[],undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;">»»</a>
</li>
</ul>
</body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,10 @@
"mappingMode": "InnerElement",
"onlyHardcoded": true
},
"LoadData": {
"type": "DotVVM.Framework.Binding.Expressions.ICommandBinding, DotVVM.Framework",
"onlyBindings": true
},
"NextPageTemplate": {
"type": "DotVVM.Framework.Controls.ITemplate, DotVVM.Framework",
"mappingMode": "InnerElement",
Expand Down Expand Up @@ -1641,23 +1645,6 @@
"isActive": true,
"isAttached": true
}
},
"DotvvmMarkupControl-33jwRoNrnlbAOVpOnCErXw==": {
"ShowDescription": {
"type": "System.Boolean",
"defaultValue": false
}
},
"DotvvmMarkupControl-koCHqjx2oIk1rVwG1PzLJQ==": {
"Click": {
"type": "DotVVM.Framework.Binding.Expressions.Command, DotVVM.Framework",
"isCommand": true
}
},
"DotvvmMarkupControl-YYPITyOzVEL518wclEMJZw==": {
"SomeProperty": {
"type": "System.String"
}
}
},
"capabilities": {
Expand Down
Loading

0 comments on commit 8869876

Please sign in to comment.