Skip to content

Commit

Permalink
data context matching based on types: fixed few problems
Browse files Browse the repository at this point in the history
  • Loading branch information
exyi committed Nov 23, 2024
1 parent 23c5f55 commit a1342d5
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 6 deletions.
25 changes: 24 additions & 1 deletion src/Framework/Framework/Binding/BindingHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,30 @@ internal static (int stepsUp, DotvvmBindableObject target) FindDataContextTarget
{
// only count changes which are visible client-side
// server-side context are not present in the client-side stack at all, so we need to skip them here
changes++;

// don't count changes which only extend the data context, but don't nest it

var isNesting = ancestorContext.IsAncestorOf(lastAncestorContext);
if (isNesting)
{
changes++;
}
#if DEBUG
else if (!lastAncestorContext.DataContextType.IsAssignableFrom(ancestorContext.DataContextType))
{
// this should not happen - data context type should not randomly change without nesting.
// we change data context stack when we get into different compilation context - a markup control
// but that will be always the same viewmodel type (or supertype)

var previousAncestor = control.GetAllAncestors(includingThis: true).TakeWhile(aa => aa != a).LastOrDefault();
var config = (control.GetValue(Internal.RequestContextProperty) as Hosting.IDotvvmRequestContext)?.Configuration;
throw new DotvvmControlException(
previousAncestor ?? a,
$"DataContext type changed from '{lastAncestorContext.DataContextType.ToCode()}' to '{ancestorContext.DataContextType.ToCode()}' without nesting. " +
$"{previousAncestor?.DebugString(config)} has DataContext: {lastAncestorContext}, " +
$"{a.DebugString(config)} has DataContext: {ancestorContext}");
}
#endif
lastAncestorContext = ancestorContext;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,21 @@ public IEnumerable<Type> Parents()
}
}

public bool IsAncestorOf(DataContextStack x)
{
var c = x.Parent;
while (c != null)
{
if (this.hashCode == c.hashCode)
{
if (this.Equals(c))
return true;
}
c = c.Parent;
}
return false;
}

ITypeDescriptor IDataContextStack.DataContextType => new ResolvedTypeDescriptor(DataContextType);
IDataContextStack? IDataContextStack.Parent => Parent;

Expand Down
2 changes: 1 addition & 1 deletion src/Samples/Tests/Tests/Complex/TaskListTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public void Complex_TaskList_ServerRenderedTaskList()

//add task
browser.SendKeys("input[type=text]", "DotVVM");
browser.Click("input[type=button]");
browser.Click("input[type=submit]");

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

Expand Down
31 changes: 28 additions & 3 deletions src/Tests/ControlTests/MarkupControlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class MarkupControlTests
_ = Repeater.RenderAsNamedTemplateProperty;
config.Resources.RegisterScriptModuleUrl("somemodule", "http://localhost:99999/somemodule.js", null);
config.Markup.AddMarkupControl("cc", "CustomControl", "CustomControl.dotcontrol");
config.Markup.AddMarkupControl("cc", "CustomControlWithStaticCommand", "CustomControlWithStaticCommand.dotcontrol");
config.Markup.AddMarkupControl("cc", "CustomControlWithCommand", "CustomControlWithCommand.dotcontrol");
config.Markup.AddMarkupControl("cc", "CustomControlWithProperty", "CustomControlWithProperty.dotcontrol");
config.Markup.AddMarkupControl("cc", "CustomControlWithInvalidVM", "CustomControlWithInvalidVM.dotcontrol");
Expand Down Expand Up @@ -71,14 +72,14 @@ public async Task MarkupControl_PassingStaticCommand()
{
var r = await cth.RunPage(typeof(BasicTestViewModel), @"
<cc:CustomControlWithCommand DataContext={value: Integer} Click={staticCommand: s.Save(_parent.Integer)} Another={value: _this} />
<cc:CustomControlWithStaticCommand DataContext={value: Integer} Click={staticCommand: s.Save(_parent.Integer)} Another={value: _this} />
<dot:Repeater DataSource={value: Collection}>
<cc:CustomControlWithCommand Click={staticCommand: s.Save(_this)} Another={value: _root.Integer} />
<cc:CustomControlWithStaticCommand Click={staticCommand: s.Save(_this)} Another={value: _root.Integer} />
</dot:Repeater>
",
directives: $"@service s = {typeof(TestService)}",
markupFiles: new Dictionary<string, string> {
["CustomControlWithCommand.dotcontrol"] = @"
["CustomControlWithStaticCommand.dotcontrol"] = @"
@viewModel int
@baseType DotVVM.Framework.Tests.ControlTests.CustomControlWithCommand
@wrapperTag div
Expand All @@ -89,6 +90,30 @@ @wrapperTag div
check.CheckString(r.FormattedHtml, fileExtension: "html");
}

[TestMethod]
public async Task MarkupControl_CommandInRepeater()
{
var r = await cth.RunPage(typeof(BasicTestViewModel), @"
<cc:CustomControlWithCommand DataContext={value: Integer} Click={command: s.Save(_parent.Integer)} Another={value: _this} />
<dot:Repeater DataSource={value: Collection}>
<cc:CustomControlWithCommand Click={command: s.Save(_this)} Another={value: _root.Integer} />
</dot:Repeater>
",
directives: $"@service s = {typeof(TestService)}",
markupFiles: new Dictionary<string, string> {
["CustomControlWithCommand.dotcontrol"] = @"
@viewModel int
@baseType DotVVM.Framework.Tests.ControlTests.CustomControlWithCommand
@wrapperTag div
<dot:Button Click={command: _control.Click()} Text={resource: $'Button with number = {_control.Another}'} />"
}
);

check.CheckString(r.FormattedHtml, fileExtension: "html");
}


[TestMethod]
public async Task MarkupControl_UpdateSource()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<!-- Resource c7-item of type TemplateResource. -->
<template id=c7-item><!-- ko with: $item -->
<!-- ko with: $rawData --><div>
<input type=button onclick='dotvvm.postBack(this,["HItems/[$indexPath]","$parent"],"RR93KduwUKJIXxFp","c7"+&#39;_&#39;+(ko.contextFor(this).$parentContext.$parentContext.$indexPath.map(ko.unwrap).join("_")+"L")+&#39;_&#39;+"c9",null,[],[],undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;' data-bind="value: Label" /></div><!-- /ko -->
<input type=button onclick='dotvvm.postBack(this,["HItems/[$indexPath]","$parent"],"RR93KduwUKJIXxFp","c7"+&#39;_&#39;+(ko.contextFor(this).$parentContext.$indexPath.map(ko.unwrap).join("_")+"L")+&#39;_&#39;+"c9",null,[],[],undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;' data-bind="value: Label" /></div><!-- /ko -->
<!-- /ko --><!-- ko template: { foreach: $item().Children, name: "c7-item", hierarchyRole: "Child" } --><!-- ko if: Children()?.length --><!-- /ko --><!-- /ko --></template>

</body>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<html>
<head></head>
<body>

<!-- ko with: int -->
<div>
<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">
</div>
<!-- /ko -->
<div data-bind="foreach: { data: Collection }">
<div>
<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">
</div>
</div>
</body>
</html>

0 comments on commit a1342d5

Please sign in to comment.