Support 'ref' syntax for capturing references to elements and components (#685)
This commit is contained in:
parent
a700fa945e
commit
4033560734
|
|
@ -1,11 +1,14 @@
|
|||
import { platform } from '../Environment';
|
||||
import { System_String } from '../Platform/Platform';
|
||||
import { getRegisteredFunction } from './RegisteredFunction';
|
||||
import { getElementByCaptureId } from '../Rendering/ElementReferenceCapture';
|
||||
|
||||
const elementRefKey = '_blazorElementRef'; // Keep in sync with ElementRef.cs
|
||||
|
||||
export function invokeWithJsonMarshalling(identifier: System_String, ...argsJson: System_String[]) {
|
||||
const identifierJsString = platform.toJavaScriptString(identifier);
|
||||
const funcInstance = getRegisteredFunction(identifierJsString);
|
||||
const args = argsJson.map(json => JSON.parse(platform.toJavaScriptString(json)));
|
||||
const args = argsJson.map(json => JSON.parse(platform.toJavaScriptString(json), jsonReviver));
|
||||
const result = funcInstance.apply(null, args);
|
||||
if (result !== null && result !== undefined) {
|
||||
const resultJson = JSON.stringify(result);
|
||||
|
|
@ -14,3 +17,11 @@ export function invokeWithJsonMarshalling(identifier: System_String, ...argsJson
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function jsonReviver(key: string, value: any): any {
|
||||
if (value && typeof value === 'object' && value.hasOwnProperty(elementRefKey) && typeof value[elementRefKey] === 'number') {
|
||||
return getElementByCaptureId(value[elementRefKey]);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import { platform } from '../Environment';
|
|||
import { EventDelegator } from './EventDelegator';
|
||||
import { EventForDotNet, UIEventArgs } from './EventForDotNet';
|
||||
import { LogicalElement, toLogicalElement, insertLogicalChild, removeLogicalChild, getLogicalParent, getLogicalChild, createAndInsertLogicalContainer, isSvgElement } from './LogicalElements';
|
||||
import { applyCaptureIdToElement } from './ElementReferenceCapture';
|
||||
const selectValuePropname = '_blazorSelectValue';
|
||||
let raiseEventMethod: MethodHandle;
|
||||
let renderComponentMethod: MethodHandle;
|
||||
|
|
@ -138,6 +139,13 @@ export class BrowserRenderer {
|
|||
return 1;
|
||||
case FrameType.region:
|
||||
return this.insertFrameRange(componentId, parent, childIndex, frames, frameIndex + 1, frameIndex + renderTreeFrame.subtreeLength(frame));
|
||||
case FrameType.elementReferenceCapture:
|
||||
if (parent instanceof Element) {
|
||||
applyCaptureIdToElement(parent, renderTreeFrame.elementReferenceCaptureId(frame));
|
||||
return 0; // A "capture" is a child in the diff, but has no node in the DOM
|
||||
} else {
|
||||
throw new Error('Reference capture frames can only be children of element frames.');
|
||||
}
|
||||
default:
|
||||
const unknownType: never = frameType; // Compile-time verification that the switch was exhaustive
|
||||
throw new Error(`Unknown frame type: ${unknownType}`);
|
||||
|
|
@ -252,16 +260,27 @@ export class BrowserRenderer {
|
|||
childIndex += numChildrenInserted;
|
||||
|
||||
// Skip over any descendants, since they are already dealt with recursively
|
||||
const subtreeLength = renderTreeFrame.subtreeLength(frame);
|
||||
if (subtreeLength > 1) {
|
||||
index += subtreeLength - 1;
|
||||
}
|
||||
index += countDescendantFrames(frame);
|
||||
}
|
||||
|
||||
return (childIndex - origChildIndex); // Total number of children inserted
|
||||
}
|
||||
}
|
||||
|
||||
function countDescendantFrames(frame: RenderTreeFramePointer): number {
|
||||
switch (renderTreeFrame.frameType(frame)) {
|
||||
// The following frame types have a subtree length. Other frames may use that memory slot
|
||||
// to mean something else, so we must not read it. We should consider having nominal subtypes
|
||||
// of RenderTreeFramePointer that prevent access to non-applicable fields.
|
||||
case FrameType.component:
|
||||
case FrameType.element:
|
||||
case FrameType.region:
|
||||
return renderTreeFrame.subtreeLength(frame) - 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function isCheckbox(element: Element) {
|
||||
return element.tagName === 'INPUT' && element.getAttribute('type') === 'checkbox';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
export function applyCaptureIdToElement(element: Element, referenceCaptureId: number) {
|
||||
element.setAttribute(getCaptureIdAttributeName(referenceCaptureId), '');
|
||||
}
|
||||
|
||||
export function getElementByCaptureId(referenceCaptureId: number) {
|
||||
const selector = `[${getCaptureIdAttributeName(referenceCaptureId)}]`;
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
|
||||
function getCaptureIdAttributeName(referenceCaptureId: number) {
|
||||
return `_bl_${referenceCaptureId}`;
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ export const renderTreeFrame = {
|
|||
// The properties and memory layout must be kept in sync with the .NET equivalent in RenderTreeFrame.cs
|
||||
frameType: (frame: RenderTreeFramePointer) => platform.readInt32Field(frame, 4) as FrameType,
|
||||
subtreeLength: (frame: RenderTreeFramePointer) => platform.readInt32Field(frame, 8) as FrameType,
|
||||
elementReferenceCaptureId: (frame: RenderTreeFramePointer) => platform.readInt32Field(frame, 8),
|
||||
componentId: (frame: RenderTreeFramePointer) => platform.readInt32Field(frame, 12),
|
||||
elementName: (frame: RenderTreeFramePointer) => platform.readStringField(frame, 16),
|
||||
textContent: (frame: RenderTreeFramePointer) => platform.readStringField(frame, 16),
|
||||
|
|
@ -29,6 +30,7 @@ export enum FrameType {
|
|||
attribute = 3,
|
||||
component = 4,
|
||||
region = 5,
|
||||
elementReferenceCapture = 6,
|
||||
}
|
||||
|
||||
// Nominal type to ensure only valid pointers are passed to the renderTreeFrame functions.
|
||||
|
|
|
|||
|
|
@ -54,6 +54,10 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
|
||||
public static readonly string AddAttribute = nameof(AddAttribute);
|
||||
|
||||
public static readonly string AddElementReferenceCapture = nameof(AddElementReferenceCapture);
|
||||
|
||||
public static readonly string AddComponentReferenceCapture = nameof(AddComponentReferenceCapture);
|
||||
|
||||
public static readonly string Clear = nameof(Clear);
|
||||
|
||||
public static readonly string GetFrames = nameof(GetFrames);
|
||||
|
|
@ -91,5 +95,10 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
{
|
||||
public static readonly string FullTypeName = "Microsoft.AspNetCore.Blazor.Components.EventHandlerAttribute";
|
||||
}
|
||||
|
||||
public static class ElementRef
|
||||
{
|
||||
public static readonly string FullTypeName = "Microsoft.AspNetCore.Blazor.ElementRef";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -482,6 +482,39 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
}
|
||||
}
|
||||
|
||||
public override void WriteReferenceCapture(CodeRenderingContext context, RefExtensionNode refNode)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (refNode == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(refNode));
|
||||
}
|
||||
|
||||
// The runtime node writer moves the call elsewhere. At design time we
|
||||
// just want sufficiently similar code that any unknown-identifier or type
|
||||
// errors will be equivalent
|
||||
var captureTypeName = refNode.IsComponentCapture
|
||||
? refNode.ComponentCaptureTypeName
|
||||
: BlazorApi.ElementRef.FullTypeName;
|
||||
WriteCSharpCode(context, new CSharpCodeIntermediateNode
|
||||
{
|
||||
Source = refNode.Source,
|
||||
Children =
|
||||
{
|
||||
refNode.IdentifierToken,
|
||||
new IntermediateToken
|
||||
{
|
||||
Kind = TokenKind.CSharp,
|
||||
Content = $" = default({captureTypeName});"
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void WriteCSharpToken(CodeRenderingContext context, IntermediateToken token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(token.Content))
|
||||
|
|
|
|||
|
|
@ -76,11 +76,13 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
builder.Features.Add(new BindLoweringPass());
|
||||
builder.Features.Add(new EventHandlerLoweringPass());
|
||||
builder.Features.Add(new ComponentLoweringPass());
|
||||
builder.Features.Add(new RefLoweringPass());
|
||||
builder.Features.Add(new OrphanTagHelperLoweringPass());
|
||||
|
||||
builder.Features.Add(new ComponentTagHelperDescriptorProvider());
|
||||
builder.Features.Add(new BindTagHelperDescriptorProvider());
|
||||
builder.Features.Add(new EventHandlerTagHelperDescriptorProvider());
|
||||
builder.Features.Add(new RefTagHelperDescriptorProvider());
|
||||
|
||||
if (isDeclarationOnlyCompile)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -43,5 +43,12 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
|
||||
public readonly static string TagHelperKind = "Blazor.EventHandler";
|
||||
}
|
||||
|
||||
public static class Ref
|
||||
{
|
||||
public readonly static string TagHelperKind = "Blazor.Ref";
|
||||
|
||||
public static readonly string RuntimeName = "Blazor.None";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,5 +27,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
public abstract void WriteComponentBody(CodeRenderingContext context, ComponentBodyExtensionNode node);
|
||||
|
||||
public abstract void WriteComponentAttribute(CodeRenderingContext context, ComponentAttributeExtensionNode node);
|
||||
|
||||
public abstract void WriteReferenceCapture(CodeRenderingContext context, RefExtensionNode node);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
private string _unconsumedHtml;
|
||||
private List<IntermediateToken> _currentAttributeValues;
|
||||
private IDictionary<string, PendingAttribute> _currentElementAttributes = new Dictionary<string, PendingAttribute>();
|
||||
private List<RefExtensionNode> _currentElementRefCaptures = new List<RefExtensionNode>();
|
||||
private int _sourceSequence = 0;
|
||||
|
||||
private struct PendingAttribute
|
||||
|
|
@ -269,6 +270,15 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
_currentElementAttributes.Clear();
|
||||
}
|
||||
|
||||
if (_currentElementRefCaptures.Count > 0)
|
||||
{
|
||||
foreach (var refNode in _currentElementRefCaptures)
|
||||
{
|
||||
WriteAddReferenceCaptureCall(context, refNode);
|
||||
}
|
||||
_currentElementRefCaptures.Clear();
|
||||
}
|
||||
|
||||
_scopeStack.OpenScope( tagName: nextTag.Data, isComponent: false);
|
||||
}
|
||||
|
||||
|
|
@ -306,6 +316,39 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
}
|
||||
}
|
||||
|
||||
private void WriteAddReferenceCaptureCall(CodeRenderingContext context, RefExtensionNode refNode)
|
||||
{
|
||||
var codeWriter = context.CodeWriter;
|
||||
|
||||
var methodName = refNode.IsComponentCapture
|
||||
? nameof(BlazorApi.RenderTreeBuilder.AddComponentReferenceCapture)
|
||||
: nameof(BlazorApi.RenderTreeBuilder.AddElementReferenceCapture);
|
||||
codeWriter
|
||||
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{methodName}")
|
||||
.Write((_sourceSequence++).ToString())
|
||||
.WriteParameterSeparator();
|
||||
|
||||
const string refCaptureParamName = "__value";
|
||||
using (var lambdaScope = codeWriter.BuildLambda(refCaptureParamName))
|
||||
{
|
||||
var typecastIfNeeded = refNode.IsComponentCapture ? $"({refNode.ComponentCaptureTypeName})" : string.Empty;
|
||||
WriteCSharpCode(context, new CSharpCodeIntermediateNode
|
||||
{
|
||||
Source = refNode.Source,
|
||||
Children =
|
||||
{
|
||||
refNode.IdentifierToken,
|
||||
new IntermediateToken {
|
||||
Kind = TokenKind.CSharp,
|
||||
Content = $" = {typecastIfNeeded}{refCaptureParamName};"
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
codeWriter.WriteEndMethodInvocation();
|
||||
}
|
||||
|
||||
private void RejectDisallowedHtmlTags(IntermediateNode node, HtmlTagToken tagToken)
|
||||
{
|
||||
// Disallow <script> in components as per #552
|
||||
|
|
@ -484,6 +527,26 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
context.CodeWriter.WriteLine();
|
||||
}
|
||||
|
||||
public override void WriteReferenceCapture(CodeRenderingContext context, RefExtensionNode node)
|
||||
{
|
||||
if (node.IsComponentCapture)
|
||||
{
|
||||
// AddComponentReferenceCapture calls can be inserted directly because they are
|
||||
// already in the desired place in the IR
|
||||
WriteAddReferenceCaptureCall(context, node);
|
||||
}
|
||||
else
|
||||
{
|
||||
// AddElementReferenceCapture calls must appear in the RenderTree as children of the element
|
||||
// being captured, so store the RefCapture until we're ready to write its children.
|
||||
// We could instead modify RefLoweringPass to insert the RefExtensionNode at the right
|
||||
// place, but this is harder because it would involve understanding which HtmlContent
|
||||
// node represents the end of the tag. It's simpler to do it here because here we are
|
||||
// already doing HTML parsing to understand the structure.
|
||||
_currentElementRefCaptures.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
private SourceSpan? CalculateSourcePosition(
|
||||
SourceSpan? razorTokenPosition,
|
||||
TextPosition htmlNodePosition)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||
{
|
||||
internal class RefExtensionNode : ExtensionIntermediateNode
|
||||
{
|
||||
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
|
||||
|
||||
public IntermediateToken IdentifierToken { get; }
|
||||
|
||||
public bool IsComponentCapture { get; }
|
||||
|
||||
public string ComponentCaptureTypeName { get; }
|
||||
|
||||
public RefExtensionNode(IntermediateToken identifierToken)
|
||||
{
|
||||
IdentifierToken = identifierToken ?? throw new ArgumentNullException(nameof(identifierToken));
|
||||
Source = IdentifierToken.Source;
|
||||
}
|
||||
|
||||
public RefExtensionNode(IntermediateToken identifierToken, string componentCaptureTypeName)
|
||||
: this(identifierToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(componentCaptureTypeName))
|
||||
{
|
||||
throw new ArgumentException("Cannot be null or empty", nameof(componentCaptureTypeName));
|
||||
}
|
||||
|
||||
IsComponentCapture = true;
|
||||
ComponentCaptureTypeName = componentCaptureTypeName;
|
||||
}
|
||||
|
||||
public override void Accept(IntermediateNodeVisitor visitor)
|
||||
{
|
||||
if (visitor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(visitor));
|
||||
}
|
||||
|
||||
AcceptExtensionNode<RefExtensionNode>(this, visitor);
|
||||
}
|
||||
|
||||
public override void WriteNode(CodeTarget target, CodeRenderingContext context)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var writer = (BlazorNodeWriter)context.NodeWriter;
|
||||
writer.WriteReferenceCapture(context, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||
{
|
||||
internal class RefLoweringPass : IntermediateNodePassBase, IRazorOptimizationPass
|
||||
{
|
||||
// Run after our other passes
|
||||
public override int Order => 1000;
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
var @namespace = documentNode.FindPrimaryNamespace();
|
||||
var @class = documentNode.FindPrimaryClass();
|
||||
if (@namespace == null || @class == null)
|
||||
{
|
||||
// Nothing to do, bail. We can't function without the standard structure.
|
||||
return;
|
||||
}
|
||||
|
||||
var nodes = documentNode.FindDescendantNodes<TagHelperIntermediateNode>();
|
||||
for (var i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
var node = nodes[i];
|
||||
|
||||
for (var j = node.Children.Count - 1; j >= 0; j--)
|
||||
{
|
||||
var attributeNode = node.Children[j] as ComponentAttributeExtensionNode;
|
||||
if (attributeNode != null &&
|
||||
attributeNode.TagHelper != null &&
|
||||
attributeNode.TagHelper.IsRefTagHelper())
|
||||
{
|
||||
RewriteUsage(@class, node, j, attributeNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RewriteUsage(ClassDeclarationIntermediateNode classNode, TagHelperIntermediateNode node, int index, ComponentAttributeExtensionNode attributeNode)
|
||||
{
|
||||
// If we can't get a nonempty attribute name, do nothing because there will
|
||||
// already be a diagnostic for empty values
|
||||
var identifierToken = DetermineIdentifierToken(attributeNode);
|
||||
if (identifierToken != null)
|
||||
{
|
||||
node.Children.Remove(attributeNode);
|
||||
|
||||
// Determine whether this is an element capture or a component capture, and
|
||||
// if applicable the type name that will appear in the resulting capture code
|
||||
var componentTagHelper = node.TagHelpers.FirstOrDefault(x => x.IsComponentTagHelper());
|
||||
if (componentTagHelper != null)
|
||||
{
|
||||
// For components, the RefExtensionNode must go after all ComponentAttributeExtensionNode
|
||||
// and ComponentBodyExtensionNode siblings because they translate to AddAttribute calls.
|
||||
// We can therefore put it immediately before the ComponentCloseExtensionNode.
|
||||
var componentCloseNodePosition = LastIndexOf(node.Children, n => n is ComponentCloseExtensionNode);
|
||||
if (componentCloseNodePosition < 0)
|
||||
{
|
||||
// Should never happen - would imply we're running the lowering passes in the wrong order
|
||||
throw new InvalidOperationException($"Cannot find {nameof(ComponentCloseExtensionNode)} among ref node siblings.");
|
||||
}
|
||||
|
||||
var refExtensionNode = new RefExtensionNode(identifierToken, componentTagHelper.GetTypeName());
|
||||
node.Children.Insert(componentCloseNodePosition, refExtensionNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
// For elements, it doesn't matter how the RefExtensionNode is positioned
|
||||
// among the children, as the node writer takes care of emitting the
|
||||
// code at the right point after the AddAttribute calls
|
||||
node.Children.Add(new RefExtensionNode(identifierToken));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IntermediateToken DetermineIdentifierToken(ComponentAttributeExtensionNode attributeNode)
|
||||
{
|
||||
IntermediateToken foundToken = null;
|
||||
|
||||
if (attributeNode.Children.Count == 1)
|
||||
{
|
||||
if (attributeNode.Children[0] is IntermediateToken token)
|
||||
{
|
||||
foundToken = token;
|
||||
}
|
||||
else if (attributeNode.Children[0] is CSharpExpressionIntermediateNode csharpNode)
|
||||
{
|
||||
if (csharpNode.Children.Count == 1)
|
||||
{
|
||||
foundToken = csharpNode.Children[0] as IntermediateToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !string.IsNullOrWhiteSpace(foundToken?.Content) ? foundToken : null;
|
||||
}
|
||||
|
||||
private static int LastIndexOf<T>(IList<T> items, Predicate<T> predicate)
|
||||
{
|
||||
for (var index = items.Count - 1; index >= 0; index--)
|
||||
{
|
||||
if (predicate(items[index]))
|
||||
{
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||
{
|
||||
internal class RefTagHelperDescriptorProvider : ITagHelperDescriptorProvider
|
||||
{
|
||||
// Run after the component tag helper provider, because later we may want component-type-specific variants of this
|
||||
public int Order { get; set; } = 1000;
|
||||
|
||||
public RazorEngine Engine { get; set; }
|
||||
|
||||
public void Execute(TagHelperDescriptorProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
context.Results.Add(CreateRefTagHelper());
|
||||
}
|
||||
|
||||
private TagHelperDescriptor CreateRefTagHelper()
|
||||
{
|
||||
var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.Ref.TagHelperKind, "Ref", BlazorApi.AssemblyName);
|
||||
builder.Documentation = Resources.RefTagHelper_Documentation;
|
||||
|
||||
builder.Metadata.Add(BlazorMetadata.SpecialKindKey, BlazorMetadata.Ref.TagHelperKind);
|
||||
builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.Ref.RuntimeName;
|
||||
|
||||
// WTE has a bug in 15.7p1 where a Tag Helper without a display-name that looks like
|
||||
// a C# property will crash trying to create the toolips.
|
||||
builder.SetTypeName("Microsoft.AspNetCore.Blazor.Components.Ref");
|
||||
|
||||
builder.TagMatchingRule(rule =>
|
||||
{
|
||||
rule.TagName = "*";
|
||||
rule.Attribute(attribute =>
|
||||
{
|
||||
attribute.Name = "ref";
|
||||
});
|
||||
});
|
||||
|
||||
builder.BindAttribute(attribute =>
|
||||
{
|
||||
attribute.Documentation = Resources.RefTagHelper_Documentation;
|
||||
attribute.Name = "ref";
|
||||
|
||||
// WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like
|
||||
// a C# property will crash trying to create the toolips.
|
||||
attribute.SetPropertyName("Ref");
|
||||
attribute.TypeName = typeof(object).FullName;
|
||||
});
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -203,5 +203,14 @@ namespace Microsoft.AspNetCore.Blazor.Razor {
|
|||
return ResourceManager.GetString("PageDirectiveCannotBeImported", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Populates the specified field or property with a reference to the element or component..
|
||||
/// </summary>
|
||||
internal static string RefTagHelper_Documentation {
|
||||
get {
|
||||
return ResourceManager.GetString("RefTagHelper_Documentation", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,4 +165,7 @@
|
|||
<data name="PageDirective_RouteToken_Name" xml:space="preserve">
|
||||
<value>route template</value>
|
||||
</data>
|
||||
<data name="RefTagHelper_Documentation" xml:space="preserve">
|
||||
<value>Populates the specified field or property with a reference to the element or component.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -103,6 +103,18 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
string.Equals(BlazorMetadata.EventHandler.TagHelperKind, kind);
|
||||
}
|
||||
|
||||
public static bool IsRefTagHelper(this TagHelperDescriptor tagHelper)
|
||||
{
|
||||
if (tagHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelper));
|
||||
}
|
||||
|
||||
return
|
||||
tagHelper.Metadata.TryGetValue(BlazorMetadata.SpecialKindKey, out var kind) &&
|
||||
string.Equals(BlazorMetadata.Ref.TagHelperKind, kind);
|
||||
}
|
||||
|
||||
public static string GetEventArgsType(this TagHelperDescriptor tagHelper)
|
||||
{
|
||||
if (tagHelper == null)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Blazor.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a reference to a rendered element.
|
||||
/// </summary>
|
||||
public readonly struct ElementRef : ICustomJsonSerializer
|
||||
{
|
||||
// Static to ensure uniqueness even if there are multiple Renderer instances
|
||||
// This would not be necessary if the JS-side code maintained a lookup from capureId to Element instances,
|
||||
// but we're not doing that presently as it causes more work during disposal to remove those entries
|
||||
// WARNING: Once we support server-side rendering, we should check if running on the server and avoid
|
||||
// populating element reference capture IDs at all, because doing so could (a) eventually
|
||||
// overflow the static int, and (b) disclose information to clients about how many other
|
||||
// requests the server is handling, etc. In general, as part of implementing SSR, we need to
|
||||
// audit the code it calls for any use of statics.
|
||||
private static int _nextId = 0;
|
||||
|
||||
internal int Id { get; }
|
||||
|
||||
private ElementRef(int id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
|
||||
internal static ElementRef CreateWithUniqueId()
|
||||
=> new ElementRef(Interlocked.Increment(ref _nextId));
|
||||
|
||||
object ICustomJsonSerializer.ToJsonPrimitive()
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{ "_blazorElementRef", Id }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Json
|
||||
{
|
||||
// This is internal because we're trying to avoid expanding JsonUtil into a sophisticated
|
||||
// API. Developers who want that would be better served by using a different JSON package
|
||||
// instead. Also the perf implications of the ICustomJsonSerializer approach aren't ideal
|
||||
// (it forces structs to be boxed, and returning a dictionary means lots more allocations
|
||||
// and boxing of any value-typed properties).
|
||||
internal interface ICustomJsonSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Supplies a representation suitable for JSON serialization. For example, the
|
||||
/// return value may be a string->object dictionary containing properties to
|
||||
/// serialize, or simply a string.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
object ToJsonPrimitive();
|
||||
}
|
||||
}
|
||||
|
|
@ -66,6 +66,7 @@ using System.Globalization;
|
|||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Blazor.Json;
|
||||
using SimpleJson.Reflection;
|
||||
|
||||
// ReSharper disable LoopCanBeConvertedToQuery
|
||||
|
|
@ -1497,6 +1498,8 @@ namespace SimpleJson
|
|||
output = input.ToString();
|
||||
else if (input is TimeSpan)
|
||||
output = ((TimeSpan)input).ToString("c");
|
||||
else if (input is ICustomJsonSerializer customJsonSerializer)
|
||||
output = customJsonSerializer.ToJsonPrimitive();
|
||||
else
|
||||
{
|
||||
Enum inputEnum = input as Enum;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
{
|
||||
private readonly static object BoxedTrue = true;
|
||||
private readonly static object BoxedFalse = false;
|
||||
private readonly static string ComponentReferenceCaptureInvalidParentMessage = $"Component reference captures may only be added as children of frames of type {RenderTreeFrameType.Component}";
|
||||
|
||||
private readonly Renderer _renderer;
|
||||
private readonly ArrayBuilder<RenderTreeFrame> _entries = new ArrayBuilder<RenderTreeFrame>(10);
|
||||
|
|
@ -356,6 +357,43 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
entry = entry.WithComponentSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a frame representing an instruction to capture a reference to the parent element.
|
||||
/// </summary>
|
||||
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||
/// <param name="elementReferenceCaptureAction">An action to be invoked whenever the reference value changes.</param>
|
||||
public void AddElementReferenceCapture(int sequence, Action<ElementRef> elementReferenceCaptureAction)
|
||||
{
|
||||
if (GetCurrentParentFrameType() != RenderTreeFrameType.Element)
|
||||
{
|
||||
throw new InvalidOperationException($"Element reference captures may only be added as children of frames of type {RenderTreeFrameType.Element}");
|
||||
}
|
||||
|
||||
Append(RenderTreeFrame.ElementReferenceCapture(sequence, elementReferenceCaptureAction));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a frame representing an instruction to capture a reference to the parent component.
|
||||
/// </summary>
|
||||
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||
/// <param name="componentReferenceCaptureAction">An action to be invoked whenever the reference value changes.</param>
|
||||
public void AddComponentReferenceCapture(int sequence, Action<object> componentReferenceCaptureAction)
|
||||
{
|
||||
var parentFrameIndex = GetCurrentParentFrameIndex();
|
||||
if (!parentFrameIndex.HasValue)
|
||||
{
|
||||
throw new InvalidOperationException(ComponentReferenceCaptureInvalidParentMessage);
|
||||
}
|
||||
|
||||
var parentFrameIndexValue = parentFrameIndex.Value;
|
||||
if (_entries.Buffer[parentFrameIndexValue].FrameType != RenderTreeFrameType.Component)
|
||||
{
|
||||
throw new InvalidOperationException(ComponentReferenceCaptureInvalidParentMessage);
|
||||
}
|
||||
|
||||
Append(RenderTreeFrame.ComponentReferenceCapture(sequence, componentReferenceCaptureAction, parentFrameIndexValue));
|
||||
}
|
||||
|
||||
// Internal for tests
|
||||
// Not public because there's no current use case for user code defining regions arbitrarily.
|
||||
// Currently the sole use case for regions is when appending a RenderFragment.
|
||||
|
|
@ -382,6 +420,17 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
}
|
||||
}
|
||||
|
||||
private int? GetCurrentParentFrameIndex()
|
||||
=> _openElementIndices.Count == 0 ? (int?)null : _openElementIndices.Peek();
|
||||
|
||||
private RenderTreeFrameType? GetCurrentParentFrameType()
|
||||
{
|
||||
var parentIndex = GetCurrentParentFrameIndex();
|
||||
return parentIndex.HasValue
|
||||
? _entries.Buffer[parentIndex.Value].FrameType
|
||||
: (RenderTreeFrameType?)null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the builder.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -293,11 +293,17 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
|
||||
private static int NextSiblingIndex(RenderTreeFrame frame, int frameIndex)
|
||||
{
|
||||
var subtreeLength = frame.ElementSubtreeLength;
|
||||
var distanceToNextSibling = subtreeLength == 0
|
||||
? 1 // For frames that don't have a subtree length set, such as text frames
|
||||
: subtreeLength; // For element or component frames
|
||||
return frameIndex + distanceToNextSibling;
|
||||
switch (frame.FrameType)
|
||||
{
|
||||
case RenderTreeFrameType.Component:
|
||||
return frameIndex + frame.ComponentSubtreeLength;
|
||||
case RenderTreeFrameType.Element:
|
||||
return frameIndex + frame.ElementSubtreeLength;
|
||||
case RenderTreeFrameType.Region:
|
||||
return frameIndex + frame.RegionSubtreeLength;
|
||||
default:
|
||||
return frameIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendDiffEntriesForFramesWithSameSequence(
|
||||
|
|
@ -404,6 +410,16 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
break;
|
||||
}
|
||||
|
||||
case RenderTreeFrameType.ElementReferenceCapture:
|
||||
{
|
||||
// We could preserve the ElementReferenceCaptureId from the old frame to the new frame,
|
||||
// and even call newFrame.ElementReferenceCaptureAction(id) each time in case it wants
|
||||
// to do something different with the ID. However there's no known use case for
|
||||
// that, so presently the rule is that for any given element, the reference
|
||||
// capture action is only invoked once.
|
||||
break;
|
||||
}
|
||||
|
||||
// We don't handle attributes here, they have their own diff logic.
|
||||
// See AppendDiffEntriesForAttributeFrame
|
||||
default:
|
||||
|
|
@ -484,6 +500,16 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
diffContext.SiblingIndex++;
|
||||
break;
|
||||
}
|
||||
case RenderTreeFrameType.ElementReferenceCapture:
|
||||
{
|
||||
InitializeNewElementReferenceCaptureFrame(ref diffContext, ref newFrame);
|
||||
break;
|
||||
}
|
||||
case RenderTreeFrameType.ComponentReferenceCapture:
|
||||
{
|
||||
InitializeNewComponentReferenceCaptureFrame(ref diffContext, ref newFrame);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -573,6 +599,12 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
case RenderTreeFrameType.Attribute:
|
||||
InitializeNewAttributeFrame(ref diffContext, ref frame);
|
||||
break;
|
||||
case RenderTreeFrameType.ElementReferenceCapture:
|
||||
InitializeNewElementReferenceCaptureFrame(ref diffContext, ref frame);
|
||||
break;
|
||||
case RenderTreeFrameType.ComponentReferenceCapture:
|
||||
InitializeNewComponentReferenceCaptureFrame(ref diffContext, ref frame);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -609,6 +641,32 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
}
|
||||
}
|
||||
|
||||
private static void InitializeNewElementReferenceCaptureFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame)
|
||||
{
|
||||
var newElementRef = ElementRef.CreateWithUniqueId();
|
||||
newFrame = newFrame.WithElementReferenceCaptureId(newElementRef.Id);
|
||||
newFrame.ElementReferenceCaptureAction(newElementRef);
|
||||
}
|
||||
|
||||
private static void InitializeNewComponentReferenceCaptureFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame)
|
||||
{
|
||||
ref var parentFrame = ref diffContext.NewTree[newFrame.ComponentReferenceCaptureParentFrameIndex];
|
||||
if (parentFrame.FrameType != RenderTreeFrameType.Component)
|
||||
{
|
||||
// Should never happen, but will help with diagnosis if it does
|
||||
throw new InvalidOperationException($"{nameof(RenderTreeFrameType.ComponentReferenceCapture)} frame references invalid parent index.");
|
||||
}
|
||||
|
||||
var componentInstance = parentFrame.Component;
|
||||
if (componentInstance == null)
|
||||
{
|
||||
// Should never happen, but will help with diagnosis if it does
|
||||
throw new InvalidOperationException($"Trying to initialize {nameof(RenderTreeFrameType.ComponentReferenceCapture)} frame before parent component was assigned.");
|
||||
}
|
||||
|
||||
newFrame.ComponentReferenceCaptureAction(componentInstance);
|
||||
}
|
||||
|
||||
private static void DisposeFramesInRange(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int startIndex, int endIndexExcl)
|
||||
{
|
||||
for (var i = startIndex; i < endIndexExcl; i++)
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
|
||||
/// <summary>
|
||||
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Element"/>,
|
||||
/// gets a name representing the type of the element. Otherwise, the value is <see langword="null"/>.
|
||||
/// gets a name representing the type of the element. Otherwise, the value is undefined.
|
||||
/// </summary>
|
||||
[FieldOffset(16)] public readonly string ElementName;
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
|
||||
/// <summary>
|
||||
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Text"/>,
|
||||
/// gets the content of the text frame. Otherwise, the value is <see langword="null"/>.
|
||||
/// gets the content of the text frame. Otherwise, the value is undefined.
|
||||
/// </summary>
|
||||
[FieldOffset(16)] public readonly string TextContent;
|
||||
|
||||
|
|
@ -79,13 +79,13 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
|
||||
/// <summary>
|
||||
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>,
|
||||
/// gets the attribute name. Otherwise, the value is <see langword="null"/>.
|
||||
/// gets the attribute name. Otherwise, the value is undefined.
|
||||
/// </summary>
|
||||
[FieldOffset(16)] public readonly string AttributeName;
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>,
|
||||
/// gets the attribute value. Otherwise, the value is <see langword="null"/>.
|
||||
/// gets the attribute value. Otherwise, the value is undefined.
|
||||
/// </summary>
|
||||
[FieldOffset(24)] public readonly object AttributeValue;
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
|
||||
/// <summary>
|
||||
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>,
|
||||
/// gets the child component instance. Otherwise, the value is <see langword="null"/>.
|
||||
/// gets the child component instance. Otherwise, the value is undefined.
|
||||
/// </summary>
|
||||
[FieldOffset(24)] public readonly IComponent Component;
|
||||
|
||||
|
|
@ -129,6 +129,44 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
/// </summary>
|
||||
[FieldOffset(8)] public readonly int RegionSubtreeLength;
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// RenderTreeFrameType.ElementReferenceCapture
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ElementReferenceCapture"/>,
|
||||
/// gets the ID of the reference capture. Otherwise, the value is undefined.
|
||||
/// </summary>
|
||||
[FieldOffset(8)] public readonly int ElementReferenceCaptureId;
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ElementReferenceCapture"/>,
|
||||
/// gets the action that writes the reference to its target. Otherwise, the value is undefined.
|
||||
/// </summary>
|
||||
[FieldOffset(16)] public readonly Action<ElementRef> ElementReferenceCaptureAction;
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// RenderTreeFrameType.ComponentReferenceCapture
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ComponentReferenceCapture"/>,
|
||||
/// gets the index of the parent frame representing the component being captured. Otherwise, the value is undefined.
|
||||
/// WARNING: This index can only be used in the context of the frame's original render tree. If the frame is
|
||||
/// copied elsewhere, such as to the ReferenceFrames buffer of a RenderTreeDiff, then the index will
|
||||
/// not relate to entries in that other buffer.
|
||||
/// Currently there's no scenario where this matters, but if there was, we could change all of the subtree
|
||||
/// initialization logic in RenderTreeDiffBuilder to walk the frames hierarchically, then it would know
|
||||
/// the parent index at the point where it wants to initialize the ComponentReferenceCapture frame.
|
||||
/// </summary>
|
||||
[FieldOffset(8)] public readonly int ComponentReferenceCaptureParentFrameIndex;
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ComponentReferenceCapture"/>,
|
||||
/// gets the action that writes the reference to its target. Otherwise, the value is undefined.
|
||||
/// </summary>
|
||||
[FieldOffset(16)] public readonly Action<object> ComponentReferenceCaptureAction;
|
||||
|
||||
private RenderTreeFrame(int sequence, string elementName, int elementSubtreeLength)
|
||||
: this()
|
||||
{
|
||||
|
|
@ -189,6 +227,24 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
RegionSubtreeLength = regionSubtreeLength;
|
||||
}
|
||||
|
||||
private RenderTreeFrame(int sequence, Action<ElementRef> elementReferenceCaptureAction, int elementReferenceCaptureId)
|
||||
: this()
|
||||
{
|
||||
FrameType = RenderTreeFrameType.ElementReferenceCapture;
|
||||
Sequence = sequence;
|
||||
ElementReferenceCaptureAction = elementReferenceCaptureAction;
|
||||
ElementReferenceCaptureId = elementReferenceCaptureId;
|
||||
}
|
||||
|
||||
private RenderTreeFrame(int sequence, Action<object> componentReferenceCaptureAction, int parentFrameIndex)
|
||||
: this()
|
||||
{
|
||||
FrameType = RenderTreeFrameType.ComponentReferenceCapture;
|
||||
Sequence = sequence;
|
||||
ComponentReferenceCaptureAction = componentReferenceCaptureAction;
|
||||
ComponentReferenceCaptureParentFrameIndex = parentFrameIndex;
|
||||
}
|
||||
|
||||
internal static RenderTreeFrame Element(int sequence, string elementName)
|
||||
=> new RenderTreeFrame(sequence, elementName: elementName, elementSubtreeLength: 0);
|
||||
|
||||
|
|
@ -207,6 +263,12 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
internal static RenderTreeFrame Region(int sequence)
|
||||
=> new RenderTreeFrame(sequence, regionSubtreeLength: 0);
|
||||
|
||||
internal static RenderTreeFrame ElementReferenceCapture(int sequence, Action<ElementRef> elementReferenceCaptureAction)
|
||||
=> new RenderTreeFrame(sequence, elementReferenceCaptureAction: elementReferenceCaptureAction, elementReferenceCaptureId: 0);
|
||||
|
||||
internal static RenderTreeFrame ComponentReferenceCapture(int sequence, Action<object> componentReferenceCaptureAction, int parentFrameIndex)
|
||||
=> new RenderTreeFrame(sequence, componentReferenceCaptureAction: componentReferenceCaptureAction, parentFrameIndex: parentFrameIndex);
|
||||
|
||||
internal RenderTreeFrame WithElementSubtreeLength(int elementSubtreeLength)
|
||||
=> new RenderTreeFrame(Sequence, elementName: ElementName, elementSubtreeLength: elementSubtreeLength);
|
||||
|
||||
|
|
@ -225,6 +287,9 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
internal RenderTreeFrame WithRegionSubtreeLength(int regionSubtreeLength)
|
||||
=> new RenderTreeFrame(Sequence, regionSubtreeLength: regionSubtreeLength);
|
||||
|
||||
internal RenderTreeFrame WithElementReferenceCaptureId(int elementReferenceCaptureId)
|
||||
=> new RenderTreeFrame(Sequence, ElementReferenceCaptureAction, elementReferenceCaptureId);
|
||||
|
||||
/// <inheritdoc />
|
||||
// Just to be nice for debugging and unit tests.
|
||||
public override string ToString()
|
||||
|
|
@ -245,6 +310,9 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
|
||||
case RenderTreeFrameType.Text:
|
||||
return $"Text: (seq={Sequence}, len=n/a) {EscapeNewlines(TextContent)}";
|
||||
|
||||
case RenderTreeFrameType.ElementReferenceCapture:
|
||||
return $"ElementReferenceCapture: (seq={Sequence}, len=n/a) {ElementReferenceCaptureAction}";
|
||||
}
|
||||
|
||||
return base.ToString();
|
||||
|
|
|
|||
|
|
@ -35,5 +35,15 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
/// fragment are not comparable to sequence numbers outside it.
|
||||
/// </summary>
|
||||
Region = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Represents an instruction to capture or update a reference to the parent element.
|
||||
/// </summary>
|
||||
ElementReferenceCapture = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Represents an instruction to capture or update a reference to the parent component.
|
||||
/// </summary>
|
||||
ComponentReferenceCapture = 7,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -689,5 +689,58 @@ namespace Test
|
|||
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||
CompileToAssembly(generated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Element_WithRef()
|
||||
{
|
||||
// Arrange/Act
|
||||
var generated = CompileToCSharp(@"
|
||||
<elem attributebefore=""before"" ref=""myElem"" attributeafter=""after"">Hello</elem>
|
||||
|
||||
@functions {
|
||||
Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||
|
||||
void DoSomething() { myElem.GetHashCode(); } // Avoid 'assigned but not used' warning
|
||||
}
|
||||
");
|
||||
|
||||
// Assert
|
||||
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
|
||||
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||
CompileToAssembly(generated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Component_WithRef()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : BlazorComponent
|
||||
{
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
// Act
|
||||
var generated = CompileToCSharp(@"
|
||||
@addTagHelper *, TestAssembly
|
||||
<MyComponent ParamBefore=""before"" ref=""myInstance"" ParamAfter=""after"" />
|
||||
|
||||
@functions {
|
||||
Test.MyComponent myInstance;
|
||||
|
||||
void DoSomething() { myInstance.GetHashCode(); } // Avoid 'assigned but not used' warning
|
||||
}
|
||||
");
|
||||
|
||||
// Assert
|
||||
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
|
||||
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||
CompileToAssembly(generated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
|
|||
IExtensionIntermediateNodeVisitor<ComponentBodyExtensionNode>,
|
||||
IExtensionIntermediateNodeVisitor<ComponentCloseExtensionNode>,
|
||||
IExtensionIntermediateNodeVisitor<ComponentOpenExtensionNode>,
|
||||
IExtensionIntermediateNodeVisitor<RouteAttributeExtensionNode>
|
||||
IExtensionIntermediateNodeVisitor<RouteAttributeExtensionNode>,
|
||||
IExtensionIntermediateNodeVisitor<RefExtensionNode>
|
||||
{
|
||||
private readonly TextWriter _writer;
|
||||
|
||||
|
|
@ -288,5 +289,10 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
|
|||
{
|
||||
WriteContentNode(node, node.Template);
|
||||
}
|
||||
|
||||
void IExtensionIntermediateNodeVisitor<RefExtensionNode>.VisitExtension(RefExtensionNode node)
|
||||
{
|
||||
WriteContentNode(node, node.IdentifierToken.Content, node.IsComponentCapture ? node.ComponentCaptureTypeName : "Element");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -942,5 +942,87 @@ namespace Test
|
|||
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||
CompileToAssembly(generated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Element_WithRef()
|
||||
{
|
||||
// Arrange/Act
|
||||
var generated = CompileToCSharp(@"
|
||||
<elem attributebefore=""before"" ref=""myElem"" attributeafter=""after"">Hello</elem>
|
||||
|
||||
@functions {
|
||||
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||
}
|
||||
");
|
||||
|
||||
// Assert
|
||||
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
|
||||
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||
CompileToAssembly(generated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Component_WithRef()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : BlazorComponent
|
||||
{
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
// Arrange/Act
|
||||
var generated = CompileToCSharp(@"
|
||||
@addTagHelper *, TestAssembly
|
||||
<MyComponent ParamBefore=""before"" ref=""myInstance"" ParamAfter=""after"" />
|
||||
|
||||
@functions {
|
||||
private Test.MyComponent myInstance;
|
||||
}
|
||||
");
|
||||
|
||||
// Assert
|
||||
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
|
||||
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||
CompileToAssembly(generated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Component_WithRef_WithChildContent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : BlazorComponent
|
||||
{
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
// Arrange/Act
|
||||
var generated = CompileToCSharp(@"
|
||||
@addTagHelper *, TestAssembly
|
||||
<MyComponent ref=""myInstance"" SomeProp=""val"">
|
||||
Some <el>further</el> content
|
||||
</MyComponent>
|
||||
|
||||
@functions {
|
||||
private Test.MyComponent myInstance;
|
||||
}
|
||||
");
|
||||
|
||||
// Assert
|
||||
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
|
||||
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||
CompileToAssembly(generated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
namespace Test
|
||||
{
|
||||
#line hidden
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||
{
|
||||
#pragma warning disable 219
|
||||
private void __RazorDirectiveTokenHelpers__() {
|
||||
((System.Action)(() => {
|
||||
global::System.Object __typeHelper = "*, TestAssembly";
|
||||
}
|
||||
))();
|
||||
}
|
||||
#pragma warning restore 219
|
||||
#pragma warning disable 0414
|
||||
private static System.Object __o = null;
|
||||
#pragma warning restore 0414
|
||||
#pragma warning disable 1998
|
||||
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||
{
|
||||
base.BuildRenderTree(builder);
|
||||
builder.AddAttribute(-1, "ChildContent", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
|
||||
}
|
||||
));
|
||||
#line 2 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
myInstance = default(Test.MyComponent);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
#line 4 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
|
||||
Test.MyComponent myInstance;
|
||||
|
||||
void DoSomething() { myInstance.GetHashCode(); } // Avoid 'assigned but not used' warning
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
Document -
|
||||
NamespaceDeclaration - - Test
|
||||
UsingDirective - (3:1,1 [12] ) - System
|
||||
UsingDirective - (18:2,1 [32] ) - System.Collections.Generic
|
||||
UsingDirective - (53:3,1 [17] ) - System.Linq
|
||||
UsingDirective - (73:4,1 [28] ) - System.Threading.Tasks
|
||||
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||
DesignTimeDirective -
|
||||
DirectiveToken - (14:0,14 [32] ) - "*, Microsoft.AspNetCore.Blazor"
|
||||
DirectiveToken - (14:0,14 [9] ) - "*, Test"
|
||||
DirectiveToken - (14:0,14 [15] x:\dir\subdir\Test\TestComponent.cshtml) - *, TestAssembly
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - #pragma warning disable 0414
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - private static System.Object __o = null;
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - #pragma warning restore 0414
|
||||
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||
HtmlContent - (29:0,29 [2] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (29:0,29 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n
|
||||
TagHelper - (31:1,0 [72] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent - TagMode.SelfClosing
|
||||
ComponentOpenExtensionNode - - Test.MyComponent
|
||||
ComponentAttributeExtensionNode - - ParamBefore -
|
||||
HtmlContent - (57:1,26 [6] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (57:1,26 [6] x:\dir\subdir\Test\TestComponent.cshtml) - Html - before
|
||||
ComponentAttributeExtensionNode - - ParamAfter -
|
||||
HtmlContent - (94:1,63 [5] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (94:1,63 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - after
|
||||
ComponentBodyExtensionNode -
|
||||
RefExtensionNode - (70:1,39 [10] x:\dir\subdir\Test\TestComponent.cshtml) - myInstance - Test.MyComponent
|
||||
ComponentCloseExtensionNode -
|
||||
HtmlContent - (103:1,72 [4] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (103:1,72 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n\n
|
||||
HtmlContent - (253:7,1 [2] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (253:7,1 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n
|
||||
CSharpCode - (119:3,12 [133] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (119:3,12 [133] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n Test.MyComponent myInstance;\n\n void DoSomething() { myInstance.GetHashCode(); } // Avoid 'assigned but not used' warning\n
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
Source Location: (14:0,14 [15] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|*, TestAssembly|
|
||||
Generated Location: (468:14,38 [15] )
|
||||
|*, TestAssembly|
|
||||
|
||||
Source Location: (70:1,39 [10] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|myInstance|
|
||||
Generated Location: (1132:30,39 [10] )
|
||||
|myInstance|
|
||||
|
||||
Source Location: (119:3,12 [133] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|
|
||||
Test.MyComponent myInstance;
|
||||
|
||||
void DoSomething() { myInstance.GetHashCode(); } // Avoid 'assigned but not used' warning
|
||||
|
|
||||
Generated Location: (1316:37,12 [133] )
|
||||
|
|
||||
Test.MyComponent myInstance;
|
||||
|
||||
void DoSomething() { myInstance.GetHashCode(); } // Avoid 'assigned but not used' warning
|
||||
|
|
||||
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
namespace Test
|
||||
{
|
||||
#line hidden
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||
{
|
||||
#pragma warning disable 219
|
||||
private void __RazorDirectiveTokenHelpers__() {
|
||||
}
|
||||
#pragma warning restore 219
|
||||
#pragma warning disable 0414
|
||||
private static System.Object __o = null;
|
||||
#pragma warning restore 0414
|
||||
#pragma warning disable 1998
|
||||
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||
{
|
||||
base.BuildRenderTree(builder);
|
||||
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
myElem = default(Microsoft.AspNetCore.Blazor.ElementRef);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
#line 3 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
|
||||
Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||
|
||||
void DoSomething() { myElem.GetHashCode(); } // Avoid 'assigned but not used' warning
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
Document -
|
||||
NamespaceDeclaration - - Test
|
||||
UsingDirective - (3:1,1 [12] ) - System
|
||||
UsingDirective - (18:2,1 [32] ) - System.Collections.Generic
|
||||
UsingDirective - (53:3,1 [17] ) - System.Linq
|
||||
UsingDirective - (73:4,1 [28] ) - System.Threading.Tasks
|
||||
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||
DesignTimeDirective -
|
||||
DirectiveToken - (14:0,14 [32] ) - "*, Microsoft.AspNetCore.Blazor"
|
||||
DirectiveToken - (14:0,14 [9] ) - "*, Test"
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - #pragma warning disable 0414
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - private static System.Object __o = null;
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - #pragma warning restore 0414
|
||||
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - <elem
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - attributebefore="
|
||||
IntermediateToken - (23:0,23 [6] x:\dir\subdir\Test\TestComponent.cshtml) - Html - before
|
||||
IntermediateToken - - Html - "
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - attributeafter="
|
||||
IntermediateToken - (60:0,60 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - after
|
||||
IntermediateToken - - Html - "
|
||||
RefExtensionNode - (36:0,36 [6] x:\dir\subdir\Test\TestComponent.cshtml) - myElem - Element
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - >
|
||||
HtmlContent - (67:0,67 [5] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (67:0,67 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - Hello
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - </elem>
|
||||
HtmlContent - (79:0,79 [4] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (79:0,79 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n\n
|
||||
HtmlContent - (243:6,1 [2] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (243:6,1 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n
|
||||
CSharpCode - (95:2,12 [147] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (95:2,12 [147] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n Microsoft.AspNetCore.Blazor.ElementRef myElem;\n\n void DoSomething() { myElem.GetHashCode(); } // Avoid 'assigned but not used' warning\n
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
Source Location: (36:0,36 [6] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|myElem|
|
||||
Generated Location: (865:23,36 [6] )
|
||||
|myElem|
|
||||
|
||||
Source Location: (95:2,12 [147] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|
|
||||
Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||
|
||||
void DoSomething() { myElem.GetHashCode(); } // Avoid 'assigned but not used' warning
|
||||
|
|
||||
Generated Location: (1067:30,12 [147] )
|
||||
|
|
||||
Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||
|
||||
void DoSomething() { myElem.GetHashCode(); } // Avoid 'assigned but not used' warning
|
||||
|
|
||||
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
namespace Test
|
||||
{
|
||||
#line hidden
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||
{
|
||||
#pragma warning disable 1998
|
||||
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||
{
|
||||
base.BuildRenderTree(builder);
|
||||
builder.OpenComponent<Test.MyComponent>(0);
|
||||
builder.AddAttribute(1, "ParamBefore", "before");
|
||||
builder.AddAttribute(2, "ParamAfter", "after");
|
||||
builder.AddComponentReferenceCapture(3, (__value) => {
|
||||
#line 2 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
myInstance = (Test.MyComponent)__value;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
#line 4 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
|
||||
private Test.MyComponent myInstance;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
Document -
|
||||
NamespaceDeclaration - - Test
|
||||
UsingDirective - (3:1,1 [14] ) - System
|
||||
UsingDirective - (18:2,1 [34] ) - System.Collections.Generic
|
||||
UsingDirective - (53:3,1 [19] ) - System.Linq
|
||||
UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks
|
||||
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||
TagHelper - (31:1,0 [72] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent - TagMode.SelfClosing
|
||||
ComponentOpenExtensionNode - - Test.MyComponent
|
||||
ComponentAttributeExtensionNode - - ParamBefore -
|
||||
HtmlContent - (57:1,26 [6] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (57:1,26 [6] x:\dir\subdir\Test\TestComponent.cshtml) - Html - before
|
||||
ComponentAttributeExtensionNode - - ParamAfter -
|
||||
HtmlContent - (94:1,63 [5] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (94:1,63 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - after
|
||||
ComponentBodyExtensionNode -
|
||||
RefExtensionNode - (70:1,39 [10] x:\dir\subdir\Test\TestComponent.cshtml) - myInstance - Test.MyComponent
|
||||
ComponentCloseExtensionNode -
|
||||
CSharpCode - (119:3,12 [44] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (119:3,12 [44] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n private Test.MyComponent myInstance;\n
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
Source Location: (70:1,39 [10] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|myInstance|
|
||||
Generated Location: (849:20,39 [10] )
|
||||
|myInstance|
|
||||
|
||||
Source Location: (119:3,12 [44] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|
|
||||
private Test.MyComponent myInstance;
|
||||
|
|
||||
Generated Location: (1103:30,12 [44] )
|
||||
|
|
||||
private Test.MyComponent myInstance;
|
||||
|
|
||||
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
namespace Test
|
||||
{
|
||||
#line hidden
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||
{
|
||||
#pragma warning disable 1998
|
||||
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||
{
|
||||
base.BuildRenderTree(builder);
|
||||
builder.OpenComponent<Test.MyComponent>(0);
|
||||
builder.AddAttribute(1, "SomeProp", "val");
|
||||
builder.AddAttribute(2, "ChildContent", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
|
||||
builder2.AddContent(3, "\n Some ");
|
||||
builder2.OpenElement(4, "el");
|
||||
builder2.AddContent(5, "further");
|
||||
builder2.CloseElement();
|
||||
builder2.AddContent(6, " content\n");
|
||||
}
|
||||
));
|
||||
builder.AddComponentReferenceCapture(7, (__value) => {
|
||||
#line 2 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
myInstance = (Test.MyComponent)__value;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
#line 6 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
|
||||
private Test.MyComponent myInstance;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
Document -
|
||||
NamespaceDeclaration - - Test
|
||||
UsingDirective - (3:1,1 [14] ) - System
|
||||
UsingDirective - (18:2,1 [34] ) - System.Collections.Generic
|
||||
UsingDirective - (53:3,1 [19] ) - System.Linq
|
||||
UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks
|
||||
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||
TagHelper - (31:1,0 [96] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent - TagMode.StartTagAndEndTag
|
||||
ComponentOpenExtensionNode - - Test.MyComponent
|
||||
ComponentAttributeExtensionNode - - SomeProp -
|
||||
HtmlContent - (71:1,40 [3] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (71:1,40 [3] x:\dir\subdir\Test\TestComponent.cshtml) - Html - val
|
||||
ComponentBodyExtensionNode -
|
||||
HtmlContent - (76:1,45 [37] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (76:1,45 [11] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n Some
|
||||
IntermediateToken - (87:2,9 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - <el>
|
||||
IntermediateToken - (91:2,13 [7] x:\dir\subdir\Test\TestComponent.cshtml) - Html - further
|
||||
IntermediateToken - (98:2,20 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - </el>
|
||||
IntermediateToken - (103:2,25 [10] x:\dir\subdir\Test\TestComponent.cshtml) - Html - content\n
|
||||
RefExtensionNode - (49:1,18 [10] x:\dir\subdir\Test\TestComponent.cshtml) - myInstance - Test.MyComponent
|
||||
ComponentCloseExtensionNode -
|
||||
CSharpCode - (143:5,12 [44] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (143:5,12 [44] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n private Test.MyComponent myInstance;\n
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
Source Location: (49:1,18 [10] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|myInstance|
|
||||
Generated Location: (1160:28,18 [10] )
|
||||
|myInstance|
|
||||
|
||||
Source Location: (143:5,12 [44] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|
|
||||
private Test.MyComponent myInstance;
|
||||
|
|
||||
Generated Location: (1414:38,12 [44] )
|
||||
|
|
||||
private Test.MyComponent myInstance;
|
||||
|
|
||||
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
namespace Test
|
||||
{
|
||||
#line hidden
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||
{
|
||||
#pragma warning disable 1998
|
||||
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||
{
|
||||
base.BuildRenderTree(builder);
|
||||
builder.OpenElement(0, "elem");
|
||||
builder.AddAttribute(1, "attributebefore", "before");
|
||||
builder.AddAttribute(2, "attributeafter", "after");
|
||||
builder.AddElementReferenceCapture(3, (__value) => {
|
||||
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
myElem = __value;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
);
|
||||
builder.AddContent(4, "Hello");
|
||||
builder.CloseElement();
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
#line 3 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
|
||||
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
Document -
|
||||
NamespaceDeclaration - - Test
|
||||
UsingDirective - (3:1,1 [14] ) - System
|
||||
UsingDirective - (18:2,1 [34] ) - System.Collections.Generic
|
||||
UsingDirective - (53:3,1 [19] ) - System.Linq
|
||||
UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks
|
||||
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - <elem
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - attributebefore="
|
||||
IntermediateToken - (23:0,23 [6] x:\dir\subdir\Test\TestComponent.cshtml) - Html - before
|
||||
IntermediateToken - - Html - "
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - attributeafter="
|
||||
IntermediateToken - (60:0,60 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - after
|
||||
IntermediateToken - - Html - "
|
||||
RefExtensionNode - (36:0,36 [6] x:\dir\subdir\Test\TestComponent.cshtml) - myElem - Element
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - >
|
||||
HtmlContent - (67:0,67 [5] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (67:0,67 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - Hello
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - </elem>
|
||||
CSharpCode - (95:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (95:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n private Microsoft.AspNetCore.Blazor.ElementRef myElem;\n
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
Source Location: (36:0,36 [6] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|myElem|
|
||||
Generated Location: (840:20,36 [6] )
|
||||
|myElem|
|
||||
|
||||
Source Location: (95:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|
|
||||
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||
|
|
||||
Generated Location: (1115:31,12 [62] )
|
||||
|
|
||||
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||
|
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
namespace Test
|
||||
{
|
||||
#line hidden
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||
{
|
||||
#pragma warning disable 1998
|
||||
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||
{
|
||||
base.BuildRenderTree(builder);
|
||||
builder.OpenElement(0, "elem");
|
||||
builder.AddElementReferenceCapture(1, (__value) => {
|
||||
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
@class = __value;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
);
|
||||
builder.CloseElement();
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
#line 3 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
|
||||
private Microsoft.AspNetCore.Blazor.ElementRef @class;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
Document -
|
||||
NamespaceDeclaration - - Test
|
||||
UsingDirective - (3:1,1 [14] ) - System
|
||||
UsingDirective - (18:2,1 [34] ) - System.Collections.Generic
|
||||
UsingDirective - (53:3,1 [19] ) - System.Linq
|
||||
UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks
|
||||
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - <elem
|
||||
RefExtensionNode - (13:0,13 [6] x:\dir\subdir\Test\TestComponent.cshtml) - @class - Element
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - />
|
||||
CSharpCode - (40:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (40:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n private Microsoft.AspNetCore.Blazor.ElementRef @class;\n
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
Source Location: (13:0,13 [6] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|@class|
|
||||
Generated Location: (685:18,13 [6] )
|
||||
|@class|
|
||||
|
||||
Source Location: (40:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|
|
||||
private Microsoft.AspNetCore.Blazor.ElementRef @class;
|
||||
|
|
||||
Generated Location: (915:28,12 [62] )
|
||||
|
|
||||
private Microsoft.AspNetCore.Blazor.ElementRef @class;
|
||||
|
|
||||
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
namespace Test
|
||||
{
|
||||
#line hidden
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||
{
|
||||
#pragma warning disable 1998
|
||||
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||
{
|
||||
base.BuildRenderTree(builder);
|
||||
builder.OpenElement(0, "elem");
|
||||
builder.AddAttribute(1, "attributebefore", "before");
|
||||
builder.AddAttribute(2, "attributeafter", "after");
|
||||
builder.AddElementReferenceCapture(3, (__value) => {
|
||||
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
myElem = __value;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
);
|
||||
builder.AddContent(4, "Hello");
|
||||
builder.CloseElement();
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
#line 3 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
|
||||
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
Document -
|
||||
NamespaceDeclaration - - Test
|
||||
UsingDirective - (3:1,1 [14] ) - System
|
||||
UsingDirective - (18:2,1 [34] ) - System.Collections.Generic
|
||||
UsingDirective - (53:3,1 [19] ) - System.Linq
|
||||
UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks
|
||||
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - <elem
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - attributebefore="
|
||||
IntermediateToken - (23:0,23 [6] x:\dir\subdir\Test\TestComponent.cshtml) - Html - before
|
||||
IntermediateToken - - Html - "
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - attributeafter="
|
||||
IntermediateToken - (61:0,61 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - after
|
||||
IntermediateToken - - Html - "
|
||||
RefExtensionNode - (37:0,37 [6] x:\dir\subdir\Test\TestComponent.cshtml) - myElem - Element
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - >
|
||||
HtmlContent - (68:0,68 [5] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (68:0,68 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - Hello
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - </elem>
|
||||
CSharpCode - (96:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (96:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n private Microsoft.AspNetCore.Blazor.ElementRef myElem;\n
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
Source Location: (37:0,37 [6] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|myElem|
|
||||
Generated Location: (841:20,37 [6] )
|
||||
|myElem|
|
||||
|
||||
Source Location: (96:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|
|
||||
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||
|
|
||||
Generated Location: (1116:31,12 [62] )
|
||||
|
|
||||
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||
|
|
||||
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
namespace Test
|
||||
{
|
||||
#line hidden
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||
{
|
||||
#pragma warning disable 1998
|
||||
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||
{
|
||||
base.BuildRenderTree(builder);
|
||||
builder.OpenElement(0, "elem");
|
||||
builder.AddAttribute(1, "attributebefore", "before");
|
||||
builder.AddAttribute(2, "attributeafter", "after");
|
||||
builder.AddElementReferenceCapture(3, (__value) => {
|
||||
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
myElem = __value;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
);
|
||||
builder.AddContent(4, "Hello");
|
||||
builder.CloseElement();
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
#line 3 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
|
||||
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
Document -
|
||||
NamespaceDeclaration - - Test
|
||||
UsingDirective - (3:1,1 [14] ) - System
|
||||
UsingDirective - (18:2,1 [34] ) - System.Collections.Generic
|
||||
UsingDirective - (53:3,1 [19] ) - System.Linq
|
||||
UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks
|
||||
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - <elem
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - attributebefore="
|
||||
IntermediateToken - (23:0,23 [6] x:\dir\subdir\Test\TestComponent.cshtml) - Html - before
|
||||
IntermediateToken - - Html - "
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - attributeafter="
|
||||
IntermediateToken - (60:0,60 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - after
|
||||
IntermediateToken - - Html - "
|
||||
RefExtensionNode - (36:0,36 [6] x:\dir\subdir\Test\TestComponent.cshtml) - myElem - Element
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - >
|
||||
HtmlContent - (67:0,67 [5] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (67:0,67 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - Hello
|
||||
HtmlContent -
|
||||
IntermediateToken - - Html - </elem>
|
||||
CSharpCode - (95:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (95:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n private Microsoft.AspNetCore.Blazor.ElementRef myElem;\n
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
Source Location: (36:0,36 [6] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|myElem|
|
||||
Generated Location: (840:20,36 [6] )
|
||||
|myElem|
|
||||
|
||||
Source Location: (95:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|
|
||||
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||
|
|
||||
Generated Location: (1115:31,12 [62] )
|
||||
|
|
||||
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||
|
|
||||
|
||||
|
|
@ -299,5 +299,76 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
|||
var appElement = MountTestComponent<LogicalElementInsertionCases>();
|
||||
Assert.Equal("First Second Third", appElement.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanUseJsInteropToReferenceElements()
|
||||
{
|
||||
var appElement = MountTestComponent<ElementRefComponent>();
|
||||
var inputElement = appElement.FindElement(By.Id("capturedElement"));
|
||||
var buttonElement = appElement.FindElement(By.TagName("button"));
|
||||
|
||||
Assert.Equal(string.Empty, inputElement.GetAttribute("value"));
|
||||
|
||||
buttonElement.Click();
|
||||
Assert.Equal("Clicks: 1", inputElement.GetAttribute("value"));
|
||||
buttonElement.Click();
|
||||
Assert.Equal("Clicks: 2", inputElement.GetAttribute("value"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanCaptureReferencesToDynamicallyAddedElements()
|
||||
{
|
||||
var appElement = MountTestComponent<ElementRefComponent>();
|
||||
var buttonElement = appElement.FindElement(By.TagName("button"));
|
||||
var checkbox = appElement.FindElement(By.CssSelector("input[type=checkbox]"));
|
||||
|
||||
// We're going to remove the input. But first, put in some contents
|
||||
// so we can observe it's not the same instance later
|
||||
appElement.FindElement(By.Id("capturedElement")).SendKeys("some text");
|
||||
|
||||
// Remove the captured element
|
||||
checkbox.Click();
|
||||
Assert.Empty(appElement.FindElements(By.Id("capturedElement")));
|
||||
|
||||
// Re-add it; observe it starts empty again
|
||||
checkbox.Click();
|
||||
var inputElement = appElement.FindElement(By.Id("capturedElement"));
|
||||
Assert.NotNull(inputElement);
|
||||
Assert.Equal(string.Empty, inputElement.GetAttribute("value"));
|
||||
|
||||
// See that the capture variable was automatically updated to reference the new instance
|
||||
buttonElement.Click();
|
||||
Assert.Equal("Clicks: 1", inputElement.GetAttribute("value"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanCaptureReferencesToDynamicallyAddedComponents()
|
||||
{
|
||||
var appElement = MountTestComponent<ComponentRefComponent>();
|
||||
var incrementButtonSelector = By.CssSelector("#child-component button");
|
||||
var currentCountTextSelector = By.CssSelector("#child-component p:first-of-type");
|
||||
var resetButton = appElement.FindElement(By.Id("reset-child"));
|
||||
var toggleChildCheckbox = appElement.FindElement(By.Id("toggle-child"));
|
||||
|
||||
// Verify the reference was captured initially
|
||||
appElement.FindElement(incrementButtonSelector).Click();
|
||||
Assert.Equal("Current count: 1", appElement.FindElement(currentCountTextSelector).Text);
|
||||
resetButton.Click();
|
||||
Assert.Equal("Current count: 0", appElement.FindElement(currentCountTextSelector).Text);
|
||||
appElement.FindElement(incrementButtonSelector).Click();
|
||||
Assert.Equal("Current count: 1", appElement.FindElement(currentCountTextSelector).Text);
|
||||
|
||||
// Remove and re-add a new instance of the child, checking the text was reset
|
||||
toggleChildCheckbox.Click();
|
||||
Assert.Empty(appElement.FindElements(incrementButtonSelector));
|
||||
toggleChildCheckbox.Click();
|
||||
Assert.Equal("Current count: 0", appElement.FindElement(currentCountTextSelector).Text);
|
||||
|
||||
// Verify we have a new working reference
|
||||
appElement.FindElement(incrementButtonSelector).Click();
|
||||
Assert.Equal("Current count: 1", appElement.FindElement(currentCountTextSelector).Text);
|
||||
resetButton.Click();
|
||||
Assert.Equal("Current count: 0", appElement.FindElement(currentCountTextSelector).Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Razor.Extensions.Test
|
||||
{
|
||||
public class RefTagHelperDescriptorProviderTest : BaseTagHelperDescriptorProviderTest
|
||||
{
|
||||
[Fact]
|
||||
public void Execute_CreatesDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
var provider = new RefTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var matches = context.Results.Where(result => result.IsRefTagHelper());
|
||||
var item = Assert.Single(matches);
|
||||
|
||||
Assert.Empty(item.AllowedChildTags);
|
||||
Assert.Null(item.TagOutputHint);
|
||||
Assert.Empty(item.Diagnostics);
|
||||
Assert.False(item.HasErrors);
|
||||
Assert.Equal(BlazorMetadata.Ref.TagHelperKind, item.Kind);
|
||||
Assert.Equal(BlazorMetadata.Ref.RuntimeName, item.Metadata[TagHelperMetadata.Runtime.Name]);
|
||||
Assert.False(item.IsDefaultKind());
|
||||
Assert.False(item.KindUsesDefaultTagHelperRuntime());
|
||||
|
||||
Assert.Equal(
|
||||
"Populates the specified field or property with a reference to the element or component.",
|
||||
item.Documentation);
|
||||
|
||||
Assert.Equal("Microsoft.AspNetCore.Blazor", item.AssemblyName);
|
||||
Assert.Equal("Ref", item.Name);
|
||||
Assert.Equal("Microsoft.AspNetCore.Blazor.Components.Ref", item.DisplayName);
|
||||
Assert.Equal("Microsoft.AspNetCore.Blazor.Components.Ref", item.GetTypeName());
|
||||
|
||||
// The tag matching rule for a ref is just the attribute name "ref"
|
||||
var rule = Assert.Single(item.TagMatchingRules);
|
||||
Assert.Empty(rule.Diagnostics);
|
||||
Assert.False(rule.HasErrors);
|
||||
Assert.Null(rule.ParentTag);
|
||||
Assert.Equal("*", rule.TagName);
|
||||
Assert.Equal(TagStructure.Unspecified, rule.TagStructure);
|
||||
|
||||
var requiredAttribute = Assert.Single(rule.Attributes);
|
||||
Assert.Empty(requiredAttribute.Diagnostics);
|
||||
Assert.Equal("ref", requiredAttribute.DisplayName);
|
||||
Assert.Equal("ref", requiredAttribute.Name);
|
||||
Assert.Equal(RequiredAttributeDescriptor.NameComparisonMode.FullMatch, requiredAttribute.NameComparison);
|
||||
Assert.Null(requiredAttribute.Value);
|
||||
Assert.Equal(RequiredAttributeDescriptor.ValueComparisonMode.None, requiredAttribute.ValueComparison);
|
||||
|
||||
var attribute = Assert.Single(item.BoundAttributes);
|
||||
Assert.Empty(attribute.Diagnostics);
|
||||
Assert.False(attribute.HasErrors);
|
||||
Assert.Equal(BlazorMetadata.Ref.TagHelperKind, attribute.Kind);
|
||||
Assert.False(attribute.IsDefaultKind());
|
||||
Assert.False(attribute.HasIndexer);
|
||||
Assert.Null(attribute.IndexerNamePrefix);
|
||||
Assert.Null(attribute.IndexerTypeName);
|
||||
Assert.False(attribute.IsIndexerBooleanProperty);
|
||||
Assert.False(attribute.IsIndexerStringProperty);
|
||||
|
||||
Assert.Equal(
|
||||
"Populates the specified field or property with a reference to the element or component.",
|
||||
attribute.Documentation);
|
||||
|
||||
Assert.Equal("ref", attribute.Name);
|
||||
Assert.Equal("Ref", attribute.GetPropertyName());
|
||||
Assert.Equal("object Microsoft.AspNetCore.Blazor.Components.Ref.Ref", attribute.DisplayName);
|
||||
Assert.Equal("System.Object", attribute.TypeName);
|
||||
Assert.False(attribute.IsStringProperty);
|
||||
Assert.False(attribute.IsBooleanProperty);
|
||||
Assert.False(attribute.IsEnum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Blazor.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
|
|
@ -132,6 +133,16 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsInternalCustomSerializer()
|
||||
{
|
||||
// Arrange/Act
|
||||
var json = JsonUtil.Serialize(new WithCustomSerializer());
|
||||
|
||||
// Asssert
|
||||
Assert.Equal("{\"key1\":\"value1\",\"key2\":123}", json);
|
||||
}
|
||||
|
||||
class NonEmptyConstructorPoco
|
||||
{
|
||||
public NonEmptyConstructorPoco(int parameter) {}
|
||||
|
|
@ -158,5 +169,17 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
}
|
||||
|
||||
enum Hobbies { Reading = 1, Swordfighting = 2 }
|
||||
|
||||
class WithCustomSerializer : ICustomJsonSerializer
|
||||
{
|
||||
public object ToJsonPrimitive()
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{ "key1", "value1" },
|
||||
{ "key2", 123 },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Blazor.Test.Helpers;
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
using Xunit.Extensions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Test
|
||||
{
|
||||
|
|
@ -260,6 +259,36 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotAddAttributeToElementReferenceCapture()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||
|
||||
// Act/Assert
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
builder.OpenElement(0, "some element");
|
||||
builder.AddElementReferenceCapture(1, _ => { });
|
||||
builder.AddAttribute(2, "name", "value");
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotAddAttributeToComponentReferenceCapture()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||
|
||||
// Act/Assert
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
builder.OpenComponent<TestComponent>(0);
|
||||
builder.AddComponentReferenceCapture(1, _ => { });
|
||||
builder.AddAttribute(2, "name", "value");
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanAddChildComponentsUsingGenericParam()
|
||||
{
|
||||
|
|
@ -370,6 +399,184 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
frame => AssertFrame.Text(frame, "Some text", 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanAddElementReferenceCaptureInsideElement()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||
Action<ElementRef> referenceCaptureAction = elementRef => { };
|
||||
|
||||
// Act
|
||||
builder.OpenElement(0, "myelement"); // 0: <myelement
|
||||
builder.AddAttribute(1, "attribute2", 123); // 1: attribute2=intExpression123>
|
||||
builder.AddElementReferenceCapture(2, referenceCaptureAction); // 2: # capture: referenceCaptureAction
|
||||
builder.AddContent(3, "some text"); // 3: some text
|
||||
builder.CloseElement(); // </myelement>
|
||||
|
||||
// Assert
|
||||
Assert.Collection(builder.GetFrames(),
|
||||
frame => AssertFrame.Element(frame, "myelement", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "attribute2", "123", 1),
|
||||
frame => AssertFrame.ElementReferenceCapture(frame, referenceCaptureAction, 2),
|
||||
frame => AssertFrame.Text(frame, "some text", 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotAddElementReferenceCaptureWithNoParent()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||
|
||||
// Act/Assert
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
builder.AddElementReferenceCapture(0, _ => { });
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotAddElementReferenceCaptureInsideComponent()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||
|
||||
// Act/Assert
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
builder.OpenComponent<TestComponent>(0);
|
||||
builder.AddElementReferenceCapture(1, _ => { });
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotAddElementReferenceCaptureInsideRegion()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||
|
||||
// Act/Assert
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
builder.OpenRegion(0);
|
||||
builder.AddElementReferenceCapture(1, _ => { });
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanAddMultipleReferenceCapturesToSameElement()
|
||||
{
|
||||
// There won't be any way of doing this from Razor because there's no known use
|
||||
// case for it. However it's harder to *not* support it than to support it, and
|
||||
// there's no known reason to prevent it, so here's test coverage to show it
|
||||
// just works.
|
||||
|
||||
// Arrange
|
||||
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||
Action<ElementRef> referenceCaptureAction1 = elementRef => { };
|
||||
Action<ElementRef> referenceCaptureAction2 = elementRef => { };
|
||||
|
||||
// Act
|
||||
builder.OpenElement(0, "myelement");
|
||||
builder.AddElementReferenceCapture(0, referenceCaptureAction1);
|
||||
builder.AddElementReferenceCapture(0, referenceCaptureAction2);
|
||||
builder.CloseElement();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(builder.GetFrames(),
|
||||
frame => AssertFrame.Element(frame, "myelement", 3),
|
||||
frame => AssertFrame.ElementReferenceCapture(frame, referenceCaptureAction1),
|
||||
frame => AssertFrame.ElementReferenceCapture(frame, referenceCaptureAction2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanAddComponentReferenceCaptureInsideComponent()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||
Action<object> myAction = elementRef => { };
|
||||
|
||||
// Act
|
||||
builder.OpenComponent<TestComponent>(0); // 0: <TestComponent
|
||||
builder.AddAttribute(1, "attribute2", 123); // 1: attribute2=intExpression123>
|
||||
builder.AddComponentReferenceCapture(2, myAction); // 2: # capture: myAction
|
||||
builder.AddContent(3, "some text"); // 3: some text
|
||||
builder.CloseComponent(); // </TestComponent>
|
||||
|
||||
// Assert
|
||||
Assert.Collection(builder.GetFrames(),
|
||||
frame => AssertFrame.Component<TestComponent>(frame, 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "attribute2", 123, 1),
|
||||
frame => AssertFrame.ComponentReferenceCapture(frame, myAction, 2),
|
||||
frame => AssertFrame.Text(frame, "some text", 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotAddComponentReferenceCaptureWithNoParent()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||
|
||||
// Act/Assert
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
builder.AddComponentReferenceCapture(0, _ => { });
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotAddComponentReferenceCaptureInsideElement()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||
|
||||
// Act/Assert
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
builder.OpenElement(0, "myelement");
|
||||
builder.AddComponentReferenceCapture(1, _ => { });
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotAddComponentReferenceCaptureInsideRegion()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||
|
||||
// Act/Assert
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
builder.OpenRegion(0);
|
||||
builder.AddComponentReferenceCapture(1, _ => { });
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanAddMultipleReferenceCapturesToSameComponent()
|
||||
{
|
||||
// There won't be any way of doing this from Razor because there's no known use
|
||||
// case for it. However it's harder to *not* support it than to support it, and
|
||||
// there's no known reason to prevent it, so here's test coverage to show it
|
||||
// just works.
|
||||
|
||||
// Arrange
|
||||
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||
Action<object> referenceCaptureAction1 = elementRef => { };
|
||||
Action<object> referenceCaptureAction2 = elementRef => { };
|
||||
|
||||
// Act
|
||||
builder.OpenComponent<TestComponent>(0);
|
||||
builder.AddComponentReferenceCapture(0, referenceCaptureAction1);
|
||||
builder.AddComponentReferenceCapture(0, referenceCaptureAction2);
|
||||
builder.CloseComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(builder.GetFrames(),
|
||||
frame => AssertFrame.Component<TestComponent>(frame, 3),
|
||||
frame => AssertFrame.ComponentReferenceCapture(frame, referenceCaptureAction1),
|
||||
frame => AssertFrame.ComponentReferenceCapture(frame, referenceCaptureAction2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanClear()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -44,7 +44,11 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
=> new RenderFragment[]
|
||||
{
|
||||
builder => builder.AddContent(0, "Hello"),
|
||||
builder => builder.OpenElement(0, "Some Element"),
|
||||
builder =>
|
||||
{
|
||||
builder.OpenElement(0, "Some Element");
|
||||
builder.CloseElement();
|
||||
},
|
||||
builder =>
|
||||
{
|
||||
builder.OpenElement(0, "Some Element");
|
||||
|
|
@ -1323,6 +1327,137 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
Assert.Equal(new[] { 0, 1 }, batchBuilder.ComponentDisposalQueue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AssignsDistinctIdToNewElementReferenceCaptures()
|
||||
{
|
||||
// Arrange
|
||||
ElementRef ref1 = default, ref2 = default;
|
||||
Action<ElementRef> capture1 = val => { ref1 = val; };
|
||||
Action<ElementRef> capture2 = val => { ref2 = val; };
|
||||
newTree.OpenElement(0, "My element");
|
||||
newTree.AddElementReferenceCapture(1, capture1);
|
||||
newTree.AddElementReferenceCapture(2, capture2);
|
||||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var (diff, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert: Distinct nonzero IDs
|
||||
Assert.NotEqual(0, ref1.Id);
|
||||
Assert.NotEqual(0, ref2.Id);
|
||||
Assert.NotEqual(ref1.Id, ref2.Id);
|
||||
|
||||
// Assert: Also specified in diff
|
||||
Assert.Collection(diff.Edits, edit =>
|
||||
{
|
||||
AssertEdit(edit, RenderTreeEditType.PrependFrame, 0);
|
||||
Assert.Equal(0, edit.ReferenceFrameIndex);
|
||||
});
|
||||
Assert.Collection(referenceFrames,
|
||||
frame => AssertFrame.Element(frame, "My element", 3),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.ElementReferenceCapture(frame, capture1);
|
||||
Assert.Equal(ref1.Id, frame.ElementReferenceCaptureId);
|
||||
},
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.ElementReferenceCapture(frame, capture2);
|
||||
Assert.Equal(ref2.Id, frame.ElementReferenceCaptureId);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreservesIdsOnRetainedElementReferenceCaptures()
|
||||
{
|
||||
// Arrange
|
||||
var refWriteCount = 0;
|
||||
ElementRef ref1 = default;
|
||||
Action<ElementRef> capture1 = val => { ref1 = val; refWriteCount++; };
|
||||
oldTree.OpenElement(0, "My element");
|
||||
oldTree.AddElementReferenceCapture(1, capture1);
|
||||
oldTree.CloseElement();
|
||||
newTree.OpenElement(0, "My element");
|
||||
newTree.AddElementReferenceCapture(1, capture1);
|
||||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var (diff, referenceFrames) = GetSingleUpdatedComponent(initializeFromFrames: true);
|
||||
|
||||
// Assert: Did not invoke the capture action a second time
|
||||
// Note: We're not preserving the ReferenceCaptureId on the actual RenderTreeFrames in the same
|
||||
// way we do for event handler IDs, simply because there's no need to do so. We only do
|
||||
// anything with ReferenceCaptureId when frames are first inserted into the document.
|
||||
Assert.NotEqual(0, ref1.Id);
|
||||
Assert.Equal(1, refWriteCount);
|
||||
Assert.Empty(diff.Edits);
|
||||
Assert.Empty(referenceFrames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvokesAssignerForComponentReferenceCapturesOnInsertion()
|
||||
{
|
||||
// Arrange
|
||||
FakeComponent capturedInstance1 = null, capturedInstance2 = null;
|
||||
Action<object> assigner1 = val => { capturedInstance1 = (FakeComponent)val; };
|
||||
Action<object> assigner2 = val => { capturedInstance2 = (FakeComponent)val; };
|
||||
newTree.OpenComponent<FakeComponent>(0);
|
||||
newTree.AddComponentReferenceCapture(1, assigner1);
|
||||
newTree.AddComponentReferenceCapture(2, assigner2);
|
||||
newTree.CloseComponent();
|
||||
|
||||
// Act
|
||||
var (diff, referenceFrames) = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert: Assigned references
|
||||
Assert.NotNull(capturedInstance1);
|
||||
Assert.NotNull(capturedInstance2);
|
||||
Assert.IsType<FakeComponent>(capturedInstance1);
|
||||
Assert.IsType<FakeComponent>(capturedInstance2);
|
||||
Assert.Same(capturedInstance1, capturedInstance2);
|
||||
|
||||
// Assert: Also in diff, even though we have no use for it there
|
||||
// (it would be costly to exclude given how the array range is copied)
|
||||
Assert.Collection(diff.Edits, edit =>
|
||||
{
|
||||
AssertEdit(edit, RenderTreeEditType.PrependFrame, 0);
|
||||
Assert.Equal(0, edit.ReferenceFrameIndex);
|
||||
});
|
||||
Assert.Collection(referenceFrames,
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Component<FakeComponent>(frame, 3, 0);
|
||||
Assert.Same(capturedInstance1, frame.Component);
|
||||
},
|
||||
frame => AssertFrame.ComponentReferenceCapture(frame, assigner1, 1),
|
||||
frame => AssertFrame.ComponentReferenceCapture(frame, assigner2, 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoesNotInvokeAssignerAgainForRetainedComponents()
|
||||
{
|
||||
// Arrange
|
||||
var refWriteCount = 0;
|
||||
FakeComponent capturedInstance = null;
|
||||
Action<object> assigner = val => { capturedInstance = (FakeComponent)val; refWriteCount++; };
|
||||
oldTree.OpenComponent<FakeComponent>(0);
|
||||
oldTree.AddComponentReferenceCapture(1, assigner);
|
||||
oldTree.CloseComponent();
|
||||
newTree.OpenComponent<FakeComponent>(0);
|
||||
newTree.AddComponentReferenceCapture(1, assigner);
|
||||
newTree.CloseComponent();
|
||||
|
||||
// Act
|
||||
var (diff, referenceFrames) = GetSingleUpdatedComponent(initializeFromFrames: true);
|
||||
|
||||
// Assert: Did not invoke the capture action a second time
|
||||
Assert.NotNull(capturedInstance);
|
||||
Assert.IsType<FakeComponent>(capturedInstance);
|
||||
Assert.Equal(1, refWriteCount);
|
||||
Assert.Empty(diff.Edits);
|
||||
Assert.Empty(referenceFrames);
|
||||
}
|
||||
|
||||
private (RenderTreeDiff, RenderTreeFrame[]) GetSingleUpdatedComponent(bool initializeFromFrames = false)
|
||||
{
|
||||
var result = GetSingleUpdatedComponentWithBatch(initializeFromFrames);
|
||||
|
|
|
|||
|
|
@ -950,6 +950,47 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
&& edit.RemovedAttributeName == "disabled");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HandlesNestedElementCapturesDuringRefresh()
|
||||
{
|
||||
// This may seem like a very arbitrary test case, but at once stage there was a bug
|
||||
// whereby the diff output was incorrect given a ref capture on an element whose
|
||||
// parent element also had a ref capture
|
||||
|
||||
// Arrange
|
||||
var attrValue = 0;
|
||||
var component = new TestComponent(builder =>
|
||||
{
|
||||
builder.OpenElement(0, "parent elem");
|
||||
builder.AddAttribute(1, "parent elem attr", attrValue);
|
||||
builder.AddElementReferenceCapture(2, _ => { });
|
||||
builder.OpenElement(3, "child elem");
|
||||
builder.AddElementReferenceCapture(4, _ => { });
|
||||
builder.AddContent(5, "child text");
|
||||
builder.CloseElement();
|
||||
builder.CloseElement();
|
||||
});
|
||||
var renderer = new TestRenderer();
|
||||
renderer.AssignComponentId(component);
|
||||
|
||||
// Act: Update the attribute value on the parent
|
||||
component.TriggerRender();
|
||||
attrValue++;
|
||||
component.TriggerRender();
|
||||
|
||||
// Assert
|
||||
var latestBatch = renderer.Batches.Skip(1).Single();
|
||||
var latestDiff = latestBatch.DiffsInOrder.Single();
|
||||
Assert.Collection(latestDiff.Edits,
|
||||
edit =>
|
||||
{
|
||||
Assert.Equal(RenderTreeEditType.SetAttribute, edit.Type);
|
||||
Assert.Equal(0, edit.SiblingIndex);
|
||||
AssertFrame.Attribute(latestBatch.ReferenceFrames[edit.ReferenceFrameIndex],
|
||||
"parent elem attr", 1);
|
||||
});
|
||||
}
|
||||
|
||||
private class NoOpRenderer : Renderer
|
||||
{
|
||||
public NoOpRenderer() : base(new TestServiceProvider())
|
||||
|
|
|
|||
|
|
@ -107,5 +107,19 @@ namespace Microsoft.AspNetCore.Blazor.Test.Helpers
|
|||
AssertFrame.Sequence(frame, sequence);
|
||||
Assert.True(string.IsNullOrWhiteSpace(frame.TextContent));
|
||||
}
|
||||
|
||||
public static void ElementReferenceCapture(RenderTreeFrame frame, Action<ElementRef> action, int? sequence = null)
|
||||
{
|
||||
Assert.Equal(RenderTreeFrameType.ElementReferenceCapture, frame.FrameType);
|
||||
Assert.Same(action, frame.ElementReferenceCaptureAction);
|
||||
AssertFrame.Sequence(frame, sequence);
|
||||
}
|
||||
|
||||
public static void ComponentReferenceCapture(RenderTreeFrame frame, Action<object> action, int? sequence = null)
|
||||
{
|
||||
Assert.Equal(RenderTreeFrameType.ComponentReferenceCapture, frame.FrameType);
|
||||
Assert.Same(action, frame.ComponentReferenceCaptureAction);
|
||||
AssertFrame.Sequence(frame, sequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
@using Microsoft.AspNetCore.Blazor
|
||||
|
||||
<h1>Component capture</h1>
|
||||
|
||||
<p>
|
||||
This shows how a component reference may be captured as a field value using 'ref' syntax.
|
||||
This feature is intended only for cases where you're triggering an action on the child
|
||||
instance. It should <strong>not</strong> be used as a way of mutating state in the child,
|
||||
because that would bypass all the benefits of flowing parameters to children and re-rendering
|
||||
automatically only when required.
|
||||
</p>
|
||||
|
||||
@if (_toggleCapturedComponentPresence)
|
||||
{
|
||||
<div id="child-component">
|
||||
<CounterComponent ref="_myChildCounter" />
|
||||
</div>
|
||||
}
|
||||
|
||||
<fieldset>
|
||||
<legend>External controls</legend>
|
||||
<button id="reset-child" onclick="@ResetChildCounter">Reset</button>
|
||||
<label>
|
||||
<input id="toggle-child" type="checkbox" bind="_toggleCapturedComponentPresence" />
|
||||
Toggle counter presence
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
@functions {
|
||||
bool _toggleCapturedComponentPresence = true;
|
||||
CounterComponent _myChildCounter;
|
||||
|
||||
void ResetChildCounter()
|
||||
{
|
||||
_myChildCounter.Reset();
|
||||
}
|
||||
}
|
||||
|
|
@ -15,4 +15,10 @@
|
|||
{
|
||||
currentCount++;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
currentCount = 0;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
@using Microsoft.AspNetCore.Blazor
|
||||
@using Microsoft.AspNetCore.Blazor.Browser.Interop
|
||||
|
||||
<h1>Element capture</h1>
|
||||
|
||||
<p>
|
||||
This shows how an element reference may be captured as a field value using 'ref' syntax and then
|
||||
passed to JavaScript code, which receives the actual DOM element instance.
|
||||
</p>
|
||||
<p>
|
||||
Note that 'ref' syntax is primarily intended for use with JavaScript interop. It is <strong>not</strong>
|
||||
recommended to use it for mutating the DOM routinely. All DOM construction and mutation that can be
|
||||
done declaratively is better, as it automatically happens at the correct time and with minimal diffs.
|
||||
Plus, whenever you use 'ref', you will not be able to run the same code during unit tests or
|
||||
server-side rendering. So it's always better to prefer declarative UI construction when possible.
|
||||
</p>
|
||||
|
||||
@if (_toggleCapturedElementPresence)
|
||||
{
|
||||
<input id="capturedElement" ref="_myInput" />
|
||||
}
|
||||
<button onclick="@MakeInteropCall">Click me</button>
|
||||
<label>
|
||||
<input type="checkbox" bind="_toggleCapturedElementPresence" />
|
||||
Toggle input
|
||||
</label>
|
||||
|
||||
@functions {
|
||||
int _count = 0;
|
||||
bool _toggleCapturedElementPresence = true;
|
||||
ElementRef _myInput;
|
||||
|
||||
void MakeInteropCall()
|
||||
{
|
||||
RegisteredFunction.Invoke<object>("setElementValue", _myInput, $"Clicks: {++_count}");
|
||||
}
|
||||
}
|
||||
|
|
@ -29,6 +29,8 @@
|
|||
<option value="BasicTestApp.SvgComponent">SVG</option>
|
||||
<option value="BasicTestApp.SvgWithChildComponent">SVG with child component</option>
|
||||
<option value="BasicTestApp.LogicalElementInsertionCases">Logical element insertion cases</option>
|
||||
<option value="BasicTestApp.ElementRefComponent">Element ref component</option>
|
||||
<option value="BasicTestApp.ComponentRefComponent">Component ref component</option>
|
||||
<!--<option value="BasicTestApp.RouterTest.Default">Router</option> Excluded because it requires additional setup to work correctly when loaded manually -->
|
||||
</select>
|
||||
|
||||
|
|
@ -54,6 +56,11 @@
|
|||
var method = Blazor.platform.findMethod('BasicTestApp', 'BasicTestApp', 'Program', 'MountTestComponent');
|
||||
Blazor.platform.callMethod(method, null, [Blazor.platform.toDotNetString(typeName)]);
|
||||
}
|
||||
|
||||
// Used by ElementRefComponent
|
||||
Blazor.registerFunction('setElementValue', function (element, newValue) {
|
||||
element.value = newValue;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Reference in New Issue