From 615db69e2f1f6ffdc6e3f0639ecee0a3899410fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sat, 21 Aug 2021 17:52:19 +0200 Subject: [PATCH 1/3] Added TemplateHost control with samples UI tests --- src/DotVVM.Framework/Controls/TemplateHost.cs | 38 ++++++++++++ .../DotVVM.Samples.Common.csproj | 3 + src/DotVVM.Samples.Common/DotvvmStartup.cs | 3 +- .../TemplateHost/BasicViewModel.cs | 31 ++++++++++ .../ControlSamples/TemplateHost/Basic.dothtml | 30 ++++++++++ .../TemplateHost/TemplatedListControl.cs | 59 +++++++++++++++++++ .../TemplatedListControl.dotcontrol | 21 +++++++ .../TemplateHost/TemplatedMarkupControl.cs | 33 +++++++++++ .../TemplatedMarkupControl.dotcontrol | 7 +++ .../Control/TemplateHostTests.cs | 54 +++++++++++++++++ .../DotVVM.Samples.Tests.csproj | 14 ++--- .../SamplesRouteUrls.designer.cs | 1 + 12 files changed, 286 insertions(+), 8 deletions(-) create mode 100644 src/DotVVM.Framework/Controls/TemplateHost.cs create mode 100644 src/DotVVM.Samples.Common/ViewModels/ControlSamples/TemplateHost/BasicViewModel.cs create mode 100644 src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/Basic.dothtml create mode 100644 src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/TemplatedListControl.cs create mode 100644 src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/TemplatedListControl.dotcontrol create mode 100644 src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/TemplatedMarkupControl.cs create mode 100644 src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/TemplatedMarkupControl.dotcontrol create mode 100644 src/DotVVM.Samples.Tests/Control/TemplateHostTests.cs diff --git a/src/DotVVM.Framework/Controls/TemplateHost.cs b/src/DotVVM.Framework/Controls/TemplateHost.cs new file mode 100644 index 0000000000..cc859e06d5 --- /dev/null +++ b/src/DotVVM.Framework/Controls/TemplateHost.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DotVVM.Framework.Binding; +using DotVVM.Framework.Hosting; + +namespace DotVVM.Framework.Controls +{ + /// + /// Renders a template supplied by a resource binding or from a runtime. + /// + [ControlMarkupOptions(AllowContent = false)] + public class TemplateHost : DotvvmControl + { + + /// + /// Gets or sets the template that will be rendered inside this control. + /// + [MarkupOptions(AllowBinding = false, MappingMode = MappingMode.Attribute, Required = true)] + public ITemplate Template + { + get { return (ITemplate)GetValue(TemplateProperty); } + set { SetValue(TemplateProperty, value); } + } + public static readonly DotvvmProperty TemplateProperty + = DotvvmProperty.Register(c => c.Template, null); + + + + protected internal override void OnLoad(IDotvvmRequestContext context) + { + Template.BuildContent(context, this); + base.OnLoad(context); + } + } +} diff --git a/src/DotVVM.Samples.Common/DotVVM.Samples.Common.csproj b/src/DotVVM.Samples.Common/DotVVM.Samples.Common.csproj index 489414b3da..e4ab6123af 100644 --- a/src/DotVVM.Samples.Common/DotVVM.Samples.Common.csproj +++ b/src/DotVVM.Samples.Common/DotVVM.Samples.Common.csproj @@ -41,6 +41,9 @@ + + + diff --git a/src/DotVVM.Samples.Common/DotvvmStartup.cs b/src/DotVVM.Samples.Common/DotvvmStartup.cs index 13c2650072..cbf2620ea6 100644 --- a/src/DotVVM.Samples.Common/DotvvmStartup.cs +++ b/src/DotVVM.Samples.Common/DotvvmStartup.cs @@ -195,8 +195,9 @@ private static void AddControls(DotvvmConfiguration config) config.Markup.AddMarkupControl("cc", "RecursiveTextRepeater2", "Views/FeatureSamples/PostBack/RecursiveTextRepeater2.dotcontrol"); config.Markup.AddMarkupControl("cc", "ModuleControl", "Views/FeatureSamples/ViewModules/ModuleControl.dotcontrol"); config.Markup.AddMarkupControl("cc", "Incrementer", "Views/FeatureSamples/ViewModules/Incrementer.dotcontrol"); + config.Markup.AddMarkupControl("cc", "TemplatedListControl", "Views/ControlSamples/TemplateHost/TemplatedListControl.dotcontrol"); + config.Markup.AddMarkupControl("cc", "TemplatedMarkupControl", "Views/ControlSamples/TemplateHost/TemplatedMarkupControl.dotcontrol"); config.Markup.AddCodeControls("cc", typeof(Loader)); - config.Markup.AddMarkupControl("sample", "EmbeddedResourceControls_Button", "embedded://EmbeddedResourceControls/Button.dotcontrol"); config.Markup.AutoDiscoverControls(new DefaultControlRegistrationStrategy(config, "sample", "Views/")); diff --git a/src/DotVVM.Samples.Common/ViewModels/ControlSamples/TemplateHost/BasicViewModel.cs b/src/DotVVM.Samples.Common/ViewModels/ControlSamples/TemplateHost/BasicViewModel.cs new file mode 100644 index 0000000000..ef2efc6ae3 --- /dev/null +++ b/src/DotVVM.Samples.Common/ViewModels/ControlSamples/TemplateHost/BasicViewModel.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DotVVM.Framework.ViewModel; + +namespace DotVVM.Samples.Common.ViewModels.ControlSamples.TemplateHost +{ + public class BasicViewModel : DotvvmViewModelBase + { + + public List ObjectList { get; set; } = new List() + { + new IntValue() { Value = 1 }, + new IntValue() { Value = 2 }, + new IntValue() { Value = 3 } + }; + + public IntValue CreateObject() + { + return new IntValue() { Value = 1 }; + } + } + + public class IntValue + { + public int Value { get; set; } + } +} + diff --git a/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/Basic.dothtml b/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/Basic.dothtml new file mode 100644 index 0000000000..4919beb1cd --- /dev/null +++ b/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/Basic.dothtml @@ -0,0 +1,30 @@ +@viewModel DotVVM.Samples.Common.ViewModels.ControlSamples.TemplateHost.BasicViewModel, DotVVM.Samples.Common + + + + + + + + + + +

TemplateHost

+ + + +

hello from template

+
+
+ + + + {{value: Value}} + + + + + + + + diff --git a/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/TemplatedListControl.cs b/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/TemplatedListControl.cs new file mode 100644 index 0000000000..4f353b5775 --- /dev/null +++ b/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/TemplatedListControl.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using DotVVM.Framework.Binding; +using DotVVM.Framework.Binding.Expressions; +using DotVVM.Framework.Controls; + +namespace DotVVM.Samples.Common.Views.ControlSamples.TemplateHost +{ + public class TemplatedListControl : DotvvmMarkupControl + { + + [MarkupOptions(AllowHardCodedValue = false, Required = true)] + public IEnumerable DataSource + { + get { return (IEnumerable)GetValue(DataSourceProperty); } + set { SetValue(DataSourceProperty, value); } + } + public static readonly DotvvmProperty DataSourceProperty + = DotvvmProperty.Register(c => c.DataSource, null); + + [ControlPropertyBindingDataContextChange(nameof(DataSource), order: 0)] + [CollectionElementDataContextChange(order: 1)] + [MarkupOptions(AllowBinding = false, Required = true, MappingMode = MappingMode.InnerElement)] + public ITemplate ItemTemplate + { + get { return (ITemplate)GetValue(ItemTemplateProperty); } + set { SetValue(ItemTemplateProperty, value); } + } + public static readonly DotvvmProperty ItemTemplateProperty + = DotvvmProperty.Register(c => c.ItemTemplate, null); + + [MarkupOptions(AllowHardCodedValue = false, Required = true)] + public ICommandBinding OnCreateItem + { + get { return (ICommandBinding)GetValue(OnCreateItemProperty); } + set { SetValue(OnCreateItemProperty, value); } + } + public static readonly DotvvmProperty OnCreateItemProperty + = DotvvmProperty.Register, TemplatedListControl>(c => c.OnCreateItem, null); + + + public void AddItem() + { + var item = OnCreateItem.BindingDelegate(this.GetDataContexts().ToArray(), this); + ((dynamic)DataSource).Add(((dynamic)item)()); + } + + public void RemoveItem(object item) + { + ((dynamic)DataSource).Remove((dynamic)item); + } + + } + +} + diff --git a/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/TemplatedListControl.dotcontrol b/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/TemplatedListControl.dotcontrol new file mode 100644 index 0000000000..f66babafb8 --- /dev/null +++ b/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/TemplatedListControl.dotcontrol @@ -0,0 +1,21 @@ +@viewModel System.Object, mscorlib +@baseType DotVVM.Samples.Common.Views.ControlSamples.TemplateHost.TemplatedListControl, DotVVM.Samples.Common + + + +
+ + +

+ +

+
+
+ +
+
+
+ +

+ +

diff --git a/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/TemplatedMarkupControl.cs b/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/TemplatedMarkupControl.cs new file mode 100644 index 0000000000..2cc0bf837c --- /dev/null +++ b/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/TemplatedMarkupControl.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using DotVVM.Framework.Binding; +using DotVVM.Framework.Controls; + +namespace DotVVM.Samples.Common.Views.ControlSamples.TemplateHost +{ + public class TemplatedMarkupControl : DotvvmMarkupControl + { + + public string HeaderText + { + get { return (string)GetValue(HeaderTextProperty); } + set { SetValue(HeaderTextProperty, value); } + } + public static readonly DotvvmProperty HeaderTextProperty + = DotvvmProperty.Register(c => c.HeaderText, null); + + [MarkupOptions(AllowBinding = false, MappingMode = MappingMode.InnerElement, Required = true)] + public ITemplate ContentTemplate + { + get { return (ITemplate)GetValue(ContentTemplateProperty); } + set { SetValue(ContentTemplateProperty, value); } + } + public static readonly DotvvmProperty ContentTemplateProperty + = DotvvmProperty.Register(c => c.ContentTemplate, null); + + + } +} + diff --git a/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/TemplatedMarkupControl.dotcontrol b/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/TemplatedMarkupControl.dotcontrol new file mode 100644 index 0000000000..ddc723f018 --- /dev/null +++ b/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/TemplatedMarkupControl.dotcontrol @@ -0,0 +1,7 @@ +@viewModel System.Object, mscorlib +@baseType DotVVM.Samples.Common.Views.ControlSamples.TemplateHost.TemplatedMarkupControl, DotVVM.Samples.Common + +
+ {{value: _control.HeaderText}} + +
diff --git a/src/DotVVM.Samples.Tests/Control/TemplateHostTests.cs b/src/DotVVM.Samples.Tests/Control/TemplateHostTests.cs new file mode 100644 index 0000000000..15f40d9aea --- /dev/null +++ b/src/DotVVM.Samples.Tests/Control/TemplateHostTests.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DotVVM.Samples.Tests.Base; +using DotVVM.Testing.Abstractions; +using Riganti.Selenium.Core; +using Xunit; +using Xunit.Abstractions; + +namespace DotVVM.Samples.Tests.Control +{ + public class TemplateHostTests : AppSeleniumTest + { + public TemplateHostTests(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void Control_TemplateHost_Basic() + { + RunInAllBrowsers(browser => { + browser.NavigateToUrl(SamplesRouteUrls.ControlSamples_TemplateHost_Basic); + + AssertUI.TextEquals(browser.Single("fieldset legend"), "Form 1"); + AssertUI.TextEquals(browser.Single("fieldset p"), "hello from template"); + + var items = browser.FindElements(".templated-list div"); + items.ThrowIfDifferentCountThan(3); + + // increment item + AssertUI.TextEquals(items[0].Single("big"), "1"); + items[0].ElementAt("input[type=button]", 1).Click(); + AssertUI.TextEquals(items[0].Single("big"), "0"); + + // remove item + items[0].Single("a").Click(); + browser.WaitFor(() => { + items = browser.FindElements(".templated-list div"); + items.ThrowIfDifferentCountThan(2); + }, 2000); + AssertUI.TextEquals(items[0].Single("big"), "2"); + + // add item + browser.Last("input[type=button]").Click(); + browser.WaitFor(() => { + items = browser.FindElements(".templated-list div"); + items.ThrowIfDifferentCountThan(3); + }, 2000); + }); + } + } +} diff --git a/src/DotVVM.Samples.Tests/DotVVM.Samples.Tests.csproj b/src/DotVVM.Samples.Tests/DotVVM.Samples.Tests.csproj index 0e05ff7433..742bb6a067 100644 --- a/src/DotVVM.Samples.Tests/DotVVM.Samples.Tests.csproj +++ b/src/DotVVM.Samples.Tests/DotVVM.Samples.Tests.csproj @@ -14,13 +14,13 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - + + + + + + + diff --git a/src/DotVVM.Testing.Abstractions/SamplesRouteUrls.designer.cs b/src/DotVVM.Testing.Abstractions/SamplesRouteUrls.designer.cs index 2081ab4841..08a077890e 100644 --- a/src/DotVVM.Testing.Abstractions/SamplesRouteUrls.designer.cs +++ b/src/DotVVM.Testing.Abstractions/SamplesRouteUrls.designer.cs @@ -134,6 +134,7 @@ public partial class SamplesRouteUrls public const string ControlSamples_SpaContentPlaceHolder_HistoryApi_Spa1Spa2Page = "ControlSamples/SpaContentPlaceHolder_HistoryApi/Spa1Spa2Page"; public const string ControlSamples_SpaContentPlaceHolder_HistoryApi_Spa2PageA = "ControlSamples/SpaContentPlaceHolder_HistoryApi/Spa2PageA"; public const string ControlSamples_SpaContentPlaceHolder_HistoryApi_Spa2PageB = "ControlSamples/SpaContentPlaceHolder_HistoryApi/Spa2PageB"; + public const string ControlSamples_TemplateHost_Basic = "ControlSamples/TemplateHost/Basic"; public const string ControlSamples_TextBox_IntBoundTextBox = "ControlSamples/TextBox/IntBoundTextBox"; public const string ControlSamples_TextBox_SelectAllOnFocus = "ControlSamples/TextBox/SelectAllOnFocus"; public const string ControlSamples_TextBox_SimpleDateBox = "ControlSamples/TextBox/SimpleDateBox"; From 4840ac19211a80e73ac9b1fdb276af1bb2f7bd15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Fri, 27 Aug 2021 11:24:30 +0200 Subject: [PATCH 2/3] TemplateHost - data context checks added Rewritten UI test to test usage in composite controls --- src/DotVVM.Framework/Controls/TemplateHost.cs | 21 ++++++- src/DotVVM.Samples.Common/DotvvmStartup.cs | 2 + .../ControlSamples/TemplateHost/Basic.dothtml | 18 +++++- .../CompositeControlWithTemplate.cs | 24 +++++++ .../CompositeListControlWithTemplate.cs | 62 +++++++++++++++++++ .../DotVVM.Samples.Tests.csproj | 14 ++--- 6 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/CompositeControlWithTemplate.cs create mode 100644 src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/CompositeListControlWithTemplate.cs diff --git a/src/DotVVM.Framework/Controls/TemplateHost.cs b/src/DotVVM.Framework/Controls/TemplateHost.cs index cc859e06d5..aa1304e9cb 100644 --- a/src/DotVVM.Framework/Controls/TemplateHost.cs +++ b/src/DotVVM.Framework/Controls/TemplateHost.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using DotVVM.Framework.Binding; +using DotVVM.Framework.Compilation.ControlTree; using DotVVM.Framework.Hosting; namespace DotVVM.Framework.Controls @@ -31,8 +32,26 @@ public ITemplate Template protected internal override void OnLoad(IDotvvmRequestContext context) { - Template.BuildContent(context, this); + var placeHolder = new PlaceHolder(); + Template.BuildContent(context, placeHolder); + + // validate data context of the passed template + var myDataContext = this.GetDataContextType()!; + if (!CheckChildrenDataContextStackEquality(myDataContext, placeHolder.Children)) + { + throw new DotvvmControlException(this, "Passing templates into markup controls or to controls which change the binding context, is not supported!"); + } + + Children.Add(placeHolder); + base.OnLoad(context); } + + private bool CheckChildrenDataContextStackEquality(DataContextStack desiredDataContext, DotvvmControlCollection children) + { + return children.Select(c => c.GetDataContextType()) + .Where(t => t != null) + .All(t => Equals(t, desiredDataContext)); + } } } diff --git a/src/DotVVM.Samples.Common/DotvvmStartup.cs b/src/DotVVM.Samples.Common/DotvvmStartup.cs index 790a73d4d0..d258bcf113 100644 --- a/src/DotVVM.Samples.Common/DotvvmStartup.cs +++ b/src/DotVVM.Samples.Common/DotvvmStartup.cs @@ -28,6 +28,7 @@ using DotVVM.Samples.Common.ViewModels.FeatureSamples.JavascriptTranslation; using DotVVM.Samples.Common.Views.FeatureSamples.PostbackAbortSignal; using DotVVM.Samples.Common.ViewModels.FeatureSamples.BindingVariables; +using DotVVM.Samples.Common.Views.ControlSamples.TemplateHost; namespace DotVVM.Samples.BasicSamples { @@ -195,6 +196,7 @@ private static void AddControls(DotvvmConfiguration config) config.Markup.AddMarkupControl("cc", "Incrementer", "Views/FeatureSamples/ViewModules/Incrementer.dotcontrol"); config.Markup.AddMarkupControl("cc", "TemplatedListControl", "Views/ControlSamples/TemplateHost/TemplatedListControl.dotcontrol"); config.Markup.AddMarkupControl("cc", "TemplatedMarkupControl", "Views/ControlSamples/TemplateHost/TemplatedMarkupControl.dotcontrol"); + config.Markup.AddCodeControls("cc", typeof(CompositeControlWithTemplate)); config.Markup.AddCodeControls("cc", typeof(Loader)); config.Markup.AddMarkupControl("sample", "EmbeddedResourceControls_Button", "embedded://EmbeddedResourceControls/Button.dotcontrol"); diff --git a/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/Basic.dothtml b/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/Basic.dothtml index 4919beb1cd..4f3f49d7d2 100644 --- a/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/Basic.dothtml +++ b/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/Basic.dothtml @@ -11,7 +11,21 @@

TemplateHost

- + + +

hello from template

+
+
+ + + + {{value: Value}} + + + + + + <%--

hello from template

@@ -23,7 +37,7 @@ - + --%> diff --git a/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/CompositeControlWithTemplate.cs b/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/CompositeControlWithTemplate.cs new file mode 100644 index 0000000000..2cb6d98dba --- /dev/null +++ b/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/CompositeControlWithTemplate.cs @@ -0,0 +1,24 @@ +using System; +using System.Text; +using DotVVM.Framework.Binding; +using DotVVM.Framework.Controls; + +namespace DotVVM.Samples.Common.Views.ControlSamples.TemplateHost +{ + public class CompositeControlWithTemplate : CompositeControl + { + + public static DotvvmControl GetContents( + ValueOrBinding headerText, + ITemplate contentTemplate + ) + { + return new HtmlGenericControl("fieldset") + .AppendChildren( + new HtmlGenericControl("legend", new TextOrContentCapability() { Text = headerText }), + new Framework.Controls.TemplateHost() { Template = contentTemplate } + ); + } + + } +} diff --git a/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/CompositeListControlWithTemplate.cs b/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/CompositeListControlWithTemplate.cs new file mode 100644 index 0000000000..a263e255c6 --- /dev/null +++ b/src/DotVVM.Samples.Common/Views/ControlSamples/TemplateHost/CompositeListControlWithTemplate.cs @@ -0,0 +1,62 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using DotVVM.Framework.Binding; +using DotVVM.Framework.Binding.Expressions; +using DotVVM.Framework.Controls; +using DotVVM.Framework.Utils; + +namespace DotVVM.Samples.Common.Views.ControlSamples.TemplateHost +{ + public class CompositeListControlWithTemplate : CompositeControl + { + private readonly BindingCompilationService bindingCompilationService; + + public CompositeListControlWithTemplate(BindingCompilationService bindingCompilationService) + { + this.bindingCompilationService = bindingCompilationService; + } + + public IEnumerable GetContents( + IValueBinding dataSource, + + [ControlPropertyBindingDataContextChange("DataSource", order: 0)] + [CollectionElementDataContextChange(order: 1)] + ITemplate itemTemplate, + + ICommandBinding onCreateItem + ) + { + yield return new Framework.Controls.Repeater() { + ItemTemplate = new DelegateTemplate(_ => new HtmlGenericControl("div") + .AppendChildren( + new Framework.Controls.TemplateHost() { Template = itemTemplate }, + new HtmlGenericControl("p") + .AppendChildren( + new LinkButton() { Text = "Remove" } + .SetProperty( + ButtonBase.ClickProperty, + new CommandBindingExpression(bindingCompilationService, contexts => { + ((dynamic)dataSource.GetBindingValue(this)).Remove((dynamic)contexts[0]); + }, "564787DE-E882-4C2D-BA39-482D1AB8F0CD")) + ) + ) + ), + SeparatorTemplate = new DelegateTemplate(_ => new HtmlGenericControl("hr")) + } + .SetAttribute("class", "templated-list") + .SetProperty(ItemsControl.DataSourceProperty, dataSource); + + yield return new HtmlGenericControl("p") + .AppendChildren(new Button() { Text = "Add item" } + .SetProperty( + ButtonBase.ClickProperty, + new CommandBindingExpression(bindingCompilationService, contexts => { + var item = onCreateItem.BindingDelegate(this.GetDataContexts().ToArray(), this); + ((dynamic)dataSource.GetBindingValue(this)).Add(((dynamic)item)()); + }, "38921DE7-936D-4862-921A-5051DA0CAEB1"))); + } + + } +} diff --git a/src/DotVVM.Samples.Tests/DotVVM.Samples.Tests.csproj b/src/DotVVM.Samples.Tests/DotVVM.Samples.Tests.csproj index 742bb6a067..65fb2a8acc 100644 --- a/src/DotVVM.Samples.Tests/DotVVM.Samples.Tests.csproj +++ b/src/DotVVM.Samples.Tests/DotVVM.Samples.Tests.csproj @@ -14,13 +14,13 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - + + + + + + + From a6d683ea25aaeb9048a715ed9c39ea5ee6e17bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sun, 5 Sep 2021 10:42:27 +0200 Subject: [PATCH 3/3] Added null check for the TemplateHost's Template property. --- src/Framework/Framework/Controls/TemplateHost.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Framework/Framework/Controls/TemplateHost.cs b/src/Framework/Framework/Controls/TemplateHost.cs index aa1304e9cb..d83a8871f9 100644 --- a/src/Framework/Framework/Controls/TemplateHost.cs +++ b/src/Framework/Framework/Controls/TemplateHost.cs @@ -6,6 +6,7 @@ using DotVVM.Framework.Binding; using DotVVM.Framework.Compilation.ControlTree; using DotVVM.Framework.Hosting; +using DotVVM.Framework.Utils; namespace DotVVM.Framework.Controls { @@ -20,9 +21,9 @@ public class TemplateHost : DotvvmControl /// Gets or sets the template that will be rendered inside this control. /// [MarkupOptions(AllowBinding = false, MappingMode = MappingMode.Attribute, Required = true)] - public ITemplate Template + public ITemplate? Template { - get { return (ITemplate)GetValue(TemplateProperty); } + get { return (ITemplate?)GetValue(TemplateProperty); } set { SetValue(TemplateProperty, value); } } public static readonly DotvvmProperty TemplateProperty @@ -33,7 +34,7 @@ public ITemplate Template protected internal override void OnLoad(IDotvvmRequestContext context) { var placeHolder = new PlaceHolder(); - Template.BuildContent(context, placeHolder); + Template.NotNull("TemplateHost.Template is required").BuildContent(context, placeHolder); // validate data context of the passed template var myDataContext = this.GetDataContextType()!;