Skip to content

Commit bc1e371

Browse files
committed
Add basic support for DataSource=resource to auto:GridViewColumn(s)
1 parent 1eadf17 commit bc1e371

File tree

12 files changed

+160
-18
lines changed

12 files changed

+160
-18
lines changed

src/AutoUI/Core/AutoUIContext.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using DotVVM.Framework.Binding.Expressions;
1414
using DotVVM.Framework.Binding.Properties;
1515
using DotVVM.Framework.Compilation.ControlTree;
16+
using DotVVM.Framework.Utils;
1617
using DotVVM.Framework.ViewModel.Validation;
1718
using Microsoft.Extensions.DependencyInjection;
1819

@@ -57,9 +58,8 @@ public ValidationAttribute[] GetPropertyValidators(PropertyDisplayMetadata prope
5758
return Array.Empty<ValidationAttribute>();
5859
return GetPropertyValidators(property.PropertyInfo);
5960
}
60-
6161

62-
public IValueBinding CreateValueBinding(PropertyDisplayMetadata property)
62+
public IStaticValueBinding CreateValueBinding(PropertyDisplayMetadata property)
6363
{
6464
if (property.ValueBinding is not null)
6565
return property.ValueBinding;
@@ -68,10 +68,12 @@ public IValueBinding CreateValueBinding(PropertyDisplayMetadata property)
6868
throw new ArgumentException("property.PropertyInfo is null => cannot create value binding for this property");
6969

7070
var s = this.BindingService;
71-
return s.Cache.CreateCachedBinding("AutoUI-Value", new object?[] { property.PropertyInfo, DataContextStack }, () => {
71+
var serverOnly = this.DataContextStack.ServerSideOnly;
72+
return s.Cache.CreateCachedBinding("AutoUI-Value", new object?[] { BoxingUtils.Box(serverOnly), property.PropertyInfo, DataContextStack }, () => {
7273
var _this = Expression.Parameter(DataContextStack.DataContextType, "_this").AddParameterAnnotation(new BindingParameterAnnotation(DataContextStack));
7374
var expr = Expression.Property(_this, property.PropertyInfo);
74-
return (IValueBinding)BindingService.CreateBinding(typeof(ValueBindingExpression<>), new object[] {
75+
var bindingType = serverOnly ? typeof(ResourceBindingExpression<>) : typeof(ValueBindingExpression<>);
76+
return (IStaticValueBinding)BindingService.CreateBinding(bindingType, new object[] {
7577
DataContextStack,
7678
new ParsedExpressionBindingProperty(expr)
7779
});

src/AutoUI/Core/Controls/AutoEditor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public sealed record Props
6363
/// <summary>
6464
/// Gets or sets the viewmodel property for which the editor should be generated.
6565
/// </summary>
66-
public IValueBinding? Property { get; init; }
66+
public IStaticValueBinding? Property { get; init; }
6767

6868
/// <summary>
6969
/// Gets or sets the command that will be triggered when the value in the editor is changed.

src/AutoUI/Core/Controls/AutoGridViewColumn.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ namespace DotVVM.AutoUI.Controls
1414
[ControlMarkupOptions(PrimaryName = "GridViewColumn")]
1515
public class AutoGridViewColumn : GridViewColumn
1616
{
17-
[MarkupOptions(AllowHardCodedValue = false, Required = true)]
18-
public IValueBinding? Property
17+
[MarkupOptions(AllowHardCodedValue = false, AllowResourceBinding = true, Required = true)]
18+
public IStaticValueBinding? Property
1919
{
20-
get { return (IValueBinding?)GetValue(PropertyProperty); }
20+
get { return (IStaticValueBinding?)GetValue(PropertyProperty); }
2121
set { SetValue(PropertyProperty, value); }
2222
}
2323
public static readonly DotvvmProperty PropertyProperty =
24-
DotvvmProperty.Register<IValueBinding, AutoGridViewColumn>(nameof(Property));
24+
DotvvmProperty.Register<IStaticValueBinding, AutoGridViewColumn>(nameof(Property));
2525

2626

2727
public static DotvvmCapabilityProperty PropsProperty =
@@ -93,7 +93,7 @@ private static GridViewColumn CreateColumn(AutoUIContext context, Props props, M
9393
[DotvvmControlCapability]
9494
public sealed record Props
9595
{
96-
public IValueBinding? Property { get; init; }
96+
public IStaticValueBinding? Property { get; init; }
9797
public ValueOrBinding<bool> IsEditable { get; init; } = new(true);
9898
public ValueOrBinding<string>? HeaderText { get; init; }
9999
public ITemplate? HeaderTemplate { get; init; }

src/AutoUI/Core/Controls/BootstrapForm.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ public DotvvmControl GetContents(FieldProps props)
121121
Label? labelElement;
122122
HtmlGenericControl controlElement;
123123
var formGroup = InitializeFormGroup(property, context, props, out labelElement, out controlElement);
124-
125124

126125
// create the validator
127126
InitializeValidation(controlElement, labelElement, property, context);

src/AutoUI/Core/Metadata/PropertyDisplayMetadata.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public record PropertyDisplayMetadata
1818
public string Name { get; init; }
1919
public Type Type { get; init; }
2020

21-
public IValueBinding? ValueBinding { get; init; }
21+
public IStaticValueBinding? ValueBinding { get; init; }
2222

2323
public LocalizableString? DisplayName { get; set; }
2424
public LocalizableString? Placeholder { get; set; }

src/Framework/Framework/Binding/BindingProperties.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,5 +234,5 @@ public sealed record IsNullOrEmptyBindingExpression(IBinding Binding);
234234
/// <summary> Contains the same binding as this binding but converted to a string. </summary>
235235
public sealed record ExpectedAsStringBindingExpression(IBinding Binding);
236236
/// <summary> Contains references to the .NET properties referenced in the binding. MainProperty is the property on the root node (modulo conversions and simple expressions). </summary>
237-
public sealed record ReferencedViewModelPropertiesBindingProperty(PropertyInfo? MainProperty, PropertyInfo[] OtherProperties, IValueBinding UnwrappedBindingExpression);
237+
public sealed record ReferencedViewModelPropertiesBindingProperty(PropertyInfo? MainProperty, PropertyInfo[] OtherProperties, IValueBinding? UnwrappedBindingExpression);
238238
}

src/Framework/Framework/Compilation/Binding/GeneralBindingPropertyResolvers.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ public IsMoreThanZeroBindingProperty IsMoreThanZero(ParsedExpressionBindingPrope
468468
));
469469
}
470470

471-
public ReferencedViewModelPropertiesBindingProperty GetReferencedViewModelProperties(IValueBinding binding, ParsedExpressionBindingProperty expression)
471+
public ReferencedViewModelPropertiesBindingProperty GetReferencedViewModelProperties(IStaticValueBinding binding, ParsedExpressionBindingProperty expression)
472472
{
473473
var allProperties = new List<PropertyInfo>();
474474
var expr = expression.Expression;
@@ -483,7 +483,7 @@ public ReferencedViewModelPropertiesBindingProperty GetReferencedViewModelProper
483483
expr = ExpressionHelper.UnwrapPassthroughOperations(expr);
484484

485485
var mainProperty = (expr as MemberExpression)?.Member as PropertyInfo;
486-
var unwrappedBinding = binding.DeriveBinding(expr);
486+
var unwrappedBinding = (binding as IValueBinding)?.DeriveBinding(expr);
487487

488488
return new(
489489
mainProperty,

src/Framework/Framework/Controls/Validator.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using DotVVM.Framework.Configuration;
88
using DotVVM.Framework.Binding.Properties;
99
using System.Text.Json;
10+
using DotVVM.Framework.Utils;
1011

1112
namespace DotVVM.Framework.Controls
1213
{
@@ -90,7 +91,7 @@ private static void AddValidatedValue(IHtmlWriter writer, IDotvvmRequestContext
9091
if (binding is not null)
9192
{
9293
var referencedPropertyExpressions = binding.GetProperty<ReferencedViewModelPropertiesBindingProperty>();
93-
var unwrappedPropertyExpression = referencedPropertyExpressions.UnwrappedBindingExpression;
94+
var unwrappedPropertyExpression = referencedPropertyExpressions.UnwrappedBindingExpression.NotNull();
9495

9596
// We were able to unwrap the the provided expression
9697
writer.AddKnockoutDataBind(validationDataBindName, control, unwrappedPropertyExpression);

src/Tests/ControlTests/ResourceDataContextTests.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Microsoft.VisualStudio.TestTools.UnitTesting;
1212
using DotVVM.Framework.Testing;
1313
using DotVVM.Framework.Compilation.Styles;
14+
using DotVVM.AutoUI;
1415

1516
namespace DotVVM.Framework.Tests.ControlTests
1617
{
@@ -20,6 +21,8 @@ public class ResourceDataContextTests
2021
static readonly ControlTestHelper cth = new ControlTestHelper(config: config => {
2122
_ = Controls.Repeater.RenderAsNamedTemplateProperty;
2223
config.Styles.Register<Repeater>().SetProperty(r => r.RenderAsNamedTemplate, false, StyleOverrideOptions.Ignore);
24+
}, services: s => {
25+
s.AddAutoUI();
2326
});
2427
OutputChecker check = new OutputChecker("testoutputs");
2528

@@ -177,6 +180,70 @@ public async Task GridView()
177180
check.CheckString(r.FormattedHtml, fileExtension: "html");
178181
}
179182

183+
[TestMethod]
184+
public async Task EmptyData()
185+
{
186+
var r = await cth.RunPage(typeof(TestViewModel), @"
187+
<!-- no header row -->
188+
<dot:GridView DataSource={resource: EmptyDataSet} ShowHeaderWhenNoData=false>
189+
<dot:GridViewTextColumn HeaderText=Id ValueBinding={resource: Id} />
190+
<dot:GridViewTemplateColumn HeaderText=Name>
191+
{{resource: Name}}
192+
</dot:GridViewTemplateColumn>
193+
</dot:GridView>
194+
195+
<!-- with empty table -->
196+
<dot:GridView DataSource={resource: EmptyDataSet} ShowHeaderWhenNoData=true>
197+
<dot:GridViewTextColumn HeaderText=Id ValueBinding={resource: Id} />
198+
<dot:GridViewTemplateColumn HeaderText=Name>
199+
{{resource: Name}}
200+
</dot:GridViewTemplateColumn>
201+
</dot:GridView>
202+
203+
204+
<!-- Repeater without wrapper tag -->
205+
<dot:Repeater DataSource={resource: EmptyArray} RenderWrapperTag=false>
206+
<EmptyDataTemplate> empty data template </EmptyDataTemplate>
207+
208+
This would be here if any data was present
209+
</dot:Repeater>
210+
211+
<!-- Repeater with wrapper tag -->
212+
<dot:Repeater DataSource={resource: EmptyArray} WrapperTagName=div>
213+
<EmptyDataTemplate> empty data template </EmptyDataTemplate>
214+
215+
This would be here if any data was present
216+
</dot:Repeater>
217+
218+
<dot:EmptyData DataSource={resource: EmptyArray} RenderWrapperTag=false>
219+
dot:EmptyData
220+
</dot:EmptyData>
221+
222+
<dot:EmptyData DataSource={resource: EmptyDataSet} WrapperTagName=div>
223+
dot:EmptyData
224+
</dot:EmptyData>
225+
226+
<dot:EmptyData DataSource={resource: Customers} WrapperTagName=div>
227+
not shown
228+
</dot:EmptyData>
229+
230+
");
231+
check.CheckString(r.FormattedHtml, fileExtension: "html");
232+
}
233+
234+
[TestMethod]
235+
public async Task AutoGrid()
236+
{
237+
var r = await cth.RunPage(typeof(TestViewModel), @"
238+
<!-- no header row -->
239+
<dot:GridView DataSource={resource: Customers}>
240+
<auto:GridViewColumns />
241+
</dot:GridView>
242+
243+
");
244+
check.CheckString(r.FormattedHtml, fileExtension: "html");
245+
}
246+
180247

181248
public class TestViewModel: DotvvmViewModelBase
182249
{
@@ -207,11 +274,15 @@ public class TestViewModel: DotvvmViewModelBase
207274

208275
public UploadedFilesCollection Files { get; set; } = new UploadedFilesCollection();
209276

277+
public GridViewDataSet<CustomerData> EmptyDataSet { get; set; } = new();
278+
public CustomerData[] EmptyArray { get; set; } = [];
279+
210280
public record CustomerData(
211281
int Id,
212282
[property: Required]
213283
string Name,
214284
// software for running MLM 😂
285+
[property: Display(AutoGenerateField = false)]
215286
List<CustomerData> NextLevelCustomers
216287
);
217288

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<html>
2+
<head></head>
3+
<body>
4+
5+
<!-- no header row -->
6+
<table>
7+
<thead>
8+
<tr>
9+
<th class="">
10+
<span>Id</span>
11+
</th>
12+
<th class="">
13+
<span>Name</span>
14+
</th>
15+
</tr>
16+
</thead>
17+
<tbody>
18+
<tr>
19+
<td>
20+
<span>1</span>
21+
</td>
22+
<td>
23+
<span>One</span>
24+
</td>
25+
</tr>
26+
<tr>
27+
<td>
28+
<span>2</span>
29+
</td>
30+
<td>
31+
<span>Two</span>
32+
</td>
33+
</tr>
34+
</tbody>
35+
</table>
36+
</body>
37+
</html>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<html>
2+
<head></head>
3+
<body>
4+
5+
<!-- no header row -->
6+
<!-- with empty table -->
7+
<table>
8+
<thead>
9+
<tr>
10+
<th>
11+
<span>Id</span>
12+
</th>
13+
<th>
14+
<span>Name</span>
15+
</th>
16+
</tr>
17+
</thead>
18+
<tbody></tbody>
19+
</table>
20+
21+
<!-- Repeater without wrapper tag --> empty data template
22+
<!-- Repeater with wrapper tag -->
23+
<div></div>
24+
<div>
25+
empty data template
26+
</div>
27+
dot:EmptyData
28+
<div>
29+
dot:EmptyData
30+
</div>
31+
</body>
32+
</html>

src/Tests/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
"fromCapability": "Props"
163163
},
164164
"Property": {
165-
"type": "DotVVM.Framework.Binding.Expressions.IValueBinding, DotVVM.Framework",
165+
"type": "DotVVM.Framework.Binding.Expressions.IStaticValueBinding, DotVVM.Framework",
166166
"onlyBindings": true,
167167
"fromCapability": "Props"
168168
},
@@ -212,7 +212,7 @@
212212
"fromCapability": "Props"
213213
},
214214
"Property": {
215-
"type": "DotVVM.Framework.Binding.Expressions.IValueBinding, DotVVM.Framework",
215+
"type": "DotVVM.Framework.Binding.Expressions.IStaticValueBinding, DotVVM.Framework",
216216
"required": true,
217217
"onlyBindings": true
218218
}

0 commit comments

Comments
 (0)