Implement basics of @onclick()-type shorthand syntax

This commit is contained in:
Steve Sanderson 2018-01-16 18:01:06 +00:00
parent 946e25462e
commit d1f96153d3
5 changed files with 86 additions and 3 deletions

View File

@ -31,6 +31,7 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
private string _unconsumedHtml;
private IList<object> _currentAttributeValues;
private IDictionary<string, object> _currentElementAttributes = new Dictionary<string, object>();
private IList<IntermediateToken> _currentElementAttributeTokens = new List<IntermediateToken>();
public override void BeginWriterScope(CodeRenderingContext context, string writer)
{
@ -91,8 +92,19 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
public override void WriteCSharpExpression(CodeRenderingContext context, CSharpExpressionIntermediateNode node)
{
// Render as text node. Later, we'll need to add different handling for expressions
// that appear inside elements, e.g., <a href="@url">
// To support syntax like <elem @completeAttributePair /> (which in turn supports syntax
// like <elem @OnSomeEvent(Handler) />), check whether we are currently in the middle of
// writing an element. If so, treat this C# expression as something that should evaluate
// as a RenderTreeNode of type Attribute.
if (_unconsumedHtml != null)
{
var token = (IntermediateToken)node.Children.Single();
_currentElementAttributeTokens.Add(token);
return;
}
// Since we're not in the middle of writing an element, this must evaluate as some
// text to display
context.CodeWriter
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddText)}");
@ -206,6 +218,18 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
_currentElementAttributes.Clear();
}
if (_currentElementAttributeTokens.Count > 0)
{
foreach (var token in _currentElementAttributeTokens)
{
codeWriter
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddAttribute)}")
.Write(token.Content)
.WriteEndMethodInvocation();
}
_currentElementAttributeTokens.Clear();
}
if (nextToken.Type == HtmlTokenType.EndTag
|| nextTag.IsSelfClosing
|| htmlVoidElementsLookup.Contains(nextTag.Data))

View File

@ -34,5 +34,13 @@ namespace Microsoft.Blazor.Components
/// <returns>Always throws an exception.</returns>
public virtual Task ExecuteAsync()
=> throw new NotImplementedException($"Blazor components do not implement {nameof(ExecuteAsync)}.");
/// <summary>
/// Handles click events by invoking <paramref name="handler"/>.
/// </summary>
/// <param name="handler">The handler to be invoked when the event occurs.</param>
/// <returns>A <see cref="RenderTreeNode"/> that represents the event handler.</returns>
protected RenderTreeNode onclick(Action handler)
=> RenderTreeNode.Attribute("onclick", _ => handler());
}
}

View File

@ -102,6 +102,23 @@ namespace Microsoft.Blazor.RenderTree
Append(RenderTreeNode.Attribute(name, value.ToString()));
}
/// <summary>
/// Appends a node representing an attribute.
/// The attribute is associated with the most recently added element.
/// </summary>
/// <param name="name">The name of the attribute.</param>
/// <param name="value">The value of the attribute.</param>
public void AddAttribute(RenderTreeNode node)
{
if (node.NodeType != RenderTreeNodeType.Attribute)
{
throw new ArgumentException($"The {nameof(node.NodeType)} must be {RenderTreeNodeType.Attribute}.");
}
AssertCanAddAttribute();
Append(node);
}
/// <summary>
/// Appends a node representing a child component.
/// </summary>

View File

@ -303,6 +303,35 @@ namespace Microsoft.Blazor.Build.Test
node => AssertNode.Text(node, typeof(List<string>).FullName));
}
[Fact]
public void SupportsAttributeNodesEvaluatedInline()
{
// Arrange/Act
var component = CompileToComponent(
@"<elem @onclick(MyHandler) />
@functions {
public bool DidInvokeCode { get; set; } = false;
void MyHandler()
{
DidInvokeCode = true;
}
}");
var didInvokeCodeProperty = component.GetType().GetProperty("DidInvokeCode");
// Assert
Assert.False((bool)didInvokeCodeProperty.GetValue(component));
Assert.Collection(GetRenderTree(component).Where(NotWhitespace),
node => AssertNode.Element(node, "elem", 1),
node =>
{
Assert.Equal(RenderTreeNodeType.Attribute, node.NodeType);
Assert.NotNull(node.AttributeEventHandlerValue);
node.AttributeEventHandlerValue(null);
Assert.True((bool)didInvokeCodeProperty.GetValue(component));
});
}
private static bool NotWhitespace(RenderTreeNode node)
=> node.NodeType != RenderTreeNodeType.Text
|| !string.IsNullOrWhiteSpace(node.TextContent);

View File

@ -1,7 +1,12 @@
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button onclick=@{ currentCount++; }>Click me</button>
<button @onclick(IncrementCount)>Click me</button>
@functions {
int currentCount = 0;
void IncrementCount()
{
currentCount++;
}
}