Add support for most (if not all events)

This change adds tag helpers and defines event types for all of the DOM
events we could find. You'll find it much easier now to subcribe to
these events.

While we did define new event types, we didn't substantially expand the
set of data that we serialize for events. We're looking for feedback on
what is most critical to have, and looking for contributions adding
those data and tests.
This commit is contained in:
Ryan Nowak 2018-04-30 21:44:15 -07:00 committed by Steve Sanderson
parent 05f5bb423a
commit 87375f6ba1
9 changed files with 878 additions and 14 deletions

View File

@ -5,17 +5,66 @@
static fromDOMEvent(event: Event): EventForDotNet<UIEventArgs> {
const element = event.target as Element;
switch (event.type) {
case 'click':
case 'mousedown':
case 'mouseup':
return new EventForDotNet<UIMouseEventArgs>('mouse', { Type: event.type });
case 'change': {
const targetIsCheckbox = isCheckbox(element);
const newValue = targetIsCheckbox ? !!element['checked'] : element['value'];
return new EventForDotNet<UIChangeEventArgs>('change', { Type: event.type, Value: newValue });
}
case 'copy':
case 'cut':
case 'paste':
return new EventForDotNet<UIClipboardEventArgs>('clipboard', { Type: event.type });
case 'drag':
case 'dragend':
case 'dragenter':
case 'dragleave':
case 'dragover':
case 'dragstart':
case 'drop':
return new EventForDotNet<UIDragEventArgs>('drag', { Type: event.type });
case 'error':
return new EventForDotNet<UIProgressEventArgs>('error', { Type: event.type });
case 'focus':
case 'blur':
case 'focusin':
case 'focusout':
return new EventForDotNet<UIFocusEventArgs>('focus', { Type: event.type });
case 'keydown':
case 'keyup':
case 'keypress':
return new EventForDotNet<UIKeyboardEventArgs>('keyboard', { Type: event.type, Key: (event as any).key });
case 'click':
case 'mouseover':
case 'mouseout':
case 'mousemove':
case 'mousedown':
case 'mouseup':
case 'dblclick':
return new EventForDotNet<UIMouseEventArgs>('mouse', { Type: event.type });
case 'contextmenu':
return new EventForDotNet<UIPointerEventArgs>('pointer', { Type: event.type });
case 'progress':
return new EventForDotNet<UIProgressEventArgs>('progress', { Type: event.type });
case 'touchcancel':
case 'touchend':
case 'touchmove':
case 'touchstart':
return new EventForDotNet<UITouchEventArgs>('touch', { Type: event.type });
case 'mousewheel':
return new EventForDotNet<UIWheelEventArgs>('wheel', { Type: event.type });
default:
return new EventForDotNet<UIEventArgs>('unknown', { Type: event.type });
}
@ -28,19 +77,43 @@ function isCheckbox(element: Element | null) {
// The following interfaces must be kept in sync with the UIEventArgs C# classes
type EventArgsType = 'mouse' | 'keyboard' | 'change' | 'unknown';
type EventArgsType = 'change' | 'clipboard' | 'drag' | 'error' | 'focus' | 'keyboard' | 'mouse' | 'pointer' | 'progress' | 'touch' | 'unknown' | 'wheel';
export interface UIEventArgs {
Type: string;
}
interface UIMouseEventArgs extends UIEventArgs {
interface UIChangeEventArgs extends UIEventArgs {
Value: string | boolean;
}
interface UIClipboardEventArgs extends UIEventArgs {
}
interface UIDragEventArgs extends UIEventArgs {
}
interface UIErrorEventArgs extends UIEventArgs {
}
interface UIFocusEventArgs extends UIEventArgs {
}
interface UIKeyboardEventArgs extends UIEventArgs {
Key: string;
}
interface UIChangeEventArgs extends UIEventArgs {
Value: string | boolean;
interface UIMouseEventArgs extends UIEventArgs {
}
interface UIPointerEventArgs extends UIMouseEventArgs {
}
interface UIProgressEventArgs extends UIEventArgs {
}
interface UITouchEventArgs extends UIEventArgs {
}
interface UIWheelEventArgs extends UIEventArgs {
}

View File

@ -32,16 +32,32 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Rendering
{
switch (eventArgsType)
{
case "mouse":
return JsonUtil.Deserialize<UIMouseEventArgs>(eventArgsJson);
case "keyboard":
return JsonUtil.Deserialize<UIKeyboardEventArgs>(eventArgsJson);
case "change":
return JsonUtil.Deserialize<UIChangeEventArgs>(eventArgsJson);
case "clipboard":
return JsonUtil.Deserialize<UIClipboardEventArgs>(eventArgsJson);
case "drag":
return JsonUtil.Deserialize<UIDragEventArgs>(eventArgsJson);
case "error":
return JsonUtil.Deserialize<UIErrorEventArgs>(eventArgsJson);
case "focus":
return JsonUtil.Deserialize<UIFocusEventArgs>(eventArgsJson);
case "keyboard":
return JsonUtil.Deserialize<UIKeyboardEventArgs>(eventArgsJson);
case "mouse":
return JsonUtil.Deserialize<UIMouseEventArgs>(eventArgsJson);
case "pointer":
return JsonUtil.Deserialize<UIPointerEventArgs>(eventArgsJson);
case "progress":
return JsonUtil.Deserialize<UIProgressEventArgs>(eventArgsJson);
case "touch":
return JsonUtil.Deserialize<UITouchEventArgs>(eventArgsJson);
case "unknown":
return JsonUtil.Deserialize<UIEventArgs>(eventArgsJson);
case "wheel":
return JsonUtil.Deserialize<UIWheelEventArgs>(eventArgsJson);
default:
throw new ArgumentException($"Unsupported value '{eventArgsType}'.", nameof(eventArgsType));
throw new ArgumentException($"Unsupported value '{eventArgsType}'.", nameof(eventArgsType));
}
}

View File

@ -9,8 +9,106 @@ namespace Microsoft.AspNetCore.Blazor.Components
/// Holds <see cref="EventHandler"/> attributes to configure the mappings between event names and
/// event argument types.
/// </summary>
[EventHandler("onchange", typeof(UIChangeEventArgs))]
// Focus events
[EventHandler("onfocus", typeof(UIFocusEventArgs))]
[EventHandler("onblur", typeof(UIFocusEventArgs))]
[EventHandler("onfocusin", typeof(UIFocusEventArgs))]
[EventHandler("onfocusout", typeof(UIFocusEventArgs))]
// Mouse events
[EventHandler("onmouseover", typeof(UIMouseEventArgs))]
[EventHandler("onmouseout", typeof(UIMouseEventArgs))]
[EventHandler("onmousemove", typeof(UIMouseEventArgs))]
[EventHandler("onmousedown", typeof(UIMouseEventArgs))]
[EventHandler("onmouseup", typeof(UIMouseEventArgs))]
[EventHandler("onclick", typeof(UIMouseEventArgs))]
[EventHandler("ondblclick", typeof(UIMouseEventArgs))]
[EventHandler("onmousewheel", typeof(UIWheelEventArgs))]
// Drag events
[EventHandler("ondrag", typeof(UIDragEventArgs))]
[EventHandler("ondragend", typeof(UIDragEventArgs))]
[EventHandler("ondragenter", typeof(UIDragEventArgs))]
[EventHandler("ondragleave", typeof(UIDragEventArgs))]
[EventHandler("ondragover", typeof(UIDragEventArgs))]
[EventHandler("ondragstart", typeof(UIDragEventArgs))]
[EventHandler("ondrop", typeof(UIDragEventArgs))]
// Keyboard events
[EventHandler("onkeydown", typeof(UIKeyboardEventArgs))]
[EventHandler("onkeyup", typeof(UIKeyboardEventArgs))]
[EventHandler("onkeypress", typeof(UIKeyboardEventArgs))]
// Pointer events
[EventHandler("oncontextmenu", typeof(UIPointerEventArgs))]
// Input events
[EventHandler("onchange", typeof(UIChangeEventArgs))]
[EventHandler("oninput", typeof(UIEventArgs))]
[EventHandler("oninvalid", typeof(UIEventArgs))]
[EventHandler("onreset", typeof(UIEventArgs))]
[EventHandler("onselect", typeof(UIEventArgs))]
[EventHandler("onselectstart", typeof(UIEventArgs))]
[EventHandler("onselectionchange", typeof(UIEventArgs))]
[EventHandler("onsubmit", typeof(UIEventArgs))]
// Clipboard events
[EventHandler("onbeforecopy", typeof(UIEventArgs))]
[EventHandler("onbeforecut", typeof(UIEventArgs))]
[EventHandler("onbeforepaste", typeof(UIEventArgs))]
[EventHandler("oncopy", typeof(UIClipboardEventArgs))]
[EventHandler("oncut", typeof(UIClipboardEventArgs))]
[EventHandler("onpaste", typeof(UIClipboardEventArgs))]
// Touch events
[EventHandler("ontouchcancel", typeof(UITouchEventArgs))]
[EventHandler("ontouchend", typeof(UITouchEventArgs))]
[EventHandler("ontouchmove", typeof(UITouchEventArgs))]
[EventHandler("ontouchstart", typeof(UITouchEventArgs))]
// Media events
[EventHandler("oncanplay", typeof(UIEventArgs))]
[EventHandler("oncanplaythrough", typeof(UIEventArgs))]
[EventHandler("oncuechange", typeof(UIEventArgs))]
[EventHandler("ondurationchange", typeof(UIEventArgs))]
[EventHandler("onemptied", typeof(UIEventArgs))]
[EventHandler("onpause", typeof(UIEventArgs))]
[EventHandler("onplay", typeof(UIEventArgs))]
[EventHandler("onplaying", typeof(UIEventArgs))]
[EventHandler("onratechange", typeof(UIEventArgs))]
[EventHandler("onseeked", typeof(UIEventArgs))]
[EventHandler("onseeking", typeof(UIEventArgs))]
[EventHandler("onstalled", typeof(UIEventArgs))]
[EventHandler("onstop", typeof(UIEventArgs))]
[EventHandler("onsuspend", typeof(UIEventArgs))]
[EventHandler("ontimeupdate", typeof(UIEventArgs))]
[EventHandler("onvolumechange", typeof(UIEventArgs))]
[EventHandler("onwaiting", typeof(UIEventArgs))]
// Error events
[EventHandler("onerror", typeof(UIErrorEventArgs))]
// Progress events
[EventHandler("onprogress", typeof(UIProgressEventArgs))]
// General events
[EventHandler("onabort", typeof(UIEventArgs))]
[EventHandler("onactivate", typeof(UIEventArgs))]
[EventHandler("onbeforeactivate", typeof(UIEventArgs))]
[EventHandler("onbeforedeactivate", typeof(UIEventArgs))]
[EventHandler("ondeactivate", typeof(UIEventArgs))]
[EventHandler("onended", typeof(UIEventArgs))]
[EventHandler("onfullscreenchange", typeof(UIEventArgs))]
[EventHandler("onfullscreenerror", typeof(UIEventArgs))]
[EventHandler("onload", typeof(UIEventArgs))]
[EventHandler("onloadeddata", typeof(UIEventArgs))]
[EventHandler("onloadedmetadata", typeof(UIEventArgs))]
[EventHandler("onloadstart", typeof(UIEventArgs))]
[EventHandler("onpointerlockchange", typeof(UIEventArgs))]
[EventHandler("onpointerlockerror", typeof(UIEventArgs))]
[EventHandler("onreadystatechange", typeof(UIEventArgs))]
[EventHandler("onscroll", typeof(UIEventArgs))]
public static class EventHandlers
{
}

View File

@ -26,6 +26,36 @@ namespace Microsoft.AspNetCore.Blazor
public object Value { get; set; }
}
/// <summary>
/// Supplies information about an clipboard event that is being raised.
/// </summary>
public class UIClipboardEventArgs : UIEventArgs
{
}
/// <summary>
/// Supplies information about an drag event that is being raised.
/// </summary>
public class UIDragEventArgs : UIEventArgs
{
}
/// <summary>
/// Supplies information about an error event that is being raised.
/// </summary>
public class UIErrorEventArgs : UIEventArgs
{
}
/// <summary>
/// Supplies information about a focus event that is being raised.
/// </summary>
public class UIFocusEventArgs : UIEventArgs
{
// Not including support for 'relatedTarget' since we don't have a good way to represent it.
// see: https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent
}
/// <summary>
/// Supplies information about a keyboard event that is being raised.
/// </summary>
@ -43,4 +73,32 @@ namespace Microsoft.AspNetCore.Blazor
public class UIMouseEventArgs : UIEventArgs
{
}
/// <summary>
/// Supplies information about a mouse event that is being raised.
/// </summary>
public class UIPointerEventArgs : UIMouseEventArgs
{
}
/// <summary>
/// Supplies information about a progress event that is being raised.
/// </summary>
public class UIProgressEventArgs : UIMouseEventArgs
{
}
/// <summary>
/// Supplies information about a touch event that is being raised.
/// </summary>
public class UITouchEventArgs : UIEventArgs
{
}
/// <summary>
/// Supplies information about a mouse wheel event that is being raised.
/// </summary>
public class UIWheelEventArgs : UIEventArgs
{
}
}

View File

@ -84,6 +84,190 @@ namespace Microsoft.AspNetCore.Blazor
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
/// <summary>
/// <para>
/// Appends a frame representing an <see cref="Action{UIDragEventArgs}"/>-valued 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="builder">The <see cref="RenderTreeBuilder"/>.</param>
/// <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>
public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, Action<UIDragEventArgs> value)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
/// <summary>
/// <para>
/// Appends a frame representing an <see cref="Func{UIDragEventArgs, Task}"/>-valued 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="builder">The <see cref="RenderTreeBuilder"/>.</param>
/// <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>
public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, Func<UIDragEventArgs, Task> value)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
/// <summary>
/// <para>
/// Appends a frame representing an <see cref="Action{UIClipboardEventArgs}"/>-valued 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="builder">The <see cref="RenderTreeBuilder"/>.</param>
/// <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>
public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, Action<UIClipboardEventArgs> value)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
/// <summary>
/// <para>
/// Appends a frame representing an <see cref="Func{UIClipboardEventArgs, Task}"/>-valued 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="builder">The <see cref="RenderTreeBuilder"/>.</param>
/// <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>
public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, Func<UIClipboardEventArgs, Task> value)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
/// <summary>
/// <para>
/// Appends a frame representing an <see cref="Action{UIErrorEventArgs}"/>-valued 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="builder">The <see cref="RenderTreeBuilder"/>.</param>
/// <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>
public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, Action<UIErrorEventArgs> value)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
/// <summary>
/// <para>
/// Appends a frame representing an <see cref="Func{UIErrorEventArgs, Task}"/>-valued 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="builder">The <see cref="RenderTreeBuilder"/>.</param>
/// <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>
public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, Func<UIErrorEventArgs, Task> value)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
/// <summary>
/// <para>
/// Appends a frame representing an <see cref="Action{UIFocusEventArgs}"/>-valued 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="builder">The <see cref="RenderTreeBuilder"/>.</param>
/// <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>
public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, Action<UIFocusEventArgs> value)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
/// <summary>
/// <para>
/// Appends a frame representing an <see cref="Func{UIFocusEventArgs, Task}"/>-valued 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="builder">The <see cref="RenderTreeBuilder"/>.</param>
/// <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>
public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, Func<UIFocusEventArgs, Task> value)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
/// <summary>
/// <para>
/// Appends a frame representing an <see cref="Action{UIKeyboardEventArgs}"/>-valued attribute.
@ -175,5 +359,189 @@ namespace Microsoft.AspNetCore.Blazor
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
/// <summary>
/// <para>
/// Appends a frame representing an <see cref="Action{UIPointerEventArgs}"/>-valued 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="builder">The <see cref="RenderTreeBuilder"/>.</param>
/// <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>
public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, Action<UIPointerEventArgs> value)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
/// <summary>
/// <para>
/// Appends a frame representing an <see cref="Func{UIPointerEventArgs, Task}"/>-valued 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="builder">The <see cref="RenderTreeBuilder"/>.</param>
/// <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>
public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, Func<UIPointerEventArgs, Task> value)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
/// <summary>
/// <para>
/// Appends a frame representing an <see cref="Action{UIProgressEventArgs}"/>-valued 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="builder">The <see cref="RenderTreeBuilder"/>.</param>
/// <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>
public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, Action<UIProgressEventArgs> value)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
/// <summary>
/// <para>
/// Appends a frame representing an <see cref="Func{UIProgressEventArgs, Task}"/>-valued 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="builder">The <see cref="RenderTreeBuilder"/>.</param>
/// <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>
public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, Func<UIProgressEventArgs, Task> value)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
/// <summary>
/// <para>
/// Appends a frame representing an <see cref="Action{UITouchEventArgs}"/>-valued 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="builder">The <see cref="RenderTreeBuilder"/>.</param>
/// <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>
public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, Action<UITouchEventArgs> value)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
/// <summary>
/// <para>
/// Appends a frame representing an <see cref="Func{UITouchEventArgs, Task}"/>-valued 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="builder">The <see cref="RenderTreeBuilder"/>.</param>
/// <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>
public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, Func<UITouchEventArgs, Task> value)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
/// <summary>
/// <para>
/// Appends a frame representing an <see cref="Action{UIWheelEventArgs}"/>-valued 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="builder">The <see cref="RenderTreeBuilder"/>.</param>
/// <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>
public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, Action<UIWheelEventArgs> value)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
/// <summary>
/// <para>
/// Appends a frame representing an <see cref="Func{UIWheelEventArgs, Task}"/>-valued 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="builder">The <see cref="RenderTreeBuilder"/>.</param>
/// <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>
public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, Func<UIWheelEventArgs, Task> value)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddAttribute(sequence, name, (MulticastDelegate)value);
}
}
}

View File

@ -0,0 +1,130 @@
// 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 BasicTestApp;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
using OpenQA.Selenium;
using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Support.UI;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{
public class EventTest : BasicTestAppTestBase
{
public EventTest(
BrowserFixture browserFixture,
DevHostServerFixture<Program> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
Navigate(ServerPathBase, noReload: true);
MountTestComponent<EventBubblingComponent>();
}
[Fact]
public void FocusEvents_CanTrigger()
{
MountTestComponent<FocusEventComponent>();
var input = Browser.FindElement(By.Id("input"));
var output = Browser.FindElement(By.Id("output"));
Assert.Equal(string.Empty, output.Text);
// Focus the target, verify onfocusin is fired
input.Click();
Assert.Equal("onfocus,onfocusin,", output.Text);
// Focus something else, verify onfocusout is also fired
var other = Browser.FindElement(By.Id("other"));
other.Click();
Assert.Equal("onfocus,onfocusin,onblur,onfocusout,", output.Text);
}
[Fact]
public void MouseOverAndMouseOut_CanTrigger()
{
MountTestComponent<MouseEventComponent>();
var input = Browser.FindElement(By.Id("mouseover_input"));
var output = Browser.FindElement(By.Id("output"));
Assert.Equal(string.Empty, output.Text);
var other = Browser.FindElement(By.Id("other"));
// Mouse over the button and then back off
var actions = new Actions(Browser)
.MoveToElement(input)
.MoveToElement(other);
actions.Perform();
Assert.Equal("onmouseover,onmouseout,", output.Text);
}
[Fact]
public void MouseMove_CanTrigger()
{
MountTestComponent<MouseEventComponent>();
var input = Browser.FindElement(By.Id("mousemove_input"));
var output = Browser.FindElement(By.Id("output"));
Assert.Equal(string.Empty, output.Text);
// Move a little bit
var actions = new Actions(Browser)
.MoveToElement(input)
.MoveToElement(input, 10, 10);
actions.Perform();
Assert.Contains("onmousemove,", output.Text);
}
[Fact]
public void MouseDownAndMouseUp_CanTrigger()
{
MountTestComponent<MouseEventComponent>();
var input = Browser.FindElement(By.Id("mousedown_input"));
var output = Browser.FindElement(By.Id("output"));
Assert.Equal(string.Empty, output.Text);
var other = Browser.FindElement(By.Id("other"));
// Mousedown
var actions = new Actions(Browser).ClickAndHold(input);
actions.Perform();
Assert.Equal("onmousedown,", output.Text);
actions = new Actions(Browser).Release(input);
actions.Perform();
Assert.Equal("onmousedown,onmouseup,", output.Text);
}
private string[] GetLogLines()
=> Browser.FindElement(By.TagName("textarea"))
.GetAttribute("value")
.Replace("\r\n", "\n")
.Split('\n', StringSplitOptions.RemoveEmptyEntries);
private void TriggerCustomBubblingEvent(string elementId, string eventName)
{
var jsExecutor = (IJavaScriptExecutor)Browser;
jsExecutor.ExecuteScript(
$"document.getElementById('{elementId}').dispatchEvent(" +
$" new Event('{eventName}', {{ bubbles: true }})" +
$")");
MountTestComponent<FocusEventComponent>();
}
}
}

View File

@ -0,0 +1,52 @@
@using System.Collections.Generic
@using Microsoft.AspNetCore.Blazor
<h2>Focus and activation</h2>
<p onfocusin="@OnFocusIn" onfocusout="@OnFocusOut">
Input: <input id="input" type="text" onfocus="@OnFocus" onblur="@OnBlur"/>
</p>
<p>
Output: <span id="output">@message</span>
</p>
<p>
<button onclick="@Clear">Clear</button>
</p>
<p>
Another input (to distract you) <input id="other" />
</p>
@functions {
string message;
void OnFocus(UIFocusEventArgs e)
{
message += "onfocus,";
StateHasChanged();
}
void OnBlur(UIFocusEventArgs e)
{
message += "onblur,";
StateHasChanged();
}
void OnFocusIn(UIFocusEventArgs e)
{
message += "onfocusin,";
StateHasChanged();
}
void OnFocusOut(UIFocusEventArgs e)
{
message += "onfocusout,";
StateHasChanged();
}
void Clear()
{
message = string.Empty;
}
}

View File

@ -0,0 +1,67 @@
@using System.Collections.Generic
@using Microsoft.AspNetCore.Blazor
<div>
<h2>Mouse position</h2>
<p>
Output: <span id="output">@message</span>
</p>
<p>
Mouseover: <input id="mouseover_input" type="text" onmouseover="@OnMouseOver" onmouseout="@OnMouseOut" />
</p>
<p>
<span id="mousemove_input" onmousemove="@OnMouseMove">Mousemove city!</span>
</p>
<p>
Mousedown: <input id="mousedown_input" onmousedown="@OnMouseDown" onmouseup="@OnMouseUp" />
</p>
<p>
<button onclick="@Clear">Clear</button>
</p>
<p>
Another input (to distract you) <input id="other" />
</p>
</div>
@functions {
string message;
void OnMouseOver(UIMouseEventArgs e)
{
message += "onmouseover,";
StateHasChanged();
}
void OnMouseOut(UIMouseEventArgs e)
{
message += "onmouseout,";
StateHasChanged();
}
void OnMouseMove(UIMouseEventArgs e)
{
message += "onmousemove,";
StateHasChanged();
}
void OnMouseDown(UIMouseEventArgs e)
{
message += "onmousedown,";
StateHasChanged();
}
void OnMouseUp(UIMouseEventArgs e)
{
message += "onmouseup,";
StateHasChanged();
}
void Clear()
{
message = string.Empty;
}
}

View File

@ -15,7 +15,9 @@
<option value="BasicTestApp.CounterComponent">Counter</option>
<option value="BasicTestApp.CounterComponentUsingChild">Counter using child component</option>
<option value="BasicTestApp.CounterComponentWrapper">Counter wrapped in parent</option>
<option value="BasicTestApp.FocusEventComponent">Focus events</option>
<option value="BasicTestApp.KeyPressEventComponent">Key press event</option>
<option value="BasicTestApp.MouseEventComponent">Mouse events</option>
<option value="BasicTestApp.ParentChildComponent">Parent component with child</option>
<option value="BasicTestApp.PropertiesChangedHandlerParent">Parent component that changes parameters on child</option>
<option value="BasicTestApp.RedTextComponent">Red text</option>