Track event handlers via explicit IDs rather than by index into frames
array
This commit is contained in:
parent
1e37943c31
commit
b8ed7bc2c5
|
|
@ -52,7 +52,7 @@ export class BrowserRenderer {
|
||||||
const frame = getTreeFramePtr(referenceTree, frameIndex);
|
const frame = getTreeFramePtr(referenceTree, frameIndex);
|
||||||
const siblingIndex = renderTreeEdit.siblingIndex(edit);
|
const siblingIndex = renderTreeEdit.siblingIndex(edit);
|
||||||
const element = parent.childNodes[childIndexAtCurrentDepth + siblingIndex] as HTMLElement;
|
const element = parent.childNodes[childIndexAtCurrentDepth + siblingIndex] as HTMLElement;
|
||||||
this.applyAttribute(componentId, element, frame, frameIndex);
|
this.applyAttribute(componentId, element, frame);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EditType.removeAttribute: {
|
case EditType.removeAttribute: {
|
||||||
|
|
@ -119,7 +119,7 @@ export class BrowserRenderer {
|
||||||
for (let descendantIndex = frameIndex + 1; descendantIndex < descendantsEndIndexExcl; descendantIndex++) {
|
for (let descendantIndex = frameIndex + 1; descendantIndex < descendantsEndIndexExcl; descendantIndex++) {
|
||||||
const descendantFrame = getTreeFramePtr(frames, descendantIndex);
|
const descendantFrame = getTreeFramePtr(frames, descendantIndex);
|
||||||
if (renderTreeFrame.frameType(descendantFrame) === FrameType.attribute) {
|
if (renderTreeFrame.frameType(descendantFrame) === FrameType.attribute) {
|
||||||
this.applyAttribute(componentId, newDomElement, descendantFrame, descendantIndex);
|
this.applyAttribute(componentId, newDomElement, descendantFrame);
|
||||||
} else {
|
} else {
|
||||||
// As soon as we see a non-attribute child, all the subsequent child frames are
|
// As soon as we see a non-attribute child, all the subsequent child frames are
|
||||||
// not attributes, so bail out and insert the remnants recursively
|
// not attributes, so bail out and insert the remnants recursively
|
||||||
|
|
@ -160,16 +160,17 @@ export class BrowserRenderer {
|
||||||
insertNodeIntoDOM(newDomTextNode, parent, childIndex);
|
insertNodeIntoDOM(newDomTextNode, parent, childIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyAttribute(componentId: number, toDomElement: Element, attributeFrame: RenderTreeFramePointer, attributeFrameIndex: number) {
|
applyAttribute(componentId: number, toDomElement: Element, attributeFrame: RenderTreeFramePointer) {
|
||||||
const attributeName = renderTreeFrame.attributeName(attributeFrame)!;
|
const attributeName = renderTreeFrame.attributeName(attributeFrame)!;
|
||||||
const browserRendererId = this.browserRendererId;
|
const browserRendererId = this.browserRendererId;
|
||||||
|
const eventHandlerId = renderTreeFrame.attributeEventHandlerId(attributeFrame);
|
||||||
|
|
||||||
// TODO: Instead of applying separate event listeners to each DOM element, use event delegation
|
// TODO: Instead of applying separate event listeners to each DOM element, use event delegation
|
||||||
// and remove all the _blazor*Listener hacks
|
// and remove all the _blazor*Listener hacks
|
||||||
switch (attributeName) {
|
switch (attributeName) {
|
||||||
case 'onclick': {
|
case 'onclick': {
|
||||||
toDomElement.removeEventListener('click', toDomElement['_blazorClickListener']);
|
toDomElement.removeEventListener('click', toDomElement['_blazorClickListener']);
|
||||||
const listener = () => raiseEvent(browserRendererId, componentId, attributeFrameIndex, 'mouse', { Type: 'click' });
|
const listener = () => raiseEvent(browserRendererId, componentId, eventHandlerId, 'mouse', { Type: 'click' });
|
||||||
toDomElement['_blazorClickListener'] = listener;
|
toDomElement['_blazorClickListener'] = listener;
|
||||||
toDomElement.addEventListener('click', listener);
|
toDomElement.addEventListener('click', listener);
|
||||||
break;
|
break;
|
||||||
|
|
@ -181,7 +182,7 @@ export class BrowserRenderer {
|
||||||
// just to establish that we can pass parameters when raising events.
|
// just to establish that we can pass parameters when raising events.
|
||||||
// We use C#-style PascalCase on the eventInfo to simplify deserialization, but this could
|
// We use C#-style PascalCase on the eventInfo to simplify deserialization, but this could
|
||||||
// change if we introduced a richer JSON library on the .NET side.
|
// change if we introduced a richer JSON library on the .NET side.
|
||||||
raiseEvent(browserRendererId, componentId, attributeFrameIndex, 'keyboard', { Type: evt.type, Key: (evt as any).key });
|
raiseEvent(browserRendererId, componentId, eventHandlerId, 'keyboard', { Type: evt.type, Key: (evt as any).key });
|
||||||
};
|
};
|
||||||
toDomElement['_blazorKeypressListener'] = listener;
|
toDomElement['_blazorKeypressListener'] = listener;
|
||||||
toDomElement.addEventListener('keypress', listener);
|
toDomElement.addEventListener('keypress', listener);
|
||||||
|
|
@ -229,7 +230,7 @@ function removeAttributeFromDOM(parent: Element, childIndex: number, attributeNa
|
||||||
element.removeAttribute(attributeName);
|
element.removeAttribute(attributeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
function raiseEvent(browserRendererId: number, componentId: number, referenceTreeFrameIndex: number, eventInfoType: EventInfoType, eventInfo: any) {
|
function raiseEvent(browserRendererId: number, componentId: number, eventHandlerId: number, eventInfoType: EventInfoType, eventInfo: any) {
|
||||||
if (!raiseEventMethod) {
|
if (!raiseEventMethod) {
|
||||||
raiseEventMethod = platform.findMethod(
|
raiseEventMethod = platform.findMethod(
|
||||||
'Microsoft.AspNetCore.Blazor.Browser', 'Microsoft.AspNetCore.Blazor.Browser.Rendering', 'BrowserRendererEventDispatcher', 'DispatchEvent'
|
'Microsoft.AspNetCore.Blazor.Browser', 'Microsoft.AspNetCore.Blazor.Browser.Rendering', 'BrowserRendererEventDispatcher', 'DispatchEvent'
|
||||||
|
|
@ -239,7 +240,7 @@ function raiseEvent(browserRendererId: number, componentId: number, referenceTre
|
||||||
const eventDescriptor = {
|
const eventDescriptor = {
|
||||||
BrowserRendererId: browserRendererId,
|
BrowserRendererId: browserRendererId,
|
||||||
ComponentId: componentId,
|
ComponentId: componentId,
|
||||||
ReferenceTreeFrameIndex: referenceTreeFrameIndex,
|
EventHandlerId: eventHandlerId,
|
||||||
EventArgsType: eventInfoType
|
EventArgsType: eventInfoType
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ export const renderTreeFrame = {
|
||||||
textContent: (frame: RenderTreeFramePointer) => platform.readStringField(frame, 16),
|
textContent: (frame: RenderTreeFramePointer) => platform.readStringField(frame, 16),
|
||||||
attributeName: (frame: RenderTreeFramePointer) => platform.readStringField(frame, 16),
|
attributeName: (frame: RenderTreeFramePointer) => platform.readStringField(frame, 16),
|
||||||
attributeValue: (frame: RenderTreeFramePointer) => platform.readStringField(frame, 24),
|
attributeValue: (frame: RenderTreeFramePointer) => platform.readStringField(frame, 24),
|
||||||
|
attributeEventHandlerId: (frame: RenderTreeFramePointer) => platform.readInt32Field(frame, 8),
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum FrameType {
|
export enum FrameType {
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Rendering
|
||||||
_browserRendererId = BrowserRendererRegistry.Add(this);
|
_browserRendererId = BrowserRendererRegistry.Add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void DispatchBrowserEvent(int componentId, int referenceTreeIndex, UIEventArgs eventArgs)
|
internal void DispatchBrowserEvent(int componentId, int eventHandlerId, UIEventArgs eventArgs)
|
||||||
=> DispatchEvent(componentId, referenceTreeIndex, eventArgs);
|
=> DispatchEvent(componentId, eventHandlerId, eventArgs);
|
||||||
|
|
||||||
internal void RenderNewBatchInternal(int componentId)
|
internal void RenderNewBatchInternal(int componentId)
|
||||||
=> RenderNewBatch(componentId);
|
=> RenderNewBatch(componentId);
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Rendering
|
||||||
var browserRenderer = BrowserRendererRegistry.Find(eventDescriptor.BrowserRendererId);
|
var browserRenderer = BrowserRendererRegistry.Find(eventDescriptor.BrowserRendererId);
|
||||||
browserRenderer.DispatchBrowserEvent(
|
browserRenderer.DispatchBrowserEvent(
|
||||||
eventDescriptor.ComponentId,
|
eventDescriptor.ComponentId,
|
||||||
eventDescriptor.ReferenceTreeFrameIndex,
|
eventDescriptor.EventHandlerId,
|
||||||
eventArgs);
|
eventArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Rendering
|
||||||
{
|
{
|
||||||
public int BrowserRendererId { get; set; }
|
public int BrowserRendererId { get; set; }
|
||||||
public int ComponentId { get; set; }
|
public int ComponentId { get; set; }
|
||||||
public int ReferenceTreeFrameIndex { get; set; }
|
public int EventHandlerId { get; set; }
|
||||||
public string EventArgsType { get; set; }
|
public string EventArgsType { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,49 +134,15 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
|
|
||||||
if (treatAsInsert)
|
if (treatAsInsert)
|
||||||
{
|
{
|
||||||
ref var newFrame = ref newTree[newStartIndex];
|
InsertNewFrame(batchBuilder, newTree, newStartIndex, ref siblingIndex);
|
||||||
var newFrameType = newFrame.FrameType;
|
newStartIndex = NextSiblingIndex(newTree[newStartIndex], newStartIndex);
|
||||||
if (newFrameType == RenderTreeFrameType.Attribute)
|
|
||||||
{
|
|
||||||
var referenceFrameIndex = _referenceFrames.Append(newFrame);
|
|
||||||
Append(RenderTreeEdit.SetAttribute(siblingIndex, referenceFrameIndex));
|
|
||||||
newStartIndex++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (newFrameType == RenderTreeFrameType.Element || newFrameType == RenderTreeFrameType.Component)
|
|
||||||
{
|
|
||||||
InstantiateChildComponents(batchBuilder, newTree, newStartIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
var referenceFrameIndex = AppendSubtreeToReferenceFrames(newTree, newStartIndex);
|
|
||||||
Append(RenderTreeEdit.PrependFrame(siblingIndex, referenceFrameIndex));
|
|
||||||
newStartIndex = NextSiblingIndex(newFrame, newStartIndex);
|
|
||||||
siblingIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasMoreNew = newEndIndexExcl > newStartIndex;
|
hasMoreNew = newEndIndexExcl > newStartIndex;
|
||||||
prevNewSeq = newSeq;
|
prevNewSeq = newSeq;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ref var oldFrame = ref oldTree[oldStartIndex];
|
RemoveOldFrame(batchBuilder, oldTree, oldStartIndex, siblingIndex);
|
||||||
var oldFrameType = oldFrame.FrameType;
|
oldStartIndex = NextSiblingIndex(oldTree[oldStartIndex], oldStartIndex);
|
||||||
if (oldFrameType == RenderTreeFrameType.Attribute)
|
|
||||||
{
|
|
||||||
Append(RenderTreeEdit.RemoveAttribute(siblingIndex, oldFrame.AttributeName));
|
|
||||||
oldStartIndex++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (oldFrameType == RenderTreeFrameType.Element || oldFrameType == RenderTreeFrameType.Component)
|
|
||||||
{
|
|
||||||
DisposeChildComponents(batchBuilder, oldTree, oldStartIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
Append(RenderTreeEdit.RemoveFrame(siblingIndex));
|
|
||||||
oldStartIndex = NextSiblingIndex(oldFrame, oldStartIndex);
|
|
||||||
}
|
|
||||||
hasMoreOld = oldEndIndexExcl > oldStartIndex;
|
hasMoreOld = oldEndIndexExcl > oldStartIndex;
|
||||||
prevOldSeq = oldSeq;
|
prevOldSeq = oldSeq;
|
||||||
}
|
}
|
||||||
|
|
@ -184,35 +150,6 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public UIEventHandler TemporaryGetEventHandlerMethod(int referenceFrameIndex)
|
|
||||||
{
|
|
||||||
// TODO: Change how event handlers are referenced
|
|
||||||
// The approach used here is invalid. We can't really assume that the event handler exists
|
|
||||||
// in the most recent diff's _referenceFrames. Nor can we assume that its index in the
|
|
||||||
// ComponentState's _renderTreeBuilderCurrent frames array is unchanged (any number of rerenders
|
|
||||||
// might have taken place since then, inserting frames before it, but not actually changing
|
|
||||||
// the delegate instance and therefore not including it in any _referenceFrames).
|
|
||||||
// Need some way of referencing event handlers that is independent of this.
|
|
||||||
var eventHandler = _referenceFrames.Buffer[referenceFrameIndex].AttributeValue as UIEventHandler;
|
|
||||||
return eventHandler
|
|
||||||
?? throw new ArgumentException($"The reference frame at index {referenceFrameIndex} does not specify a {nameof(UIEventHandler)}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private int AppendSubtreeToReferenceFrames(RenderTreeFrame[] fromTree, int fromIndex)
|
|
||||||
{
|
|
||||||
ref var rootFrame = ref fromTree[fromIndex];
|
|
||||||
var subtreeLength = rootFrame.ElementSubtreeLength;
|
|
||||||
if (subtreeLength == 0)
|
|
||||||
{
|
|
||||||
// It's just a single frame (e.g., a text frame)
|
|
||||||
return _referenceFrames.Append(rootFrame);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return _referenceFrames.Append(fromTree, fromIndex, subtreeLength);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateRetainedChildComponent(
|
private void UpdateRetainedChildComponent(
|
||||||
RenderBatchBuilder batchBuilder,
|
RenderBatchBuilder batchBuilder,
|
||||||
RenderTreeFrame[] oldTree, int oldComponentIndex,
|
RenderTreeFrame[] oldTree, int oldComponentIndex,
|
||||||
|
|
@ -365,6 +302,8 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
|
|
||||||
// We can assume that the old and new frames are of the same type, because they correspond
|
// We can assume that the old and new frames are of the same type, because they correspond
|
||||||
// to the same sequence number (and if not, the behaviour is undefined).
|
// to the same sequence number (and if not, the behaviour is undefined).
|
||||||
|
// TODO: Consider supporting dissimilar types at same sequence for custom IComponent implementations.
|
||||||
|
// It should only be a matter of calling RemoveOldFrame+InsertNewFrame
|
||||||
switch (newTree[newFrameIndex].FrameType)
|
switch (newTree[newFrameIndex].FrameType)
|
||||||
{
|
{
|
||||||
case RenderTreeFrameType.Text:
|
case RenderTreeFrameType.Text:
|
||||||
|
|
@ -374,7 +313,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
if (!string.Equals(oldText, newText, StringComparison.Ordinal))
|
if (!string.Equals(oldText, newText, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
var referenceFrameIndex = _referenceFrames.Append(newFrame);
|
var referenceFrameIndex = _referenceFrames.Append(newFrame);
|
||||||
Append(RenderTreeEdit.UpdateText(siblingIndex, referenceFrameIndex));
|
_entries.Append(RenderTreeEdit.UpdateText(siblingIndex, referenceFrameIndex));
|
||||||
}
|
}
|
||||||
siblingIndex++;
|
siblingIndex++;
|
||||||
break;
|
break;
|
||||||
|
|
@ -404,14 +343,14 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
newFrameChildrenEndIndexExcl > newFrameAttributesEndIndexExcl;
|
newFrameChildrenEndIndexExcl > newFrameAttributesEndIndexExcl;
|
||||||
if (hasChildrenToProcess)
|
if (hasChildrenToProcess)
|
||||||
{
|
{
|
||||||
Append(RenderTreeEdit.StepIn(siblingIndex));
|
_entries.Append(RenderTreeEdit.StepIn(siblingIndex));
|
||||||
var childSiblingIndex = 0;
|
var childSiblingIndex = 0;
|
||||||
AppendDiffEntriesForRange(
|
AppendDiffEntriesForRange(
|
||||||
batchBuilder,
|
batchBuilder,
|
||||||
oldTree, oldFrameAttributesEndIndexExcl, oldFrameChildrenEndIndexExcl,
|
oldTree, oldFrameAttributesEndIndexExcl, oldFrameChildrenEndIndexExcl,
|
||||||
newTree, newFrameAttributesEndIndexExcl, newFrameChildrenEndIndexExcl,
|
newTree, newFrameAttributesEndIndexExcl, newFrameChildrenEndIndexExcl,
|
||||||
ref childSiblingIndex);
|
ref childSiblingIndex);
|
||||||
Append(RenderTreeEdit.StepOut());
|
AppendStepOut();
|
||||||
siblingIndex++;
|
siblingIndex++;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -422,40 +361,27 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Elements with different names are treated as completely unrelated
|
// Elements with different names are treated as completely unrelated
|
||||||
InstantiateChildComponents(batchBuilder, newTree, newFrameIndex);
|
RemoveOldFrame(batchBuilder, oldTree, oldFrameIndex, siblingIndex);
|
||||||
DisposeChildComponents(batchBuilder, oldTree, oldFrameIndex);
|
InsertNewFrame(batchBuilder, newTree, newFrameIndex, ref siblingIndex);
|
||||||
|
|
||||||
var referenceFrameIndex = AppendSubtreeToReferenceFrames(newTree, newFrameIndex);
|
|
||||||
Append(RenderTreeEdit.PrependFrame(siblingIndex, referenceFrameIndex));
|
|
||||||
siblingIndex++;
|
|
||||||
Append(RenderTreeEdit.RemoveFrame(siblingIndex));
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case RenderTreeFrameType.Component:
|
case RenderTreeFrameType.Component:
|
||||||
{
|
{
|
||||||
var oldComponentType = oldFrame.ComponentType;
|
if (oldFrame.ComponentType == newFrame.ComponentType)
|
||||||
var newComponentType = newFrame.ComponentType;
|
|
||||||
if (oldComponentType == newComponentType)
|
|
||||||
{
|
{
|
||||||
UpdateRetainedChildComponent(
|
UpdateRetainedChildComponent(
|
||||||
batchBuilder,
|
batchBuilder,
|
||||||
oldTree, oldFrameIndex,
|
oldTree, oldFrameIndex,
|
||||||
newTree, newFrameIndex);
|
newTree, newFrameIndex);
|
||||||
|
|
||||||
siblingIndex++;
|
siblingIndex++;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Child components of different types are treated as completely unrelated
|
// Child components of different types are treated as completely unrelated
|
||||||
InstantiateChildComponents(batchBuilder, newTree, newFrameIndex);
|
RemoveOldFrame(batchBuilder, oldTree, oldFrameIndex, siblingIndex);
|
||||||
DisposeChildComponents(batchBuilder, oldTree, oldFrameIndex);
|
InsertNewFrame(batchBuilder, newTree, newFrameIndex, ref siblingIndex);
|
||||||
|
|
||||||
var referenceFrameIndex = AppendSubtreeToReferenceFrames(newTree, newFrameIndex);
|
|
||||||
Append(RenderTreeEdit.PrependFrame(siblingIndex, referenceFrameIndex));
|
|
||||||
siblingIndex++;
|
|
||||||
Append(RenderTreeEdit.RemoveFrame(siblingIndex));
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -470,8 +396,18 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
var valueChanged = !Equals(oldFrame.AttributeValue, newFrame.AttributeValue);
|
var valueChanged = !Equals(oldFrame.AttributeValue, newFrame.AttributeValue);
|
||||||
if (valueChanged)
|
if (valueChanged)
|
||||||
{
|
{
|
||||||
|
if (oldFrame.AttributeEventHandlerId > 0)
|
||||||
|
{
|
||||||
|
batchBuilder.AddDisposedEventHandlerId(oldFrame.AttributeEventHandlerId);
|
||||||
|
}
|
||||||
|
InitializeNewAttributeFrame(ref newFrame);
|
||||||
var referenceFrameIndex = _referenceFrames.Append(newFrame);
|
var referenceFrameIndex = _referenceFrames.Append(newFrame);
|
||||||
Append(RenderTreeEdit.SetAttribute(siblingIndex, referenceFrameIndex));
|
_entries.Append(RenderTreeEdit.SetAttribute(siblingIndex, referenceFrameIndex));
|
||||||
|
}
|
||||||
|
else if (oldFrame.AttributeEventHandlerId > 0)
|
||||||
|
{
|
||||||
|
// Retain the event handler ID
|
||||||
|
newFrame = oldFrame;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -479,9 +415,8 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
// Since this code path is never reachable for Razor components (because you
|
// Since this code path is never reachable for Razor components (because you
|
||||||
// can't have two different attribute names from the same source sequence), we
|
// can't have two different attribute names from the same source sequence), we
|
||||||
// could consider removing the 'name equality' check entirely for perf
|
// could consider removing the 'name equality' check entirely for perf
|
||||||
var referenceFrameIndex = _referenceFrames.Append(newFrame);
|
RemoveOldFrame(batchBuilder, oldTree, oldFrameIndex, siblingIndex);
|
||||||
Append(RenderTreeEdit.SetAttribute(siblingIndex, referenceFrameIndex));
|
InsertNewFrame(batchBuilder, newTree, newFrameIndex, ref siblingIndex);
|
||||||
Append(RenderTreeEdit.RemoveAttribute(siblingIndex, oldName));
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -491,7 +426,68 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetAttributesEndIndexExclusive(RenderTreeFrame[] tree, int rootIndex)
|
private void InsertNewFrame(RenderBatchBuilder batchBuilder, RenderTreeFrame[] newTree, int newFrameIndex, ref int siblingIndex)
|
||||||
|
{
|
||||||
|
ref var newFrame = ref newTree[newFrameIndex];
|
||||||
|
switch (newFrame.FrameType)
|
||||||
|
{
|
||||||
|
case RenderTreeFrameType.Attribute:
|
||||||
|
{
|
||||||
|
InitializeNewAttributeFrame(ref newFrame);
|
||||||
|
var referenceFrameIndex = _referenceFrames.Append(newFrame);
|
||||||
|
_entries.Append(RenderTreeEdit.SetAttribute(siblingIndex, referenceFrameIndex));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RenderTreeFrameType.Component:
|
||||||
|
case RenderTreeFrameType.Element:
|
||||||
|
{
|
||||||
|
InitializeNewSubtree(batchBuilder, newTree, newFrameIndex);
|
||||||
|
var referenceFrameIndex = _referenceFrames.Append(newTree, newFrameIndex, newFrame.ElementSubtreeLength);
|
||||||
|
_entries.Append(RenderTreeEdit.PrependFrame(siblingIndex, referenceFrameIndex));
|
||||||
|
siblingIndex++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RenderTreeFrameType.Text:
|
||||||
|
{
|
||||||
|
var referenceFrameIndex = _referenceFrames.Append(newFrame);
|
||||||
|
_entries.Append(RenderTreeEdit.PrependFrame(siblingIndex, referenceFrameIndex));
|
||||||
|
siblingIndex++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveOldFrame(RenderBatchBuilder batchBuilder, RenderTreeFrame[] oldTree, int oldFrameIndex, int siblingIndex)
|
||||||
|
{
|
||||||
|
ref var oldFrame = ref oldTree[oldFrameIndex];
|
||||||
|
switch (oldFrame.FrameType)
|
||||||
|
{
|
||||||
|
case RenderTreeFrameType.Attribute:
|
||||||
|
{
|
||||||
|
_entries.Append(RenderTreeEdit.RemoveAttribute(siblingIndex, oldFrame.AttributeName));
|
||||||
|
if (oldFrame.AttributeEventHandlerId > 0)
|
||||||
|
{
|
||||||
|
batchBuilder.AddDisposedEventHandlerId(oldFrame.AttributeEventHandlerId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RenderTreeFrameType.Component:
|
||||||
|
case RenderTreeFrameType.Element:
|
||||||
|
{
|
||||||
|
var endIndexExcl = oldFrameIndex + oldFrame.ElementSubtreeLength;
|
||||||
|
DisposeFramesInRange(batchBuilder, oldTree, oldFrameIndex, endIndexExcl);
|
||||||
|
_entries.Append(RenderTreeEdit.RemoveFrame(siblingIndex));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RenderTreeFrameType.Text:
|
||||||
|
{
|
||||||
|
_entries.Append(RenderTreeEdit.RemoveFrame(siblingIndex));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetAttributesEndIndexExclusive(RenderTreeFrame[] tree, int rootIndex)
|
||||||
{
|
{
|
||||||
var descendantsEndIndexExcl = rootIndex + tree[rootIndex].ElementSubtreeLength;
|
var descendantsEndIndexExcl = rootIndex + tree[rootIndex].ElementSubtreeLength;
|
||||||
var index = rootIndex + 1;
|
var index = rootIndex + 1;
|
||||||
|
|
@ -506,54 +502,72 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Append(in RenderTreeEdit entry)
|
private void AppendStepOut()
|
||||||
{
|
{
|
||||||
if (entry.Type == RenderTreeEditType.StepOut)
|
// If the preceding frame is a StepIn, then the StepOut cancels it out
|
||||||
|
var previousIndex = _entries.Count - 1;
|
||||||
|
if (previousIndex >= 0 && _entries.Buffer[previousIndex].Type == RenderTreeEditType.StepIn)
|
||||||
{
|
{
|
||||||
// If the preceding frame is a StepIn, then the StepOut cancels it out
|
_entries.RemoveLast();
|
||||||
var previousIndex = _entries.Count - 1;
|
}
|
||||||
if (previousIndex >= 0 && _entries.Buffer[previousIndex].Type == RenderTreeEditType.StepIn)
|
else
|
||||||
{
|
{
|
||||||
_entries.RemoveLast();
|
_entries.Append(RenderTreeEdit.StepOut());
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_entries.Append(entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InstantiateChildComponents(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int elementOrComponentIndex)
|
private void InitializeNewSubtree(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int frameIndex)
|
||||||
{
|
{
|
||||||
var endIndexExcl = elementOrComponentIndex + frames[elementOrComponentIndex].ElementSubtreeLength;
|
var endIndexExcl = frameIndex + frames[frameIndex].ElementSubtreeLength;
|
||||||
for (var i = elementOrComponentIndex; i < endIndexExcl; i++)
|
for (var i = frameIndex; i < endIndexExcl; i++)
|
||||||
{
|
{
|
||||||
ref var frame = ref frames[i];
|
ref var frame = ref frames[i];
|
||||||
if (frame.FrameType == RenderTreeFrameType.Component)
|
switch (frame.FrameType)
|
||||||
{
|
{
|
||||||
if (frame.Component != null)
|
case RenderTreeFrameType.Component:
|
||||||
{
|
InitializeNewComponentFrame(batchBuilder, frames, i);
|
||||||
throw new InvalidOperationException($"Child component already exists during {nameof(InstantiateChildComponents)}");
|
break;
|
||||||
}
|
case RenderTreeFrameType.Attribute:
|
||||||
|
InitializeNewAttributeFrame(ref frame);
|
||||||
_renderer.InstantiateChildComponent(frames, i);
|
break;
|
||||||
var childComponentInstance = frame.Component;
|
|
||||||
|
|
||||||
// All descendants of a component are its properties
|
|
||||||
var componentDescendantsEndIndexExcl = i + frame.ComponentSubtreeLength;
|
|
||||||
for (var attributeFrameIndex = i + 1; attributeFrameIndex < componentDescendantsEndIndexExcl; attributeFrameIndex++)
|
|
||||||
{
|
|
||||||
ref var attributeFrame = ref frames[attributeFrameIndex];
|
|
||||||
SetChildComponentProperty(
|
|
||||||
childComponentInstance,
|
|
||||||
attributeFrame.AttributeName,
|
|
||||||
attributeFrame.AttributeValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
TriggerChildComponentRender(batchBuilder, frame);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void InitializeNewComponentFrame(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int frameIndex)
|
||||||
|
{
|
||||||
|
ref var frame = ref frames[frameIndex];
|
||||||
|
|
||||||
|
if (frame.Component != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Child component already exists during {nameof(InitializeNewComponentFrame)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderer.InstantiateChildComponent(ref frame);
|
||||||
|
var childComponentInstance = frame.Component;
|
||||||
|
|
||||||
|
// All descendants of a component are its properties
|
||||||
|
var componentDescendantsEndIndexExcl = frameIndex + frame.ComponentSubtreeLength;
|
||||||
|
for (var attributeFrameIndex = frameIndex + 1; attributeFrameIndex < componentDescendantsEndIndexExcl; attributeFrameIndex++)
|
||||||
|
{
|
||||||
|
ref var attributeFrame = ref frames[attributeFrameIndex];
|
||||||
|
SetChildComponentProperty(
|
||||||
|
childComponentInstance,
|
||||||
|
attributeFrame.AttributeName,
|
||||||
|
attributeFrame.AttributeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
TriggerChildComponentRender(batchBuilder, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeNewAttributeFrame(ref RenderTreeFrame newFrame)
|
||||||
|
{
|
||||||
|
if (newFrame.AttributeValue is UIEventHandler)
|
||||||
|
{
|
||||||
|
_renderer.AssignEventHandlerId(ref newFrame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void TriggerChildComponentRender(RenderBatchBuilder batchBuilder, in RenderTreeFrame frame)
|
private void TriggerChildComponentRender(RenderBatchBuilder batchBuilder, in RenderTreeFrame frame)
|
||||||
{
|
{
|
||||||
if (frame.Component is IHandlePropertiesChanged notifyableComponent)
|
if (frame.Component is IHandlePropertiesChanged notifyableComponent)
|
||||||
|
|
@ -571,16 +585,22 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
_renderer.RenderInExistingBatch(batchBuilder, frame.ComponentId);
|
_renderer.RenderInExistingBatch(batchBuilder, frame.ComponentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeChildComponents(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int elementOrComponentIndex)
|
internal void DisposeFrames(RenderBatchBuilder batchBuilder, ArrayRange<RenderTreeFrame> frames)
|
||||||
|
=> DisposeFramesInRange(batchBuilder, frames.Array, 0, frames.Count);
|
||||||
|
|
||||||
|
private void DisposeFramesInRange(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int startIndex, int endIndexExcl)
|
||||||
{
|
{
|
||||||
var endIndexExcl = elementOrComponentIndex + frames[elementOrComponentIndex].ElementSubtreeLength;
|
for (var i = startIndex; i < endIndexExcl; i++)
|
||||||
for (var i = elementOrComponentIndex; i < endIndexExcl; i++)
|
|
||||||
{
|
{
|
||||||
ref var frame = ref frames[i];
|
ref var frame = ref frames[i];
|
||||||
if (frame.FrameType == RenderTreeFrameType.Component)
|
if (frame.FrameType == RenderTreeFrameType.Component && frame.Component != null)
|
||||||
{
|
{
|
||||||
_renderer.DisposeInExistingBatch(batchBuilder, frame.ComponentId);
|
_renderer.DisposeInExistingBatch(batchBuilder, frame.ComponentId);
|
||||||
}
|
}
|
||||||
|
else if (frame.FrameType == RenderTreeFrameType.Attribute && frame.AttributeEventHandlerId > 0)
|
||||||
|
{
|
||||||
|
batchBuilder.AddDisposedEventHandlerId(frame.AttributeEventHandlerId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,12 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
// RenderTreeFrameType.Attribute
|
// RenderTreeFrameType.Attribute
|
||||||
// --------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>
|
||||||
|
/// gets the ID of the corresponding event handler, if any.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(8)] public readonly int AttributeEventHandlerId;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>,
|
/// 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 <see langword="null"/>.
|
||||||
|
|
@ -154,6 +160,16 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
AttributeValue = attributeValue;
|
AttributeValue = attributeValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RenderTreeFrame(int sequence, string attributeName, object attributeValue, int eventHandlerId)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
FrameType = RenderTreeFrameType.Attribute;
|
||||||
|
Sequence = sequence;
|
||||||
|
AttributeName = attributeName;
|
||||||
|
AttributeValue = attributeValue;
|
||||||
|
AttributeEventHandlerId = eventHandlerId;
|
||||||
|
}
|
||||||
|
|
||||||
internal static RenderTreeFrame Element(int sequence, string elementName)
|
internal static RenderTreeFrame Element(int sequence, string elementName)
|
||||||
=> new RenderTreeFrame(sequence, elementName: elementName, elementSubtreeLength: 0);
|
=> new RenderTreeFrame(sequence, elementName: elementName, elementSubtreeLength: 0);
|
||||||
|
|
||||||
|
|
@ -180,5 +196,8 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
|
|
||||||
internal RenderTreeFrame WithComponentInstance(int componentId, IComponent component)
|
internal RenderTreeFrame WithComponentInstance(int componentId, IComponent component)
|
||||||
=> new RenderTreeFrame(Sequence, ComponentType, ComponentSubtreeLength, componentId, component);
|
=> new RenderTreeFrame(Sequence, ComponentType, ComponentSubtreeLength, componentId, component);
|
||||||
|
|
||||||
|
internal RenderTreeFrame WithAttributeEventHandlerId(int eventHandlerId)
|
||||||
|
=> new RenderTreeFrame(Sequence, AttributeName, AttributeValue, eventHandlerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,36 +55,18 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||||
_renderTreeBuilderCurrent.GetFrames());
|
_renderTreeBuilderCurrent.GetFrames());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes the handler corresponding to an event.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="referenceTreeIndex">The index of the frame in the latest diff reference tree that holds the event handler to be invoked.</param>
|
|
||||||
/// <param name="eventArgs">Arguments to be passed to the event handler.</param>
|
|
||||||
public void DispatchEvent(int referenceTreeIndex, UIEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
if (eventArgs == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(eventArgs));
|
|
||||||
}
|
|
||||||
|
|
||||||
var eventHandler = _diffComputer.TemporaryGetEventHandlerMethod(referenceTreeIndex);
|
|
||||||
eventHandler.Invoke(eventArgs);
|
|
||||||
|
|
||||||
// After any event, we synchronously re-render. Most of the time this means that
|
|
||||||
// developers don't need to call Render() on their components explicitly.
|
|
||||||
_renderer.RenderNewBatch(_componentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies the component that it is being disposed.
|
/// Notifies the component that it is being disposed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void NotifyDisposed()
|
public void NotifyDisposed(RenderBatchBuilder batchBuilder)
|
||||||
{
|
{
|
||||||
// TODO: Handle components throwing during dispose. Shouldn't break the whole render batch.
|
// TODO: Handle components throwing during dispose. Shouldn't break the whole render batch.
|
||||||
if (_component is IDisposable disposable)
|
if (_component is IDisposable disposable)
|
||||||
{
|
{
|
||||||
disposable.Dispose();
|
disposable.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_diffComputer.DisposeFrames(batchBuilder, _renderTreeBuilderCurrent.GetFrames());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||||
{
|
{
|
||||||
private ArrayBuilder<RenderTreeDiff> _updatedComponentDiffs = new ArrayBuilder<RenderTreeDiff>();
|
private ArrayBuilder<RenderTreeDiff> _updatedComponentDiffs = new ArrayBuilder<RenderTreeDiff>();
|
||||||
private ArrayBuilder<int> _disposedComponentIds = new ArrayBuilder<int>();
|
private ArrayBuilder<int> _disposedComponentIds = new ArrayBuilder<int>();
|
||||||
|
private ArrayBuilder<int> _disposedEventHandlerIds = new ArrayBuilder<int>();
|
||||||
|
|
||||||
public int ReserveUpdatedComponentSlotId()
|
public int ReserveUpdatedComponentSlotId()
|
||||||
{
|
{
|
||||||
|
|
@ -21,10 +22,14 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||||
public void SetUpdatedComponent(int updatedComponentSlotId, RenderTreeDiff diff)
|
public void SetUpdatedComponent(int updatedComponentSlotId, RenderTreeDiff diff)
|
||||||
=> _updatedComponentDiffs.Overwrite(updatedComponentSlotId, diff);
|
=> _updatedComponentDiffs.Overwrite(updatedComponentSlotId, diff);
|
||||||
|
|
||||||
|
public ArrayRange<int> GetDisposedEventHandlerIds()
|
||||||
|
=> _disposedEventHandlerIds.ToRange();
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
_updatedComponentDiffs.Clear();
|
_updatedComponentDiffs.Clear();
|
||||||
_disposedComponentIds.Clear();
|
_disposedComponentIds.Clear();
|
||||||
|
_disposedEventHandlerIds.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public RenderBatch ToBatch()
|
public RenderBatch ToBatch()
|
||||||
|
|
@ -34,5 +39,8 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||||
|
|
||||||
public void AddDisposedComponent(int componentId)
|
public void AddDisposedComponent(int componentId)
|
||||||
=> _disposedComponentIds.Append(componentId);
|
=> _disposedComponentIds.Append(componentId);
|
||||||
|
|
||||||
|
public void AddDisposedEventHandlerId(int attributeEventHandlerId)
|
||||||
|
=> _disposedEventHandlerIds.Append(attributeEventHandlerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using Microsoft.AspNetCore.Blazor.Components;
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
using Microsoft.AspNetCore.Blazor.RenderTree;
|
using Microsoft.AspNetCore.Blazor.RenderTree;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
|
|
@ -30,6 +31,10 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||||
private readonly RenderBatchBuilder _sharedRenderBatchBuilder = new RenderBatchBuilder();
|
private readonly RenderBatchBuilder _sharedRenderBatchBuilder = new RenderBatchBuilder();
|
||||||
private int _renderBatchLock = 0;
|
private int _renderBatchLock = 0;
|
||||||
|
|
||||||
|
private int _lastEventHandlerId = 0;
|
||||||
|
private readonly Dictionary<int, UIEventHandler> _eventHandlersById
|
||||||
|
= new Dictionary<int, UIEventHandler>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Associates the <see cref="IComponent"/> with the <see cref="Renderer"/>, assigning
|
/// Associates the <see cref="IComponent"/> with the <see cref="Renderer"/>, assigning
|
||||||
/// an identifier that is unique within the scope of the <see cref="Renderer"/>.
|
/// an identifier that is unique within the scope of the <see cref="Renderer"/>.
|
||||||
|
|
@ -83,6 +88,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||||
{
|
{
|
||||||
RenderInExistingBatch(_sharedRenderBatchBuilder, componentId);
|
RenderInExistingBatch(_sharedRenderBatchBuilder, componentId);
|
||||||
UpdateDisplay(_sharedRenderBatchBuilder.ToBatch());
|
UpdateDisplay(_sharedRenderBatchBuilder.ToBatch());
|
||||||
|
RemoveEventHandlerIds(_sharedRenderBatchBuilder.GetDisposedEventHandlerIds());
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -98,7 +104,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||||
|
|
||||||
internal void DisposeInExistingBatch(RenderBatchBuilder batchBuilder, int componentId)
|
internal void DisposeInExistingBatch(RenderBatchBuilder batchBuilder, int componentId)
|
||||||
{
|
{
|
||||||
GetRequiredComponentState(componentId).NotifyDisposed();
|
GetRequiredComponentState(componentId).NotifyDisposed(batchBuilder);
|
||||||
batchBuilder.AddDisposedComponent(componentId);
|
batchBuilder.AddDisposedComponent(componentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,14 +112,26 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||||
/// Notifies the specified component that an event has occurred.
|
/// Notifies the specified component that an event has occurred.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="componentId">The unique identifier for the component within the scope of this <see cref="Renderer"/>.</param>
|
/// <param name="componentId">The unique identifier for the component within the scope of this <see cref="Renderer"/>.</param>
|
||||||
/// <param name="referenceTreeIndex">The index into the component's latest diff reference tree that specifies which event handler to invoke.</param>
|
/// <param name="eventHandlerId">The <see cref="RenderTreeFrame.AttributeEventHandlerId"/> value from the original event attribute.</param>
|
||||||
/// <param name="eventArgs">Arguments to be passed to the event handler.</param>
|
/// <param name="eventArgs">Arguments to be passed to the event handler.</param>
|
||||||
protected void DispatchEvent(int componentId, int referenceTreeIndex, UIEventArgs eventArgs)
|
protected void DispatchEvent(int componentId, int eventHandlerId, UIEventArgs eventArgs)
|
||||||
=> GetRequiredComponentState(componentId).DispatchEvent(referenceTreeIndex, eventArgs);
|
{
|
||||||
|
if (_eventHandlersById.TryGetValue(eventHandlerId, out var handler))
|
||||||
internal void InstantiateChildComponent(RenderTreeFrame[] frames, int componentFrameIndex)
|
{
|
||||||
|
handler.Invoke(eventArgs);
|
||||||
|
|
||||||
|
// After any event, we synchronously re-render. Most of the time this means that
|
||||||
|
// developers don't need to call Render() on their components explicitly.
|
||||||
|
RenderNewBatch(componentId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"There is no event handler with ID {eventHandlerId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void InstantiateChildComponent(ref RenderTreeFrame frame)
|
||||||
{
|
{
|
||||||
ref var frame = ref frames[componentFrameIndex];
|
|
||||||
if (frame.FrameType != RenderTreeFrameType.Component)
|
if (frame.FrameType != RenderTreeFrameType.Component)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"The frame's {nameof(RenderTreeFrame.FrameType)} property must equal {RenderTreeFrameType.Component}", nameof(frame));
|
throw new ArgumentException($"The frame's {nameof(RenderTreeFrame.FrameType)} property must equal {RenderTreeFrameType.Component}", nameof(frame));
|
||||||
|
|
@ -129,6 +147,23 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||||
frame = frame.WithComponentInstance(newComponentId, newComponent);
|
frame = frame.WithComponentInstance(newComponentId, newComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void AssignEventHandlerId(ref RenderTreeFrame frame)
|
||||||
|
{
|
||||||
|
var id = ++_lastEventHandlerId;
|
||||||
|
_eventHandlersById.Add(id, (UIEventHandler)frame.AttributeValue);
|
||||||
|
frame = frame.WithAttributeEventHandlerId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveEventHandlerIds(ArrayRange<int> eventHandlerIds)
|
||||||
|
{
|
||||||
|
var array = eventHandlerIds.Array;
|
||||||
|
var count = eventHandlerIds.Count;
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
_eventHandlersById.Remove(array[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ComponentState GetRequiredComponentState(int componentId)
|
private ComponentState GetRequiredComponentState(int componentId)
|
||||||
=> _componentStateById.TryGetValue(componentId, out var componentState)
|
=> _componentStateById.TryGetValue(componentId, out var componentState)
|
||||||
? componentState
|
? componentState
|
||||||
|
|
|
||||||
|
|
@ -310,12 +310,12 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(result.Edits,
|
Assert.Collection(result.Edits,
|
||||||
|
entry => AssertEdit(entry, RenderTreeEditType.RemoveFrame, 0),
|
||||||
entry =>
|
entry =>
|
||||||
{
|
{
|
||||||
AssertEdit(entry, RenderTreeEditType.PrependFrame, 0);
|
AssertEdit(entry, RenderTreeEditType.PrependFrame, 0);
|
||||||
Assert.Equal(0, entry.ReferenceFrameIndex);
|
Assert.Equal(0, entry.ReferenceFrameIndex);
|
||||||
},
|
});
|
||||||
entry => AssertEdit(entry, RenderTreeEditType.RemoveFrame, 1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -324,14 +324,14 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
// Arrange
|
// Arrange
|
||||||
oldTree.OpenComponent<FakeComponent>(123);
|
oldTree.OpenComponent<FakeComponent>(123);
|
||||||
oldTree.CloseComponent();
|
oldTree.CloseComponent();
|
||||||
|
GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree); // Assign initial IDs
|
||||||
newTree.OpenComponent<FakeComponent2>(123);
|
newTree.OpenComponent<FakeComponent2>(123);
|
||||||
newTree.CloseComponent();
|
newTree.CloseComponent();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var renderBatch = GetRenderedBatch();
|
var renderBatch = GetRenderedBatch();
|
||||||
|
|
||||||
// Assert: Even though we didn't assign IDs to the components, this
|
// Assert
|
||||||
// shows that FakeComponent was disposed
|
|
||||||
Assert.Collection(renderBatch.DisposedComponentIDs,
|
Assert.Collection(renderBatch.DisposedComponentIDs,
|
||||||
disposedComponentId => Assert.Equal(0, disposedComponentId));
|
disposedComponentId => Assert.Equal(0, disposedComponentId));
|
||||||
|
|
||||||
|
|
@ -340,13 +340,13 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
Assert.Equal(2, renderBatch.UpdatedComponents.Count);
|
Assert.Equal(2, renderBatch.UpdatedComponents.Count);
|
||||||
var updatedComponent1 = renderBatch.UpdatedComponents.Array[0];
|
var updatedComponent1 = renderBatch.UpdatedComponents.Array[0];
|
||||||
Assert.Collection(updatedComponent1.Edits,
|
Assert.Collection(updatedComponent1.Edits,
|
||||||
|
entry => AssertEdit(entry, RenderTreeEditType.RemoveFrame, 0),
|
||||||
entry =>
|
entry =>
|
||||||
{
|
{
|
||||||
AssertEdit(entry, RenderTreeEditType.PrependFrame, 0);
|
AssertEdit(entry, RenderTreeEditType.PrependFrame, 0);
|
||||||
Assert.Equal(0, entry.ReferenceFrameIndex);
|
Assert.Equal(0, entry.ReferenceFrameIndex);
|
||||||
Assert.IsType<FakeComponent2>(updatedComponent1.ReferenceFrames.Array[0].Component);
|
Assert.IsType<FakeComponent2>(updatedComponent1.ReferenceFrames.Array[0].Component);
|
||||||
},
|
});
|
||||||
entry => AssertEdit(entry, RenderTreeEditType.RemoveFrame, 1));
|
|
||||||
|
|
||||||
// Assert: Second updated component is the new FakeComponent2
|
// Assert: Second updated component is the new FakeComponent2
|
||||||
var updatedComponent2 = renderBatch.UpdatedComponents.Array[1];
|
var updatedComponent2 = renderBatch.UpdatedComponents.Array[1];
|
||||||
|
|
@ -483,13 +483,13 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
Assert.Collection(result.Edits,
|
Assert.Collection(result.Edits,
|
||||||
entry =>
|
entry =>
|
||||||
{
|
{
|
||||||
AssertEdit(entry, RenderTreeEditType.SetAttribute, 0);
|
AssertEdit(entry, RenderTreeEditType.RemoveAttribute, 0);
|
||||||
Assert.Equal(0, entry.ReferenceFrameIndex);
|
Assert.Equal("oldname", entry.RemovedAttributeName);
|
||||||
},
|
},
|
||||||
entry =>
|
entry =>
|
||||||
{
|
{
|
||||||
AssertEdit(entry, RenderTreeEditType.RemoveAttribute, 0);
|
AssertEdit(entry, RenderTreeEditType.SetAttribute, 0);
|
||||||
Assert.Equal("oldname", entry.RemovedAttributeName);
|
Assert.Equal(0, entry.ReferenceFrameIndex);
|
||||||
});
|
});
|
||||||
Assert.Collection(result.ReferenceFrames,
|
Assert.Collection(result.ReferenceFrames,
|
||||||
frame => AssertFrame.Attribute(frame, "newname", "same value"));
|
frame => AssertFrame.Attribute(frame, "newname", "same value"));
|
||||||
|
|
|
||||||
|
|
@ -173,16 +173,17 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
var componentId = renderer.AssignComponentId(component);
|
var componentId = renderer.AssignComponentId(component);
|
||||||
renderer.RenderNewBatch(componentId);
|
renderer.RenderNewBatch(componentId);
|
||||||
|
|
||||||
var (eventHandlerFrameIndex, _) = FirstWithIndex(
|
var eventHandlerId = renderer.Batches.Single().DiffsByComponentId[componentId].Single()
|
||||||
renderer.Batches.Single().DiffsByComponentId[componentId].Single().ReferenceFrames,
|
.ReferenceFrames
|
||||||
frame => frame.AttributeValue != null);
|
.First(frame => frame.AttributeValue != null)
|
||||||
|
.AttributeEventHandlerId;
|
||||||
|
|
||||||
// Assert: Event not yet fired
|
// Assert: Event not yet fired
|
||||||
Assert.Null(receivedArgs);
|
Assert.Null(receivedArgs);
|
||||||
|
|
||||||
// Act/Assert: Event can be fired
|
// Act/Assert: Event can be fired
|
||||||
var eventArgs = new UIEventArgs();
|
var eventArgs = new UIEventArgs();
|
||||||
renderer.DispatchEvent(componentId, eventHandlerFrameIndex, eventArgs);
|
renderer.DispatchEvent(componentId, eventHandlerId, eventArgs);
|
||||||
Assert.Same(eventArgs, receivedArgs);
|
Assert.Same(eventArgs, receivedArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,17 +212,18 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
var nestedComponentId = nestedComponentFrame.ComponentId;
|
var nestedComponentId = nestedComponentFrame.ComponentId;
|
||||||
renderer.RenderNewBatch(nestedComponentId);
|
renderer.RenderNewBatch(nestedComponentId);
|
||||||
|
|
||||||
// Find nested component's event handler ndoe
|
// Find nested component's event handler ID
|
||||||
var (eventHandlerFrameIndex, _) = FirstWithIndex(
|
var eventHandlerId = renderer.Batches[1].DiffsByComponentId[nestedComponentId].Single()
|
||||||
renderer.Batches[1].DiffsByComponentId[nestedComponentId].Single().ReferenceFrames,
|
.ReferenceFrames
|
||||||
frame => frame.AttributeValue != null);
|
.First(frame => frame.AttributeValue != null)
|
||||||
|
.AttributeEventHandlerId;
|
||||||
|
|
||||||
// Assert: Event not yet fired
|
// Assert: Event not yet fired
|
||||||
Assert.Null(receivedArgs);
|
Assert.Null(receivedArgs);
|
||||||
|
|
||||||
// Act/Assert: Event can be fired
|
// Act/Assert: Event can be fired
|
||||||
var eventArgs = new UIEventArgs();
|
var eventArgs = new UIEventArgs();
|
||||||
renderer.DispatchEvent(nestedComponentId, eventHandlerFrameIndex, eventArgs);
|
renderer.DispatchEvent(nestedComponentId, eventHandlerId, eventArgs);
|
||||||
Assert.Same(eventArgs, receivedArgs);
|
Assert.Same(eventArgs, receivedArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -474,9 +476,9 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
builder.OpenElement(7, "some element");
|
builder.OpenElement(7, "some element");
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
builder.OpenComponent<FakeComponent>(100);
|
// Nested descendants
|
||||||
builder.CloseComponent();
|
builder.OpenComponent<ConditionalParentComponent<FakeComponent>>(100);
|
||||||
builder.OpenComponent<FakeComponent>(150);
|
builder.AddAttribute(101, nameof(ConditionalParentComponent<FakeComponent>.IncludeChild), true);
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
}
|
}
|
||||||
builder.OpenComponent<FakeComponent>(200);
|
builder.OpenComponent<FakeComponent>(200);
|
||||||
|
|
@ -494,14 +496,162 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
.Where(frame => frame.FrameType == RenderTreeFrameType.Component)
|
.Where(frame => frame.FrameType == RenderTreeFrameType.Component)
|
||||||
.Select(frame => frame.ComponentId)
|
.Select(frame => frame.ComponentId)
|
||||||
.ToList();
|
.ToList();
|
||||||
Assert.Equal(childComponentIds, new[] { 1, 2, 3 });
|
Assert.Equal(new[] { 1, 3 }, childComponentIds);
|
||||||
|
|
||||||
// Act: Second render
|
// Act: Second render
|
||||||
firstRender = false;
|
firstRender = false;
|
||||||
renderer.RenderNewBatch(rootComponentId);
|
renderer.RenderNewBatch(rootComponentId);
|
||||||
|
|
||||||
// Assert: Applicable children are included in disposal list
|
// Assert: Applicable children are included in disposal list
|
||||||
Assert.Equal(renderer.Batches[1].DisposedComponentIDs, new[] { 1, 2 });
|
Assert.Equal(new[] { 2, 1 }, renderer.Batches[1].DisposedComponentIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DisposesEventHandlersWhenAttributeValueChanged()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var eventCount = 0;
|
||||||
|
UIEventHandler origEventHandler = args => { eventCount++; };
|
||||||
|
var component = new EventComponent { Handler = origEventHandler };
|
||||||
|
var componentId = renderer.AssignComponentId(component);
|
||||||
|
renderer.RenderNewBatch(componentId);
|
||||||
|
var origEventHandlerId = renderer.Batches.Single().DiffsByComponentId[componentId].Single()
|
||||||
|
.ReferenceFrames
|
||||||
|
.Where(f => f.FrameType == RenderTreeFrameType.Attribute)
|
||||||
|
.Single(f => f.AttributeEventHandlerId != 0)
|
||||||
|
.AttributeEventHandlerId;
|
||||||
|
|
||||||
|
// Act/Assert 1: Event handler fires when we trigger it
|
||||||
|
Assert.Equal(0, eventCount);
|
||||||
|
renderer.DispatchEvent(componentId, origEventHandlerId, args: null);
|
||||||
|
Assert.Equal(1, eventCount);
|
||||||
|
|
||||||
|
// Now change the attribute value
|
||||||
|
var newEventCount = 0;
|
||||||
|
component.Handler = args => { newEventCount++; };
|
||||||
|
renderer.RenderNewBatch(componentId);
|
||||||
|
|
||||||
|
// Act/Assert 2: Can no longer fire the original event, but can fire the new event
|
||||||
|
Assert.Throws<ArgumentException>(() =>
|
||||||
|
{
|
||||||
|
renderer.DispatchEvent(componentId, origEventHandlerId, args: null);
|
||||||
|
});
|
||||||
|
Assert.Equal(1, eventCount);
|
||||||
|
Assert.Equal(0, newEventCount);
|
||||||
|
renderer.DispatchEvent(componentId, origEventHandlerId + 1, args: null);
|
||||||
|
Assert.Equal(1, newEventCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DisposesEventHandlersWhenAttributeRemoved()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var eventCount = 0;
|
||||||
|
UIEventHandler origEventHandler = args => { eventCount++; };
|
||||||
|
var component = new EventComponent { Handler = origEventHandler };
|
||||||
|
var componentId = renderer.AssignComponentId(component);
|
||||||
|
renderer.RenderNewBatch(componentId);
|
||||||
|
var origEventHandlerId = renderer.Batches.Single().DiffsByComponentId[componentId].Single()
|
||||||
|
.ReferenceFrames
|
||||||
|
.Where(f => f.FrameType == RenderTreeFrameType.Attribute)
|
||||||
|
.Single(f => f.AttributeEventHandlerId != 0)
|
||||||
|
.AttributeEventHandlerId;
|
||||||
|
|
||||||
|
// Act/Assert 1: Event handler fires when we trigger it
|
||||||
|
Assert.Equal(0, eventCount);
|
||||||
|
renderer.DispatchEvent(componentId, origEventHandlerId, args: null);
|
||||||
|
Assert.Equal(1, eventCount);
|
||||||
|
|
||||||
|
// Now remove the event attribute
|
||||||
|
component.Handler = null;
|
||||||
|
renderer.RenderNewBatch(componentId);
|
||||||
|
|
||||||
|
// Act/Assert 2: Can no longer fire the original event
|
||||||
|
Assert.Throws<ArgumentException>(() =>
|
||||||
|
{
|
||||||
|
renderer.DispatchEvent(componentId, origEventHandlerId, args: null);
|
||||||
|
});
|
||||||
|
Assert.Equal(1, eventCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DisposesEventHandlersWhenOwnerComponentRemoved()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var eventCount = 0;
|
||||||
|
UIEventHandler origEventHandler = args => { eventCount++; };
|
||||||
|
var component = new ConditionalParentComponent<EventComponent>
|
||||||
|
{
|
||||||
|
IncludeChild = true,
|
||||||
|
ChildParameters = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ nameof(EventComponent.Handler), origEventHandler }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var rootComponentId = renderer.AssignComponentId(component);
|
||||||
|
renderer.RenderNewBatch(rootComponentId);
|
||||||
|
var childComponentId = renderer.Batches.Single().DiffsByComponentId[rootComponentId].Single()
|
||||||
|
.ReferenceFrames
|
||||||
|
.Where(f => f.FrameType == RenderTreeFrameType.Component)
|
||||||
|
.Single()
|
||||||
|
.ComponentId;
|
||||||
|
var eventHandlerId = renderer.Batches.Single().DiffsByComponentId[childComponentId].Single()
|
||||||
|
.ReferenceFrames
|
||||||
|
.Where(f => f.FrameType == RenderTreeFrameType.Attribute)
|
||||||
|
.Single(f => f.AttributeEventHandlerId != 0)
|
||||||
|
.AttributeEventHandlerId;
|
||||||
|
|
||||||
|
// Act/Assert 1: Event handler fires when we trigger it
|
||||||
|
Assert.Equal(0, eventCount);
|
||||||
|
renderer.DispatchEvent(childComponentId, eventHandlerId, args: null);
|
||||||
|
Assert.Equal(1, eventCount);
|
||||||
|
|
||||||
|
// Now remove the EventComponent
|
||||||
|
component.IncludeChild = false;
|
||||||
|
renderer.RenderNewBatch(rootComponentId);
|
||||||
|
|
||||||
|
// Act/Assert 2: Can no longer fire the original event
|
||||||
|
Assert.Throws<ArgumentException>(() =>
|
||||||
|
{
|
||||||
|
renderer.DispatchEvent(eventHandlerId, eventHandlerId, args: null);
|
||||||
|
});
|
||||||
|
Assert.Equal(1, eventCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DisposesEventHandlersWhenAncestorElementRemoved()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var eventCount = 0;
|
||||||
|
UIEventHandler origEventHandler = args => { eventCount++; };
|
||||||
|
var component = new EventComponent { Handler = origEventHandler };
|
||||||
|
var componentId = renderer.AssignComponentId(component);
|
||||||
|
renderer.RenderNewBatch(componentId);
|
||||||
|
var origEventHandlerId = renderer.Batches.Single().DiffsByComponentId[componentId].Single()
|
||||||
|
.ReferenceFrames
|
||||||
|
.Where(f => f.FrameType == RenderTreeFrameType.Attribute)
|
||||||
|
.Single(f => f.AttributeEventHandlerId != 0)
|
||||||
|
.AttributeEventHandlerId;
|
||||||
|
|
||||||
|
// Act/Assert 1: Event handler fires when we trigger it
|
||||||
|
Assert.Equal(0, eventCount);
|
||||||
|
renderer.DispatchEvent(componentId, origEventHandlerId, args: null);
|
||||||
|
Assert.Equal(1, eventCount);
|
||||||
|
|
||||||
|
// Now remove the ancestor element
|
||||||
|
component.SkipElement = true;
|
||||||
|
renderer.RenderNewBatch(componentId);
|
||||||
|
|
||||||
|
// Act/Assert 2: Can no longer fire the original event
|
||||||
|
Assert.Throws<ArgumentException>(() =>
|
||||||
|
{
|
||||||
|
renderer.DispatchEvent(componentId, origEventHandlerId, args: null);
|
||||||
|
});
|
||||||
|
Assert.Equal(1, eventCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NoOpRenderer : Renderer
|
private class NoOpRenderer : Renderer
|
||||||
|
|
@ -528,8 +678,8 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
public new void RenderNewBatch(int componentId)
|
public new void RenderNewBatch(int componentId)
|
||||||
=> base.RenderNewBatch(componentId);
|
=> base.RenderNewBatch(componentId);
|
||||||
|
|
||||||
public new void DispatchEvent(int componentId, int renderTreeIndex, UIEventArgs args)
|
public new void DispatchEvent(int componentId, int eventHandlerId, UIEventArgs args)
|
||||||
=> base.DispatchEvent(componentId, renderTreeIndex, args);
|
=> base.DispatchEvent(componentId, eventHandlerId, args);
|
||||||
|
|
||||||
protected internal override void UpdateDisplay(RenderBatch renderBatch)
|
protected internal override void UpdateDisplay(RenderBatch renderBatch)
|
||||||
{
|
{
|
||||||
|
|
@ -601,15 +751,51 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
private class EventComponent : IComponent
|
private class EventComponent : IComponent
|
||||||
{
|
{
|
||||||
public UIEventHandler Handler { get; set; }
|
public UIEventHandler Handler { get; set; }
|
||||||
|
public bool SkipElement { get; set; }
|
||||||
|
|
||||||
public void BuildRenderTree(RenderTreeBuilder builder)
|
public void BuildRenderTree(RenderTreeBuilder builder)
|
||||||
{
|
{
|
||||||
builder.OpenElement(0, "some element");
|
builder.OpenElement(0, "grandparent");
|
||||||
builder.AddAttribute(1, "some event", Handler);
|
if (!SkipElement)
|
||||||
|
{
|
||||||
|
builder.OpenElement(1, "parent");
|
||||||
|
builder.OpenElement(2, "some element");
|
||||||
|
if (Handler != null)
|
||||||
|
{
|
||||||
|
builder.AddAttribute(3, "some event", Handler);
|
||||||
|
}
|
||||||
|
builder.CloseElement();
|
||||||
|
builder.CloseElement();
|
||||||
|
}
|
||||||
builder.CloseElement();
|
builder.CloseElement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ConditionalParentComponent<T> : IComponent where T : IComponent
|
||||||
|
{
|
||||||
|
public bool IncludeChild { get; set; }
|
||||||
|
public IDictionary<string, object> ChildParameters { get; set; }
|
||||||
|
|
||||||
|
public void BuildRenderTree(RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
builder.AddText(0, "Parent here");
|
||||||
|
|
||||||
|
if (IncludeChild)
|
||||||
|
{
|
||||||
|
builder.OpenComponent<T>(1);
|
||||||
|
if (ChildParameters != null)
|
||||||
|
{
|
||||||
|
var sequence = 2;
|
||||||
|
foreach (var kvp in ChildParameters)
|
||||||
|
{
|
||||||
|
builder.AddAttribute(sequence++, kvp.Key, kvp.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AssertCanBeCollected(Func<object> targetFactory)
|
void AssertCanBeCollected(Func<object> targetFactory)
|
||||||
{
|
{
|
||||||
// We have to construct the WeakReference in a separate scope
|
// We have to construct the WeakReference in a separate scope
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@using System.Collections.Generic
|
@using System.Collections.Generic
|
||||||
@using Microsoft.AspNetCore.Blazor.RenderTree
|
@using Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
Type here: <input onkeypress=@CreateNewDelegateInstance() />
|
Type here: <input onkeypress=@OnKeyPressed />
|
||||||
<ul>
|
<ul>
|
||||||
@foreach (var key in keysPressed)
|
@foreach (var key in keysPressed)
|
||||||
{
|
{
|
||||||
|
|
@ -11,22 +11,7 @@ Type here: <input onkeypress=@CreateNewDelegateInstance() />
|
||||||
@functions {
|
@functions {
|
||||||
List<string> keysPressed = new List<string>();
|
List<string> keysPressed = new List<string>();
|
||||||
|
|
||||||
// TODO: Fix this
|
void OnKeyPressed(UIEventArgs eventArgs)
|
||||||
// Currently, you can only trigger an event handler whose value changed in the most
|
|
||||||
// recent render cycle. That's because we reference the event handlers by their index
|
|
||||||
// into the current diff's ReferenceFrames array. We need some better mechanism of
|
|
||||||
// locating the delegates that is independent of whether the corresponding attribute
|
|
||||||
// changed in the last diff, and not assuming the attribute in the original render
|
|
||||||
// tree is still at the same index.
|
|
||||||
// Once that's fixed, remove the 'CreateNewDelegateInstance' method entirely and
|
|
||||||
// the 'irrelevantObject' arg from below, and simplify to onkeypress=@OnKeyPressed
|
|
||||||
UIEventHandler CreateNewDelegateInstance()
|
|
||||||
{
|
|
||||||
var irrelevantObject = new object();
|
|
||||||
return args => OnKeyPressed(args, irrelevantObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnKeyPressed(UIEventArgs eventArgs, object irrelevantObject)
|
|
||||||
{
|
{
|
||||||
keysPressed.Add(((UIKeyboardEventArgs)eventArgs).Key);
|
keysPressed.Add(((UIKeyboardEventArgs)eventArgs).Key);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue