Skip to content

Commit cfae6e1

Browse files
committed
DataContext resource binding: GridView support
1 parent 3bb96ab commit cfae6e1

File tree

11 files changed

+106
-40
lines changed

11 files changed

+106
-40
lines changed

src/DynamicData/DynamicData/DataContextStackHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@ public static DataContextStack CreateChildStack(this DataContextStack dataContex
4040
dataContextStack.BindingPropertyResolvers);
4141
}
4242
}
43-
}
43+
}

src/Framework/Framework/Binding/BindingHelper.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,10 +340,12 @@ public static Func<TParam, TResult> Cache<TParam, TResult>(this Func<TParam, TRe
340340
return f => cache.GetOrAdd(f, func);
341341
}
342342

343-
public static IValueBinding GetThisBinding(this DotvvmBindableObject obj)
343+
public static IStaticValueBinding GetThisBinding(this DotvvmBindableObject obj)
344344
{
345-
var dataContext = obj.GetValueBinding(DotvvmBindableObject.DataContextProperty);
346-
return (IValueBinding)dataContext!.GetProperty<ThisBindingProperty>().binding;
345+
var dataContext = (IStaticValueBinding?)obj.GetBinding(DotvvmBindableObject.DataContextProperty);
346+
if (dataContext is null)
347+
throw new InvalidOperationException("DataContext must be set to a binding to allow creation of a {value: _this} binding");
348+
return (IStaticValueBinding)dataContext!.GetProperty<ThisBindingProperty>().binding;
347349
}
348350

349351
private static readonly ConditionalWeakTable<Expression, BindingParameterAnnotation> _expressionAnnotations =

src/Framework/Framework/Binding/ControlPropertyTypeDataContextChangeAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public ControlPropertyTypeDataContextChangeAttribute(string propertyName, int or
4747
throw new Exception($"The property '{PropertyName}' was not found on control '{controlType}'!");
4848
}
4949

50-
if (control.properties.Contains(controlProperty) && control.GetValueBinding(controlProperty) is IValueBinding valueBinding)
50+
if (control.properties.Contains(controlProperty) && control.GetBinding(controlProperty) is IStaticValueBinding valueBinding)
5151
{
5252
return valueBinding.ResultType;
5353
}

src/Framework/Framework/Compilation/ControlTree/ControlTreeResolverBase.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Diagnostics.CodeAnalysis;
1313
using DotVVM.Framework.Compilation.Directives;
1414
using DotVVM.Framework.Binding.Expressions;
15+
using FastExpressionCompiler;
1516

1617
namespace DotVVM.Framework.Compilation.ControlTree
1718
{
@@ -347,12 +348,19 @@ private void ProcessAttribute(IPropertyDescriptor property, DothtmlAttributeNode
347348
attribute.ValueNode.AddError($"The property '{ property.FullName }' cannot contain {bindingNode.Name} binding.");
348349
}
349350
var binding = ProcessBinding(bindingNode, dataContext, property);
351+
if (property.IsBindingProperty)
352+
{
353+
// check that binding types are compatible
354+
if (!property.PropertyType.IsAssignableFrom(ResolvedTypeDescriptor.Create(binding.BindingType)))
355+
{
356+
attribute.ValueNode.AddError($"The property '{property.FullName}' cannot contain a binding of type '{binding.BindingType}'!");
357+
}
358+
}
350359
var bindingProperty = treeBuilder.BuildPropertyBinding(property, binding, attribute);
351360
if (!treeBuilder.AddProperty(control, bindingProperty, out var error)) attribute.AddError(error);
352361
}
353362
else
354363
{
355-
// hard-coded value in markup
356364
var textValue = (DothtmlValueTextNode)attribute.ValueNode;
357365

358366
try

src/Framework/Framework/Controls/GridView.cs

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ private void DataBind(IDotvvmRequestContext context)
177177
head = null;
178178

179179
var dataSourceBinding = GetDataSourceBinding();
180+
var serverOnly = dataSourceBinding is not IValueBinding;
180181
var dataSource = DataSource;
181182

182183
var sortCommand = SortChanged;
@@ -196,7 +197,7 @@ private void DataBind(IDotvvmRequestContext context)
196197
foreach (var item in GetIEnumerableFromDataSource()!)
197198
{
198199
// create row
199-
var placeholder = new DataItemContainer { DataItemIndex = index };
200+
var placeholder = new DataItemContainer { DataItemIndex = index, RenderItemBinding = !serverOnly };
200201
placeholder.SetDataContextTypeFromDataSource(dataSourceBinding);
201202
placeholder.DataContext = item;
202203
placeholder.SetValue(Internal.PathFragmentProperty, GetPathFragmentExpression() + "/[" + index + "]");
@@ -431,19 +432,22 @@ protected override void RenderContents(IHtmlWriter writer, IDotvvmRequestContext
431432
head?.Render(writer, context);
432433

433434
// render body
434-
var foreachBinding = TryGetKnockoutForeachExpression().NotNull("GridView does not support DataSource={resource: ...} at this moment.");
435-
if (RenderOnServer)
435+
var foreachBinding = TryGetKnockoutForeachExpression();
436+
if (foreachBinding is {})
436437
{
437-
writer.AddKnockoutDataBind("dotvvm-SSR-foreach", "{data:" + foreachBinding + "}");
438-
}
439-
else
440-
{
441-
writer.AddKnockoutForeachDataBind(foreachBinding);
438+
if (RenderOnServer)
439+
{
440+
writer.AddKnockoutDataBind("dotvvm-SSR-foreach", "{data:" + foreachBinding + "}");
441+
}
442+
else
443+
{
444+
writer.AddKnockoutForeachDataBind(foreachBinding);
445+
}
442446
}
443447
writer.RenderBeginTag("tbody");
444448

445449
// render contents
446-
if (RenderOnServer)
450+
if (RenderOnServer || foreachBinding is null)
447451
{
448452
// render on server
449453
var index = 0;
@@ -498,16 +502,19 @@ protected override void RenderBeginTag(IHtmlWriter writer, IDotvvmRequestContext
498502
{
499503
if (!ShowHeaderWhenNoData)
500504
{
501-
writer.WriteKnockoutDataBindComment("if",
502-
GetForeachDataBindExpression().GetProperty<DataSourceLengthBinding>().Binding.CastTo<IValueBinding>().GetKnockoutBindingExpression(this));
505+
if (GetForeachDataBindExpression().GetProperty<DataSourceLengthBinding>().Binding is IValueBinding conditionValueBinding)
506+
{
507+
writer.WriteKnockoutDataBindComment("if", conditionValueBinding.GetKnockoutBindingExpression(this));
508+
}
503509
}
504510

505511
base.RenderBeginTag(writer, context);
506512
}
507513

508514
protected override void RenderControl(IHtmlWriter writer, IDotvvmRequestContext context)
509515
{
510-
if (RenderOnServer && numberOfRows == 0 && !ShowHeaderWhenNoData)
516+
var ssr = RenderOnServer || GetForeachDataBindExpression() is not IValueBinding;
517+
if (ssr && numberOfRows == 0 && !ShowHeaderWhenNoData)
511518
{
512519
emptyDataContainer?.Render(writer, context);
513520
}
@@ -521,7 +528,7 @@ protected override void RenderEndTag(IHtmlWriter writer, IDotvvmRequestContext c
521528
{
522529
base.RenderEndTag(writer, context);
523530

524-
if (!ShowHeaderWhenNoData)
531+
if (!ShowHeaderWhenNoData && GetForeachDataBindExpression() is IValueBinding)
525532
{
526533
writer.WriteKnockoutDataBindEndComment();
527534
}
@@ -531,15 +538,15 @@ protected override void RenderEndTag(IHtmlWriter writer, IDotvvmRequestContext c
531538

532539
protected override void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequestContext context)
533540
{
534-
var itemType = ReflectionUtils.GetEnumerableType(GetDataSourceBinding().ResultType);
535-
var userColumnMappingService = context.Services.GetRequiredService<UserColumnMappingCache>();
536-
var mapping = userColumnMappingService.GetMapping(itemType!);
537-
var mappingJson = JsonConvert.SerializeObject(mapping);
538-
539-
var dataBinding =
540-
(GetDataSourceBinding() as IValueBinding ?? throw new DotvvmControlException(this, "GridView does not support DataSource={resource: ...} at this moment."))
541-
.GetKnockoutBindingExpression(this, unwrapped: true);
542-
writer.AddKnockoutDataBind("dotvvm-gridviewdataset", $"{{'mapping':{mappingJson},'dataSet':{dataBinding}}}");
541+
if (GetBinding(DataSourceProperty) is IValueBinding dataBinding)
542+
{
543+
var itemType = ReflectionUtils.GetEnumerableType(GetDataSourceBinding().ResultType);
544+
var userColumnMappingService = context.Services.GetRequiredService<UserColumnMappingCache>();
545+
var mapping = userColumnMappingService.GetMapping(itemType!);
546+
var mappingJson = JsonConvert.SerializeObject(mapping);
547+
548+
writer.AddKnockoutDataBind("dotvvm-gridviewdataset", $"{{'mapping':{mappingJson},'dataSet':{dataBinding.GetKnockoutBindingExpression(this, unwrapped: true)}}}");
549+
}
543550
base.AddAttributesToRender(writer, context);
544551
}
545552

src/Framework/Framework/Controls/GridViewColumn.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ public virtual void CreateHeaderControls(IDotvvmRequestContext context, GridView
213213
var binding = new CommandBindingExpression(context.Services.GetRequiredService<BindingCompilationService>().WithoutInitialization(), h => sortCommand(sortExpression), bindingId);
214214
linkButton.SetBinding(ButtonBase.ClickProperty, binding);
215215

216-
SetSortedCssClass(cell, gridViewDataSet, gridView.GetValueBinding(GridView.DataSourceProperty)!);
216+
SetSortedCssClass(cell, gridViewDataSet, (IStaticValueBinding)gridView.GetBinding(GridView.DataSourceProperty)!);
217217
}
218218
else
219219
{
@@ -233,14 +233,13 @@ public virtual void CreateFilterControls(IDotvvmRequestContext context, GridView
233233
}
234234
}
235235

236-
private void SetSortedCssClass(HtmlGenericControl cell, ISortableGridViewDataSet? sortableGridViewDataSet, IValueBinding dataSourceBinding)
236+
private void SetSortedCssClass(HtmlGenericControl cell, ISortableGridViewDataSet? sortableGridViewDataSet, IStaticValueBinding dataSourceBinding)
237237
{
238238
if (sortableGridViewDataSet != null)
239239
{
240240
var cellAttributes = cell.Attributes;
241-
if (!RenderOnServer)
241+
if (!RenderOnServer && (dataSourceBinding as IValueBinding)?.GetKnockoutBindingExpression(cell, unwrapped: true) is {} gridViewDataSetExpr)
242242
{
243-
var gridViewDataSetExpr = dataSourceBinding.GetKnockoutBindingExpression(cell, unwrapped: true);
244243
cellAttributes["data-bind"] = $"css: {{ '{SortDescendingHeaderCssClass}': ({gridViewDataSetExpr}).SortingOptions().SortExpression() == '{GetSortExpression()}' && ({gridViewDataSetExpr}).SortingOptions().SortDescending(), '{SortAscendingHeaderCssClass}': ({gridViewDataSetExpr}).SortingOptions().SortExpression() == '{GetSortExpression()}' && !({gridViewDataSetExpr}).SortingOptions().SortDescending()}}";
245244
}
246245
else if (sortableGridViewDataSet.SortingOptions.SortExpression == GetSortExpression())

src/Framework/Framework/Controls/GridViewTextColumn.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ public ICommandBinding? ChangedBinding
4343
/// Gets or sets a binding which retrieves the value to display from the current data item.
4444
/// </summary>
4545
[MarkupOptions(Required = true)]
46-
public IValueBinding? ValueBinding
46+
public IStaticValueBinding? ValueBinding
4747
{
48-
get { return GetValueBinding(ValueBindingProperty); }
48+
get { return (IStaticValueBinding?)GetBinding(ValueBindingProperty); }
4949
set { SetValue(ValueBindingProperty, value); }
5050
}
5151
public static readonly DotvvmProperty ValueBindingProperty =
52-
DotvvmProperty.Register<IValueBinding?, GridViewTextColumn>(c => c.ValueBinding);
52+
DotvvmProperty.Register<IStaticValueBinding?, GridViewTextColumn>(c => c.ValueBinding);
5353

5454
[MarkupOptions(AllowBinding = false)]
5555
public ValidatorPlacement ValidatorPlacement
@@ -75,13 +75,18 @@ public static readonly DotvvmProperty ValidatorPlacementProperty
7575

7676
public override void CreateControls(IDotvvmRequestContext context, DotvvmControl container)
7777
{
78+
var binding = ValueBinding;
79+
if (binding is null)
80+
throw new DotvvmControlException(this, "The 'ValueBinding' property is required.");
81+
7882
var literal = new Literal();
7983
literal.FormatString = FormatString;
8084

8185
CopyProperty(UITests.NameProperty, literal, UITests.NameProperty);
8286

83-
literal.SetBinding(Literal.TextProperty, ValueBinding);
84-
Validator.Place(literal, container.Children, ValueBinding, ValidatorPlacement);
87+
literal.SetBinding(Literal.TextProperty, binding);
88+
if (binding is IValueBinding v)
89+
Validator.Place(literal, container.Children, v, ValidatorPlacement);
8590
container.Children.Add(literal);
8691
}
8792

@@ -98,7 +103,8 @@ public override void CreateEditControls(IDotvvmRequestContext context, DotvvmCon
98103

99104
textBox.SetBinding(TextBox.TextProperty, ValueBinding);
100105
textBox.SetBinding(TextBox.ChangedProperty, ChangedBinding);
101-
Validator.Place(textBox, container.Children, ValueBinding, ValidatorPlacement);
106+
if (ValueBinding is IValueBinding v)
107+
Validator.Place(textBox, container.Children, v, ValidatorPlacement);
102108
container.Children.Add(textBox);
103109
}
104110
}

src/Tests/ControlTests/RepeaterTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public class RepeaterWrapper : CompositeControl
105105
{
106106
public static DotvvmControl GetContents(
107107
HtmlCapability htmlCapability,
108-
[ControlPropertyTypeDataContextChange("DataSource"), CollectionElementDataContextChange(1)]
108+
[ControlPropertyBindingDataContextChange("DataSource"), CollectionElementDataContextChange(1)]
109109
ITemplate itemTemplate,
110110
IValueBinding<IEnumerable> dataSource
111111
)

src/Tests/ControlTests/ResourceDataContextTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,19 @@ public async Task HierarchyRepeater_SimpleTemplate()
164164
check.CheckString(r.FormattedHtml, fileExtension: "html");
165165
}
166166

167+
[TestMethod]
168+
public async Task GridView()
169+
{
170+
var r = await cth.RunPage(typeof(TestViewModel), @"
171+
<dot:GridView DataSource={resource: Customers}>
172+
<dot:GridViewTextColumn HeaderText=Id ValueBinding={resource: Id} />
173+
<dot:GridViewTemplateColumn HeaderText=Name>
174+
{{resource: Name}}
175+
</dot:GridViewTemplateColumn>
176+
</dot:GridView>");
177+
check.CheckString(r.FormattedHtml, fileExtension: "html");
178+
}
179+
167180

168181
public class TestViewModel: DotvvmViewModelBase
169182
{
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<html>
2+
<head></head>
3+
<body>
4+
<table>
5+
<thead>
6+
<tr>
7+
<th>
8+
<span>Id</span>
9+
</th>
10+
<th>
11+
<span>Name</span>
12+
</th>
13+
</tr>
14+
</thead>
15+
<tbody>
16+
<tr>
17+
<td>
18+
<span>1</span>
19+
</td>
20+
<td>One</td>
21+
</tr>
22+
<tr>
23+
<td>
24+
<span>2</span>
25+
</td>
26+
<td>Two</td>
27+
</tr>
28+
</tbody>
29+
</table>
30+
</body>
31+
</html>

0 commit comments

Comments
 (0)