Improvements to bind and event handling
The changes here make event dispatching (including bind) more user-friendly by avoiding the need for manual calls to StateHasChnaged. We also introduce a new type `EventCallback` (and `EventCallback<T>`). This is a new primitive that is like a super-powered version of a delegate. When writing a component that accepts delegates as parameters, consider using `EventCallback` for the following reasons: - Allows consumer to pass a variety of different delegate signatures - Does proper event dispatching and error handling Using `EventCallback` will eliminate most of the remaining cases where a manual `StateHasChanged` is required when components are passing content and delegates to each other. `EventCallback` is inherently async for the reason that this is really the only way to provide correct error handling. ----- The fix for this will be two-phase by first creating a set of APIs that can be targeted by the compiler that has the desired behaviour and then updating the compiler to target this new infrastructure.
This commit is contained in:
parent
cc7b35439c
commit
98fe8a8328
|
|
@ -376,6 +376,11 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
protected RenderTreeFrame[] GetRenderTree(IComponent component)
|
||||
{
|
||||
var renderer = new TestRenderer();
|
||||
return GetRenderTree(renderer, component);
|
||||
}
|
||||
|
||||
protected private RenderTreeFrame[] GetRenderTree(TestRenderer renderer, IComponent component)
|
||||
{
|
||||
renderer.AttachComponent(component);
|
||||
var task = renderer.InvokeAsync(() => component.SetParametersAsync(ParameterCollection.Empty));
|
||||
// we will have to change this method if we add a test that does actual async work.
|
||||
|
|
@ -432,7 +437,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
public IEnumerable<Diagnostic> Diagnostics { get; set; }
|
||||
}
|
||||
|
||||
private class TestRenderer : Renderer
|
||||
protected class TestRenderer : Renderer
|
||||
{
|
||||
public TestRenderer() : base(new TestServiceProvider(), CreateDefaultDispatcher())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||
|
|
@ -351,7 +352,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsTwoWayBindingForTextboxes()
|
||||
public async Task SupportsTwoWayBindingForTextboxes()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
|
|
@ -361,26 +362,27 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
}");
|
||||
var myValueProperty = component.GetType().GetProperty("MyValue");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
var frames = GetRenderTree(component);
|
||||
Action<UIEventArgs> setter = null;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", "Initial value", 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
((Action<UIEventArgs>)frame.AttributeValue)(new UIChangeEventArgs
|
||||
{
|
||||
Value = "Modified value"
|
||||
});
|
||||
Assert.Equal("Modified value", myValueProperty.GetValue(component));
|
||||
setter = Assert.IsType<Action<UIEventArgs>>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
await renderer.Invoke(() => setter(new UIChangeEventArgs { Value = "Modified value", }));
|
||||
Assert.Equal("Modified value", myValueProperty.GetValue(component));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsTwoWayBindingForTextareas()
|
||||
public async Task SupportsTwoWayBindingForTextareas()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
|
|
@ -390,26 +392,27 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
}");
|
||||
var myValueProperty = component.GetType().GetProperty("MyValue");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
var frames = GetRenderTree(component);
|
||||
Action<UIEventArgs> setter = null;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "textarea", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", "Initial value", 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
((Action<UIEventArgs>)frame.AttributeValue)(new UIChangeEventArgs
|
||||
{
|
||||
Value = "Modified value"
|
||||
});
|
||||
Assert.Equal("Modified value", myValueProperty.GetValue(component));
|
||||
setter = Assert.IsType<Action<UIEventArgs>>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
await renderer.Invoke(() => setter(new UIChangeEventArgs() { Value = "Modified value", }));
|
||||
Assert.Equal("Modified value", myValueProperty.GetValue(component));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsTwoWayBindingForDateValues()
|
||||
public async Task SupportsTwoWayBindingForDateValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
|
|
@ -419,27 +422,28 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
}");
|
||||
var myDateProperty = component.GetType().GetProperty("MyDate");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
var frames = GetRenderTree(component);
|
||||
Action<UIEventArgs> setter = null;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 3, 4, 1, 2, 3).ToString(), 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
var newDateValue = new DateTime(2018, 3, 5, 4, 5, 6);
|
||||
((Action<UIEventArgs>)frame.AttributeValue)(new UIChangeEventArgs
|
||||
{
|
||||
Value = newDateValue.ToString()
|
||||
});
|
||||
Assert.Equal(newDateValue, myDateProperty.GetValue(component));
|
||||
setter = Assert.IsType<Action<UIEventArgs>>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
var newDateValue = new DateTime(2018, 3, 5, 4, 5, 6);
|
||||
await renderer.Invoke(() => setter(new UIChangeEventArgs() { Value = newDateValue.ToString(), }));
|
||||
Assert.Equal(newDateValue, myDateProperty.GetValue(component));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsTwoWayBindingForDateValuesWithFormatString()
|
||||
public async Task SupportsTwoWayBindingForDateValuesWithFormatString()
|
||||
{
|
||||
// Arrange/Act
|
||||
var testDateFormat = "ddd yyyy-MM-dd";
|
||||
|
|
@ -450,22 +454,23 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
}}");
|
||||
var myDateProperty = component.GetType().GetProperty("MyDate");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
var frames = GetRenderTree(component);
|
||||
Action<UIEventArgs> setter = null;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 3, 4).ToString(testDateFormat), 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
((Action<UIEventArgs>)frame.AttributeValue)(new UIChangeEventArgs
|
||||
{
|
||||
Value = new DateTime(2018, 3, 5).ToString(testDateFormat)
|
||||
});
|
||||
Assert.Equal(new DateTime(2018, 3, 5), myDateProperty.GetValue(component));
|
||||
setter = Assert.IsType<Action<UIEventArgs>>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
await renderer.Invoke(() => setter(new UIChangeEventArgs() { Value = new DateTime(2018, 3, 5).ToString(testDateFormat), }));
|
||||
Assert.Equal(new DateTime(2018, 3, 5), myDateProperty.GetValue(component));
|
||||
}
|
||||
|
||||
[Fact] // In this case, onclick is just a normal HTML attribute
|
||||
|
|
@ -496,8 +501,10 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
|
||||
var clicked = component.GetType().GetProperty("Clicked");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(frames,
|
||||
|
|
@ -527,8 +534,10 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
|
||||
var clicked = component.GetType().GetProperty("Clicked");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(frames,
|
||||
|
|
@ -546,7 +555,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsTwoWayBindingForBoolValues()
|
||||
public async Task SupportsTwoWayBindingForBoolValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
|
|
@ -556,26 +565,27 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
}");
|
||||
var myValueProperty = component.GetType().GetProperty("MyValue");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
var frames = GetRenderTree(component);
|
||||
Action<UIEventArgs> setter = null;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", true, 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
((Action<UIEventArgs>)frame.AttributeValue)(new UIChangeEventArgs
|
||||
{
|
||||
Value = false
|
||||
});
|
||||
Assert.False((bool)myValueProperty.GetValue(component));
|
||||
setter = Assert.IsType<Action<UIEventArgs>>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
await renderer.Invoke(() => setter(new UIChangeEventArgs() { Value = false, }));
|
||||
Assert.False((bool)myValueProperty.GetValue(component));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsTwoWayBindingForEnumValues()
|
||||
public async Task SupportsTwoWayBindingForEnumValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var myEnumType = FullTypeName<MyEnum>();
|
||||
|
|
@ -586,22 +596,23 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
}}");
|
||||
var myValueProperty = component.GetType().GetProperty("MyValue");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
var frames = GetRenderTree(component);
|
||||
Action<UIEventArgs> setter = null;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", MyEnum.FirstValue.ToString(), 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
((Action<UIEventArgs>)frame.AttributeValue)(new UIChangeEventArgs
|
||||
{
|
||||
Value = MyEnum.SecondValue.ToString()
|
||||
});
|
||||
Assert.Equal(MyEnum.SecondValue, (MyEnum)myValueProperty.GetValue(component));
|
||||
setter = Assert.IsType<Action<UIEventArgs>>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
await renderer.Invoke(() => setter(new UIChangeEventArgs() { Value = MyEnum.SecondValue.ToString(), }));
|
||||
Assert.Equal(MyEnum.SecondValue, (MyEnum)myValueProperty.GetValue(component));
|
||||
}
|
||||
|
||||
public enum MyEnum { FirstValue, SecondValue }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import { System_Array, MethodHandle } from '../Platform/Platform';
|
||||
import { RenderBatch, ArraySegment, ArrayRange, RenderTreeEdit, RenderTreeFrame, EditType, FrameType, ArrayValues } from './RenderBatch/RenderBatch';
|
||||
import { platform } from '../Environment';
|
||||
import { RenderBatch, ArraySegment, RenderTreeEdit, RenderTreeFrame, EditType, FrameType, ArrayValues } from './RenderBatch/RenderBatch';
|
||||
import { EventDelegator } from './EventDelegator';
|
||||
import { EventForDotNet, UIEventArgs } from './EventForDotNet';
|
||||
import { LogicalElement, toLogicalElement, insertLogicalChild, removeLogicalChild, getLogicalParent, getLogicalChild, createAndInsertLogicalContainer, isSvgElement } from './LogicalElements';
|
||||
|
|
@ -9,16 +7,14 @@ const selectValuePropname = '_blazorSelectValue';
|
|||
const sharedTemplateElemForParsing = document.createElement('template');
|
||||
const sharedSvgElemForParsing = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
||||
const preventDefaultEvents: { [eventType: string]: boolean } = { submit: true };
|
||||
let raiseEventMethod: MethodHandle;
|
||||
let renderComponentMethod: MethodHandle;
|
||||
|
||||
export class BrowserRenderer {
|
||||
private eventDelegator: EventDelegator;
|
||||
private childComponentLocations: { [componentId: number]: LogicalElement } = {};
|
||||
|
||||
constructor(private browserRendererId: number) {
|
||||
this.eventDelegator = new EventDelegator((event, componentId, eventHandlerId, eventArgs) => {
|
||||
raiseEvent(event, this.browserRendererId, componentId, eventHandlerId, eventArgs);
|
||||
this.eventDelegator = new EventDelegator((event, eventHandlerId, eventArgs) => {
|
||||
raiseEvent(event, this.browserRendererId, eventHandlerId, eventArgs);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -32,7 +28,7 @@ export class BrowserRenderer {
|
|||
throw new Error(`No element is currently associated with component ${componentId}`);
|
||||
}
|
||||
|
||||
this.applyEdits(batch, componentId, element, 0, edits, referenceFrames);
|
||||
this.applyEdits(batch, element, 0, edits, referenceFrames);
|
||||
}
|
||||
|
||||
public disposeComponent(componentId: number) {
|
||||
|
|
@ -47,7 +43,7 @@ export class BrowserRenderer {
|
|||
this.childComponentLocations[componentId] = element;
|
||||
}
|
||||
|
||||
private applyEdits(batch: RenderBatch, componentId: number, parent: LogicalElement, childIndex: number, edits: ArraySegment<RenderTreeEdit>, referenceFrames: ArrayValues<RenderTreeFrame>) {
|
||||
private applyEdits(batch: RenderBatch, parent: LogicalElement, childIndex: number, edits: ArraySegment<RenderTreeEdit>, referenceFrames: ArrayValues<RenderTreeFrame>) {
|
||||
let currentDepth = 0;
|
||||
let childIndexAtCurrentDepth = childIndex;
|
||||
|
||||
|
|
@ -67,7 +63,7 @@ export class BrowserRenderer {
|
|||
const frameIndex = editReader.newTreeIndex(edit);
|
||||
const frame = batch.referenceFramesEntry(referenceFrames, frameIndex);
|
||||
const siblingIndex = editReader.siblingIndex(edit);
|
||||
this.insertFrame(batch, componentId, parent, childIndexAtCurrentDepth + siblingIndex, referenceFrames, frame, frameIndex);
|
||||
this.insertFrame(batch, parent, childIndexAtCurrentDepth + siblingIndex, referenceFrames, frame, frameIndex);
|
||||
break;
|
||||
}
|
||||
case EditType.removeFrame: {
|
||||
|
|
@ -81,7 +77,7 @@ export class BrowserRenderer {
|
|||
const siblingIndex = editReader.siblingIndex(edit);
|
||||
const element = getLogicalChild(parent, childIndexAtCurrentDepth + siblingIndex);
|
||||
if (element instanceof Element) {
|
||||
this.applyAttribute(batch, componentId, element, frame);
|
||||
this.applyAttribute(batch, element, frame);
|
||||
} else {
|
||||
throw new Error(`Cannot set attribute on non-element child`);
|
||||
}
|
||||
|
|
@ -145,12 +141,12 @@ export class BrowserRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
private insertFrame(batch: RenderBatch, componentId: number, parent: LogicalElement, childIndex: number, frames: ArrayValues<RenderTreeFrame>, frame: RenderTreeFrame, frameIndex: number): number {
|
||||
private insertFrame(batch: RenderBatch, parent: LogicalElement, childIndex: number, frames: ArrayValues<RenderTreeFrame>, frame: RenderTreeFrame, frameIndex: number): number {
|
||||
const frameReader = batch.frameReader;
|
||||
const frameType = frameReader.frameType(frame);
|
||||
switch (frameType) {
|
||||
case FrameType.element:
|
||||
this.insertElement(batch, componentId, parent, childIndex, frames, frame, frameIndex);
|
||||
this.insertElement(batch, parent, childIndex, frames, frame, frameIndex);
|
||||
return 1;
|
||||
case FrameType.text:
|
||||
this.insertText(batch, parent, childIndex, frame);
|
||||
|
|
@ -161,7 +157,7 @@ export class BrowserRenderer {
|
|||
this.insertComponent(batch, parent, childIndex, frame);
|
||||
return 1;
|
||||
case FrameType.region:
|
||||
return this.insertFrameRange(batch, componentId, parent, childIndex, frames, frameIndex + 1, frameIndex + frameReader.subtreeLength(frame));
|
||||
return this.insertFrameRange(batch, parent, childIndex, frames, frameIndex + 1, frameIndex + frameReader.subtreeLength(frame));
|
||||
case FrameType.elementReferenceCapture:
|
||||
if (parent instanceof Element) {
|
||||
applyCaptureIdToElement(parent, frameReader.elementReferenceCaptureId(frame)!);
|
||||
|
|
@ -178,7 +174,7 @@ export class BrowserRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
private insertElement(batch: RenderBatch, componentId: number, parent: LogicalElement, childIndex: number, frames: ArrayValues<RenderTreeFrame>, frame: RenderTreeFrame, frameIndex: number) {
|
||||
private insertElement(batch: RenderBatch, parent: LogicalElement, childIndex: number, frames: ArrayValues<RenderTreeFrame>, frame: RenderTreeFrame, frameIndex: number) {
|
||||
const frameReader = batch.frameReader;
|
||||
const tagName = frameReader.elementName(frame)!;
|
||||
const newDomElementRaw = tagName === 'svg' || isSvgElement(parent) ?
|
||||
|
|
@ -192,11 +188,11 @@ export class BrowserRenderer {
|
|||
for (let descendantIndex = frameIndex + 1; descendantIndex < descendantsEndIndexExcl; descendantIndex++) {
|
||||
const descendantFrame = batch.referenceFramesEntry(frames, descendantIndex);
|
||||
if (frameReader.frameType(descendantFrame) === FrameType.attribute) {
|
||||
this.applyAttribute(batch, componentId, newDomElementRaw, descendantFrame);
|
||||
this.applyAttribute(batch, newDomElementRaw, descendantFrame);
|
||||
} else {
|
||||
// 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
|
||||
this.insertFrameRange(batch, componentId, newElement, 0, frames, descendantIndex, descendantsEndIndexExcl);
|
||||
this.insertFrameRange(batch, newElement, 0, frames, descendantIndex, descendantsEndIndexExcl);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -228,7 +224,7 @@ export class BrowserRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
private applyAttribute(batch: RenderBatch, componentId: number, toDomElement: Element, attributeFrame: RenderTreeFrame) {
|
||||
private applyAttribute(batch: RenderBatch, toDomElement: Element, attributeFrame: RenderTreeFrame) {
|
||||
const frameReader = batch.frameReader;
|
||||
const attributeName = frameReader.attributeName(attributeFrame)!;
|
||||
const browserRendererId = this.browserRendererId;
|
||||
|
|
@ -240,7 +236,7 @@ export class BrowserRenderer {
|
|||
if (firstTwoChars !== 'on' || !eventName) {
|
||||
throw new Error(`Attribute has nonzero event handler ID, but attribute name '${attributeName}' does not start with 'on'.`);
|
||||
}
|
||||
this.eventDelegator.setListener(toDomElement, eventName, componentId, eventHandlerId);
|
||||
this.eventDelegator.setListener(toDomElement, eventName, eventHandlerId);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -315,11 +311,11 @@ export class BrowserRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
private insertFrameRange(batch: RenderBatch, componentId: number, parent: LogicalElement, childIndex: number, frames: ArrayValues<RenderTreeFrame>, startIndex: number, endIndexExcl: number): number {
|
||||
private insertFrameRange(batch: RenderBatch, parent: LogicalElement, childIndex: number, frames: ArrayValues<RenderTreeFrame>, startIndex: number, endIndexExcl: number): number {
|
||||
const origChildIndex = childIndex;
|
||||
for (let index = startIndex; index < endIndexExcl; index++) {
|
||||
const frame = batch.referenceFramesEntry(frames, index);
|
||||
const numChildrenInserted = this.insertFrame(batch, componentId, parent, childIndex, frames, frame, index);
|
||||
const numChildrenInserted = this.insertFrame(batch, parent, childIndex, frames, frame, index);
|
||||
childIndex += numChildrenInserted;
|
||||
|
||||
// Skip over any descendants, since they are already dealt with recursively
|
||||
|
|
@ -355,14 +351,13 @@ function countDescendantFrames(batch: RenderBatch, frame: RenderTreeFrame): numb
|
|||
}
|
||||
}
|
||||
|
||||
function raiseEvent(event: Event, browserRendererId: number, componentId: number, eventHandlerId: number, eventArgs: EventForDotNet<UIEventArgs>) {
|
||||
function raiseEvent(event: Event, browserRendererId: number, eventHandlerId: number, eventArgs: EventForDotNet<UIEventArgs>) {
|
||||
if (preventDefaultEvents[event.type]) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
const eventDescriptor = {
|
||||
browserRendererId,
|
||||
componentId,
|
||||
eventHandlerId,
|
||||
eventArgsType: eventArgs.type
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ const nonBubblingEvents = toLookup([
|
|||
]);
|
||||
|
||||
export interface OnEventCallback {
|
||||
(event: Event, componentId: number, eventHandlerId: number, eventArgs: EventForDotNet<UIEventArgs>): void;
|
||||
(event: Event, eventHandlerId: number, eventArgs: EventForDotNet<UIEventArgs>): void;
|
||||
}
|
||||
|
||||
// Responsible for adding/removing the eventInfo on an expando property on DOM elements, and
|
||||
|
|
@ -23,7 +23,7 @@ export class EventDelegator {
|
|||
this.eventInfoStore = new EventInfoStore(this.onGlobalEvent.bind(this));
|
||||
}
|
||||
|
||||
public setListener(element: Element, eventName: string, componentId: number, eventHandlerId: number) {
|
||||
public setListener(element: Element, eventName: string, eventHandlerId: number) {
|
||||
// Ensure we have a place to store event info for this element
|
||||
let infoForElement: EventHandlerInfosForElement = element[this.eventsCollectionKey];
|
||||
if (!infoForElement) {
|
||||
|
|
@ -36,7 +36,7 @@ export class EventDelegator {
|
|||
this.eventInfoStore.update(oldInfo.eventHandlerId, eventHandlerId);
|
||||
} else {
|
||||
// Go through the whole flow which might involve registering a new global handler
|
||||
const newInfo = { element, eventName, componentId, eventHandlerId };
|
||||
const newInfo = { element, eventName, eventHandlerId };
|
||||
this.eventInfoStore.add(newInfo);
|
||||
infoForElement[eventName] = newInfo;
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ export class EventDelegator {
|
|||
}
|
||||
|
||||
const handlerInfo = handlerInfos[evt.type];
|
||||
this.onEvent(evt, handlerInfo.componentId, handlerInfo.eventHandlerId, eventArgs);
|
||||
this.onEvent(evt, handlerInfo.eventHandlerId, eventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +161,6 @@ interface EventHandlerInfosForElement {
|
|||
interface EventHandlerInfo {
|
||||
element: Element;
|
||||
eventName: string;
|
||||
componentId: number;
|
||||
eventHandlerId: number;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,11 +22,7 @@ namespace Microsoft.AspNetCore.Components.Browser
|
|||
{
|
||||
var eventArgs = ParseEventArgsJson(eventDescriptor.EventArgsType, eventArgsJson);
|
||||
var renderer = RendererRegistry.Current.Find(eventDescriptor.BrowserRendererId);
|
||||
|
||||
return renderer.DispatchEventAsync(
|
||||
eventDescriptor.ComponentId,
|
||||
eventDescriptor.EventHandlerId,
|
||||
eventArgs);
|
||||
return renderer.DispatchEventAsync(eventDescriptor.EventHandlerId, eventArgs);
|
||||
}
|
||||
|
||||
private static UIEventArgs ParseEventArgsJson(string eventArgsType, string eventArgsJson)
|
||||
|
|
@ -72,11 +68,6 @@ namespace Microsoft.AspNetCore.Components.Browser
|
|||
/// </summary>
|
||||
public int BrowserRendererId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// For framework use only.
|
||||
/// </summary>
|
||||
public int ComponentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// For framework use only.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -69,12 +69,34 @@ namespace Microsoft.AspNetCore.Components
|
|||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not intended to be used directly.
|
||||
/// </summary>
|
||||
public static EventCallback GetEventHandlerValue<T>(EventCallback value)
|
||||
where T : UIEventArgs
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not intended to be used directly.
|
||||
/// </summary>
|
||||
public static EventCallback<T> GetEventHandlerValue<T>(EventCallback<T> value)
|
||||
where T : UIEventArgs
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not intended to be used directly.
|
||||
/// </summary>
|
||||
public static Action<UIEventArgs> SetValueHandler(Action<string> setter, string existingValue)
|
||||
{
|
||||
return _ => setter((string)((UIChangeEventArgs)_).Value);
|
||||
return eventArgs =>
|
||||
{
|
||||
setter((string)((UIChangeEventArgs)eventArgs).Value);
|
||||
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -82,7 +104,11 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
public static Action<UIEventArgs> SetValueHandler(Action<bool> setter, bool existingValue)
|
||||
{
|
||||
return _ => setter((bool)((UIChangeEventArgs)_).Value);
|
||||
return eventArgs =>
|
||||
{
|
||||
setter((bool)((UIChangeEventArgs)eventArgs).Value);
|
||||
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -90,7 +116,11 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
public static Action<UIEventArgs> SetValueHandler(Action<bool?> setter, bool? existingValue)
|
||||
{
|
||||
return _ => setter((bool?)((UIChangeEventArgs)_).Value);
|
||||
return eventArgs =>
|
||||
{
|
||||
setter((bool?)((UIChangeEventArgs)eventArgs).Value);
|
||||
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -98,7 +128,11 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
public static Action<UIEventArgs> SetValueHandler(Action<int> setter, int existingValue)
|
||||
{
|
||||
return _ => setter(int.Parse((string)((UIChangeEventArgs)_).Value));
|
||||
return eventArgs =>
|
||||
{
|
||||
setter(int.Parse((string)((UIChangeEventArgs)eventArgs).Value));
|
||||
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -106,9 +140,11 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
public static Action<UIEventArgs> SetValueHandler(Action<int?> setter, int? existingValue)
|
||||
{
|
||||
return _ => setter(int.TryParse((string)((UIChangeEventArgs)_).Value, out var tmpvalue)
|
||||
? tmpvalue
|
||||
: (int?)null);
|
||||
return eventArgs =>
|
||||
{
|
||||
setter(int.TryParse((string)((UIChangeEventArgs)eventArgs).Value, out var value) ? value : (int?)null);
|
||||
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -116,7 +152,11 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
public static Action<UIEventArgs> SetValueHandler(Action<long> setter, long existingValue)
|
||||
{
|
||||
return _ => setter(long.Parse((string)((UIChangeEventArgs)_).Value));
|
||||
return eventArgs =>
|
||||
{
|
||||
setter(long.Parse((string)((UIChangeEventArgs)eventArgs).Value));
|
||||
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -124,9 +164,11 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
public static Action<UIEventArgs> SetValueHandler(Action<long?> setter, long? existingValue)
|
||||
{
|
||||
return _ => setter(long.TryParse((string)((UIChangeEventArgs)_).Value, out var tmpvalue)
|
||||
? tmpvalue
|
||||
: (long?)null);
|
||||
return eventArgs =>
|
||||
{
|
||||
setter(long.TryParse((string)((UIChangeEventArgs)eventArgs).Value, out var value) ? value : (long?)null);
|
||||
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -134,7 +176,11 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
public static Action<UIEventArgs> SetValueHandler(Action<float> setter, float existingValue)
|
||||
{
|
||||
return _ => setter(float.Parse((string)((UIChangeEventArgs)_).Value));
|
||||
return eventArgs =>
|
||||
{
|
||||
setter(float.Parse((string)((UIChangeEventArgs)eventArgs).Value));
|
||||
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -142,9 +188,11 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
public static Action<UIEventArgs> SetValueHandler(Action<float?> setter, float? existingValue)
|
||||
{
|
||||
return _ => setter(float.TryParse((string)((UIChangeEventArgs)_).Value, out var tmpvalue)
|
||||
? tmpvalue
|
||||
: (float?)null);
|
||||
return eventArgs =>
|
||||
{
|
||||
setter(float.TryParse((string)((UIChangeEventArgs)eventArgs).Value, out var value) ? value : (float?)null);
|
||||
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -152,7 +200,11 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
public static Action<UIEventArgs> SetValueHandler(Action<double> setter, double existingValue)
|
||||
{
|
||||
return _ => setter(double.Parse((string)((UIChangeEventArgs)_).Value));
|
||||
return eventArgs =>
|
||||
{
|
||||
setter(double.Parse((string)((UIChangeEventArgs)eventArgs).Value));
|
||||
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -160,9 +212,11 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
public static Action<UIEventArgs> SetValueHandler(Action<double?> setter, double? existingValue)
|
||||
{
|
||||
return _ => setter(double.TryParse((string)((UIChangeEventArgs)_).Value, out var tmpvalue)
|
||||
? tmpvalue
|
||||
: (double?)null);
|
||||
return eventArgs =>
|
||||
{
|
||||
setter(double.TryParse((string)((UIChangeEventArgs)eventArgs).Value, out var value) ? value : (double?)null);
|
||||
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -170,7 +224,11 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
public static Action<UIEventArgs> SetValueHandler(Action<decimal> setter, decimal existingValue)
|
||||
{
|
||||
return _ => setter(decimal.Parse((string)((UIChangeEventArgs)_).Value));
|
||||
return eventArgs =>
|
||||
{
|
||||
setter(decimal.Parse((string)((UIChangeEventArgs)eventArgs).Value));
|
||||
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -178,9 +236,11 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
public static Action<UIEventArgs> SetValueHandler(Action<decimal?> setter, decimal? existingValue)
|
||||
{
|
||||
return _ => setter(decimal.TryParse((string)((UIChangeEventArgs)_).Value, out var tmpvalue)
|
||||
? tmpvalue
|
||||
: (decimal?)null);
|
||||
return eventArgs =>
|
||||
{
|
||||
setter(decimal.TryParse((string)((UIChangeEventArgs)eventArgs).Value, out var tmpvalue) ? tmpvalue : (decimal?)null);
|
||||
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -188,7 +248,10 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
public static Action<UIEventArgs> SetValueHandler(Action<DateTime> setter, DateTime existingValue)
|
||||
{
|
||||
return _ => SetDateTimeValue(setter, ((UIChangeEventArgs)_).Value, null);
|
||||
return eventArgs =>
|
||||
{
|
||||
SetDateTimeValue(setter, ((UIChangeEventArgs)eventArgs).Value, null);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -196,7 +259,10 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
public static Action<UIEventArgs> SetValueHandler(Action<DateTime> setter, DateTime existingValue, string format)
|
||||
{
|
||||
return _ => SetDateTimeValue(setter, ((UIChangeEventArgs)_).Value, format);
|
||||
return eventArgs =>
|
||||
{
|
||||
SetDateTimeValue(setter, ((UIChangeEventArgs)eventArgs).Value, format);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -209,11 +275,12 @@ namespace Microsoft.AspNetCore.Components
|
|||
throw new ArgumentException($"'bind' does not accept values of type {typeof(T).FullName}. To read and write this value type, wrap it in a property of type string with suitable getters and setters.");
|
||||
}
|
||||
|
||||
return _ =>
|
||||
return eventArgs =>
|
||||
{
|
||||
var value = (string)((UIChangeEventArgs)_).Value;
|
||||
var value = (string)((UIChangeEventArgs)eventArgs).Value;
|
||||
var parsed = (T)Enum.Parse(typeof(T), value);
|
||||
setter(parsed);
|
||||
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -224,6 +291,24 @@ namespace Microsoft.AspNetCore.Components
|
|||
: format != null && DateTime.TryParseExact(stringValue, format, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsedExact) ? parsedExact
|
||||
: DateTime.Parse(stringValue);
|
||||
setter(parsedValue);
|
||||
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||
}
|
||||
|
||||
// This is a temporary polyfill for these old-style bind methods until they can be removed.
|
||||
// This doesn't do proper error handling (usage is fire-and-forget).
|
||||
private static Task DispatchEventAsync(object component, EventCallbackWorkItem callback, object arg)
|
||||
{
|
||||
if (component == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(component));
|
||||
}
|
||||
|
||||
if (component is IHandleEvent handler)
|
||||
{
|
||||
return handler.HandleEventAsync(callback, arg);
|
||||
}
|
||||
|
||||
return callback.InvokeAsync(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// <summary>
|
||||
/// Method invoked when the component is ready to start, having received its
|
||||
/// initial parameters from its parent in the render tree.
|
||||
///
|
||||
///
|
||||
/// Override this method if you will perform an asynchronous operation and
|
||||
/// want the component to refresh when that operation is completed.
|
||||
/// </summary>
|
||||
|
|
@ -210,8 +210,8 @@ namespace Microsoft.AspNetCore.Components
|
|||
catch when (task.IsCanceled)
|
||||
{
|
||||
// Ignore exceptions from task cancelletions.
|
||||
// Awaiting a canceled task may produce either an OperationCanceledException (if produced as a consequence of
|
||||
// CancellationToken.ThrowIfCancellationRequested()) or a TaskCanceledException (produced as a consequence of awaiting Task.FromCanceled).
|
||||
// Awaiting a canceled task may produce either an OperationCanceledException (if produced as a consequence of
|
||||
// CancellationToken.ThrowIfCancellationRequested()) or a TaskCanceledException (produced as a consequence of awaiting Task.FromCanceled).
|
||||
// It's much easier to check the state of the Task (i.e. Task.IsCanceled) rather than catch two distinct exceptions.
|
||||
}
|
||||
|
||||
|
|
@ -255,9 +255,9 @@ namespace Microsoft.AspNetCore.Components
|
|||
StateHasChanged();
|
||||
}
|
||||
|
||||
Task IHandleEvent.HandleEventAsync(EventHandlerInvoker binding, UIEventArgs args)
|
||||
Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object arg)
|
||||
{
|
||||
var task = binding.Invoke(args);
|
||||
var task = callback.InvokeAsync(arg);
|
||||
var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
|
||||
task.Status != TaskStatus.Canceled;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
// 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 System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A bound event handler delegate.
|
||||
/// </summary>
|
||||
public readonly struct EventCallback
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a reference to the <see cref="EventCallbackFactory"/>.
|
||||
/// </summary>
|
||||
public static readonly EventCallbackFactory Factory = new EventCallbackFactory();
|
||||
|
||||
/// <summary>
|
||||
/// Gets an empty <see cref="EventCallback{T}"/>.
|
||||
/// </summary>
|
||||
public static readonly EventCallback Empty = new EventCallback(null, (Action)(() => { }));
|
||||
|
||||
internal readonly MulticastDelegate Delegate;
|
||||
internal readonly IHandleEvent Receiver;
|
||||
|
||||
/// <summary>
|
||||
/// Creates the new <see cref="EventCallback{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="delegate">The delegate to bind.</param>
|
||||
public EventCallback(IHandleEvent receiver, MulticastDelegate @delegate)
|
||||
{
|
||||
Receiver = receiver;
|
||||
Delegate = @delegate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether the delegate associated with this event dispatcher is non-null.
|
||||
/// </summary>
|
||||
public bool HasDelegate => Delegate != null;
|
||||
|
||||
// This is a hint to the runtime that Receiver is a different object than what
|
||||
// Delegate.Target points to. This allows us to avoid boxing the command object
|
||||
// when building the render tree. See logic where this is used.
|
||||
internal bool RequiresExplicitReceiver => Receiver != null && !object.ReferenceEquals(Receiver, Delegate?.Target);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the delegate associated with this binding and dispatches an event notification to the
|
||||
/// appropriate component.
|
||||
/// </summary>
|
||||
/// <param name="arg">The argument.</param>
|
||||
/// <returns>A <see cref="Task"/> which completes asynchronously once event processing has completed.</returns>
|
||||
public Task InvokeAsync(object arg)
|
||||
{
|
||||
if (Receiver == null)
|
||||
{
|
||||
return EventCallbackWorkItem.InvokeAsync<object>(Delegate, arg);
|
||||
}
|
||||
|
||||
return Receiver.HandleEventAsync(new EventCallbackWorkItem(Delegate), arg);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A bound event handler delegate.
|
||||
/// </summary>
|
||||
public readonly struct EventCallback<T>
|
||||
{
|
||||
internal readonly MulticastDelegate Delegate;
|
||||
internal readonly IHandleEvent Receiver;
|
||||
|
||||
/// <summary>
|
||||
/// Creates the new <see cref="EventCallback{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="delegate">The delegate to bind.</param>
|
||||
public EventCallback(IHandleEvent receiver, MulticastDelegate @delegate)
|
||||
{
|
||||
Receiver = receiver;
|
||||
Delegate = @delegate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether the delegate associated with this event dispatcher is non-null.
|
||||
/// </summary>
|
||||
public bool HasDelegate => Delegate != null;
|
||||
|
||||
// This is a hint to the runtime that Reciever is a different object than what
|
||||
// Delegate.Target points to. This allows us to avoid boxing the command object
|
||||
// when building the render tree. See logic where this is used.
|
||||
internal bool RequiresExplicitReceiver => Receiver != null && !object.ReferenceEquals(Receiver, Delegate?.Target);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the delegate associated with this binding and dispatches an event notification to the
|
||||
/// appropriate component.
|
||||
/// </summary>
|
||||
/// <param name="arg">The argument.</param>
|
||||
/// <returns>A <see cref="Task"/> which completes asynchronously once event processing has completed.</returns>
|
||||
public Task InvokeAsync(T arg)
|
||||
{
|
||||
if (Receiver == null)
|
||||
{
|
||||
return EventCallbackWorkItem.InvokeAsync<T>(Delegate, arg);
|
||||
}
|
||||
|
||||
return Receiver.HandleEventAsync(new EventCallbackWorkItem(Delegate), arg);
|
||||
}
|
||||
|
||||
internal EventCallback AsUntyped()
|
||||
{
|
||||
return new EventCallback(Receiver ?? Delegate?.Target as IHandleEvent, Delegate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
// 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 System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A factory for creating <see cref="EventCallback"/> and <see cref="EventCallback{T}"/>
|
||||
/// instances.
|
||||
/// </summary>
|
||||
public sealed class EventCallbackFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public EventCallback Create(object receiver, Action callback)
|
||||
{
|
||||
if (receiver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(receiver));
|
||||
}
|
||||
|
||||
if (callback == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(callback));
|
||||
}
|
||||
|
||||
return CreateCore(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public EventCallback Create(object receiver, Action<object> callback)
|
||||
{
|
||||
if (receiver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(receiver));
|
||||
}
|
||||
|
||||
if (callback == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(callback));
|
||||
}
|
||||
|
||||
return CreateCore(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public EventCallback Create(object receiver, Func<Task> callback)
|
||||
{
|
||||
if (receiver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(receiver));
|
||||
}
|
||||
|
||||
if (callback == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(callback));
|
||||
}
|
||||
|
||||
return CreateCore(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public EventCallback Create(object receiver, Func<object, Task> callback)
|
||||
{
|
||||
if (receiver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(receiver));
|
||||
}
|
||||
|
||||
if (callback == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(callback));
|
||||
}
|
||||
|
||||
return CreateCore(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public EventCallback<T> Create<T>(object receiver, Action callback)
|
||||
{
|
||||
if (receiver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(receiver));
|
||||
}
|
||||
|
||||
if (callback == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(callback));
|
||||
}
|
||||
|
||||
return CreateCore<T>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public EventCallback<T> Create<T>(object receiver, Action<T> callback)
|
||||
{
|
||||
if (receiver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(receiver));
|
||||
}
|
||||
|
||||
if (callback == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(callback));
|
||||
}
|
||||
|
||||
return CreateCore<T>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public EventCallback<T> Create<T>(object receiver, Func<Task> callback)
|
||||
{
|
||||
if (receiver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(receiver));
|
||||
}
|
||||
|
||||
if (callback == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(callback));
|
||||
}
|
||||
|
||||
return CreateCore<T>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public EventCallback<T> Create<T>(object receiver, Func<T, Task> callback)
|
||||
{
|
||||
if (receiver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(receiver));
|
||||
}
|
||||
|
||||
if (callback == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(callback));
|
||||
}
|
||||
|
||||
return CreateCore<T>(receiver, callback);
|
||||
}
|
||||
|
||||
private EventCallback CreateCore(object receiver, MulticastDelegate callback)
|
||||
{
|
||||
if (!object.ReferenceEquals(receiver, callback.Target) && receiver is IHandleEvent handler)
|
||||
{
|
||||
return new EventCallback(handler, callback);
|
||||
}
|
||||
|
||||
return new EventCallback(callback.Target as IHandleEvent, callback);
|
||||
}
|
||||
|
||||
private EventCallback<T> CreateCore<T>(object receiver, MulticastDelegate callback)
|
||||
{
|
||||
if (!object.ReferenceEquals(receiver, callback.Target) && receiver is IHandleEvent handler)
|
||||
{
|
||||
return new EventCallback<T>(handler, callback);
|
||||
}
|
||||
|
||||
return new EventCallback<T>(callback.Target as IHandleEvent, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,411 @@
|
|||
// 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 System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods for two-way binding using <see cref="EventCallback"/>. For internal use only.
|
||||
/// </summary>
|
||||
public static class EventCallbackFactoryBinderExtensions
|
||||
{
|
||||
// Perf: conversion delegates are written as static funcs so we can prevent
|
||||
// allocations for these simple cases.
|
||||
private static Func<object, string> ConvertToString = (obj) => (string)obj;
|
||||
|
||||
private static Func<object, bool> ConvertToBool = (obj) => (bool)obj;
|
||||
private static Func<object, bool?> ConvertToNullableBool = (obj) => (bool?)obj;
|
||||
|
||||
private static Func<object, int> ConvertToInt = (obj) => int.Parse((string)obj);
|
||||
private static Func<object, int?> ConvertToNullableInt = (obj) =>
|
||||
{
|
||||
if (int.TryParse((string)obj, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
private static Func<object, long> ConvertToLong = (obj) => long.Parse((string)obj);
|
||||
private static Func<object, long?> ConvertToNullableLong = (obj) =>
|
||||
{
|
||||
if (long.TryParse((string)obj, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
private static Func<object, float> ConvertToFloat = (obj) => float.Parse((string)obj);
|
||||
private static Func<object, float?> ConvertToNullableFloat = (obj) =>
|
||||
{
|
||||
if (float.TryParse((string)obj, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
private static Func<object, double> ConvertToDouble = (obj) => double.Parse((string)obj);
|
||||
private static Func<object, double?> ConvertToNullableDouble = (obj) =>
|
||||
{
|
||||
if (double.TryParse((string)obj, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
private static Func<object, decimal> ConvertToDecimal = (obj) => decimal.Parse((string)obj);
|
||||
private static Func<object, decimal?> ConvertToNullableDecimal = (obj) =>
|
||||
{
|
||||
if (decimal.TryParse((string)obj, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
private static class EnumConverter<T> where T : Enum
|
||||
{
|
||||
public static Func<object, T> Convert = (obj) =>
|
||||
{
|
||||
return (T)Enum.Parse(typeof(T), (string)obj);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<string> setter,
|
||||
string existingValue)
|
||||
{
|
||||
;
|
||||
return CreateBinderCore<string>(factory, receiver, setter, ConvertToString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<bool> setter,
|
||||
bool existingValue)
|
||||
{
|
||||
return CreateBinderCore<bool>(factory, receiver, setter, ConvertToBool);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<bool?> setter,
|
||||
bool? existingValue)
|
||||
{
|
||||
return CreateBinderCore<bool?>(factory, receiver, setter, ConvertToNullableBool);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<int> setter,
|
||||
int existingValue)
|
||||
{
|
||||
return CreateBinderCore<int>(factory, receiver, setter, ConvertToInt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<int?> setter,
|
||||
int? existingValue)
|
||||
{
|
||||
return CreateBinderCore<int?>(factory, receiver, setter, ConvertToNullableInt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<long> setter,
|
||||
long existingValue)
|
||||
{
|
||||
return CreateBinderCore<long>(factory, receiver, setter, ConvertToLong);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<long?> setter,
|
||||
long? existingValue)
|
||||
{
|
||||
return CreateBinderCore<long?>(factory, receiver, setter, ConvertToNullableLong);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<float> setter,
|
||||
float existingValue)
|
||||
{
|
||||
return CreateBinderCore<float>(factory, receiver, setter, ConvertToFloat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<float?> setter,
|
||||
float? existingValue)
|
||||
{
|
||||
return CreateBinderCore<float?>(factory, receiver, setter, ConvertToNullableFloat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<double> setter,
|
||||
double existingValue)
|
||||
{
|
||||
return CreateBinderCore<double>(factory, receiver, setter, ConvertToDouble);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<double?> setter,
|
||||
double? existingValue)
|
||||
{
|
||||
return CreateBinderCore<double?>(factory, receiver, setter, ConvertToNullableDouble);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<decimal> setter,
|
||||
decimal existingValue)
|
||||
{
|
||||
return CreateBinderCore<decimal>(factory, receiver, setter, ConvertToDecimal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<decimal?> setter,
|
||||
decimal? existingValue)
|
||||
{
|
||||
Func<object, decimal?> converter = (obj) =>
|
||||
{
|
||||
if (decimal.TryParse((string)obj, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
return CreateBinderCore<decimal?>(factory, receiver, setter, ConvertToNullableDecimal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<DateTime> setter,
|
||||
DateTime existingValue)
|
||||
{
|
||||
// Avoiding CreateBinderCore so we can avoid an extra allocating lambda
|
||||
// when a format is used.
|
||||
Action<UIChangeEventArgs> callback = (e) =>
|
||||
{
|
||||
setter(ConvertDateTime(e.Value, format: null));
|
||||
};
|
||||
return factory.Create<UIChangeEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <param name="format"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<DateTime> setter,
|
||||
DateTime existingValue,
|
||||
string format)
|
||||
{
|
||||
// Avoiding CreateBinderCore so we can avoid an extra allocating lambda
|
||||
// when a format is used.
|
||||
Action<UIChangeEventArgs> callback = (e) =>
|
||||
{
|
||||
setter(ConvertDateTime(e.Value, format));
|
||||
};
|
||||
return factory.Create<UIChangeEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder<T>(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<T> setter,
|
||||
T existingValue) where T : Enum
|
||||
{
|
||||
return CreateBinderCore<T>(factory, receiver, setter, EnumConverter<T>.Convert);
|
||||
}
|
||||
|
||||
private static DateTime ConvertDateTime(object obj, string format)
|
||||
{
|
||||
var text = (string)obj;
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
else if (format != null && DateTime.TryParseExact(text, format, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return DateTime.Parse(text);
|
||||
}
|
||||
}
|
||||
|
||||
private static EventCallback<UIChangeEventArgs> CreateBinderCore<T>(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<T> setter,
|
||||
Func<object, T> converter)
|
||||
{
|
||||
Action<UIChangeEventArgs> callback = e =>
|
||||
{
|
||||
setter(converter(e.Value));
|
||||
};
|
||||
return factory.Create<UIChangeEventArgs>(receiver, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,446 @@
|
|||
// 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 System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="EventCallbackFactory"/> and <see cref="UIEventArgs"/> types. For internal
|
||||
/// framework use.
|
||||
/// </summary>
|
||||
public static class EventCallbackFactoryUIEventArgsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIEventArgs> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIEventArgs, Task> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIChangeEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIChangeEventArgs> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIChangeEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIChangeEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIChangeEventArgs, Task> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIChangeEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIClipboardEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIClipboardEventArgs> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIClipboardEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIClipboardEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIClipboardEventArgs, Task> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIClipboardEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIDragEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIDragEventArgs> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIDragEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIDragEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIDragEventArgs, Task> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIDragEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIErrorEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIErrorEventArgs> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIErrorEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIErrorEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIErrorEventArgs, Task> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIErrorEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIFocusEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIFocusEventArgs> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIFocusEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIFocusEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIFocusEventArgs, Task> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIFocusEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIKeyboardEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIKeyboardEventArgs> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIKeyboardEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIKeyboardEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIKeyboardEventArgs, Task> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIKeyboardEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIMouseEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIMouseEventArgs> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIMouseEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIMouseEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIMouseEventArgs, Task> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIMouseEventArgs>(receiver, callback);
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIPointerEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIPointerEventArgs> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIPointerEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIPointerEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIPointerEventArgs, Task> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIPointerEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIProgressEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIProgressEventArgs> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIProgressEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIProgressEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIProgressEventArgs, Task> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIProgressEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UITouchEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UITouchEventArgs> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UITouchEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UITouchEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UITouchEventArgs, Task> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UITouchEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIWheelEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIWheelEventArgs> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIWheelEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EventCallback"/> for the provided <paramref name="receiver"/> and
|
||||
/// <paramref name="callback"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">The <see cref="EventCallbackFactory"/>.</param>
|
||||
/// <param name="receiver">The event receiver.</param>
|
||||
/// <param name="callback">The event callback.</param>
|
||||
/// <returns>The <see cref="EventCallback"/>.</returns>
|
||||
public static EventCallback<UIWheelEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIWheelEventArgs, Task> callback)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return factory.Create<UIWheelEventArgs>(receiver, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
// 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 System;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps a callback delegate associated with an event.
|
||||
/// </summary>
|
||||
public struct EventCallbackWorkItem
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty <see cref="EventCallbackWorkItem"/>.
|
||||
/// </summary>
|
||||
public static readonly EventCallbackWorkItem Empty = new EventCallbackWorkItem(null);
|
||||
|
||||
private readonly MulticastDelegate _delegate;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="EventCallbackWorkItem"/> with the provided <paramref name="delegate"/>.
|
||||
/// </summary>
|
||||
/// <param name="delegate">The callback delegate.</param>
|
||||
public EventCallbackWorkItem(MulticastDelegate @delegate)
|
||||
{
|
||||
_delegate = @delegate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the delegate associated with this <see cref="EventCallbackWorkItem"/>.
|
||||
/// </summary>
|
||||
/// <param name="arg">The argument to provide to the delegate. May be <c>null</c>.</param>
|
||||
/// <returns>A <see cref="Task"/> then will complete asynchronously once the delegate has completed.</returns>
|
||||
public Task InvokeAsync(object arg)
|
||||
{
|
||||
return InvokeAsync<object>(_delegate, arg);
|
||||
}
|
||||
|
||||
internal static Task InvokeAsync<T>(MulticastDelegate @delegate, T arg)
|
||||
{
|
||||
switch (@delegate)
|
||||
{
|
||||
case null:
|
||||
return Task.CompletedTask;
|
||||
|
||||
case Action action:
|
||||
action.Invoke();
|
||||
return Task.CompletedTask;
|
||||
|
||||
case Action<T> actionEventArgs:
|
||||
actionEventArgs.Invoke(arg);
|
||||
return Task.CompletedTask;
|
||||
|
||||
case Func<Task> func:
|
||||
return func.Invoke();
|
||||
|
||||
case Func<T, Task> funcEventArgs:
|
||||
return funcEventArgs.Invoke(arg);
|
||||
|
||||
default:
|
||||
{
|
||||
try
|
||||
{
|
||||
return @delegate.DynamicInvoke(arg) as Task ?? Task.CompletedTask;
|
||||
}
|
||||
catch (TargetInvocationException e)
|
||||
{
|
||||
// Since we fell into the DynamicInvoke case, any exception will be wrapped
|
||||
// in a TIE. We can expect this to be thrown synchronously, so it's low overhead
|
||||
// to unwrap it.
|
||||
return Task.FromException(e.InnerException);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
// 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 System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A bound event handler delegate.
|
||||
/// </summary>
|
||||
public readonly struct EventHandlerInvoker
|
||||
{
|
||||
private readonly MulticastDelegate _delegate;
|
||||
|
||||
/// <summary>
|
||||
/// Creates the new <see cref="EventHandlerInvoker"/>.
|
||||
/// </summary>
|
||||
/// <param name="delegate">The delegate to bind.</param>
|
||||
public EventHandlerInvoker(MulticastDelegate @delegate)
|
||||
{
|
||||
_delegate = @delegate;
|
||||
}
|
||||
/// <summary>
|
||||
/// Invokes the delegate associated with this binding.
|
||||
/// </summary>
|
||||
/// <param name="e">The <see cref="UIEventArgs"/>.</param>
|
||||
/// <returns></returns>
|
||||
public Task Invoke(UIEventArgs e)
|
||||
{
|
||||
switch (_delegate)
|
||||
{
|
||||
case Action action:
|
||||
action.Invoke();
|
||||
return Task.CompletedTask;
|
||||
|
||||
case Action<UIEventArgs> actionEventArgs:
|
||||
actionEventArgs.Invoke(e);
|
||||
return Task.CompletedTask;
|
||||
|
||||
case Func<Task> func:
|
||||
return func.Invoke();
|
||||
|
||||
case Func<UIEventArgs, Task> funcEventArgs:
|
||||
return funcEventArgs.Invoke(e);
|
||||
|
||||
case MulticastDelegate @delegate:
|
||||
return @delegate.DynamicInvoke(e) as Task ?? Task.CompletedTask;
|
||||
|
||||
case null:
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,16 +6,18 @@ using System.Threading.Tasks;
|
|||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface implemented by components that receive notification of their events.
|
||||
/// Interface implemented by components that receive notification of state changes.
|
||||
/// </summary>
|
||||
public interface IHandleEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Notifies the component that one of its event handlers has been triggered.
|
||||
/// Notifies the a state change has been triggered.
|
||||
/// </summary>
|
||||
/// <param name="binding">The event binding.</param>
|
||||
/// <param name="args">Arguments for the event handler.</param>
|
||||
/// <returns>A <see cref="Task"/> that represents the asynchronous event handling operation.</returns>
|
||||
Task HandleEventAsync(EventHandlerInvoker binding, UIEventArgs args);
|
||||
/// <param name="item">The <see cref="EventCallbackWorkItem"/> associated with this event.</param>
|
||||
/// <param name="arg">The argument associated with this event.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task"/> that completes once the component has processed the state change.
|
||||
/// </returns>
|
||||
Task HandleEventAsync(EventCallbackWorkItem item, object arg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -277,6 +277,84 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Appends a frame representing an <see cref="EventCallback"/> attribute.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The attribute is associated with the most recently added element. If the value is <c>null</c> and the
|
||||
/// current element is not a component, the frame will be omitted.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||
/// <param name="name">The name of the attribute.</param>
|
||||
/// <param name="value">The value of the attribute.</param>
|
||||
/// <remarks>
|
||||
/// This method is provided for infrastructure purposes, and is used to support generated code
|
||||
/// that uses <see cref="EventCallbackFactory"/>.
|
||||
/// </remarks>
|
||||
public void AddAttribute(int sequence, string name, EventCallback value)
|
||||
{
|
||||
AssertCanAddAttribute();
|
||||
if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
|
||||
{
|
||||
// Since this is a component, we need to preserve the type of the EventCallabck, so we have
|
||||
// to box.
|
||||
Append(RenderTreeFrame.Attribute(sequence, name, (object)value));
|
||||
}
|
||||
else if (value.RequiresExplicitReceiver)
|
||||
{
|
||||
// If we need to preserve the receiver, we just box the EventCallback
|
||||
// so we can get it out on the other side.
|
||||
Append(RenderTreeFrame.Attribute(sequence, name, (object)value));
|
||||
}
|
||||
else
|
||||
{
|
||||
// In the common case the receiver is also the delegate's target, so we
|
||||
// just need to retain the delegate. This allows us to avoid an allocation.
|
||||
Append(RenderTreeFrame.Attribute(sequence, name, value.Delegate));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Appends a frame representing an <see cref="EventCallback"/> attribute.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The attribute is associated with the most recently added element. If the value is <c>null</c> and the
|
||||
/// current element is not a component, the frame will be omitted.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||
/// <param name="name">The name of the attribute.</param>
|
||||
/// <param name="value">The value of the attribute.</param>
|
||||
/// <remarks>
|
||||
/// This method is provided for infrastructure purposes, and is used to support generated code
|
||||
/// that uses <see cref="EventCallbackFactory"/>.
|
||||
/// </remarks>
|
||||
public void AddAttribute<T>(int sequence, string name, EventCallback<T> value)
|
||||
{
|
||||
AssertCanAddAttribute();
|
||||
if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
|
||||
{
|
||||
// Since this is a component, we need to preserve the type of the EventCallback, so we have
|
||||
// to box.
|
||||
Append(RenderTreeFrame.Attribute(sequence, name, (object)value));
|
||||
}
|
||||
else if (value.RequiresExplicitReceiver)
|
||||
{
|
||||
// If we need to preserve the receiver - we convert this to an untyped EventCallback. We don't
|
||||
// need to preserve the type of an EventCallback<T> when it's invoked from the DOM.
|
||||
Append(RenderTreeFrame.Attribute(sequence, name, (object)value.AsUntyped()));
|
||||
}
|
||||
else
|
||||
{
|
||||
// In the common case the receiver is also the delegate's target, so we
|
||||
// just need to retain the delegate. This allows us to avoid an allocation.
|
||||
Append(RenderTreeFrame.Attribute(sequence, name, value.Delegate));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a frame representing a string-valued attribute.
|
||||
/// The attribute is associated with the most recently added element. If the value is <c>null</c>, or
|
||||
|
|
|
|||
|
|
@ -652,7 +652,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
//
|
||||
// We're following a simple heuristic here that's reflected in the ts runtime
|
||||
// based on the common usage of attributes for DOM events.
|
||||
if (newFrame.AttributeValue is MulticastDelegate &&
|
||||
if ((newFrame.AttributeValue is MulticastDelegate || newFrame.AttributeValue is EventCallback) &&
|
||||
newFrame.AttributeName.Length >= 3 &&
|
||||
newFrame.AttributeName.StartsWith("on"))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -93,20 +93,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
}
|
||||
}
|
||||
|
||||
public Task DispatchEventAsync(EventHandlerInvoker binding, UIEventArgs eventArgs)
|
||||
{
|
||||
if (Component is IHandleEvent handleEventComponent)
|
||||
{
|
||||
return handleEventComponent.HandleEventAsync(binding, eventArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"The component of type {Component.GetType().FullName} cannot receive " +
|
||||
$"events because it does not implement {typeof(IHandleEvent).FullName}.");
|
||||
}
|
||||
}
|
||||
|
||||
public Task NotifyRenderCompletedAsync()
|
||||
{
|
||||
if (Component is IHandleAfterRender handlerAfterRender)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
private readonly ComponentFactory _componentFactory;
|
||||
private readonly Dictionary<int, ComponentState> _componentStateById = new Dictionary<int, ComponentState>();
|
||||
private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder();
|
||||
private readonly Dictionary<int, EventHandlerInvoker> _eventBindings = new Dictionary<int, EventHandlerInvoker>();
|
||||
private readonly Dictionary<int, EventCallback> _eventBindings = new Dictionary<int, EventCallback>();
|
||||
private IDispatcher _dispatcher;
|
||||
|
||||
private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it
|
||||
|
|
@ -200,39 +200,44 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
protected abstract Task UpdateDisplayAsync(in RenderBatch renderBatch);
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the specified component that an event has occurred.
|
||||
/// Notifies the renderer that an event has occurred.
|
||||
/// </summary>
|
||||
/// <param name="componentId">The unique identifier for the component within the scope of this <see cref="Renderer"/>.</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>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous execution operation.</returns>
|
||||
public Task DispatchEventAsync(int componentId, int eventHandlerId, UIEventArgs eventArgs)
|
||||
/// <returns>
|
||||
/// A <see cref="Task"/> which will complete once all asynchronous processing related to the event
|
||||
/// has completed.
|
||||
/// </returns>
|
||||
public Task DispatchEventAsync(int eventHandlerId, UIEventArgs eventArgs)
|
||||
{
|
||||
EnsureSynchronizationContext();
|
||||
|
||||
if (_eventBindings.TryGetValue(eventHandlerId, out var binding))
|
||||
{
|
||||
// The event handler might request multiple renders in sequence. Capture them
|
||||
// all in a single batch.
|
||||
var componentState = GetRequiredComponentState(componentId);
|
||||
Task task = null;
|
||||
try
|
||||
{
|
||||
_isBatchInProgress = true;
|
||||
task = componentState.DispatchEventAsync(binding, eventArgs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isBatchInProgress = false;
|
||||
ProcessRenderQueue();
|
||||
}
|
||||
|
||||
return GetErrorHandledTask(task);
|
||||
}
|
||||
else
|
||||
if (!_eventBindings.TryGetValue(eventHandlerId, out var callback))
|
||||
{
|
||||
throw new ArgumentException($"There is no event handler with ID {eventHandlerId}");
|
||||
}
|
||||
|
||||
Task task = null;
|
||||
try
|
||||
{
|
||||
// The event handler might request multiple renders in sequence. Capture them
|
||||
// all in a single batch.
|
||||
_isBatchInProgress = true;
|
||||
|
||||
task = callback.InvokeAsync(eventArgs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isBatchInProgress = false;
|
||||
|
||||
// Since the task has yielded - process any queued rendering work before we return control
|
||||
// to the caller.
|
||||
ProcessRenderQueue();
|
||||
}
|
||||
|
||||
// Task completed synchronously or is still running. We already processed all of the rendering
|
||||
// work that was queued so let our error handler deal with it.
|
||||
return GetErrorHandledTask(task);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -338,10 +343,27 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
{
|
||||
var id = ++_lastEventHandlerId;
|
||||
|
||||
if (frame.AttributeValue is MulticastDelegate @delegate)
|
||||
if (frame.AttributeValue is EventCallback callback)
|
||||
{
|
||||
_eventBindings.Add(id, new EventHandlerInvoker(@delegate));
|
||||
// We hit this case when a EventCallback object is produced that needs an explicit receiver.
|
||||
// Common cases for this are "chained bind" or "chained event handler" when a component
|
||||
// accepts a delegate as a parameter and then hooks it up to a DOM event.
|
||||
//
|
||||
// When that happens we intentionally box the EventCallback because we need to hold on to
|
||||
// the receiver.
|
||||
_eventBindings.Add(id, callback);
|
||||
}
|
||||
else if (frame.AttributeValue is MulticastDelegate @delegate)
|
||||
{
|
||||
// This is the common case for a delegate, where the receiver of the event
|
||||
// is the same as delegate.Target. In this case since the receiver is implicit we can
|
||||
// avoid boxing the EventCallback object and just re-hydrate it on the other side of the
|
||||
// render tree.
|
||||
_eventBindings.Add(id, new EventCallback(@delegate.Target as IHandleEvent, @delegate));
|
||||
}
|
||||
|
||||
// NOTE: we do not to handle EventCallback<T> here. EventCallback<T> is only used when passing
|
||||
// a callback to a component, and never when used to attaching a DOM event handler.
|
||||
|
||||
frame = frame.WithAttributeEventHandlerId(id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
action();
|
||||
completion.SetResult(null);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
completion.SetCanceled();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
completion.SetException(exception);
|
||||
|
|
@ -66,6 +70,10 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
await asyncAction();
|
||||
completion.SetResult(null);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
completion.SetCanceled();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
completion.SetException(exception);
|
||||
|
|
@ -85,6 +93,10 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
var result = function();
|
||||
completion.SetResult(result);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
completion.SetCanceled();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
completion.SetException(exception);
|
||||
|
|
@ -104,6 +116,10 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
var result = await asyncFunction();
|
||||
completion.SetResult(result);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
completion.SetCanceled();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
completion.SetException(exception);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
public class UIEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty instance of <see cref="UIEventArgs"/>.
|
||||
/// </summary>
|
||||
public static readonly UIEventArgs Empty = new UIEventArgs();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the event.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,358 @@
|
|||
// 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 System;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
public class EventCallbackFactoryBinderExtensionsTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task CreateBinder_ThrowsConversionException()
|
||||
{
|
||||
// Arrange
|
||||
var value = 17;
|
||||
var component = new EventCountingComponent();
|
||||
Action<int> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
// Act
|
||||
await Assert.ThrowsAsync<FormatException>(() =>
|
||||
{
|
||||
return binder.InvokeAsync(new UIChangeEventArgs() { Value = "not-an-integer!", });
|
||||
});
|
||||
|
||||
Assert.Equal(17, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_String()
|
||||
{
|
||||
// Arrange
|
||||
var value = "hi";
|
||||
var component = new EventCountingComponent();
|
||||
Action<string> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
var expectedValue = "bye";
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue, });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_Bool()
|
||||
{
|
||||
// Arrange
|
||||
var value = false;
|
||||
var component = new EventCountingComponent();
|
||||
Action<bool> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
var expectedValue = true;
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = true, });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_NullableBool()
|
||||
{
|
||||
// Arrange
|
||||
var value = (bool?)false;
|
||||
var component = new EventCountingComponent();
|
||||
Action<bool?> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
var expectedValue = (bool?)true;
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = true, });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_Int()
|
||||
{
|
||||
// Arrange
|
||||
var value = 17;
|
||||
var component = new EventCountingComponent();
|
||||
Action<int> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
var expectedValue = 42;
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = "42", });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_NullableInt()
|
||||
{
|
||||
// Arrange
|
||||
var value = (int?)17;
|
||||
var component = new EventCountingComponent();
|
||||
Action<int?> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
var expectedValue = (int?)42;
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = "42", });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_Long()
|
||||
{
|
||||
// Arrange
|
||||
var value = (long)17;
|
||||
var component = new EventCountingComponent();
|
||||
Action<long> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
var expectedValue = (long)42;
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = "42", });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_NullableLong()
|
||||
{
|
||||
// Arrange
|
||||
var value = (long?)17;
|
||||
var component = new EventCountingComponent();
|
||||
Action<long?> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
var expectedValue = (long?)42;
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = "42", });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_Float()
|
||||
{
|
||||
// Arrange
|
||||
var value = (float)17;
|
||||
var component = new EventCountingComponent();
|
||||
Action<float> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
var expectedValue = (float)42;
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = "42", });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_NullableFloat()
|
||||
{
|
||||
// Arrange
|
||||
var value = (float?)17;
|
||||
var component = new EventCountingComponent();
|
||||
Action<float?> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
var expectedValue = (float?)42;
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = "42", });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_Double()
|
||||
{
|
||||
// Arrange
|
||||
var value = (double)17;
|
||||
var component = new EventCountingComponent();
|
||||
Action<double> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
var expectedValue = (double)42;
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = "42", });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_NullableDouble()
|
||||
{
|
||||
// Arrange
|
||||
var value = (double?)17;
|
||||
var component = new EventCountingComponent();
|
||||
Action<double?> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
var expectedValue = (double?)42;
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = "42", });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_Decimal()
|
||||
{
|
||||
// Arrange
|
||||
var value = (decimal)17;
|
||||
var component = new EventCountingComponent();
|
||||
Action<decimal> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
var expectedValue = (decimal)42;
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = "42", });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_NullableDecimal()
|
||||
{
|
||||
// Arrange
|
||||
var value = (decimal?)17;
|
||||
var component = new EventCountingComponent();
|
||||
Action<decimal?> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
var expectedValue = (decimal?)42;
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = "42", });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_Enum()
|
||||
{
|
||||
// Arrange
|
||||
var value = AttributeTargets.All;
|
||||
var component = new EventCountingComponent();
|
||||
Action<AttributeTargets> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
var expectedValue = AttributeTargets.Class;
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(), });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_DateTime()
|
||||
{
|
||||
// Arrange
|
||||
var value = DateTime.Now;
|
||||
var component = new EventCountingComponent();
|
||||
Action<DateTime> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
var expectedValue = new DateTime(2018, 3, 4, 1, 2, 3);
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(), });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_DateTime_Format()
|
||||
{
|
||||
// Arrange
|
||||
var value = DateTime.Now;
|
||||
var component = new EventCountingComponent();
|
||||
Action<DateTime> setter = (_) => value = _;
|
||||
var format = "ddd yyyy-MM-dd";
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value, format);
|
||||
|
||||
var expectedValue = new DateTime(2018, 3, 4);
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(format), });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
private class EventCountingComponent : IComponent, IHandleEvent
|
||||
{
|
||||
public int Count;
|
||||
|
||||
public Task HandleEventAsync(EventCallbackWorkItem item, object arg)
|
||||
{
|
||||
Count++;
|
||||
return item.InvokeAsync(arg);
|
||||
}
|
||||
|
||||
public void Configure(RenderHandle renderHandle)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Task SetParametersAsync(ParameterCollection parameters)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,464 @@
|
|||
// 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 System;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
public class EventCallbackFactoryTest
|
||||
{
|
||||
[Fact]
|
||||
public void Create_Action_AlreadyBoundToReceiver()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Action)component.SomeAction;
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create(component, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(component, callback.Receiver);
|
||||
Assert.False(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_Action_DifferentReceiver()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Action)component.SomeAction;
|
||||
|
||||
var anotherComponent = new EventComponent();
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create(anotherComponent, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(anotherComponent, callback.Receiver);
|
||||
Assert.True(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_Action_Unbound()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Action)(() => { });
|
||||
|
||||
var anotherComponent = new EventComponent();
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create(anotherComponent, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(anotherComponent, callback.Receiver);
|
||||
Assert.True(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_ActionT_AlreadyBoundToReceiver()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Action<string>)component.SomeActionOfT;
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create(component, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(component, callback.Receiver);
|
||||
Assert.False(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_ActionT_DifferentReceiver()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Action<string>)component.SomeActionOfT;
|
||||
|
||||
var anotherComponent = new EventComponent();
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create(anotherComponent, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(anotherComponent, callback.Receiver);
|
||||
Assert.True(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_ActionT_Unbound()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Action<string>)((s) => { });
|
||||
|
||||
var anotherComponent = new EventComponent();
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create(anotherComponent, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(anotherComponent, callback.Receiver);
|
||||
Assert.True(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_FuncTask_AlreadyBoundToReceiver()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Func<Task>)component.SomeFuncTask;
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create(component, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(component, callback.Receiver);
|
||||
Assert.False(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_FuncTask_DifferentReceiver()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Func<Task>)component.SomeFuncTask;
|
||||
|
||||
var anotherComponent = new EventComponent();
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create(anotherComponent, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(anotherComponent, callback.Receiver);
|
||||
Assert.True(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_FuncTask_Unbound()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Func<Task>)(() => Task.CompletedTask);
|
||||
|
||||
var anotherComponent = new EventComponent();
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create(anotherComponent, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(anotherComponent, callback.Receiver);
|
||||
Assert.True(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_FuncTTask_AlreadyBoundToReceiver()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Func<string, Task>)component.SomeFuncTTask;
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create(component, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(component, callback.Receiver);
|
||||
Assert.False(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_FuncTTask_DifferentReceiver()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Func<string, Task>)component.SomeFuncTTask;
|
||||
|
||||
var anotherComponent = new EventComponent();
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create(anotherComponent, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(anotherComponent, callback.Receiver);
|
||||
Assert.True(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_FuncTTask_Unbound()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Func<string, Task>)((s) => Task.CompletedTask);
|
||||
|
||||
var anotherComponent = new EventComponent();
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create(anotherComponent, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(anotherComponent, callback.Receiver);
|
||||
Assert.True(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateT_Action_AlreadyBoundToReceiver()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Action)component.SomeAction;
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create<string>(component, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(component, callback.Receiver);
|
||||
Assert.False(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateT_Action_DifferentReceiver()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Action)component.SomeAction;
|
||||
|
||||
var anotherComponent = new EventComponent();
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create<string>(anotherComponent, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(anotherComponent, callback.Receiver);
|
||||
Assert.True(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateT_Action_Unbound()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Action)(() => { });
|
||||
|
||||
var anotherComponent = new EventComponent();
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create<string>(anotherComponent, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(anotherComponent, callback.Receiver);
|
||||
Assert.True(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateT_ActionT_AlreadyBoundToReceiver()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Action<string>)component.SomeActionOfT;
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create<string>(component, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(component, callback.Receiver);
|
||||
Assert.False(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateT_ActionT_DifferentReceiver()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Action<string>)component.SomeActionOfT;
|
||||
|
||||
var anotherComponent = new EventComponent();
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create<string>(anotherComponent, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(anotherComponent, callback.Receiver);
|
||||
Assert.True(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateT_ActionT_Unbound()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Action<string>)((s) => { });
|
||||
|
||||
var anotherComponent = new EventComponent();
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create<string>(anotherComponent, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(anotherComponent, callback.Receiver);
|
||||
Assert.True(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateT_FuncTask_AlreadyBoundToReceiver()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Func<Task>)component.SomeFuncTask;
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create<string>(component, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(component, callback.Receiver);
|
||||
Assert.False(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateT_FuncTask_DifferentReceiver()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Func<Task>)component.SomeFuncTask;
|
||||
|
||||
var anotherComponent = new EventComponent();
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create<string>(anotherComponent, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(anotherComponent, callback.Receiver);
|
||||
Assert.True(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateT_FuncTask_Unbound()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Func<Task>)(() => Task.CompletedTask);
|
||||
|
||||
var anotherComponent = new EventComponent();
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create<string>(anotherComponent, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(anotherComponent, callback.Receiver);
|
||||
Assert.True(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateT_FuncTTask_AlreadyBoundToReceiver()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Func<string, Task>)component.SomeFuncTTask;
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create<string>(component, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(component, callback.Receiver);
|
||||
Assert.False(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateT_FuncTTask_DifferentReceiver()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Func<string, Task>)component.SomeFuncTTask;
|
||||
|
||||
var anotherComponent = new EventComponent();
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create<string>(anotherComponent, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(anotherComponent, callback.Receiver);
|
||||
Assert.True(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateT_FuncTTask_Unbound()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventComponent();
|
||||
var @delegate = (Func<string, Task>)((s) => Task.CompletedTask);
|
||||
|
||||
var anotherComponent = new EventComponent();
|
||||
|
||||
// Act
|
||||
var callback = EventCallback.Factory.Create<string>(anotherComponent, @delegate);
|
||||
|
||||
// Assert
|
||||
Assert.Same(@delegate, callback.Delegate);
|
||||
Assert.Same(anotherComponent, callback.Receiver);
|
||||
Assert.True(callback.RequiresExplicitReceiver);
|
||||
}
|
||||
|
||||
private class EventComponent : IComponent, IHandleEvent
|
||||
{
|
||||
public void SomeAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void SomeActionOfT(string e)
|
||||
{
|
||||
}
|
||||
|
||||
public Task SomeFuncTask()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SomeFuncTTask(string s)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Configure(RenderHandle renderHandle)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task HandleEventAsync(EventCallbackWorkItem item, object arg)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task SetParametersAsync(ParameterCollection parameters)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,457 @@
|
|||
// 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 System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
public class EventCallbackTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task EventCallback_Default()
|
||||
{
|
||||
// Arrange
|
||||
var callback = default(EventCallback);
|
||||
|
||||
// Act & Assert (Does not throw)
|
||||
await callback.InvokeAsync(null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallbackOfT_Default()
|
||||
{
|
||||
// Arrange
|
||||
var callback = default(EventCallback<UIEventArgs>);
|
||||
|
||||
// Act & Assert (Does not throw)
|
||||
await callback.InvokeAsync(null);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallback_NullReceiver()
|
||||
{
|
||||
// Arrange
|
||||
int runCount = 0;
|
||||
var callback = new EventCallback(null, (Action)(() => runCount++));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(null);
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, runCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallbackOfT_NullReceiver()
|
||||
{
|
||||
// Arrange
|
||||
int runCount = 0;
|
||||
var callback = new EventCallback<UIEventArgs>(null, (Action)(() => runCount++));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(null);
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, runCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallback_Action_Null()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
var callback = new EventCallback(component, (Action)(() => runCount++));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(null);
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallback_Action_IgnoresArg()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
var callback = new EventCallback(component, (Action)(() => runCount++));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(new UIEventArgs());
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallback_ActionT_Null()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
UIEventArgs arg = null;
|
||||
var callback = new EventCallback(component, (Action<UIEventArgs>)((e) => { arg = e; runCount++; }));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(null);
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.Null(arg);
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallback_ActionT_Arg()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
UIEventArgs arg = null;
|
||||
var callback = new EventCallback(component, (Action<UIEventArgs>)((e) => { arg = e; runCount++; }));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(new UIEventArgs());
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(arg);
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallback_ActionT_Arg_ValueType()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
int arg = -1;
|
||||
var callback = new EventCallback(component, (Action<int>)((e) => { arg = e; runCount++; }));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(17);
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.Equal(17, arg);
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallback_ActionT_ArgMismatch()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
UIEventArgs arg = null;
|
||||
var callback = new EventCallback(component, (Action<UIEventArgs>)((e) => { arg = e; runCount++; }));
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<ArgumentException>(() =>
|
||||
{
|
||||
return callback.InvokeAsync(new StringBuilder());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallback_FuncTask_Null()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
var callback = new EventCallback(component, (Func<Task>)(() => { runCount++; return Task.CompletedTask; }));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(null);
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallback_FuncTask_IgnoresArg()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
var callback = new EventCallback(component, (Func<Task>)(() => { runCount++; return Task.CompletedTask; }));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(new UIEventArgs());
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallback_FuncTTask_Null()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
UIEventArgs arg = null;
|
||||
var callback = new EventCallback(component, (Func<UIEventArgs, Task>)((e) => { arg = e; runCount++; return Task.CompletedTask; }));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(null);
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.Null(arg);
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallback_FuncTTask_Arg()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
UIEventArgs arg = null;
|
||||
var callback = new EventCallback(component, (Func<UIEventArgs, Task>)((e) => { arg = e; runCount++; return Task.CompletedTask; }));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(new UIEventArgs());
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(arg);
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallback_FuncTTask_Arg_ValueType()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
int arg = -1;
|
||||
var callback = new EventCallback(component, (Func<int, Task>)((e) => { arg = e; runCount++; return Task.CompletedTask; }));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(17);
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.Equal(17, arg);
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallback_FuncTTask_ArgMismatch()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
UIEventArgs arg = null;
|
||||
var callback = new EventCallback(component, (Func<UIEventArgs, Task>)((e) => { arg = e; runCount++; return Task.CompletedTask; }));
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<ArgumentException>(() =>
|
||||
{
|
||||
return callback.InvokeAsync(new StringBuilder());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallbackOfT_Action_Null()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
var callback = new EventCallback<UIEventArgs>(component, (Action)(() => runCount++));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(null);
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallbackOfT_Action_IgnoresArg()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
var callback = new EventCallback<UIEventArgs>(component, (Action)(() => runCount++));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(new UIEventArgs());
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallbackOfT_ActionT_Null()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
UIEventArgs arg = null;
|
||||
var callback = new EventCallback<UIEventArgs>(component, (Action<UIEventArgs>)((e) => { arg = e; runCount++; }));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(null);
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.Null(arg);
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallbackOfT_ActionT_Arg()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
UIEventArgs arg = null;
|
||||
var callback = new EventCallback<UIEventArgs>(component, (Action<UIEventArgs>)((e) => { arg = e; runCount++; }));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(new UIEventArgs());
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(arg);
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallbackOfT_FuncTask_Null()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
var callback = new EventCallback<UIEventArgs>(component, (Func<Task>)(() => { runCount++; return Task.CompletedTask; }));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(null);
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallbackOfT_FuncTask_IgnoresArg()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
var callback = new EventCallback<UIEventArgs>(component, (Func<Task>)(() => { runCount++; return Task.CompletedTask; }));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(new UIEventArgs());
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallbackOfT_FuncTTask_Null()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
UIEventArgs arg = null;
|
||||
var callback = new EventCallback<UIEventArgs>(component, (Func<UIEventArgs, Task>)((e) => { arg = e; runCount++; return Task.CompletedTask; }));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(null);
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.Null(arg);
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EventCallbackOfT_FuncTTask_Arg()
|
||||
{
|
||||
// Arrange
|
||||
var component = new EventCountingComponent();
|
||||
|
||||
int runCount = 0;
|
||||
UIEventArgs arg = null;
|
||||
var callback = new EventCallback<UIEventArgs>(component, (Func<UIEventArgs, Task>)((e) => { arg = e; runCount++; return Task.CompletedTask; }));
|
||||
|
||||
// Act
|
||||
await callback.InvokeAsync(new UIEventArgs());
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(arg);
|
||||
Assert.Equal(1, runCount);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
private class EventCountingComponent : IComponent, IHandleEvent
|
||||
{
|
||||
public int Count;
|
||||
|
||||
public Task HandleEventAsync(EventCallbackWorkItem item, object arg)
|
||||
{
|
||||
Count++;
|
||||
return item.InvokeAsync(arg);
|
||||
}
|
||||
|
||||
public void Configure(RenderHandle renderHandle) => throw new NotImplementedException();
|
||||
|
||||
public Task SetParametersAsync(ParameterCollection parameters) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<RootNamespace>Microsoft.AspNetCore.Components</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -459,6 +459,23 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
await Assert.ThrowsAsync<InvalidTimeZoneException>(async () => await task);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_Void_CanReportCancellation()
|
||||
{
|
||||
// Arrange
|
||||
var context = new RendererSynchronizationContext();
|
||||
|
||||
// Act
|
||||
var task = context.Invoke(() =>
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal(TaskStatus.Canceled, task.Status);
|
||||
await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_T_CanRunSynchronously_WhenNotBusy()
|
||||
{
|
||||
|
|
@ -530,6 +547,23 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
await Assert.ThrowsAsync<InvalidTimeZoneException>(async () => await task);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_T_CanReportCancellation()
|
||||
{
|
||||
// Arrange
|
||||
var context = new RendererSynchronizationContext();
|
||||
|
||||
// Act
|
||||
var task = context.Invoke<string>(() =>
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal(TaskStatus.Canceled, task.Status);
|
||||
await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAsync_Void_CanRunSynchronously_WhenNotBusy()
|
||||
{
|
||||
|
|
@ -607,6 +641,23 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
await Assert.ThrowsAsync<InvalidTimeZoneException>(async () => await task);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAsync_Void_CanReportCancellation()
|
||||
{
|
||||
// Arrange
|
||||
var context = new RendererSynchronizationContext();
|
||||
|
||||
// Act
|
||||
var task = context.InvokeAsync(() =>
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal(TaskStatus.Canceled, task.Status);
|
||||
await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAsync_T_CanRunSynchronously_WhenNotBusy()
|
||||
{
|
||||
|
|
@ -677,5 +728,22 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
// Assert
|
||||
await Assert.ThrowsAsync<InvalidTimeZoneException>(async () => await task);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAsync_T_CanReportCancellation()
|
||||
{
|
||||
// Arrange
|
||||
var context = new RendererSynchronizationContext();
|
||||
|
||||
// Act
|
||||
var task = context.InvokeAsync<string>(() =>
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal(TaskStatus.Canceled, task.Status);
|
||||
await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,11 +49,8 @@ namespace Microsoft.AspNetCore.Components.Test.Helpers
|
|||
public new Task RenderRootComponentAsync(int componentId, ParameterCollection parameters)
|
||||
=> InvokeAsync(() => base.RenderRootComponentAsync(componentId, parameters));
|
||||
|
||||
public new Task DispatchEventAsync(int componentId, int eventHandlerId, UIEventArgs args)
|
||||
{
|
||||
var task = InvokeAsync(() => base.DispatchEventAsync(componentId, eventHandlerId, args));
|
||||
return UnwrapTask(task);
|
||||
}
|
||||
public new Task DispatchEventAsync(int eventHandlerId, UIEventArgs args)
|
||||
=> InvokeAsync(() => base.DispatchEventAsync(eventHandlerId, args));
|
||||
|
||||
private static Task UnwrapTask(Task task)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -107,13 +107,16 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure
|
|||
{
|
||||
|
||||
}
|
||||
await Task.Delay(1000);
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
waitForStart.TimeoutAfter(Timeout).Wait(1000);
|
||||
// Wait in intervals instead of indefinitely to prevent thread starvation.
|
||||
while (!waitForStart.TimeoutAfter(Timeout).Wait(1000))
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -77,10 +77,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
decrementButton.Click();
|
||||
WaitAssert.Equal("98", () => currentCount.Text);
|
||||
|
||||
// Renders the descendant the same number of times we triggered
|
||||
// events on it, because we always re-render components after they
|
||||
// have an event
|
||||
Assert.Equal("3", Browser.FindElement(By.Id("receive-by-interface-num-renders")).Text);
|
||||
// Didn't re-render descendants
|
||||
Assert.Equal("1", Browser.FindElement(By.Id("receive-by-interface-num-renders")).Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
// 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 BasicTestApp;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
using OpenQA.Selenium;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||
{
|
||||
public class EventCallbackTest : BasicTestAppTestBase
|
||||
{
|
||||
public EventCallbackTest(
|
||||
BrowserFixture browserFixture,
|
||||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
// On WebAssembly, page reloads are expensive so skip if possible
|
||||
Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
|
||||
MountTestComponent<BasicTestApp.EventCallbackTest.EventCallbackCases>();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("capturing_lambda")]
|
||||
[InlineData("unbound_lambda")]
|
||||
[InlineData("unbound_lambda_nested")]
|
||||
[InlineData("unbound_lambda_strongly_typed")]
|
||||
[InlineData("unbound_lambda_child_content")]
|
||||
[InlineData("unbound_lambda_bind_to_component")]
|
||||
public void EventCallback_RerendersOuterComponent(string @case)
|
||||
{
|
||||
var target = Browser.FindElement(By.CssSelector($"#{@case} button"));
|
||||
var count = Browser.FindElement(By.Id("render_count"));
|
||||
Assert.Equal("Render Count: 1", count.Text);
|
||||
target.Click();
|
||||
Assert.Equal("Render Count: 2", count.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
|
||||
<p><button id="increment-count" onclick=@counterState.IncrementCount>Increment</button></p>
|
||||
<p><button id="increment-count" onclick="@((args) => counterState.IncrementCount())">Increment</button></p>
|
||||
<p><label><input type="checkbox" id="toggle-flag-1" bind=currentFlagValue1 /> Flag 1</label></p>
|
||||
<p><label><input type="checkbox" id="toggle-flag-2" bind=currentFlagValue2 /> Flag 2</label></p>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
<button onclick="@OnClick">@Text</button>
|
||||
|
||||
@functions {
|
||||
[Parameter] int Count { get; set; }
|
||||
[Parameter] EventCallback<int> CountChanged { get; set; }
|
||||
[Parameter] string Text { get; set; }
|
||||
|
||||
Task OnClick(UIMouseEventArgs e)
|
||||
{
|
||||
Count++;
|
||||
return CountChanged.InvokeAsync(Count);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
@*
|
||||
Test cases for using EventCallback with various delegate scenarios that used to be troublesome.
|
||||
|
||||
Currently these cases are **VERBOSE** because we haven't yet landed the compiler support for EventCallback.
|
||||
This will be cleaned up soon, and all of the explicit calls to EventCallback.Factory will go away.
|
||||
*@
|
||||
<div>
|
||||
<p>Clicking any of these buttons should cause the count to go up by one!</p>
|
||||
<p id="render_count">Render Count: @(++renderCount)</p>
|
||||
</div>
|
||||
<div id="capturing_lambda">
|
||||
<h3>Passing Capturing Lambda to Button</h3>
|
||||
<InnerButton OnClick="@EventCallback.Factory.Create(this, () => { GC.KeepAlive(this); })" Text="Capturing Lambda" />
|
||||
</div>
|
||||
<div id="unbound_lambda">
|
||||
<h3>Passing Unbound Lambda to Button</h3>
|
||||
<InnerButton OnClick="@EventCallback.Factory.Create(this, () => { })" Text="Unbound Lambda" />
|
||||
</div>
|
||||
<div id="unbound_lambda_nested">
|
||||
<h3>Passing Unbound Lambda to Nested Button</h3>
|
||||
<MiddleButton OnClick="@EventCallback.Factory.Create(this, () => { })" Text="Unbound Lambda Nested" />
|
||||
</div>
|
||||
<div id="unbound_lambda_strongly_typed">
|
||||
<h3>Passing Capturing Lambda to Strongly Typed Button</h3>
|
||||
<StronglyTypedButton OnClick="@(EventCallback.Factory.Create<UIMouseEventArgs>(this, () => { GC.KeepAlive(this); }))" Text="Unbound Lambda Strongly-Typed" />
|
||||
</div>
|
||||
<div id="unbound_lambda_child_content">
|
||||
<h3>Passing Child Content</h3>
|
||||
<TemplatedControl>
|
||||
<button onclick="@EventCallback.Factory.Create(this, () => { })">Unbound Lambda Child Content</button>
|
||||
</TemplatedControl>
|
||||
</div>
|
||||
<div id="unbound_lambda_bind_to_component">
|
||||
<h3>Passing Child Content</h3>
|
||||
<ButtonComponent Count="@buttonComponentCount" CountChanged="@(EventCallback.Factory.Create<int>(this, () => { }))" Text="Unbound Lambda Bind-To-Component" />
|
||||
</div>
|
||||
|
||||
@functions {
|
||||
int renderCount;
|
||||
|
||||
int buttonComponentCount = 1; // Avoid CS0649
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
<button onclick="@OnClick">@Text</button>
|
||||
|
||||
@functions {
|
||||
[Parameter] EventCallback OnClick { get; set; }
|
||||
[Parameter] string Text { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
<InnerButton OnClick="@OnClick" Text="@Text"/>
|
||||
|
||||
@functions {
|
||||
[Parameter] EventCallback OnClick { get; set; }
|
||||
[Parameter] string Text { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
<button onclick="@OnClick">@Text</button>
|
||||
|
||||
@functions {
|
||||
[Parameter] EventCallback<UIMouseEventArgs> OnClick { get; set; }
|
||||
[Parameter] string Text { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
<div>
|
||||
@ChildContent
|
||||
</div>
|
||||
|
||||
@functions {
|
||||
[Parameter] RenderFragment ChildContent { get; set; }
|
||||
}
|
||||
|
|
@ -45,6 +45,7 @@
|
|||
<option value="BasicTestApp.CascadingValueTest.CascadingValueSupplier">Cascading values</option>
|
||||
<option value="BasicTestApp.ConcurrentRenderParent">Concurrent rendering</option>
|
||||
<option value="BasicTestApp.DispatchingComponent">Dispatching to sync context</option>
|
||||
<option value="BasicTestApp.EventCallbackTest.EventCallbackCases">EventCallback</option>
|
||||
</select>
|
||||
|
||||
@if (SelectedComponentType != null)
|
||||
|
|
|
|||
Loading…
Reference in New Issue