Skip to content

Commit

Permalink
JsComponent: error when template and property collides
Browse files Browse the repository at this point in the history
  • Loading branch information
exyi committed Apr 15, 2024
1 parent 955b2e8 commit f3ef75f
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using DotVVM.Framework.Compilation.Parser.Dothtml.Parser;

Expand Down Expand Up @@ -27,6 +28,19 @@ public static bool HasPropertyValue(this IAbstractControl control, IPropertyDesc
return value;
}

public static Dictionary<string, IAbstractPropertySetter> GetPropertyGroup(this IAbstractControl control, IPropertyGroupDescriptor group)
{
var result = new Dictionary<string, IAbstractPropertySetter>();
foreach (var prop in control.Properties)
{
if (prop.Key is IGroupedPropertyDescriptor member && member.PropertyGroup == group)
{
result.Add(member.GroupMemberName, prop.Value);
}
}
return result;
}

public static IPropertyDescriptor GetHtmlAttributeDescriptor(this IControlResolverMetadata metadata, string name)
=> metadata.GetPropertyGroupMember("", name);
public static IPropertyDescriptor GetPropertyGroupMember(this IControlResolverMetadata metadata, string prefix, string name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace DotVVM.Framework.Compilation.ControlTree
public interface IAbstractControl : IAbstractContentNode
{
IEnumerable<IPropertyDescriptor> PropertyNames { get; }
IEnumerable<KeyValuePair<IPropertyDescriptor, IAbstractPropertySetter>> Properties { get; }

bool TryGetProperty(IPropertyDescriptor property, [NotNullWhen(true)] out IAbstractPropertySetter? value);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ public class ResolvedControl : ResolvedContentNode, IAbstractControl

IEnumerable<IPropertyDescriptor> IAbstractControl.PropertyNames => Properties.Keys;

IEnumerable<KeyValuePair<IPropertyDescriptor, IAbstractPropertySetter>> IAbstractControl.Properties =>
Properties.Select(p => new KeyValuePair<IPropertyDescriptor, IAbstractPropertySetter>(p.Key, p.Value));


public ResolvedControl(ControlResolverMetadata metadata, DothtmlNode? node, DataContextStack dataContext)
: base(metadata, node, dataContext) { }

Expand Down
14 changes: 14 additions & 0 deletions src/Framework/Framework/Controls/JsComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,20 @@ public static IEnumerable<ControlUsageError> ValidateUsage(ResolvedControl contr
(control.DothtmlNode as DothtmlElementNode)?.TagNameNode
);
}

var props = control.GetPropertyGroup(PropsGroupDescriptor);
var templates = control.GetPropertyGroup(TemplatesGroupDescriptor);

foreach (var name in props.Keys.Intersect(templates.Keys))
{
var templateElement = templates[name].DothtmlNode;
yield return new ControlUsageError(
$"JsComponent property and template must not share the same name ('{name}').",
DiagnosticSeverity.Error,
props[name].DothtmlNode,
(templateElement as DothtmlElementNode)?.TagNameNode ?? templateElement
);
}
}

}
Expand Down
17 changes: 14 additions & 3 deletions src/Framework/Framework/Resources/Scripts/global-declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,15 +316,26 @@ type DotvvmFileSize = {
}

type DotvvmJsComponent = {
updateProps(p: { [key: string]: any }): void
dispose(): void
/** Called after each update of dotvvm.state which changes any of the bound properties. Only the changed properties are listed in the `updatedProps` argument. */
updateProps(updatedProps: { [key: string]: any }): void
/** Called after the HTMLElement is removed from DOM.
* The component does not need to remove any child elements, but should clean any external data, such as subscription to dotvvm events */
dispose?(): void
}
type DotvvmJsComponentFactory = {
/** Initializes the component on the specified HTMLElement.
* @param element The root HTMLElement of this component
* @param props An object listing all constants and `value` bindings from the `dot:JsComponent` instance
* @param commands An object listing all `command` and `staticCommand` bindings from the `dot:JsComponent` instance
* @param templates An object listing all content properties of the `dot:JsComponent`. The template is identified using its HTML id attribute, it can be rendered using ko.renderTemplate, KnockoutTemplateReactComponent or KnockoutTemplateSvelteComponent
* @param setProps A function which will attempt to write a value back into the bound property. Only certain `value` bindings can be updated, an exception is thown if it isn't possible
* @returns An object which will be notified about subsequent changes to the bound properties and when the component
*/
create(
element: HTMLElement,
props: { [key: string]: any },
commands: { [key: string]: (args: any[]) => Promise<any> },
templates: { [key: string]: string }, // TODO
templates: { [key: string]: string },
setProps: (p: { [key: string]: any }) => void
): DotvvmJsComponent
}
Expand Down

0 comments on commit f3ef75f

Please sign in to comment.