Skip to content

Commit

Permalink
Add basic support for DataSource=resource to auto:GridViewColumn(s)
Browse files Browse the repository at this point in the history
  • Loading branch information
exyi committed Feb 8, 2025
1 parent b3e27e5 commit 27ae8e1
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 18 deletions.
10 changes: 6 additions & 4 deletions src/AutoUI/Core/AutoUIContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using DotVVM.Framework.Binding.Expressions;
using DotVVM.Framework.Binding.Properties;
using DotVVM.Framework.Compilation.ControlTree;
using DotVVM.Framework.Utils;
using DotVVM.Framework.ViewModel.Validation;
using Microsoft.Extensions.DependencyInjection;

Expand Down Expand Up @@ -57,9 +58,8 @@ public ValidationAttribute[] GetPropertyValidators(PropertyDisplayMetadata prope
return Array.Empty<ValidationAttribute>();
return GetPropertyValidators(property.PropertyInfo);
}


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

var s = this.BindingService;
return s.Cache.CreateCachedBinding("AutoUI-Value", new object?[] { property.PropertyInfo, DataContextStack }, () => {
var serverOnly = this.DataContextStack.ServerSideOnly;
return s.Cache.CreateCachedBinding("AutoUI-Value", new object?[] { BoxingUtils.Box(serverOnly), property.PropertyInfo, DataContextStack }, () => {
var _this = Expression.Parameter(DataContextStack.DataContextType, "_this").AddParameterAnnotation(new BindingParameterAnnotation(DataContextStack));
var expr = Expression.Property(_this, property.PropertyInfo);
return (IValueBinding)BindingService.CreateBinding(typeof(ValueBindingExpression<>), new object[] {
var bindingType = serverOnly ? typeof(ResourceBindingExpression<>) : typeof(ValueBindingExpression<>);
return (IStaticValueBinding)BindingService.CreateBinding(bindingType, new object[] {
DataContextStack,
new ParsedExpressionBindingProperty(expr)
});
Expand Down
2 changes: 1 addition & 1 deletion src/AutoUI/Core/Controls/AutoEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public sealed record Props
/// <summary>
/// Gets or sets the viewmodel property for which the editor should be generated.
/// </summary>
public IValueBinding? Property { get; init; }
public IStaticValueBinding? Property { get; init; }

/// <summary>
/// Gets or sets the command that will be triggered when the value in the editor is changed.
Expand Down
10 changes: 5 additions & 5 deletions src/AutoUI/Core/Controls/AutoGridViewColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ namespace DotVVM.AutoUI.Controls
[ControlMarkupOptions(PrimaryName = "GridViewColumn")]
public class AutoGridViewColumn : GridViewColumn
{
[MarkupOptions(AllowHardCodedValue = false, Required = true)]
public IValueBinding? Property
[MarkupOptions(AllowHardCodedValue = false, AllowResourceBinding = true, Required = true)]
public IStaticValueBinding? Property
{
get { return (IValueBinding?)GetValue(PropertyProperty); }
get { return (IStaticValueBinding?)GetValue(PropertyProperty); }
set { SetValue(PropertyProperty, value); }
}
public static readonly DotvvmProperty PropertyProperty =
DotvvmProperty.Register<IValueBinding, AutoGridViewColumn>(nameof(Property));
DotvvmProperty.Register<IStaticValueBinding, AutoGridViewColumn>(nameof(Property));


public static DotvvmCapabilityProperty PropsProperty =
Expand Down Expand Up @@ -93,7 +93,7 @@ private static GridViewColumn CreateColumn(AutoUIContext context, Props props, M
[DotvvmControlCapability]
public sealed record Props
{
public IValueBinding? Property { get; init; }
public IStaticValueBinding? Property { get; init; }
public ValueOrBinding<bool> IsEditable { get; init; } = new(true);
public ValueOrBinding<string>? HeaderText { get; init; }
public ITemplate? HeaderTemplate { get; init; }
Expand Down
1 change: 0 additions & 1 deletion src/AutoUI/Core/Controls/BootstrapForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ public DotvvmControl GetContents(FieldProps props)
Label? labelElement;
HtmlGenericControl controlElement;
var formGroup = InitializeFormGroup(property, context, props, out labelElement, out controlElement);


// create the validator
InitializeValidation(controlElement, labelElement, property, context);
Expand Down
2 changes: 1 addition & 1 deletion src/AutoUI/Core/Metadata/PropertyDisplayMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public record PropertyDisplayMetadata
public string Name { get; init; }
public Type Type { get; init; }

public IValueBinding? ValueBinding { get; init; }
public IStaticValueBinding? ValueBinding { get; init; }

public LocalizableString? DisplayName { get; set; }
public LocalizableString? Placeholder { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion src/Framework/Framework/Binding/BindingProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -234,5 +234,5 @@ public sealed record IsNullOrEmptyBindingExpression(IBinding Binding);
/// <summary> Contains the same binding as this binding but converted to a string. </summary>
public sealed record ExpectedAsStringBindingExpression(IBinding Binding);
/// <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>
public sealed record ReferencedViewModelPropertiesBindingProperty(PropertyInfo? MainProperty, PropertyInfo[] OtherProperties, IValueBinding UnwrappedBindingExpression);
public sealed record ReferencedViewModelPropertiesBindingProperty(PropertyInfo? MainProperty, PropertyInfo[] OtherProperties, IValueBinding? UnwrappedBindingExpression);
}
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ public IsMoreThanZeroBindingProperty IsMoreThanZero(ParsedExpressionBindingPrope
));
}

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

var mainProperty = (expr as MemberExpression)?.Member as PropertyInfo;
var unwrappedBinding = binding.DeriveBinding(expr);
var unwrappedBinding = (binding as IValueBinding)?.DeriveBinding(expr);

return new(
mainProperty,
Expand Down
3 changes: 2 additions & 1 deletion src/Framework/Framework/Controls/Validator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using DotVVM.Framework.Configuration;
using DotVVM.Framework.Binding.Properties;
using System.Text.Json;
using DotVVM.Framework.Utils;

namespace DotVVM.Framework.Controls
{
Expand Down Expand Up @@ -90,7 +91,7 @@ private static void AddValidatedValue(IHtmlWriter writer, IDotvvmRequestContext
if (binding is not null)
{
var referencedPropertyExpressions = binding.GetProperty<ReferencedViewModelPropertiesBindingProperty>();
var unwrappedPropertyExpression = referencedPropertyExpressions.UnwrappedBindingExpression;
var unwrappedPropertyExpression = referencedPropertyExpressions.UnwrappedBindingExpression.NotNull();

// We were able to unwrap the the provided expression
writer.AddKnockoutDataBind(validationDataBindName, control, unwrappedPropertyExpression);
Expand Down
71 changes: 71 additions & 0 deletions src/Tests/ControlTests/ResourceDataContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using DotVVM.Framework.Testing;
using DotVVM.Framework.Compilation.Styles;
using DotVVM.AutoUI;

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

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

[TestMethod]
public async Task EmptyData()
{
var r = await cth.RunPage(typeof(TestViewModel), @"
<!-- no header row -->
<dot:GridView DataSource={resource: EmptyDataSet} ShowHeaderWhenNoData=false>
<dot:GridViewTextColumn HeaderText=Id ValueBinding={resource: Id} />
<dot:GridViewTemplateColumn HeaderText=Name>
{{resource: Name}}
</dot:GridViewTemplateColumn>
</dot:GridView>
<!-- with empty table -->
<dot:GridView DataSource={resource: EmptyDataSet} ShowHeaderWhenNoData=true>
<dot:GridViewTextColumn HeaderText=Id ValueBinding={resource: Id} />
<dot:GridViewTemplateColumn HeaderText=Name>
{{resource: Name}}
</dot:GridViewTemplateColumn>
</dot:GridView>
<!-- Repeater without wrapper tag -->
<dot:Repeater DataSource={resource: EmptyArray} RenderWrapperTag=false>
<EmptyDataTemplate> empty data template </EmptyDataTemplate>
This would be here if any data was present
</dot:Repeater>
<!-- Repeater with wrapper tag -->
<dot:Repeater DataSource={resource: EmptyArray} WrapperTagName=div>
<EmptyDataTemplate> empty data template </EmptyDataTemplate>
This would be here if any data was present
</dot:Repeater>
<dot:EmptyData DataSource={resource: EmptyArray} RenderWrapperTag=false>
dot:EmptyData
</dot:EmptyData>
<dot:EmptyData DataSource={resource: EmptyDataSet} WrapperTagName=div>
dot:EmptyData
</dot:EmptyData>
<dot:EmptyData DataSource={resource: Customers} WrapperTagName=div>
not shown
</dot:EmptyData>
");
check.CheckString(r.FormattedHtml, fileExtension: "html");
}

[TestMethod]
public async Task AutoGrid()
{
var r = await cth.RunPage(typeof(TestViewModel), @"
<!-- no header row -->
<dot:GridView DataSource={resource: Customers}>
<auto:GridViewColumns />
</dot:GridView>
");
check.CheckString(r.FormattedHtml, fileExtension: "html");
}


public class TestViewModel: DotvvmViewModelBase
{
Expand Down Expand Up @@ -207,11 +274,15 @@ public class TestViewModel: DotvvmViewModelBase

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

public GridViewDataSet<CustomerData> EmptyDataSet { get; set; } = new();
public CustomerData[] EmptyArray { get; set; } = [];

public record CustomerData(
int Id,
[property: Required]
string Name,
// software for running MLM 😂
[property: Display(AutoGenerateField = false)]
List<CustomerData> NextLevelCustomers
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<html>
<head></head>
<body>

<!-- no header row -->
<table>
<thead>
<tr>
<th class="">
<span>Id</span>
</th>
<th class="">
<span>Name</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<span>1</span>
</td>
<td>
<span>One</span>
</td>
</tr>
<tr>
<td>
<span>2</span>
</td>
<td>
<span>Two</span>
</td>
</tr>
</tbody>
</table>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<html>
<head></head>
<body>

<!-- no header row -->
<!-- with empty table -->
<table>
<thead>
<tr>
<th>
<span>Id</span>
</th>
<th>
<span>Name</span>
</th>
</tr>
</thead>
<tbody></tbody>
</table>

<!-- Repeater without wrapper tag --> empty data template
<!-- Repeater with wrapper tag -->
<div></div>
<div>
empty data template
</div>
dot:EmptyData
<div>
dot:EmptyData
</div>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@
"fromCapability": "Props"
},
"Property": {
"type": "DotVVM.Framework.Binding.Expressions.IValueBinding, DotVVM.Framework",
"type": "DotVVM.Framework.Binding.Expressions.IStaticValueBinding, DotVVM.Framework",
"onlyBindings": true,
"fromCapability": "Props"
},
Expand Down Expand Up @@ -212,7 +212,7 @@
"fromCapability": "Props"
},
"Property": {
"type": "DotVVM.Framework.Binding.Expressions.IValueBinding, DotVVM.Framework",
"type": "DotVVM.Framework.Binding.Expressions.IStaticValueBinding, DotVVM.Framework",
"required": true,
"onlyBindings": true
}
Expand Down

0 comments on commit 27ae8e1

Please sign in to comment.