Add IHandleEvent concept so components can define their own lifecycle around events
This commit is contained in:
parent
a889cd3152
commit
804ab2d89f
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Blazor.Components
|
||||||
/// Optional base class for Blazor components. Alternatively, Blazor components may
|
/// Optional base class for Blazor components. Alternatively, Blazor components may
|
||||||
/// implement <see cref="IComponent"/> directly.
|
/// implement <see cref="IComponent"/> directly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class BlazorComponent : IComponent
|
public abstract class BlazorComponent : IComponent, IHandleEvent
|
||||||
{
|
{
|
||||||
private RenderHandle _renderHandle;
|
private RenderHandle _renderHandle;
|
||||||
private bool _hasNeverRendered = true;
|
private bool _hasNeverRendered = true;
|
||||||
|
|
@ -83,6 +83,16 @@ namespace Microsoft.AspNetCore.Blazor.Components
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IHandleEvent.HandleEvent(UIEventHandler handler, UIEventArgs args)
|
||||||
|
{
|
||||||
|
handler(args);
|
||||||
|
|
||||||
|
// After each event, we synchronously re-render (unless !ShouldRender())
|
||||||
|
// This just saves the developer the trouble of putting "StateHasChanged();"
|
||||||
|
// at the end of every event callback.
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
// At present, if you have a .cshtml file in a project with <Project Sdk="Microsoft.NET.Sdk.Web">,
|
// At present, if you have a .cshtml file in a project with <Project Sdk="Microsoft.NET.Sdk.Web">,
|
||||||
// Visual Studio will run design-time builds for it, codegenning a class that attempts to override
|
// Visual Studio will run design-time builds for it, codegenning a class that attempts to override
|
||||||
// this method. Therefore the virtual method must be defined, even though it won't be used at runtime,
|
// this method. Therefore the virtual method must be defined, even though it won't be used at runtime,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Blazor.RenderTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Blazor.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface implemented by components that receive notification of their events.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHandleEvent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Notifies the component that one of its event handlers has been triggered.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">The event handler.</param>
|
||||||
|
/// <param name="args">Arguments for the event handler.</param>
|
||||||
|
void HandleEvent(UIEventHandler handler, UIEventArgs args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -62,5 +62,19 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||||
|
|
||||||
RenderTreeDiffBuilder.DisposeFrames(batchBuilder, _renderTreeBuilderCurrent.GetFrames());
|
RenderTreeDiffBuilder.DisposeFrames(batchBuilder, _renderTreeBuilderCurrent.GetFrames());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DispatchEvent(UIEventHandler handler, UIEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (_component is IHandleEvent handleEventComponent)
|
||||||
|
{
|
||||||
|
handleEventComponent.HandleEvent(handler, eventArgs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"The component of type {_component.GetType().FullName} cannot receive " +
|
||||||
|
$"events because it does not implement {typeof(IHandleEvent).FullName}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -120,11 +120,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||||
{
|
{
|
||||||
if (_eventHandlersById.TryGetValue(eventHandlerId, out var handler))
|
if (_eventHandlersById.TryGetValue(eventHandlerId, out var handler))
|
||||||
{
|
{
|
||||||
handler.Invoke(eventArgs);
|
GetRequiredComponentState(componentId).DispatchEvent(handler, eventArgs);
|
||||||
|
|
||||||
// After any event, we synchronously re-render. Most of the time this means that
|
|
||||||
// developers don't need to call Render() on their components explicitly.
|
|
||||||
RenderNewBatch(componentId);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,37 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
Assert.Same(eventArgs, receivedArgs);
|
Assert.Same(eventArgs, receivedArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ThrowsIfComponentDoesNotHandleEvents()
|
||||||
|
{
|
||||||
|
// Arrange: Render a component with an event handler
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
UIEventHandler handler = args => throw new NotImplementedException();
|
||||||
|
var component = new TestComponent(builder =>
|
||||||
|
{
|
||||||
|
builder.OpenElement(0, "mybutton");
|
||||||
|
builder.AddAttribute(1, "my click event", handler);
|
||||||
|
builder.CloseElement();
|
||||||
|
});
|
||||||
|
|
||||||
|
var componentId = renderer.AssignComponentId(component);
|
||||||
|
renderer.RenderNewBatch(componentId);
|
||||||
|
|
||||||
|
var eventHandlerId = renderer.Batches.Single()
|
||||||
|
.ReferenceFrames
|
||||||
|
.First(frame => frame.AttributeValue != null)
|
||||||
|
.AttributeEventHandlerId;
|
||||||
|
var eventArgs = new UIEventArgs();
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
renderer.DispatchEvent(componentId, eventHandlerId, eventArgs);
|
||||||
|
});
|
||||||
|
Assert.Equal($"The component of type {typeof(TestComponent).FullName} cannot receive " +
|
||||||
|
$"events because it does not implement {typeof(IHandleEvent).FullName}.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CannotRenderUnknownComponents()
|
public void CannotRenderUnknownComponents()
|
||||||
{
|
{
|
||||||
|
|
@ -812,7 +843,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
=> parameters.AssignToProperties(this);
|
=> parameters.AssignToProperties(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class EventComponent : AutoRenderComponent, IComponent
|
private class EventComponent : AutoRenderComponent, IComponent, IHandleEvent
|
||||||
{
|
{
|
||||||
public UIEventHandler Handler { get; set; }
|
public UIEventHandler Handler { get; set; }
|
||||||
public bool SkipElement { get; set; }
|
public bool SkipElement { get; set; }
|
||||||
|
|
@ -833,6 +864,9 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
}
|
}
|
||||||
builder.CloseElement();
|
builder.CloseElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void HandleEvent(UIEventHandler handler, UIEventArgs args)
|
||||||
|
=> handler(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ConditionalParentComponent<T> : AutoRenderComponent where T : IComponent
|
private class ConditionalParentComponent<T> : AutoRenderComponent where T : IComponent
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue