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)
|
protected RenderTreeFrame[] GetRenderTree(IComponent component)
|
||||||
{
|
{
|
||||||
var renderer = new TestRenderer();
|
var renderer = new TestRenderer();
|
||||||
|
return GetRenderTree(renderer, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected private RenderTreeFrame[] GetRenderTree(TestRenderer renderer, IComponent component)
|
||||||
|
{
|
||||||
renderer.AttachComponent(component);
|
renderer.AttachComponent(component);
|
||||||
var task = renderer.InvokeAsync(() => component.SetParametersAsync(ParameterCollection.Empty));
|
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.
|
// 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; }
|
public IEnumerable<Diagnostic> Diagnostics { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestRenderer : Renderer
|
protected class TestRenderer : Renderer
|
||||||
{
|
{
|
||||||
public TestRenderer() : base(new TestServiceProvider(), CreateDefaultDispatcher())
|
public TestRenderer() : base(new TestServiceProvider(), CreateDefaultDispatcher())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.AspNetCore.Components.RenderTree;
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
using Microsoft.AspNetCore.Components.Test.Helpers;
|
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||||
|
|
@ -351,7 +352,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SupportsTwoWayBindingForTextboxes()
|
public async Task SupportsTwoWayBindingForTextboxes()
|
||||||
{
|
{
|
||||||
// Arrange/Act
|
// Arrange/Act
|
||||||
var component = CompileToComponent(
|
var component = CompileToComponent(
|
||||||
|
|
@ -361,26 +362,27 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
}");
|
}");
|
||||||
var myValueProperty = component.GetType().GetProperty("MyValue");
|
var myValueProperty = component.GetType().GetProperty("MyValue");
|
||||||
|
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var frames = GetRenderTree(component);
|
Action<UIEventArgs> setter = null;
|
||||||
|
var frames = GetRenderTree(renderer, component);
|
||||||
Assert.Collection(frames,
|
Assert.Collection(frames,
|
||||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||||
frame => AssertFrame.Attribute(frame, "value", "Initial value", 1),
|
frame => AssertFrame.Attribute(frame, "value", "Initial value", 1),
|
||||||
frame =>
|
frame =>
|
||||||
{
|
{
|
||||||
AssertFrame.Attribute(frame, "onchange", 2);
|
AssertFrame.Attribute(frame, "onchange", 2);
|
||||||
|
setter = Assert.IsType<Action<UIEventArgs>>(frame.AttributeValue);
|
||||||
// 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));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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]
|
[Fact]
|
||||||
public void SupportsTwoWayBindingForTextareas()
|
public async Task SupportsTwoWayBindingForTextareas()
|
||||||
{
|
{
|
||||||
// Arrange/Act
|
// Arrange/Act
|
||||||
var component = CompileToComponent(
|
var component = CompileToComponent(
|
||||||
|
|
@ -390,26 +392,27 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
}");
|
}");
|
||||||
var myValueProperty = component.GetType().GetProperty("MyValue");
|
var myValueProperty = component.GetType().GetProperty("MyValue");
|
||||||
|
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var frames = GetRenderTree(component);
|
Action<UIEventArgs> setter = null;
|
||||||
|
var frames = GetRenderTree(renderer, component);
|
||||||
Assert.Collection(frames,
|
Assert.Collection(frames,
|
||||||
frame => AssertFrame.Element(frame, "textarea", 3, 0),
|
frame => AssertFrame.Element(frame, "textarea", 3, 0),
|
||||||
frame => AssertFrame.Attribute(frame, "value", "Initial value", 1),
|
frame => AssertFrame.Attribute(frame, "value", "Initial value", 1),
|
||||||
frame =>
|
frame =>
|
||||||
{
|
{
|
||||||
AssertFrame.Attribute(frame, "onchange", 2);
|
AssertFrame.Attribute(frame, "onchange", 2);
|
||||||
|
setter = Assert.IsType<Action<UIEventArgs>>(frame.AttributeValue);
|
||||||
// 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));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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]
|
[Fact]
|
||||||
public void SupportsTwoWayBindingForDateValues()
|
public async Task SupportsTwoWayBindingForDateValues()
|
||||||
{
|
{
|
||||||
// Arrange/Act
|
// Arrange/Act
|
||||||
var component = CompileToComponent(
|
var component = CompileToComponent(
|
||||||
|
|
@ -419,27 +422,28 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
}");
|
}");
|
||||||
var myDateProperty = component.GetType().GetProperty("MyDate");
|
var myDateProperty = component.GetType().GetProperty("MyDate");
|
||||||
|
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var frames = GetRenderTree(component);
|
Action<UIEventArgs> setter = null;
|
||||||
|
var frames = GetRenderTree(renderer, component);
|
||||||
Assert.Collection(frames,
|
Assert.Collection(frames,
|
||||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
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, "value", new DateTime(2018, 3, 4, 1, 2, 3).ToString(), 1),
|
||||||
frame =>
|
frame =>
|
||||||
{
|
{
|
||||||
AssertFrame.Attribute(frame, "onchange", 2);
|
AssertFrame.Attribute(frame, "onchange", 2);
|
||||||
|
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);
|
|
||||||
((Action<UIEventArgs>)frame.AttributeValue)(new UIChangeEventArgs
|
|
||||||
{
|
|
||||||
Value = newDateValue.ToString()
|
|
||||||
});
|
|
||||||
Assert.Equal(newDateValue, myDateProperty.GetValue(component));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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]
|
[Fact]
|
||||||
public void SupportsTwoWayBindingForDateValuesWithFormatString()
|
public async Task SupportsTwoWayBindingForDateValuesWithFormatString()
|
||||||
{
|
{
|
||||||
// Arrange/Act
|
// Arrange/Act
|
||||||
var testDateFormat = "ddd yyyy-MM-dd";
|
var testDateFormat = "ddd yyyy-MM-dd";
|
||||||
|
|
@ -450,22 +454,23 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
}}");
|
}}");
|
||||||
var myDateProperty = component.GetType().GetProperty("MyDate");
|
var myDateProperty = component.GetType().GetProperty("MyDate");
|
||||||
|
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var frames = GetRenderTree(component);
|
Action<UIEventArgs> setter = null;
|
||||||
|
var frames = GetRenderTree(renderer, component);
|
||||||
Assert.Collection(frames,
|
Assert.Collection(frames,
|
||||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||||
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 3, 4).ToString(testDateFormat), 1),
|
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 3, 4).ToString(testDateFormat), 1),
|
||||||
frame =>
|
frame =>
|
||||||
{
|
{
|
||||||
AssertFrame.Attribute(frame, "onchange", 2);
|
AssertFrame.Attribute(frame, "onchange", 2);
|
||||||
|
setter = Assert.IsType<Action<UIEventArgs>>(frame.AttributeValue);
|
||||||
// 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));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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
|
[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 clicked = component.GetType().GetProperty("Clicked");
|
||||||
|
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var frames = GetRenderTree(component);
|
var frames = GetRenderTree(renderer, component);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(frames,
|
Assert.Collection(frames,
|
||||||
|
|
@ -527,8 +534,10 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
|
|
||||||
var clicked = component.GetType().GetProperty("Clicked");
|
var clicked = component.GetType().GetProperty("Clicked");
|
||||||
|
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var frames = GetRenderTree(component);
|
var frames = GetRenderTree(renderer, component);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(frames,
|
Assert.Collection(frames,
|
||||||
|
|
@ -546,7 +555,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SupportsTwoWayBindingForBoolValues()
|
public async Task SupportsTwoWayBindingForBoolValues()
|
||||||
{
|
{
|
||||||
// Arrange/Act
|
// Arrange/Act
|
||||||
var component = CompileToComponent(
|
var component = CompileToComponent(
|
||||||
|
|
@ -556,26 +565,27 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
}");
|
}");
|
||||||
var myValueProperty = component.GetType().GetProperty("MyValue");
|
var myValueProperty = component.GetType().GetProperty("MyValue");
|
||||||
|
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var frames = GetRenderTree(component);
|
Action<UIEventArgs> setter = null;
|
||||||
|
var frames = GetRenderTree(renderer, component);
|
||||||
Assert.Collection(frames,
|
Assert.Collection(frames,
|
||||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||||
frame => AssertFrame.Attribute(frame, "value", true, 1),
|
frame => AssertFrame.Attribute(frame, "value", true, 1),
|
||||||
frame =>
|
frame =>
|
||||||
{
|
{
|
||||||
AssertFrame.Attribute(frame, "onchange", 2);
|
AssertFrame.Attribute(frame, "onchange", 2);
|
||||||
|
setter = Assert.IsType<Action<UIEventArgs>>(frame.AttributeValue);
|
||||||
// Trigger the change event to show it updates the property
|
|
||||||
((Action<UIEventArgs>)frame.AttributeValue)(new UIChangeEventArgs
|
|
||||||
{
|
|
||||||
Value = false
|
|
||||||
});
|
|
||||||
Assert.False((bool)myValueProperty.GetValue(component));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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]
|
[Fact]
|
||||||
public void SupportsTwoWayBindingForEnumValues()
|
public async Task SupportsTwoWayBindingForEnumValues()
|
||||||
{
|
{
|
||||||
// Arrange/Act
|
// Arrange/Act
|
||||||
var myEnumType = FullTypeName<MyEnum>();
|
var myEnumType = FullTypeName<MyEnum>();
|
||||||
|
|
@ -586,22 +596,23 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
}}");
|
}}");
|
||||||
var myValueProperty = component.GetType().GetProperty("MyValue");
|
var myValueProperty = component.GetType().GetProperty("MyValue");
|
||||||
|
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var frames = GetRenderTree(component);
|
Action<UIEventArgs> setter = null;
|
||||||
|
var frames = GetRenderTree(renderer, component);
|
||||||
Assert.Collection(frames,
|
Assert.Collection(frames,
|
||||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||||
frame => AssertFrame.Attribute(frame, "value", MyEnum.FirstValue.ToString(), 1),
|
frame => AssertFrame.Attribute(frame, "value", MyEnum.FirstValue.ToString(), 1),
|
||||||
frame =>
|
frame =>
|
||||||
{
|
{
|
||||||
AssertFrame.Attribute(frame, "onchange", 2);
|
AssertFrame.Attribute(frame, "onchange", 2);
|
||||||
|
setter = Assert.IsType<Action<UIEventArgs>>(frame.AttributeValue);
|
||||||
// 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));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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 }
|
public enum MyEnum { FirstValue, SecondValue }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import { System_Array, MethodHandle } from '../Platform/Platform';
|
import { RenderBatch, ArraySegment, RenderTreeEdit, RenderTreeFrame, EditType, FrameType, ArrayValues } from './RenderBatch/RenderBatch';
|
||||||
import { RenderBatch, ArraySegment, ArrayRange, RenderTreeEdit, RenderTreeFrame, EditType, FrameType, ArrayValues } from './RenderBatch/RenderBatch';
|
|
||||||
import { platform } from '../Environment';
|
|
||||||
import { EventDelegator } from './EventDelegator';
|
import { EventDelegator } from './EventDelegator';
|
||||||
import { EventForDotNet, UIEventArgs } from './EventForDotNet';
|
import { EventForDotNet, UIEventArgs } from './EventForDotNet';
|
||||||
import { LogicalElement, toLogicalElement, insertLogicalChild, removeLogicalChild, getLogicalParent, getLogicalChild, createAndInsertLogicalContainer, isSvgElement } from './LogicalElements';
|
import { LogicalElement, toLogicalElement, insertLogicalChild, removeLogicalChild, getLogicalParent, getLogicalChild, createAndInsertLogicalContainer, isSvgElement } from './LogicalElements';
|
||||||
|
|
@ -9,16 +7,14 @@ const selectValuePropname = '_blazorSelectValue';
|
||||||
const sharedTemplateElemForParsing = document.createElement('template');
|
const sharedTemplateElemForParsing = document.createElement('template');
|
||||||
const sharedSvgElemForParsing = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
const sharedSvgElemForParsing = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
||||||
const preventDefaultEvents: { [eventType: string]: boolean } = { submit: true };
|
const preventDefaultEvents: { [eventType: string]: boolean } = { submit: true };
|
||||||
let raiseEventMethod: MethodHandle;
|
|
||||||
let renderComponentMethod: MethodHandle;
|
|
||||||
|
|
||||||
export class BrowserRenderer {
|
export class BrowserRenderer {
|
||||||
private eventDelegator: EventDelegator;
|
private eventDelegator: EventDelegator;
|
||||||
private childComponentLocations: { [componentId: number]: LogicalElement } = {};
|
private childComponentLocations: { [componentId: number]: LogicalElement } = {};
|
||||||
|
|
||||||
constructor(private browserRendererId: number) {
|
constructor(private browserRendererId: number) {
|
||||||
this.eventDelegator = new EventDelegator((event, componentId, eventHandlerId, eventArgs) => {
|
this.eventDelegator = new EventDelegator((event, eventHandlerId, eventArgs) => {
|
||||||
raiseEvent(event, this.browserRendererId, componentId, 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}`);
|
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) {
|
public disposeComponent(componentId: number) {
|
||||||
|
|
@ -47,7 +43,7 @@ export class BrowserRenderer {
|
||||||
this.childComponentLocations[componentId] = element;
|
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 currentDepth = 0;
|
||||||
let childIndexAtCurrentDepth = childIndex;
|
let childIndexAtCurrentDepth = childIndex;
|
||||||
|
|
||||||
|
|
@ -67,7 +63,7 @@ export class BrowserRenderer {
|
||||||
const frameIndex = editReader.newTreeIndex(edit);
|
const frameIndex = editReader.newTreeIndex(edit);
|
||||||
const frame = batch.referenceFramesEntry(referenceFrames, frameIndex);
|
const frame = batch.referenceFramesEntry(referenceFrames, frameIndex);
|
||||||
const siblingIndex = editReader.siblingIndex(edit);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case EditType.removeFrame: {
|
case EditType.removeFrame: {
|
||||||
|
|
@ -81,7 +77,7 @@ export class BrowserRenderer {
|
||||||
const siblingIndex = editReader.siblingIndex(edit);
|
const siblingIndex = editReader.siblingIndex(edit);
|
||||||
const element = getLogicalChild(parent, childIndexAtCurrentDepth + siblingIndex);
|
const element = getLogicalChild(parent, childIndexAtCurrentDepth + siblingIndex);
|
||||||
if (element instanceof Element) {
|
if (element instanceof Element) {
|
||||||
this.applyAttribute(batch, componentId, element, frame);
|
this.applyAttribute(batch, element, frame);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Cannot set attribute on non-element child`);
|
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 frameReader = batch.frameReader;
|
||||||
const frameType = frameReader.frameType(frame);
|
const frameType = frameReader.frameType(frame);
|
||||||
switch (frameType) {
|
switch (frameType) {
|
||||||
case FrameType.element:
|
case FrameType.element:
|
||||||
this.insertElement(batch, componentId, parent, childIndex, frames, frame, frameIndex);
|
this.insertElement(batch, parent, childIndex, frames, frame, frameIndex);
|
||||||
return 1;
|
return 1;
|
||||||
case FrameType.text:
|
case FrameType.text:
|
||||||
this.insertText(batch, parent, childIndex, frame);
|
this.insertText(batch, parent, childIndex, frame);
|
||||||
|
|
@ -161,7 +157,7 @@ export class BrowserRenderer {
|
||||||
this.insertComponent(batch, parent, childIndex, frame);
|
this.insertComponent(batch, parent, childIndex, frame);
|
||||||
return 1;
|
return 1;
|
||||||
case FrameType.region:
|
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:
|
case FrameType.elementReferenceCapture:
|
||||||
if (parent instanceof Element) {
|
if (parent instanceof Element) {
|
||||||
applyCaptureIdToElement(parent, frameReader.elementReferenceCaptureId(frame)!);
|
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 frameReader = batch.frameReader;
|
||||||
const tagName = frameReader.elementName(frame)!;
|
const tagName = frameReader.elementName(frame)!;
|
||||||
const newDomElementRaw = tagName === 'svg' || isSvgElement(parent) ?
|
const newDomElementRaw = tagName === 'svg' || isSvgElement(parent) ?
|
||||||
|
|
@ -192,11 +188,11 @@ export class BrowserRenderer {
|
||||||
for (let descendantIndex = frameIndex + 1; descendantIndex < descendantsEndIndexExcl; descendantIndex++) {
|
for (let descendantIndex = frameIndex + 1; descendantIndex < descendantsEndIndexExcl; descendantIndex++) {
|
||||||
const descendantFrame = batch.referenceFramesEntry(frames, descendantIndex);
|
const descendantFrame = batch.referenceFramesEntry(frames, descendantIndex);
|
||||||
if (frameReader.frameType(descendantFrame) === FrameType.attribute) {
|
if (frameReader.frameType(descendantFrame) === FrameType.attribute) {
|
||||||
this.applyAttribute(batch, componentId, newDomElementRaw, descendantFrame);
|
this.applyAttribute(batch, newDomElementRaw, descendantFrame);
|
||||||
} else {
|
} else {
|
||||||
// As soon as we see a non-attribute child, all the subsequent child frames are
|
// As soon as we see a non-attribute child, all the subsequent child frames are
|
||||||
// not attributes, so bail out and insert the remnants recursively
|
// not attributes, so bail out and insert the remnants recursively
|
||||||
this.insertFrameRange(batch, componentId, newElement, 0, frames, descendantIndex, descendantsEndIndexExcl);
|
this.insertFrameRange(batch, newElement, 0, frames, descendantIndex, descendantsEndIndexExcl);
|
||||||
break;
|
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 frameReader = batch.frameReader;
|
||||||
const attributeName = frameReader.attributeName(attributeFrame)!;
|
const attributeName = frameReader.attributeName(attributeFrame)!;
|
||||||
const browserRendererId = this.browserRendererId;
|
const browserRendererId = this.browserRendererId;
|
||||||
|
|
@ -240,7 +236,7 @@ export class BrowserRenderer {
|
||||||
if (firstTwoChars !== 'on' || !eventName) {
|
if (firstTwoChars !== 'on' || !eventName) {
|
||||||
throw new Error(`Attribute has nonzero event handler ID, but attribute name '${attributeName}' does not start with 'on'.`);
|
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;
|
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;
|
const origChildIndex = childIndex;
|
||||||
for (let index = startIndex; index < endIndexExcl; index++) {
|
for (let index = startIndex; index < endIndexExcl; index++) {
|
||||||
const frame = batch.referenceFramesEntry(frames, 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;
|
childIndex += numChildrenInserted;
|
||||||
|
|
||||||
// Skip over any descendants, since they are already dealt with recursively
|
// 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]) {
|
if (preventDefaultEvents[event.type]) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventDescriptor = {
|
const eventDescriptor = {
|
||||||
browserRendererId,
|
browserRendererId,
|
||||||
componentId,
|
|
||||||
eventHandlerId,
|
eventHandlerId,
|
||||||
eventArgsType: eventArgs.type
|
eventArgsType: eventArgs.type
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ const nonBubblingEvents = toLookup([
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export interface OnEventCallback {
|
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
|
// 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));
|
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
|
// Ensure we have a place to store event info for this element
|
||||||
let infoForElement: EventHandlerInfosForElement = element[this.eventsCollectionKey];
|
let infoForElement: EventHandlerInfosForElement = element[this.eventsCollectionKey];
|
||||||
if (!infoForElement) {
|
if (!infoForElement) {
|
||||||
|
|
@ -36,7 +36,7 @@ export class EventDelegator {
|
||||||
this.eventInfoStore.update(oldInfo.eventHandlerId, eventHandlerId);
|
this.eventInfoStore.update(oldInfo.eventHandlerId, eventHandlerId);
|
||||||
} else {
|
} else {
|
||||||
// Go through the whole flow which might involve registering a new global handler
|
// 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);
|
this.eventInfoStore.add(newInfo);
|
||||||
infoForElement[eventName] = newInfo;
|
infoForElement[eventName] = newInfo;
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +80,7 @@ export class EventDelegator {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlerInfo = handlerInfos[evt.type];
|
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 {
|
interface EventHandlerInfo {
|
||||||
element: Element;
|
element: Element;
|
||||||
eventName: string;
|
eventName: string;
|
||||||
componentId: number;
|
|
||||||
eventHandlerId: number;
|
eventHandlerId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,7 @@ namespace Microsoft.AspNetCore.Components.Browser
|
||||||
{
|
{
|
||||||
var eventArgs = ParseEventArgsJson(eventDescriptor.EventArgsType, eventArgsJson);
|
var eventArgs = ParseEventArgsJson(eventDescriptor.EventArgsType, eventArgsJson);
|
||||||
var renderer = RendererRegistry.Current.Find(eventDescriptor.BrowserRendererId);
|
var renderer = RendererRegistry.Current.Find(eventDescriptor.BrowserRendererId);
|
||||||
|
return renderer.DispatchEventAsync(eventDescriptor.EventHandlerId, eventArgs);
|
||||||
return renderer.DispatchEventAsync(
|
|
||||||
eventDescriptor.ComponentId,
|
|
||||||
eventDescriptor.EventHandlerId,
|
|
||||||
eventArgs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UIEventArgs ParseEventArgsJson(string eventArgsType, string eventArgsJson)
|
private static UIEventArgs ParseEventArgsJson(string eventArgsType, string eventArgsJson)
|
||||||
|
|
@ -72,11 +68,6 @@ namespace Microsoft.AspNetCore.Components.Browser
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int BrowserRendererId { get; set; }
|
public int BrowserRendererId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For framework use only.
|
|
||||||
/// </summary>
|
|
||||||
public int ComponentId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// For framework use only.
|
/// For framework use only.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -69,12 +69,34 @@ namespace Microsoft.AspNetCore.Components
|
||||||
return value;
|
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>
|
/// <summary>
|
||||||
/// Not intended to be used directly.
|
/// Not intended to be used directly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Action<UIEventArgs> SetValueHandler(Action<string> setter, string existingValue)
|
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>
|
/// <summary>
|
||||||
|
|
@ -82,7 +104,11 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Action<UIEventArgs> SetValueHandler(Action<bool> setter, bool existingValue)
|
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>
|
/// <summary>
|
||||||
|
|
@ -90,7 +116,11 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Action<UIEventArgs> SetValueHandler(Action<bool?> setter, bool? existingValue)
|
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>
|
/// <summary>
|
||||||
|
|
@ -98,7 +128,11 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Action<UIEventArgs> SetValueHandler(Action<int> setter, int existingValue)
|
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>
|
/// <summary>
|
||||||
|
|
@ -106,9 +140,11 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Action<UIEventArgs> SetValueHandler(Action<int?> setter, int? existingValue)
|
public static Action<UIEventArgs> SetValueHandler(Action<int?> setter, int? existingValue)
|
||||||
{
|
{
|
||||||
return _ => setter(int.TryParse((string)((UIChangeEventArgs)_).Value, out var tmpvalue)
|
return eventArgs =>
|
||||||
? tmpvalue
|
{
|
||||||
: (int?)null);
|
setter(int.TryParse((string)((UIChangeEventArgs)eventArgs).Value, out var value) ? value : (int?)null);
|
||||||
|
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -116,7 +152,11 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Action<UIEventArgs> SetValueHandler(Action<long> setter, long existingValue)
|
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>
|
/// <summary>
|
||||||
|
|
@ -124,9 +164,11 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Action<UIEventArgs> SetValueHandler(Action<long?> setter, long? existingValue)
|
public static Action<UIEventArgs> SetValueHandler(Action<long?> setter, long? existingValue)
|
||||||
{
|
{
|
||||||
return _ => setter(long.TryParse((string)((UIChangeEventArgs)_).Value, out var tmpvalue)
|
return eventArgs =>
|
||||||
? tmpvalue
|
{
|
||||||
: (long?)null);
|
setter(long.TryParse((string)((UIChangeEventArgs)eventArgs).Value, out var value) ? value : (long?)null);
|
||||||
|
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -134,7 +176,11 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Action<UIEventArgs> SetValueHandler(Action<float> setter, float existingValue)
|
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>
|
/// <summary>
|
||||||
|
|
@ -142,9 +188,11 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Action<UIEventArgs> SetValueHandler(Action<float?> setter, float? existingValue)
|
public static Action<UIEventArgs> SetValueHandler(Action<float?> setter, float? existingValue)
|
||||||
{
|
{
|
||||||
return _ => setter(float.TryParse((string)((UIChangeEventArgs)_).Value, out var tmpvalue)
|
return eventArgs =>
|
||||||
? tmpvalue
|
{
|
||||||
: (float?)null);
|
setter(float.TryParse((string)((UIChangeEventArgs)eventArgs).Value, out var value) ? value : (float?)null);
|
||||||
|
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -152,7 +200,11 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Action<UIEventArgs> SetValueHandler(Action<double> setter, double existingValue)
|
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>
|
/// <summary>
|
||||||
|
|
@ -160,9 +212,11 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Action<UIEventArgs> SetValueHandler(Action<double?> setter, double? existingValue)
|
public static Action<UIEventArgs> SetValueHandler(Action<double?> setter, double? existingValue)
|
||||||
{
|
{
|
||||||
return _ => setter(double.TryParse((string)((UIChangeEventArgs)_).Value, out var tmpvalue)
|
return eventArgs =>
|
||||||
? tmpvalue
|
{
|
||||||
: (double?)null);
|
setter(double.TryParse((string)((UIChangeEventArgs)eventArgs).Value, out var value) ? value : (double?)null);
|
||||||
|
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -170,7 +224,11 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Action<UIEventArgs> SetValueHandler(Action<decimal> setter, decimal existingValue)
|
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>
|
/// <summary>
|
||||||
|
|
@ -178,9 +236,11 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Action<UIEventArgs> SetValueHandler(Action<decimal?> setter, decimal? existingValue)
|
public static Action<UIEventArgs> SetValueHandler(Action<decimal?> setter, decimal? existingValue)
|
||||||
{
|
{
|
||||||
return _ => setter(decimal.TryParse((string)((UIChangeEventArgs)_).Value, out var tmpvalue)
|
return eventArgs =>
|
||||||
? tmpvalue
|
{
|
||||||
: (decimal?)null);
|
setter(decimal.TryParse((string)((UIChangeEventArgs)eventArgs).Value, out var tmpvalue) ? tmpvalue : (decimal?)null);
|
||||||
|
_ = DispatchEventAsync(setter.Target, EventCallbackWorkItem.Empty, UIEventArgs.Empty);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -188,7 +248,10 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Action<UIEventArgs> SetValueHandler(Action<DateTime> setter, DateTime existingValue)
|
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>
|
/// <summary>
|
||||||
|
|
@ -196,7 +259,10 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Action<UIEventArgs> SetValueHandler(Action<DateTime> setter, DateTime existingValue, string format)
|
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>
|
/// <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.");
|
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);
|
var parsed = (T)Enum.Parse(typeof(T), value);
|
||||||
setter(parsed);
|
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
|
: format != null && DateTime.TryParseExact(stringValue, format, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsedExact) ? parsedExact
|
||||||
: DateTime.Parse(stringValue);
|
: DateTime.Parse(stringValue);
|
||||||
setter(parsedValue);
|
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>
|
/// <summary>
|
||||||
/// Method invoked when the component is ready to start, having received its
|
/// Method invoked when the component is ready to start, having received its
|
||||||
/// initial parameters from its parent in the render tree.
|
/// initial parameters from its parent in the render tree.
|
||||||
///
|
///
|
||||||
/// Override this method if you will perform an asynchronous operation and
|
/// Override this method if you will perform an asynchronous operation and
|
||||||
/// want the component to refresh when that operation is completed.
|
/// want the component to refresh when that operation is completed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -210,8 +210,8 @@ namespace Microsoft.AspNetCore.Components
|
||||||
catch when (task.IsCanceled)
|
catch when (task.IsCanceled)
|
||||||
{
|
{
|
||||||
// Ignore exceptions from task cancelletions.
|
// Ignore exceptions from task cancelletions.
|
||||||
// Awaiting a canceled task may produce either an OperationCanceledException (if produced as a consequence of
|
// 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).
|
// 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.
|
// 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();
|
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 &&
|
var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
|
||||||
task.Status != TaskStatus.Canceled;
|
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
|
namespace Microsoft.AspNetCore.Components
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface implemented by components that receive notification of their events.
|
/// Interface implemented by components that receive notification of state changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IHandleEvent
|
public interface IHandleEvent
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies the component that one of its event handlers has been triggered.
|
/// Notifies the a state change has been triggered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="binding">The event binding.</param>
|
/// <param name="item">The <see cref="EventCallbackWorkItem"/> associated with this event.</param>
|
||||||
/// <param name="args">Arguments for the event handler.</param>
|
/// <param name="arg">The argument associated with this event.</param>
|
||||||
/// <returns>A <see cref="Task"/> that represents the asynchronous event handling operation.</returns>
|
/// <returns>
|
||||||
Task HandleEventAsync(EventHandlerInvoker binding, UIEventArgs args);
|
/// 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>
|
/// <summary>
|
||||||
/// Appends a frame representing a string-valued attribute.
|
/// 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
|
/// 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
|
// We're following a simple heuristic here that's reflected in the ts runtime
|
||||||
// based on the common usage of attributes for DOM events.
|
// 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.Length >= 3 &&
|
||||||
newFrame.AttributeName.StartsWith("on"))
|
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()
|
public Task NotifyRenderCompletedAsync()
|
||||||
{
|
{
|
||||||
if (Component is IHandleAfterRender handlerAfterRender)
|
if (Component is IHandleAfterRender handlerAfterRender)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
private readonly ComponentFactory _componentFactory;
|
private readonly ComponentFactory _componentFactory;
|
||||||
private readonly Dictionary<int, ComponentState> _componentStateById = new Dictionary<int, ComponentState>();
|
private readonly Dictionary<int, ComponentState> _componentStateById = new Dictionary<int, ComponentState>();
|
||||||
private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder();
|
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 IDispatcher _dispatcher;
|
||||||
|
|
||||||
private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it
|
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);
|
protected abstract Task UpdateDisplayAsync(in RenderBatch renderBatch);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies the specified component that an event has occurred.
|
/// Notifies the renderer that an event has occurred.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="componentId">The unique identifier for the component within the scope of this <see cref="Renderer"/>.</param>
|
|
||||||
/// <param name="eventHandlerId">The <see cref="RenderTreeFrame.AttributeEventHandlerId"/> value from the original event attribute.</param>
|
/// <param name="eventHandlerId">The <see cref="RenderTreeFrame.AttributeEventHandlerId"/> value from the original event attribute.</param>
|
||||||
/// <param name="eventArgs">Arguments to be passed to the event handler.</param>
|
/// <param name="eventArgs">Arguments to be passed to the event handler.</param>
|
||||||
/// <returns>A <see cref="Task"/> representing the asynchronous execution operation.</returns>
|
/// <returns>
|
||||||
public Task DispatchEventAsync(int componentId, int eventHandlerId, UIEventArgs eventArgs)
|
/// 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();
|
EnsureSynchronizationContext();
|
||||||
|
|
||||||
if (_eventBindings.TryGetValue(eventHandlerId, out var binding))
|
if (!_eventBindings.TryGetValue(eventHandlerId, out var callback))
|
||||||
{
|
|
||||||
// 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
|
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"There is no event handler with ID {eventHandlerId}");
|
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>
|
/// <summary>
|
||||||
|
|
@ -338,10 +343,27 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
{
|
{
|
||||||
var id = ++_lastEventHandlerId;
|
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);
|
frame = frame.WithAttributeEventHandlerId(id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,10 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
action();
|
action();
|
||||||
completion.SetResult(null);
|
completion.SetResult(null);
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
completion.SetCanceled();
|
||||||
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
completion.SetException(exception);
|
completion.SetException(exception);
|
||||||
|
|
@ -66,6 +70,10 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
await asyncAction();
|
await asyncAction();
|
||||||
completion.SetResult(null);
|
completion.SetResult(null);
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
completion.SetCanceled();
|
||||||
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
completion.SetException(exception);
|
completion.SetException(exception);
|
||||||
|
|
@ -85,6 +93,10 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
var result = function();
|
var result = function();
|
||||||
completion.SetResult(result);
|
completion.SetResult(result);
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
completion.SetCanceled();
|
||||||
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
completion.SetException(exception);
|
completion.SetException(exception);
|
||||||
|
|
@ -104,6 +116,10 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
var result = await asyncFunction();
|
var result = await asyncFunction();
|
||||||
completion.SetResult(result);
|
completion.SetResult(result);
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
completion.SetCanceled();
|
||||||
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
completion.SetException(exception);
|
completion.SetException(exception);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,11 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class UIEventArgs
|
public class UIEventArgs
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An empty instance of <see cref="UIEventArgs"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly UIEventArgs Empty = new UIEventArgs();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type of the event.
|
/// Gets or sets the type of the event.
|
||||||
/// </summary>
|
/// </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>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
|
<RootNamespace>Microsoft.AspNetCore.Components</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<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);
|
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]
|
[Fact]
|
||||||
public async Task Invoke_T_CanRunSynchronously_WhenNotBusy()
|
public async Task Invoke_T_CanRunSynchronously_WhenNotBusy()
|
||||||
{
|
{
|
||||||
|
|
@ -530,6 +547,23 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
await Assert.ThrowsAsync<InvalidTimeZoneException>(async () => await task);
|
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]
|
[Fact]
|
||||||
public async Task InvokeAsync_Void_CanRunSynchronously_WhenNotBusy()
|
public async Task InvokeAsync_Void_CanRunSynchronously_WhenNotBusy()
|
||||||
{
|
{
|
||||||
|
|
@ -607,6 +641,23 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
await Assert.ThrowsAsync<InvalidTimeZoneException>(async () => await task);
|
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]
|
[Fact]
|
||||||
public async Task InvokeAsync_T_CanRunSynchronously_WhenNotBusy()
|
public async Task InvokeAsync_T_CanRunSynchronously_WhenNotBusy()
|
||||||
{
|
{
|
||||||
|
|
@ -677,5 +728,22 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
// Assert
|
// Assert
|
||||||
await Assert.ThrowsAsync<InvalidTimeZoneException>(async () => await task);
|
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)
|
public new Task RenderRootComponentAsync(int componentId, ParameterCollection parameters)
|
||||||
=> InvokeAsync(() => base.RenderRootComponentAsync(componentId, parameters));
|
=> InvokeAsync(() => base.RenderRootComponentAsync(componentId, parameters));
|
||||||
|
|
||||||
public new Task DispatchEventAsync(int componentId, int eventHandlerId, UIEventArgs args)
|
public new Task DispatchEventAsync(int eventHandlerId, UIEventArgs args)
|
||||||
{
|
=> InvokeAsync(() => base.DispatchEventAsync(eventHandlerId, args));
|
||||||
var task = InvokeAsync(() => base.DispatchEventAsync(componentId, eventHandlerId, args));
|
|
||||||
return UnwrapTask(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Task UnwrapTask(Task task)
|
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
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -77,10 +77,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
decrementButton.Click();
|
decrementButton.Click();
|
||||||
WaitAssert.Equal("98", () => currentCount.Text);
|
WaitAssert.Equal("98", () => currentCount.Text);
|
||||||
|
|
||||||
// Renders the descendant the same number of times we triggered
|
// Didn't re-render descendants
|
||||||
// events on it, because we always re-render components after they
|
Assert.Equal("1", Browser.FindElement(By.Id("receive-by-interface-num-renders")).Text);
|
||||||
// have an event
|
|
||||||
Assert.Equal("3", 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>
|
||||||
</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-1" bind=currentFlagValue1 /> Flag 1</label></p>
|
||||||
<p><label><input type="checkbox" id="toggle-flag-2" bind=currentFlagValue2 /> Flag 2</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.CascadingValueTest.CascadingValueSupplier">Cascading values</option>
|
||||||
<option value="BasicTestApp.ConcurrentRenderParent">Concurrent rendering</option>
|
<option value="BasicTestApp.ConcurrentRenderParent">Concurrent rendering</option>
|
||||||
<option value="BasicTestApp.DispatchingComponent">Dispatching to sync context</option>
|
<option value="BasicTestApp.DispatchingComponent">Dispatching to sync context</option>
|
||||||
|
<option value="BasicTestApp.EventCallbackTest.EventCallbackCases">EventCallback</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
@if (SelectedComponentType != null)
|
@if (SelectedComponentType != null)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue