diff --git a/src/Framework/Framework/Controls/AddTemplateDecorator.cs b/src/Framework/Framework/Controls/AddTemplateDecorator.cs new file mode 100644 index 0000000000..7dc7a1b8af --- /dev/null +++ b/src/Framework/Framework/Controls/AddTemplateDecorator.cs @@ -0,0 +1,45 @@ +using DotVVM.Framework.Hosting; +using DotVVM.Framework.Binding; + +namespace DotVVM.Framework.Controls +{ + /// Adds a template before or after the decorated control. + [ControlMarkupOptions(AllowContent = false)] + public class AddTemplateDecorator: Decorator + { + /// Template is rendered after the decorated control. + [MarkupOptions(MappingMode = MappingMode.InnerElement)] + public ITemplate AfterTemplate + { + get => (ITemplate)GetValue(AfterTemplateProperty)!; + set => SetValue(AfterTemplateProperty, value); + } + public static readonly DotvvmProperty AfterTemplateProperty = + DotvvmProperty.Register(nameof(AfterTemplate)); + + /// Template is rendered before the decorated control. + [MarkupOptions(MappingMode = MappingMode.InnerElement)] + public ITemplate BeforeTemplate + { + get => (ITemplate)GetValue(BeforeTemplateProperty)!; + set => SetValue(BeforeTemplateProperty, value); + } + public static readonly DotvvmProperty BeforeTemplateProperty = + DotvvmProperty.Register(nameof(BeforeTemplate)); + + protected internal override void OnInit(IDotvvmRequestContext context) + { + var after = this.AfterTemplate; + var before = this.BeforeTemplate; + + if (after is {}) + { + Children.Add(new TemplateHost(after)); + } + if (before is {}) + { + Children.Insert(0, new TemplateHost(before)); + } + } + } +} diff --git a/src/Tests/ControlTests/GridViewTests.cs b/src/Tests/ControlTests/GridViewTests.cs index a427af53c1..24d860e8d1 100644 --- a/src/Tests/ControlTests/GridViewTests.cs +++ b/src/Tests/ControlTests/GridViewTests.cs @@ -119,6 +119,40 @@ public async Task RequiredResourceInEditTemplate() check.CheckString(r.FormattedHtml, fileExtension: "html"); } + [TestMethod] + public async Task GridView_RowDecorators_AddChildren() + { + var r = await cth.RunPage(typeof(BasicTestViewModel), """ + + + + + + + + + vvv enabled customer vvv + + + + + + + ^^^ enabled customer ^^^ + + + + + + + + + + + """); + + check.CheckString(r.FormattedHtml, fileExtension: "html"); + } public class BasicTestViewModel: DotvvmViewModelBase { diff --git a/src/Tests/ControlTests/testoutputs/GridViewTests.GridView_RowDecorators_AddChildren.html b/src/Tests/ControlTests/testoutputs/GridViewTests.GridView_RowDecorators_AddChildren.html new file mode 100644 index 0000000000..75e9ff09ae --- /dev/null +++ b/src/Tests/ControlTests/testoutputs/GridViewTests.GridView_RowDecorators_AddChildren.html @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name +
+ vvv enabled customer vvv +
+ +
+ ^^^ enabled customer ^^^ +
+ + + diff --git a/src/Tests/DotVVM.Framework.Tests.csproj b/src/Tests/DotVVM.Framework.Tests.csproj index 4250b4ccd0..b37e5b2397 100644 --- a/src/Tests/DotVVM.Framework.Tests.csproj +++ b/src/Tests/DotVVM.Framework.Tests.csproj @@ -35,7 +35,7 @@ - + diff --git a/src/Tests/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json b/src/Tests/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json index 63a2baef13..c595e0f70a 100644 --- a/src/Tests/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json +++ b/src/Tests/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json @@ -350,6 +350,16 @@ "isCompileTimeOnly": true } }, + "DotVVM.Framework.Controls.AddTemplateDecorator": { + "AfterTemplate": { + "type": "DotVVM.Framework.Controls.ITemplate, DotVVM.Framework", + "mappingMode": "InnerElement" + }, + "BeforeTemplate": { + "type": "DotVVM.Framework.Controls.ITemplate, DotVVM.Framework", + "mappingMode": "InnerElement" + } + }, "DotVVM.Framework.Controls.AuthenticatedView": { "AuthenticatedTemplate": { "type": "DotVVM.Framework.Controls.ITemplate, DotVVM.Framework", @@ -1971,6 +1981,11 @@ "assembly": "DotVVM.Framework", "baseType": "DotVVM.Framework.Controls.DotvvmControl, DotVVM.Framework" }, + "DotVVM.Framework.Controls.AddTemplateDecorator": { + "assembly": "DotVVM.Framework", + "baseType": "DotVVM.Framework.Controls.Decorator, DotVVM.Framework", + "withoutContent": true + }, "DotVVM.Framework.Controls.AuthenticatedView": { "assembly": "DotVVM.Framework", "baseType": "DotVVM.Framework.Controls.ConfigurableHtmlControl, DotVVM.Framework",