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) {
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;
default:
// 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) {
raiseEventMethod = platform.findMethod(
'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.
platform.callMethod(raiseEventMethod, null, [
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);
}
}
type EventInfoType = 'mouse' | 'keyboard';

View File

@ -73,7 +73,7 @@ namespace Microsoft.Blazor.Browser
return _uITreeBuilder.GetNodes();
}
public void RaiseEvent(int uiTreeNodeIndex)
public void RaiseEvent(int uiTreeNodeIndex, UIEventInfo eventInfo)
{
var nodes = _uITreeBuilder.GetNodes();
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)}.");
}
eventHandler.Invoke();
eventHandler.Invoke(eventInfo);
RenderToDOM();
}

View File

@ -1,18 +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.Browser.Interop;
using Microsoft.Blazor.UITree;
using System;
namespace Microsoft.Blazor.Browser
{
// Invoked by the Microsoft.Blazor.Browser.JS code when a DOM event occurs
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
// 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.
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>
/// Handles an event raised for a <see cref="UITreeNode"/>.
/// </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);
}
[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
{
var componentTypeName = typeof(TComponent).FullName;

View File

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

View File

@ -27,7 +27,7 @@ namespace BasicTestApp
builder.CloseElement();
}
private void OnButtonClicked()
private void OnButtonClicked(UIEventInfo eventInfo)
{
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);
}
}
}