Skip to content

Commit 3ee2344

Browse files
committed
data context matching based on types: fixed few problems
1 parent cfae6e1 commit 3ee2344

File tree

8 files changed

+89
-10
lines changed

8 files changed

+89
-10
lines changed

src/Framework/Framework/Binding/BindingHelper.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,31 @@ internal static (int stepsUp, DotvvmBindableObject target) FindDataContextTarget
9797
{
9898
// only count changes which are visible client-side
9999
// server-side context are not present in the client-side stack at all, so we need to skip them here
100-
changes++;
101-
lastAncestorContext = ancestorContext;
100+
101+
// don't count changes which only extend the data context, but don't nest it
102+
103+
var isNesting = ancestorContext.IsAncestorOf(lastAncestorContext);
104+
if (isNesting)
105+
{
106+
changes++;
107+
lastAncestorContext = ancestorContext;
108+
}
109+
#if DEBUG
110+
else if (!lastAncestorContext.DataContextType.IsAssignableFrom(ancestorContext.DataContextType))
111+
{
112+
// this should not happen - data context type should not randomly change without nesting.
113+
// we change data context stack when we get into different compilation context - a markup control
114+
// but that will be always the same viewmodel type (or supertype)
115+
116+
var previousAncestor = control.GetAllAncestors(includingThis: true).TakeWhile(aa => aa != a).LastOrDefault();
117+
var config = (control.GetValue(Internal.RequestContextProperty) as Hosting.IDotvvmRequestContext)?.Configuration;
118+
throw new DotvvmControlException(
119+
previousAncestor ?? a,
120+
$"DataContext type changed from '{lastAncestorContext.DataContextType.ToCode()}' to '{ancestorContext.DataContextType.ToCode()}' without nesting. " +
121+
$"{previousAncestor?.DebugString(config)} has DataContext: {lastAncestorContext.DataContextType.ToCode(stripNamespace: true)}, " +
122+
$"{a.DebugString(config)} has DataContext: {ancestorContext.DataContextType.ToCode(stripNamespace: true)}");
123+
}
124+
#endif
102125
}
103126

104127
if (bindingContext.Equals(ancestorContext))

src/Framework/Framework/Compilation/ControlTree/DataContextStack.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
44
using System.Collections.Immutable;
@@ -108,6 +108,21 @@ public IEnumerable<Type> Parents()
108108
}
109109
}
110110

111+
public bool IsAncestorOf(DataContextStack x)
112+
{
113+
var c = x.Parent;
114+
while (c != null)
115+
{
116+
if (this.hashCode == c.hashCode)
117+
{
118+
if (this.Equals(c))
119+
return true;
120+
}
121+
c = c.Parent;
122+
}
123+
return false;
124+
}
125+
111126
ITypeDescriptor IDataContextStack.DataContextType => new ResolvedTypeDescriptor(DataContextType);
112127
IDataContextStack? IDataContextStack.Parent => Parent;
113128

src/Samples/Tests/Tests/Complex/TaskListTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public void Complex_TaskList_ServerRenderedTaskList()
5151

5252
//add task
5353
browser.SendKeys("input[type=text]", "DotVVM");
54-
browser.Click("input[type=button]");
54+
browser.Click("input[type=submit]");
5555

5656
browser.FindElements(".table tr").ThrowIfDifferentCountThan(4);
5757

src/Tests/ControlTests/MarkupControlTests.cs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class MarkupControlTests
2727
_ = Repeater.RenderAsNamedTemplateProperty;
2828
config.Resources.RegisterScriptModuleUrl("somemodule", "http://localhost:99999/somemodule.js", null);
2929
config.Markup.AddMarkupControl("cc", "CustomControl", "CustomControl.dotcontrol");
30+
config.Markup.AddMarkupControl("cc", "CustomControlWithStaticCommand", "CustomControlWithStaticCommand.dotcontrol");
3031
config.Markup.AddMarkupControl("cc", "CustomControlWithCommand", "CustomControlWithCommand.dotcontrol");
3132
config.Markup.AddMarkupControl("cc", "CustomControlWithProperty", "CustomControlWithProperty.dotcontrol");
3233
config.Markup.AddMarkupControl("cc", "CustomControlWithInvalidVM", "CustomControlWithInvalidVM.dotcontrol");
@@ -68,14 +69,14 @@ public async Task MarkupControl_PassingStaticCommand()
6869
{
6970
var r = await cth.RunPage(typeof(BasicTestViewModel), @"
7071
71-
<cc:CustomControlWithCommand DataContext={value: Integer} Click={staticCommand: s.Save(_parent.Integer)} Another={value: _this} />
72+
<cc:CustomControlWithStaticCommand DataContext={value: Integer} Click={staticCommand: s.Save(_parent.Integer)} Another={value: _this} />
7273
<dot:Repeater DataSource={value: Collection}>
73-
<cc:CustomControlWithCommand Click={staticCommand: s.Save(_this)} Another={value: _root.Integer} />
74+
<cc:CustomControlWithStaticCommand Click={staticCommand: s.Save(_this)} Another={value: _root.Integer} />
7475
</dot:Repeater>
7576
",
7677
directives: $"@service s = {typeof(TestService)}",
7778
markupFiles: new Dictionary<string, string> {
78-
["CustomControlWithCommand.dotcontrol"] = @"
79+
["CustomControlWithStaticCommand.dotcontrol"] = @"
7980
@viewModel int
8081
@baseType DotVVM.Framework.Tests.ControlTests.CustomControlWithCommand
8182
@wrapperTag div
@@ -86,6 +87,30 @@ @wrapperTag div
8687
check.CheckString(r.FormattedHtml, fileExtension: "html");
8788
}
8889

90+
[TestMethod]
91+
public async Task MarkupControl_CommandInRepeater()
92+
{
93+
var r = await cth.RunPage(typeof(BasicTestViewModel), @"
94+
95+
<cc:CustomControlWithCommand DataContext={value: Integer} Click={command: s.Save(_parent.Integer)} Another={value: _this} />
96+
<dot:Repeater DataSource={value: Collection}>
97+
<cc:CustomControlWithCommand Click={command: s.Save(_this)} Another={value: _root.Integer} />
98+
</dot:Repeater>
99+
",
100+
directives: $"@service s = {typeof(TestService)}",
101+
markupFiles: new Dictionary<string, string> {
102+
["CustomControlWithCommand.dotcontrol"] = @"
103+
@viewModel int
104+
@baseType DotVVM.Framework.Tests.ControlTests.CustomControlWithCommand
105+
@wrapperTag div
106+
<dot:Button Click={command: _control.Click()} Text={resource: $'Button with number = {_control.Another}'} />"
107+
}
108+
);
109+
110+
check.CheckString(r.FormattedHtml, fileExtension: "html");
111+
}
112+
113+
89114
[TestMethod]
90115
public async Task MarkupControl_UpdateSource()
91116
{

src/Tests/ControlTests/testoutputs/AutoUIFormTests.BootstrapFormTest.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
<ul data-bind="foreach: { data: $parent.Products()?.Items }" id="Products__input">
5656
<li>
5757
<label class="form-check-label">
58-
<input class="form-check-input" data-bind="dotvvm-checkedItems: $parents[1].Products, checkedArrayContainsObservables: true, dotvvm-checkbox-updateAfterPostback: true, dotvvm-checked-pointer: 'dotvvm-checkedItems', checkedValue: Value" type="checkbox">
58+
<input class="form-check-input" data-bind="dotvvm-checkedItems: $parent.Products, checkedArrayContainsObservables: true, dotvvm-checkbox-updateAfterPostback: true, dotvvm-checked-pointer: 'dotvvm-checkedItems', checkedValue: Value" type="checkbox">
5959
<span data-bind="text: DisplayName"></span>
6060
</label>
6161
</li>

src/Tests/ControlTests/testoutputs/AutoUIFormTests.BulmaFormTest.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
<ul data-bind="dotvvm-validation: Products, dotvvm-validationOptions: { invalidCssClass: &quot;is-danger&quot;, setToolTipText: true }, foreach: { data: $parent.Products()?.Items }" id="Products__input">
8787
<li>
8888
<label class="checkbox">
89-
<input data-bind="dotvvm-checkedItems: $parents[1].Products, checkedArrayContainsObservables: true, dotvvm-checkbox-updateAfterPostback: true, dotvvm-checked-pointer: 'dotvvm-checkedItems', checkedValue: Value" type="checkbox">
89+
<input data-bind="dotvvm-checkedItems: $parent.Products, checkedArrayContainsObservables: true, dotvvm-checkbox-updateAfterPostback: true, dotvvm-checked-pointer: 'dotvvm-checkedItems', checkedValue: Value" type="checkbox">
9090
<span data-bind="text: DisplayName"></span>
9191
</label>
9292
</li>

src/Tests/ControlTests/testoutputs/AutoUITests.Selections.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
<ul data-bind="foreach: { data: $parent.Countries()?.Items }" id="CountryId__input">
3030
<li>
3131
<label>
32-
<input data-bind="dotvvm-checkedItems: $parents[1].CountryId, checkedArrayContainsObservables: true, dotvvm-checkbox-updateAfterPostback: true, dotvvm-checked-pointer: 'dotvvm-checkedItems', checkedValue: Value" type="checkbox">
32+
<input data-bind="dotvvm-checkedItems: $parent.CountryId, checkedArrayContainsObservables: true, dotvvm-checkbox-updateAfterPostback: true, dotvvm-checked-pointer: 'dotvvm-checkedItems', checkedValue: Value" type="checkbox">
3333
<span data-bind="text: DisplayName"></span>
3434
</label>
3535
</li>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<html>
2+
<head></head>
3+
<body>
4+
5+
<!-- ko with: int -->
6+
<div>
7+
<input onclick="dotvvm.postBack(this,[&quot;$parent.int&quot;],&quot;A0nd+6BTPk/xOJQH&quot;,&quot;c7&quot;,null,[],[],undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;" type="button" value="Button with number = 10000000">
8+
</div>
9+
<!-- /ko -->
10+
<div data-bind="foreach: { data: Collection }">
11+
<div>
12+
<input onclick="dotvvm.postBack(this,[&quot;Collection/[$index]&quot;],&quot;A0nd+6BTPk/xOJQH&quot;,&quot;c9&quot;+'_'+ko.contextFor(this).$index()+'_'+&quot;c11&quot;,null,[],[],undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;" type="button" value="Button with number = 10000000">
13+
</div>
14+
</div>
15+
</body>
16+
</html>

0 commit comments

Comments
 (0)