Skip to content

Commit

Permalink
DataContext resource binding: GridView support
Browse files Browse the repository at this point in the history
  • Loading branch information
exyi committed Aug 2, 2022
1 parent dca0a1b commit a9193f7
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 37 deletions.
2 changes: 1 addition & 1 deletion src/DynamicData/DynamicData/DataContextStackHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ public static DataContextStack CreateChildStack(this DataContextStack dataContex
dataContextStack.BindingPropertyResolvers);
}
}
}
}
8 changes: 5 additions & 3 deletions src/Framework/Framework/Binding/BindingHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -323,10 +323,12 @@ public static TBinding DeriveBinding<TBinding>(this TBinding binding, params obj
return f => cache.GetOrAdd(f, func);
}

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

private static readonly ConditionalWeakTable<Expression, BindingParameterAnnotation> _expressionAnnotations =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public ControlPropertyTypeDataContextChangeAttribute(string propertyName, int or
throw new Exception($"The property '{PropertyName}' was not found on control '{controlType}'!");
}

if (control.properties.Contains(controlProperty) && control.GetValueBinding(controlProperty) is IValueBinding valueBinding)
if (control.properties.Contains(controlProperty) && control.GetBinding(controlProperty) is IStaticValueBinding valueBinding)
{
return valueBinding.ResultType;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Diagnostics.CodeAnalysis;
using DotVVM.Framework.Compilation.Directives;
using DotVVM.Framework.Binding.Expressions;
using FastExpressionCompiler;

namespace DotVVM.Framework.Compilation.ControlTree
{
Expand Down Expand Up @@ -345,13 +346,21 @@ private void ProcessAttribute(IPropertyDescriptor property, DothtmlAttributeNode
attribute.ValueNode.AddError($"The property '{ property.FullName }' cannot contain {bindingNode.Name} binding.");
}
var binding = ProcessBinding(bindingNode, dataContext, property);
if (property.IsBindingProperty)
{
// check that binding types are compatible
if (!property.PropertyType.IsAssignableFrom(ResolvedTypeDescriptor.Create(binding.BindingType)))
{
attribute.ValueNode.AddError($"The property '{property.FullName}' cannot contain a binding of type '{binding.BindingType}'!");
}
}
var bindingProperty = treeBuilder.BuildPropertyBinding(property, binding, attribute);
if (!treeBuilder.AddProperty(control, bindingProperty, out var error)) attribute.AddError(error);
}
else
{
// hard-coded value in markup
if (!property.MarkupOptions.AllowHardCodedValue)
if (!property.MarkupOptions.AllowHardCodedValue || property.IsBindingProperty)
{
attribute.ValueNode.AddError($"The property '{ property.FullName }' cannot contain hard coded value.");
}
Expand Down
48 changes: 29 additions & 19 deletions src/Framework/Framework/Controls/GridView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ private void DataBind(IDotvvmRequestContext context)
head = null;

var dataSourceBinding = GetDataSourceBinding();
var serverOnly = dataSourceBinding is not IValueBinding;
var dataSource = DataSource;

var sortCommand = SortChanged;
Expand All @@ -196,7 +197,7 @@ private void DataBind(IDotvvmRequestContext context)
foreach (var item in GetIEnumerableFromDataSource()!)
{
// create row
var placeholder = new DataItemContainer { DataItemIndex = index };
var placeholder = new DataItemContainer { DataItemIndex = index, RenderItemBinding = !serverOnly };
placeholder.SetDataContextTypeFromDataSource(dataSourceBinding);
placeholder.DataContext = item;
placeholder.SetValue(Internal.PathFragmentProperty, GetPathFragmentExpression() + "/[" + index + "]");
Expand Down Expand Up @@ -431,19 +432,22 @@ protected override void RenderContents(IHtmlWriter writer, IDotvvmRequestContext
head?.Render(writer, context);

// render body
var foreachBinding = TryGetKnockoutForeachExpression().NotNull("GridView does not support DataSource={resource: ...} at this moment.");
if (RenderOnServer)
var foreachBinding = TryGetKnockoutForeachExpression();
if (foreachBinding is {})
{
writer.AddKnockoutDataBind("dotvvm-SSR-foreach", "{data:" + foreachBinding + "}");
}
else
{
writer.AddKnockoutForeachDataBind(foreachBinding);
if (RenderOnServer)
{
writer.AddKnockoutDataBind("dotvvm-SSR-foreach", "{data:" + foreachBinding + "}");
}
else
{
writer.AddKnockoutForeachDataBind(foreachBinding);
}
}
writer.RenderBeginTag("tbody");

// render contents
if (RenderOnServer)
if (RenderOnServer || foreachBinding is null)
{
// render on server
var index = 0;
Expand Down Expand Up @@ -498,16 +502,19 @@ protected override void RenderBeginTag(IHtmlWriter writer, IDotvvmRequestContext
{
if (!ShowHeaderWhenNoData)
{
writer.WriteKnockoutDataBindComment("if",
GetForeachDataBindExpression().GetProperty<DataSourceLengthBinding>().Binding.CastTo<IValueBinding>().GetKnockoutBindingExpression(this));
if (GetForeachDataBindExpression().GetProperty<DataSourceLengthBinding>().Binding is IValueBinding conditionValueBinding)
{
writer.WriteKnockoutDataBindComment("if", conditionValueBinding.GetKnockoutBindingExpression(this));
}
}

base.RenderBeginTag(writer, context);
}

protected override void RenderControl(IHtmlWriter writer, IDotvvmRequestContext context)
{
if (RenderOnServer && numberOfRows == 0 && !ShowHeaderWhenNoData)
var ssr = RenderOnServer || GetForeachDataBindExpression() is not IValueBinding;
if (ssr && numberOfRows == 0 && !ShowHeaderWhenNoData)
{
emptyDataContainer?.Render(writer, context);
}
Expand All @@ -521,7 +528,7 @@ protected override void RenderEndTag(IHtmlWriter writer, IDotvvmRequestContext c
{
base.RenderEndTag(writer, context);

if (!ShowHeaderWhenNoData)
if (!ShowHeaderWhenNoData && GetForeachDataBindExpression() is IValueBinding)
{
writer.WriteKnockoutDataBindEndComment();
}
Expand All @@ -531,13 +538,16 @@ protected override void RenderEndTag(IHtmlWriter writer, IDotvvmRequestContext c

protected override void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequestContext context)
{
var itemType = ReflectionUtils.GetEnumerableType(GetDataSourceBinding().ResultType);
var userColumnMappingService = context.Services.GetRequiredService<UserColumnMappingCache>();
var mapping = userColumnMappingService.GetMapping(itemType!);
var mappingJson = JsonConvert.SerializeObject(mapping);

var dataBinding = TryGetKnockoutForeachExpression(unwrapped: true).NotNull("GridView does not support DataSource={resource: ...} at this moment.");
writer.AddKnockoutDataBind("dotvvm-gridviewdataset", $"{{'mapping':{mappingJson},'dataSet':{dataBinding}}}");
if (TryGetKnockoutForeachExpression(unwrapped: true) is {} dataBinding)
{
var itemType = ReflectionUtils.GetEnumerableType(GetDataSourceBinding().ResultType);
var userColumnMappingService = context.Services.GetRequiredService<UserColumnMappingCache>();
var mapping = userColumnMappingService.GetMapping(itemType!);
var mappingJson = JsonConvert.SerializeObject(mapping);

writer.AddKnockoutDataBind("dotvvm-gridviewdataset", $"{{'mapping':{mappingJson},'dataSet':{dataBinding}}}");
}
base.AddAttributesToRender(writer, context);
}

Expand Down
7 changes: 3 additions & 4 deletions src/Framework/Framework/Controls/GridViewColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ public virtual void CreateHeaderControls(IDotvvmRequestContext context, GridView
var binding = new CommandBindingExpression(context.Services.GetRequiredService<BindingCompilationService>().WithoutInitialization(), h => sortCommand(sortExpression), bindingId);
linkButton.SetBinding(ButtonBase.ClickProperty, binding);

SetSortedCssClass(cell, gridViewDataSet, gridView.GetValueBinding(GridView.DataSourceProperty)!);
SetSortedCssClass(cell, gridViewDataSet, (IStaticValueBinding)gridView.GetBinding(GridView.DataSourceProperty)!);
}
else
{
Expand All @@ -220,14 +220,13 @@ public virtual void CreateFilterControls(IDotvvmRequestContext context, GridView
}
}

private void SetSortedCssClass(HtmlGenericControl cell, ISortableGridViewDataSet? sortableGridViewDataSet, IValueBinding dataSourceBinding)
private void SetSortedCssClass(HtmlGenericControl cell, ISortableGridViewDataSet? sortableGridViewDataSet, IStaticValueBinding dataSourceBinding)
{
if (sortableGridViewDataSet != null)
{
var cellAttributes = cell.Attributes;
if (!RenderOnServer)
if (!RenderOnServer && (dataSourceBinding as IValueBinding)?.GetKnockoutBindingExpression(cell, unwrapped: true) is {} gridViewDataSetExpr)
{
var gridViewDataSetExpr = dataSourceBinding.GetKnockoutBindingExpression(cell, unwrapped: true);
cellAttributes["data-bind"] = $"css: {{ '{SortDescendingHeaderCssClass}': ({gridViewDataSetExpr}).SortingOptions().SortExpression() == '{GetSortExpression()}' && ({gridViewDataSetExpr}).SortingOptions().SortDescending(), '{SortAscendingHeaderCssClass}': ({gridViewDataSetExpr}).SortingOptions().SortExpression() == '{GetSortExpression()}' && !({gridViewDataSetExpr}).SortingOptions().SortDescending()}}";
}
else if (sortableGridViewDataSet.SortingOptions.SortExpression == GetSortExpression())
Expand Down
18 changes: 12 additions & 6 deletions src/Framework/Framework/Controls/GridViewTextColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ public class GridViewTextColumn : GridViewColumn
/// Gets or sets a binding which retrieves the value to display from the current data item.
/// </summary>
[MarkupOptions(Required = true)]
public IValueBinding? ValueBinding
public IStaticValueBinding? ValueBinding
{
get { return GetValueBinding(ValueBindingProperty); }
get { return (IStaticValueBinding?)GetBinding(ValueBindingProperty); }
set { SetValue(ValueBindingProperty, value); }
}
public static readonly DotvvmProperty ValueBindingProperty =
DotvvmProperty.Register<IValueBinding?, GridViewTextColumn>(c => c.ValueBinding);
DotvvmProperty.Register<IStaticValueBinding?, GridViewTextColumn>(c => c.ValueBinding);

[MarkupOptions(AllowBinding = false)]
public ValidatorPlacement ValidatorPlacement
Expand All @@ -75,13 +75,18 @@ public ValidatorPlacement ValidatorPlacement

public override void CreateControls(IDotvvmRequestContext context, DotvvmControl container)
{
var binding = ValueBinding;
if (binding is null)
throw new DotvvmControlException(this, "The 'ValueBinding' property is required.");

var literal = new Literal();
literal.FormatString = FormatString;

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

literal.SetBinding(Literal.TextProperty, ValueBinding);
Validator.Place(literal, container.Children, ValueBinding, ValidatorPlacement);
literal.SetBinding(Literal.TextProperty, binding);
if (binding is IValueBinding v)
Validator.Place(literal, container.Children, v, ValidatorPlacement);
container.Children.Add(literal);
}

Expand All @@ -92,7 +97,8 @@ public override void CreateEditControls(IDotvvmRequestContext context, DotvvmCon

textBox.SetBinding(TextBox.TextProperty, ValueBinding);
textBox.SetBinding(TextBox.ChangedProperty, ChangedBinding);
Validator.Place(textBox, container.Children, ValueBinding, ValidatorPlacement);
if (ValueBinding is IValueBinding v)
Validator.Place(textBox, container.Children, v, ValidatorPlacement);
container.Children.Add(textBox);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Tests/ControlTests/RepeaterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public class RepeaterWrapper : CompositeControl
{
public static DotvvmControl GetContents(
HtmlCapability htmlCapability,
[ControlPropertyTypeDataContextChange("DataSource"), CollectionElementDataContextChange(1)]
[ControlPropertyBindingDataContextChange("DataSource"), CollectionElementDataContextChange(1)]
ITemplate itemTemplate,
IValueBinding<IEnumerable> dataSource
)
Expand Down
13 changes: 13 additions & 0 deletions src/Tests/ControlTests/ResourceDataContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,19 @@ public async Task HierarchyRepeater_SimpleTemplate()
check.CheckString(r.FormattedHtml, fileExtension: "html");
}

[TestMethod]
public async Task GridView()
{
var r = await cth.RunPage(typeof(TestViewModel), @"
<dot:GridView DataSource={resource: Customers}>
<dot:GridViewTextColumn HeaderText=Id ValueBinding={resource: Id} />
<dot:GridViewTemplateColumn HeaderText=Name>
{{resource: Name}}
</dot:GridViewTemplateColumn>
</dot:GridView>");
check.CheckString(r.FormattedHtml, fileExtension: "html");
}


public class TestViewModel: DotvvmViewModelBase
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<html>
<head></head>
<body>
<table>
<thead>
<tr>
<th>
<span>Id</span>
</th>
<th>
<span>Name</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<span>1</span>
</td>
<td>One</td>
</tr>
<tr>
<td>
<span>2</span>
</td>
<td>Two</td>
</tr>
</tbody>
</table>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@
"type": "DotVVM.Framework.Controls.ValidatorPlacement, DotVVM.Framework"
},
"ValueBinding": {
"type": "DotVVM.Framework.Binding.Expressions.IValueBinding, DotVVM.Framework",
"type": "DotVVM.Framework.Binding.Expressions.IStaticValueBinding, DotVVM.Framework",
"required": true
}
},
Expand Down

0 comments on commit a9193f7

Please sign in to comment.