From d2a06b1841f537935d184d3ae63b18014522ef7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Standa=20Luke=C5=A1?= Date: Sun, 20 Feb 2022 14:11:23 +0000 Subject: [PATCH] dynamic data: support DynamicColumns virtual column --- src/DynamicData/DynamicData/DynamicColumns.cs | 77 +++++++++++++++++++ .../DynamicData/DynamicDataExtensions.cs | 3 + .../DynamicData/DynamicEntityBase.cs | 2 +- .../Styles/StyleMatchContextMethods.cs | 4 +- .../Controls/GridViewCheckBoxColumn.cs | 5 ++ .../Framework/Controls/GridViewColumn.cs | 15 +++- .../Controls/GridViewTemplateColumn.cs | 17 ---- .../Framework/Controls/GridViewTextColumn.cs | 6 ++ src/Tests/ControlTests/DynamicDataTests.cs | 14 +++- .../DynamicDataTests.BasicDynamicGrid.html | 46 +++++++++++ ...alizationTests.SerializeDefaultConfig.json | 8 +- 11 files changed, 169 insertions(+), 28 deletions(-) create mode 100644 src/DynamicData/DynamicData/DynamicColumns.cs create mode 100644 src/Tests/ControlTests/testoutputs/DynamicDataTests.BasicDynamicGrid.html diff --git a/src/DynamicData/DynamicData/DynamicColumns.cs b/src/DynamicData/DynamicData/DynamicColumns.cs new file mode 100644 index 0000000000..4087ae08d4 --- /dev/null +++ b/src/DynamicData/DynamicData/DynamicColumns.cs @@ -0,0 +1,77 @@ +using System; +using System.Linq; +using DotVVM.Framework.Binding; +using DotVVM.Framework.Binding.Expressions; +using DotVVM.Framework.Compilation.Styles; +using DotVVM.Framework.Controls.DynamicData.Metadata; +using DotVVM.Framework.Hosting; +using DotVVM.Framework.Utils; + +namespace DotVVM.Framework.Controls.DynamicData +{ + // TODO: replace this with something else + public class DummyColumnThatDoesNothing : GridViewColumn + { + public DummyColumnThatDoesNothing() + { + Visible = false; + } + + public override void CreateControls(IDotvvmRequestContext context, DotvvmControl container) { } + public override void CreateEditControls(IDotvvmRequestContext context, DotvvmControl container) { } + } + public class DynamicColumns: GridViewColumn + { + public static DotvvmCapabilityProperty PropsProperty = + DotvvmCapabilityProperty.RegisterCapability(); + + public static GridViewColumn[] Replace(IStyleMatchContext col) + { + if (col.HasProperty(c => c.EditTemplate)) + throw new NotSupportedException("EditTemplate is not supported in DynamicGridColumnGroup."); + + var props = col.PropertyValue(PropsProperty).NotNull(); + + var context = new DynamicDataContext(col.Control.DataContextTypeStack, col.Configuration.ServiceProvider) + { + ViewName = props.ViewName, + GroupName = props.GroupName + }; + + var properties = DynamicEntityBase.GetPropertiesToDisplay(context); + + var columns = properties.Select(p => CreateColumn(p, context, props)).ToArray(); + return columns; + } + + protected static DynamicGridColumn CreateColumn(PropertyDisplayMetadata property, DynamicDataContext context, Props props) + { + return + new DynamicGridColumn() + .SetProperty(p => p.Property, context.CreateValueBinding(property)); + // .SetProperty("Changed", props.Changed.GetValueOrDefault(property.PropertyInfo.Name)) + // .SetProperty("Enabled", props.Enabled.GetValueOrDefault(property.PropertyInfo.Name, true)); + } + + public override void CreateControls(IDotvvmRequestContext context, DotvvmControl container) => throw new NotImplementedException("DynamicGridColumn must be replaced using server-side styles. It cannot be used at runtime"); + public override void CreateEditControls(IDotvvmRequestContext context, DotvvmControl container) => throw new NotImplementedException("DynamicGridColumn must be replaced using server-side styles. It cannot be used at runtime"); + + [DotvvmControlCapability] + public sealed record Props + { + /// + /// Gets or sets the view name (e.g. Insert, Edit, ReadOnly). Some fields may have different metadata for each view. + /// + public string? ViewName { get; init; } + + + /// + /// Gets or sets the group of fields that should be rendered. If not set, fields from all groups will be rendered. + /// + public string? GroupName { get; init; } + + public IValueBinding? Property { get; init; } + public ValueOrBinding IsEditable { get; init; } = new(true); + } + } +} diff --git a/src/DynamicData/DynamicData/DynamicDataExtensions.cs b/src/DynamicData/DynamicData/DynamicDataExtensions.cs index d8cc5da536..25e0362b30 100644 --- a/src/DynamicData/DynamicData/DynamicDataExtensions.cs +++ b/src/DynamicData/DynamicData/DynamicDataExtensions.cs @@ -85,6 +85,9 @@ private static void AddDynamicDataConfiguration(DotvvmConfiguration config) private static void RegisterDynamicDataStyles(DotvvmConfiguration config) { var s = config.Styles; + s.Register() + .SetDotvvmProperty(Styles.AppendProperty, c => DynamicColumns.Replace(c)) + .ReplaceWith(new DummyColumnThatDoesNothing()); s.Register() .ReplaceWith(c => DynamicGridColumn.Replace(c)); } diff --git a/src/DynamicData/DynamicData/DynamicEntityBase.cs b/src/DynamicData/DynamicData/DynamicEntityBase.cs index 3cb1ee1a33..add9a05edd 100644 --- a/src/DynamicData/DynamicData/DynamicEntityBase.cs +++ b/src/DynamicData/DynamicData/DynamicEntityBase.cs @@ -60,7 +60,7 @@ protected DynamicDataContext CreateDynamicDataContext() /// /// Gets the list of properties that should be displayed. /// - protected virtual PropertyDisplayMetadata[] GetPropertiesToDisplay(DynamicDataContext context) + internal static PropertyDisplayMetadata[] GetPropertiesToDisplay(DynamicDataContext context) { var entityPropertyListProvider = context.Services.GetRequiredService(); var viewContext = context.CreateViewContext(); diff --git a/src/Framework/Framework/Compilation/Styles/StyleMatchContextMethods.cs b/src/Framework/Framework/Compilation/Styles/StyleMatchContextMethods.cs index 8f290b693a..bd39ccf811 100644 --- a/src/Framework/Framework/Compilation/Styles/StyleMatchContextMethods.cs +++ b/src/Framework/Framework/Compilation/Styles/StyleMatchContextMethods.cs @@ -154,7 +154,7 @@ public static bool HasProperty(this IStyleMatchContext c, DotvvmProperty propert /// /// Determines whether the control has the given . /// - public static bool HasProperty(this IStyleMatchContext c, Expression> property) + public static bool HasProperty(this IStyleMatchContext c, Expression> property) { var prop = DotvvmPropertyUtils.GetDotvvmPropertyFromExpression(property); return c.HasProperty(prop); @@ -171,7 +171,7 @@ public static bool HasBinding(this IStyleMatchContext c, DotvvmProperty property /// /// Determines whether the control has the given . /// - public static bool HasBinding(this IStyleMatchContext c, Expression> property) + public static bool HasBinding(this IStyleMatchContext c, Expression> property) { var prop = DotvvmPropertyUtils.GetDotvvmPropertyFromExpression(property); return c.HasBinding(prop); diff --git a/src/Framework/Framework/Controls/GridViewCheckBoxColumn.cs b/src/Framework/Framework/Controls/GridViewCheckBoxColumn.cs index 30ac21cd6d..bedb7c9972 100644 --- a/src/Framework/Framework/Controls/GridViewCheckBoxColumn.cs +++ b/src/Framework/Framework/Controls/GridViewCheckBoxColumn.cs @@ -37,6 +37,11 @@ public override void CreateControls(IDotvvmRequestContext context, DotvvmControl public override void CreateEditControls(IDotvvmRequestContext context, DotvvmControl container) { + if (EditTemplate is {} editTemplate) + { + editTemplate.BuildContent(context, container); + return; + } CreateControlsCore(container, enabled: true); } diff --git a/src/Framework/Framework/Controls/GridViewColumn.cs b/src/Framework/Framework/Controls/GridViewColumn.cs index 37cb42c226..a43ca9c0b8 100644 --- a/src/Framework/Framework/Controls/GridViewColumn.cs +++ b/src/Framework/Framework/Controls/GridViewColumn.cs @@ -157,6 +157,15 @@ public bool Visible public static readonly DotvvmProperty EditCellDecoratorsProperty = DotvvmProperty.Register?, GridViewColumn>(c => c.EditCellDecorators); + [MarkupOptions(AllowBinding = false, MappingMode = MappingMode.InnerElement)] + public ITemplate? EditTemplate + { + get { return (ITemplate?)GetValue(EditTemplateProperty); } + set { SetValue(EditTemplateProperty, value); } + } + public static readonly DotvvmProperty EditTemplateProperty + = DotvvmProperty.Register(c => c.EditTemplate, null); + /// /// Gets or sets a list of decorators that will be applied on each header cell. /// @@ -173,7 +182,11 @@ public bool Visible public abstract void CreateControls(IDotvvmRequestContext context, DotvvmControl container); - public abstract void CreateEditControls(IDotvvmRequestContext context, DotvvmControl container); + public virtual void CreateEditControls(IDotvvmRequestContext context, DotvvmControl container) + { + if (EditTemplate == null) throw new DotvvmControlException(this, $"{this.GetType().Name}.EditTemplate must be set when editing is allowed in a GridView."); + EditTemplate.BuildContent(context, container); + } public virtual void CreateHeaderControls(IDotvvmRequestContext context, GridView gridView, Action? sortCommand, HtmlGenericControl cell, IGridViewDataSet? gridViewDataSet) { diff --git a/src/Framework/Framework/Controls/GridViewTemplateColumn.cs b/src/Framework/Framework/Controls/GridViewTemplateColumn.cs index 04eec00f55..5c36ba6b52 100644 --- a/src/Framework/Framework/Controls/GridViewTemplateColumn.cs +++ b/src/Framework/Framework/Controls/GridViewTemplateColumn.cs @@ -25,27 +25,10 @@ public class GridViewTemplateColumn : GridViewColumn public static readonly DotvvmProperty ContentTemplateProperty = DotvvmProperty.Register(c => c.ContentTemplate, null); - - [MarkupOptions(AllowBinding = false, MappingMode = MappingMode.InnerElement)] - public ITemplate? EditTemplate - { - get { return (ITemplate?)GetValue(EditTemplateProperty); } - set { SetValue(EditTemplateProperty, value); } - } - public static readonly DotvvmProperty EditTemplateProperty - = DotvvmProperty.Register(c => c.EditTemplate, null); - - public override void CreateControls(IDotvvmRequestContext context, DotvvmControl container) { ContentTemplate.NotNull("GridViewTemplateColumn.ContentTemplate must be set") .BuildContent(context, container); } - - public override void CreateEditControls(IDotvvmRequestContext context, DotvvmControl container) - { - if (EditTemplate == null) throw new DotvvmControlException(this, "EditTemplate must be set when editing is allowed in a GridView."); - EditTemplate.BuildContent(context, container); - } } } diff --git a/src/Framework/Framework/Controls/GridViewTextColumn.cs b/src/Framework/Framework/Controls/GridViewTextColumn.cs index f574722bd7..bc188a529d 100644 --- a/src/Framework/Framework/Controls/GridViewTextColumn.cs +++ b/src/Framework/Framework/Controls/GridViewTextColumn.cs @@ -87,6 +87,12 @@ public override void CreateControls(IDotvvmRequestContext context, DotvvmControl public override void CreateEditControls(IDotvvmRequestContext context, DotvvmControl container) { + if (EditTemplate is {} editTemplate) + { + editTemplate.BuildContent(context, container); + return; + } + var textBox = new TextBox(); textBox.FormatString = FormatString; diff --git a/src/Tests/ControlTests/DynamicDataTests.cs b/src/Tests/ControlTests/DynamicDataTests.cs index 8ea197bf99..8723691ba6 100644 --- a/src/Tests/ControlTests/DynamicDataTests.cs +++ b/src/Tests/ControlTests/DynamicDataTests.cs @@ -95,11 +95,19 @@ public async Task DynamicEntityWithVisibleEnabledFields(string viewName, string ", user: new ClaimsPrincipal(user), fileName: $"{nameof(DynamicEntityWithVisibleEnabledFields)}-{viewName}-{userRoleName}-{isAuthenticated}" + check.CheckString(r.FormattedHtml, $"{viewName}-{userRoleName}-{isAuthenticated}", fileExtension: "html"); + } + [TestMethod] + public async Task BasicDynamicGrid() + { + var r = await cth.RunPage(typeof(BasicTestViewModel), @" + + + + " ); - CollectionAssert.AreEqual(new WrappedHtmlControl2[0], r.View.GetAllDescendants().OfType().ToArray()); - - check.CheckString(r.FormattedHtml, $"{viewName}-{userRoleName}-{isAuthenticated}", fileExtension: "html"); + check.CheckString(r.FormattedHtml, fileExtension: "html"); } public class SimpleEntity diff --git a/src/Tests/ControlTests/testoutputs/DynamicDataTests.BasicDynamicGrid.html b/src/Tests/ControlTests/testoutputs/DynamicDataTests.BasicDynamicGrid.html new file mode 100644 index 0000000000..68bc10d961 --- /dev/null +++ b/src/Tests/ControlTests/testoutputs/DynamicDataTests.BasicDynamicGrid.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + Id + + Name + + Email + + Sometime +
+ + + + + + + +
+ + + diff --git a/src/Tests/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json b/src/Tests/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json index 450e389db4..0656e969e8 100644 --- a/src/Tests/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json +++ b/src/Tests/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json @@ -498,6 +498,10 @@ "type": "System.Collections.Generic.List`1[[DotVVM.Framework.Controls.Decorator, DotVVM.Framework, Version=***, Culture=neutral, PublicKeyToken=23f3607db32275da]]", "mappingMode": "InnerElement" }, + "EditTemplate": { + "type": "DotVVM.Framework.Controls.ITemplate, DotVVM.Framework", + "mappingMode": "InnerElement" + }, "FilterTemplate": { "type": "DotVVM.Framework.Controls.ITemplate, DotVVM.Framework", "dataContextManipulation": { @@ -584,10 +588,6 @@ "type": "DotVVM.Framework.Controls.ITemplate, DotVVM.Framework", "mappingMode": "InnerElement", "required": true - }, - "EditTemplate": { - "type": "DotVVM.Framework.Controls.ITemplate, DotVVM.Framework", - "mappingMode": "InnerElement" } }, "DotVVM.Framework.Controls.GridViewTextColumn": {