Add support for passing parameters to UI event handlers (e.g., which key was pressed)

This commit is contained in:
Steve Sanderson 2018-01-08 11:31:10 +00:00
parent 23f7120b75
commit 04c582647a
9 changed files with 130 additions and 12 deletions

View File

@ -91,7 +91,16 @@ function applyAttribute(componentId: string, toDomElement: Element, attributeNod
switch (attributeName) { switch (attributeName) {
case 'onclick': case 'onclick':
toDomElement.addEventListener('click', () => raiseEvent(componentId, attributeNodeIndex, 'click')); toDomElement.addEventListener('click', () => raiseEvent(componentId, attributeNodeIndex, 'mouse', { Type: 'click' }));
break;
case 'onkeypress':
toDomElement.addEventListener('keypress', evt => {
// This does not account for special keys nor cross-browser differences. So far it's
// just to establish that we can pass parameters when raising events.
// We use C#-style PascalCase on the eventInfo to simplify deserialization, but this could
// change if we introduced a richer JSON library on the .NET side.
raiseEvent(componentId, attributeNodeIndex, 'keyboard', { Type: evt.type, Key: (evt as any).key });
});
break; break;
default: default:
// Treat as a regular string-valued attribute // Treat as a regular string-valued attribute
@ -103,7 +112,7 @@ function applyAttribute(componentId: string, toDomElement: Element, attributeNod
} }
} }
function raiseEvent(componentId: string, uiTreeNodeIndex: number, eventName: string) { function raiseEvent(componentId: string, uiTreeNodeIndex: number, eventInfoType: EventInfoType, eventInfo: any) {
if (!raiseEventMethod) { if (!raiseEventMethod) {
raiseEventMethod = platform.findMethod( raiseEventMethod = platform.findMethod(
'Microsoft.Blazor.Browser', 'Microsoft.Blazor.Browser', 'Events', 'RaiseEvent' 'Microsoft.Blazor.Browser', 'Microsoft.Blazor.Browser', 'Events', 'RaiseEvent'
@ -114,7 +123,9 @@ function raiseEvent(componentId: string, uiTreeNodeIndex: number, eventName: str
// it first if necessary. Until then we have to send it as a string. // it first if necessary. Until then we have to send it as a string.
platform.callMethod(raiseEventMethod, null, [ platform.callMethod(raiseEventMethod, null, [
platform.toDotNetString(componentId), platform.toDotNetString(componentId),
platform.toDotNetString(uiTreeNodeIndex.toString()) platform.toDotNetString(uiTreeNodeIndex.toString()),
platform.toDotNetString(eventInfoType),
platform.toDotNetString(JSON.stringify(eventInfo))
]); ]);
} }
@ -130,3 +141,5 @@ function clearElement(element: Element) {
element.removeChild(childNode); element.removeChild(childNode);
} }
} }
type EventInfoType = 'mouse' | 'keyboard';

View File

@ -73,7 +73,7 @@ namespace Microsoft.Blazor.Browser
return _uITreeBuilder.GetNodes(); return _uITreeBuilder.GetNodes();
} }
public void RaiseEvent(int uiTreeNodeIndex) public void RaiseEvent(int uiTreeNodeIndex, UIEventInfo eventInfo)
{ {
var nodes = _uITreeBuilder.GetNodes(); var nodes = _uITreeBuilder.GetNodes();
var eventHandler = nodes.Array[nodes.Offset + uiTreeNodeIndex].AttributeEventHandlerValue; var eventHandler = nodes.Array[nodes.Offset + uiTreeNodeIndex].AttributeEventHandlerValue;
@ -82,7 +82,7 @@ namespace Microsoft.Blazor.Browser
throw new ArgumentException($"Cannot raise event because the specified {nameof(UITreeNode)} at index {uiTreeNodeIndex} does not have any {nameof(UITreeNode.AttributeEventHandlerValue)}."); throw new ArgumentException($"Cannot raise event because the specified {nameof(UITreeNode)} at index {uiTreeNodeIndex} does not have any {nameof(UITreeNode.AttributeEventHandlerValue)}.");
} }
eventHandler.Invoke(); eventHandler.Invoke(eventInfo);
RenderToDOM(); RenderToDOM();
} }

View File

@ -1,18 +1,36 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Blazor.Browser.Interop;
using Microsoft.Blazor.UITree;
using System;
namespace Microsoft.Blazor.Browser namespace Microsoft.Blazor.Browser
{ {
// Invoked by the Microsoft.Blazor.Browser.JS code when a DOM event occurs // Invoked by the Microsoft.Blazor.Browser.JS code when a DOM event occurs
internal static class Events internal static class Events
{ {
public static void RaiseEvent(string domComponentID, string uiTreeNodeIndex) public static void RaiseEvent(string domComponentID, string uiTreeNodeIndex, string eventInfoType, string eventInfoJson)
{ {
// We're receiving the uiTreeNodeIndex as a string only because there's not // We're receiving the uiTreeNodeIndex as a string only because there's not
// yet a way to pass ints (or construct boxed ones) from JS with the current Mono // yet a way to pass ints (or construct boxed ones) from JS with the current Mono
// runtime. When there's a supported way to do that, this can be simplified. // runtime. When there's a supported way to do that, this can be simplified.
var renderState = DOMComponentRenderState.FindByDOMComponentID(domComponentID); var renderState = DOMComponentRenderState.FindByDOMComponentID(domComponentID);
renderState.RaiseEvent(int.Parse(uiTreeNodeIndex)); var eventInfo = ParseEventInfo(eventInfoType, eventInfoJson);
renderState.RaiseEvent(int.Parse(uiTreeNodeIndex), eventInfo);
}
private static UIEventInfo ParseEventInfo(string eventInfoType, string eventInfoJson)
{
switch (eventInfoType)
{
case "mouse":
return Json.Deserialize<UIMouseEventInfo>(eventInfoJson);
case "keyboard":
return Json.Deserialize<UIKeyboardEventInfo>(eventInfoJson);
default:
throw new ArgumentException($"Unsupported value '{eventInfoType}'.", nameof(eventInfoType));
}
} }
} }
} }

View File

@ -6,5 +6,5 @@ namespace Microsoft.Blazor.UITree
/// <summary> /// <summary>
/// Handles an event raised for a <see cref="UITreeNode"/>. /// Handles an event raised for a <see cref="UITreeNode"/>.
/// </summary> /// </summary>
public delegate void UIEventHandler(); public delegate void UIEventHandler(UIEventInfo eventInfo);
} }

View File

@ -0,0 +1,34 @@
// 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.
namespace Microsoft.Blazor.UITree
{
/// <summary>
/// Supplies information about an event that is being raised.
/// </summary>
public class UIEventInfo
{
/// <summary>
/// Gets or sets the type of the event.
/// </summary>
public string Type { get; set; }
}
/// <summary>
/// Supplies information about a mouse event that is being raised.
/// </summary>
public class UIMouseEventInfo : UIEventInfo
{
}
/// <summary>
/// Supplies information about a keyboard event that is being raised.
/// </summary>
public class UIKeyboardEventInfo : UIEventInfo
{
/// <summary>
/// If applicable, gets or sets the key that produced the event.
/// </summary>
public string Key { get; set; }
}
}

View File

@ -60,6 +60,23 @@ namespace Microsoft.Blazor.E2ETest.Tests
appElement.FindElement(By.TagName("p")).Text); appElement.FindElement(By.TagName("p")).Text);
} }
[Fact]
public void CanTriggerKeyPressEvents()
{
var appElement = MountTestComponent<KeyPressEventComponent>();
Assert.Empty(appElement.FindElements(By.TagName("li")));
appElement.FindElement(By.TagName("input")).SendKeys("a");
Assert.Collection(appElement.FindElements(By.TagName("li")),
li => Assert.Equal("a", li.Text));
appElement.FindElement(By.TagName("input")).SendKeys("b");
Assert.Collection(appElement.FindElements(By.TagName("li")),
li => Assert.Equal("a", li.Text),
li => Assert.Equal("b", li.Text));
}
private IWebElement MountTestComponent<TComponent>() where TComponent: IComponent private IWebElement MountTestComponent<TComponent>() where TComponent: IComponent
{ {
var componentTypeName = typeof(TComponent).FullName; var componentTypeName = typeof(TComponent).FullName;

View File

@ -136,7 +136,7 @@ namespace Microsoft.Blazor.Test
{ {
// Arrange // Arrange
var builder = new UITreeBuilder(); var builder = new UITreeBuilder();
UIEventHandler eventHandler = () => { }; UIEventHandler eventHandler = eventInfo => { };
// Act // Act
builder.OpenElement("myelement"); // 0: <myelement builder.OpenElement("myelement"); // 0: <myelement
@ -180,7 +180,7 @@ namespace Microsoft.Blazor.Test
// Act/Assert // Act/Assert
Assert.Throws<InvalidOperationException>(() => Assert.Throws<InvalidOperationException>(() =>
{ {
builder.AddAttribute("name", () => { }); builder.AddAttribute("name", eventInfo => { });
}); });
} }
@ -210,7 +210,7 @@ namespace Microsoft.Blazor.Test
{ {
builder.OpenElement("some element"); builder.OpenElement("some element");
builder.AddText("hello"); builder.AddText("hello");
builder.AddAttribute("name", () => { }); builder.AddAttribute("name", eventInfo => { });
}); });
} }

View File

@ -27,7 +27,7 @@ namespace BasicTestApp
builder.CloseElement(); builder.CloseElement();
} }
private void OnButtonClicked() private void OnButtonClicked(UIEventInfo eventInfo)
{ {
currentCount++; currentCount++;
} }

View File

@ -0,0 +1,36 @@
// 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.Blazor.Components;
using Microsoft.Blazor.UITree;
using System.Collections.Generic;
namespace BasicTestApp
{
public class KeyPressEventComponent : IComponent
{
private List<string> keysPressed = new List<string>();
public void BuildUITree(UITreeBuilder builder)
{
builder.AddText("Type here:");
builder.OpenElement("input");
builder.AddAttribute("onkeypress", OnKeyPressed);
builder.CloseElement();
builder.OpenElement("ul");
foreach (var key in keysPressed)
{
builder.OpenElement("li");
builder.AddText(key);
builder.CloseElement();
}
builder.CloseElement();
}
private void OnKeyPressed(UIEventInfo eventInfo)
{
keysPressed.Add(((UIKeyboardEventInfo)eventInfo).Key);
}
}
}