diff --git a/src/Microsoft.Blazor.Browser.JS/src/Rendering/Renderer.ts b/src/Microsoft.Blazor.Browser.JS/src/Rendering/Renderer.ts index 562e52cd82..afb96e8ab7 100644 --- a/src/Microsoft.Blazor.Browser.JS/src/Rendering/Renderer.ts +++ b/src/Microsoft.Blazor.Browser.JS/src/Rendering/Renderer.ts @@ -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'; diff --git a/src/Microsoft.Blazor.Browser/DOMComponentRenderState.cs b/src/Microsoft.Blazor.Browser/DOMComponentRenderState.cs index eb1b4ba916..67720c73c6 100644 --- a/src/Microsoft.Blazor.Browser/DOMComponentRenderState.cs +++ b/src/Microsoft.Blazor.Browser/DOMComponentRenderState.cs @@ -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(); } diff --git a/src/Microsoft.Blazor.Browser/Events.cs b/src/Microsoft.Blazor.Browser/Events.cs index 34a33652fa..a50e13fa6d 100644 --- a/src/Microsoft.Blazor.Browser/Events.cs +++ b/src/Microsoft.Blazor.Browser/Events.cs @@ -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(eventInfoJson); + case "keyboard": + return Json.Deserialize(eventInfoJson); + default: + throw new ArgumentException($"Unsupported value '{eventInfoType}'.", nameof(eventInfoType)); + } } } } diff --git a/src/Microsoft.Blazor/UITree/UIEventHandler.cs b/src/Microsoft.Blazor/UITree/UIEventHandler.cs index 35a645c652..550f38841e 100644 --- a/src/Microsoft.Blazor/UITree/UIEventHandler.cs +++ b/src/Microsoft.Blazor/UITree/UIEventHandler.cs @@ -6,5 +6,5 @@ namespace Microsoft.Blazor.UITree /// /// Handles an event raised for a . /// - public delegate void UIEventHandler(); + public delegate void UIEventHandler(UIEventInfo eventInfo); } diff --git a/src/Microsoft.Blazor/UITree/UIEventInfo.cs b/src/Microsoft.Blazor/UITree/UIEventInfo.cs new file mode 100644 index 0000000000..b61ce0e673 --- /dev/null +++ b/src/Microsoft.Blazor/UITree/UIEventInfo.cs @@ -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 +{ + /// + /// Supplies information about an event that is being raised. + /// + public class UIEventInfo + { + /// + /// Gets or sets the type of the event. + /// + public string Type { get; set; } + } + + /// + /// Supplies information about a mouse event that is being raised. + /// + public class UIMouseEventInfo : UIEventInfo + { + } + + /// + /// Supplies information about a keyboard event that is being raised. + /// + public class UIKeyboardEventInfo : UIEventInfo + { + /// + /// If applicable, gets or sets the key that produced the event. + /// + public string Key { get; set; } + } +} diff --git a/test/Microsoft.Blazor.E2ETest/Tests/ComponentRenderingTest.cs b/test/Microsoft.Blazor.E2ETest/Tests/ComponentRenderingTest.cs index 6474384330..597da422c7 100644 --- a/test/Microsoft.Blazor.E2ETest/Tests/ComponentRenderingTest.cs +++ b/test/Microsoft.Blazor.E2ETest/Tests/ComponentRenderingTest.cs @@ -60,6 +60,23 @@ namespace Microsoft.Blazor.E2ETest.Tests appElement.FindElement(By.TagName("p")).Text); } + [Fact] + public void CanTriggerKeyPressEvents() + { + var appElement = MountTestComponent(); + + 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() where TComponent: IComponent { var componentTypeName = typeof(TComponent).FullName; diff --git a/test/Microsoft.Blazor.Test/UITreeBuilderTest.cs b/test/Microsoft.Blazor.Test/UITreeBuilderTest.cs index c4fe80a9b1..92e5833abd 100644 --- a/test/Microsoft.Blazor.Test/UITreeBuilderTest.cs +++ b/test/Microsoft.Blazor.Test/UITreeBuilderTest.cs @@ -136,7 +136,7 @@ namespace Microsoft.Blazor.Test { // Arrange var builder = new UITreeBuilder(); - UIEventHandler eventHandler = () => { }; + UIEventHandler eventHandler = eventInfo => { }; // Act builder.OpenElement("myelement"); // 0: (() => { - 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 => { }); }); } diff --git a/test/testapps/BasicTestApp/CounterComponent.cs b/test/testapps/BasicTestApp/CounterComponent.cs index cbb1446275..895bc2d277 100644 --- a/test/testapps/BasicTestApp/CounterComponent.cs +++ b/test/testapps/BasicTestApp/CounterComponent.cs @@ -27,7 +27,7 @@ namespace BasicTestApp builder.CloseElement(); } - private void OnButtonClicked() + private void OnButtonClicked(UIEventInfo eventInfo) { currentCount++; } diff --git a/test/testapps/BasicTestApp/KeyPressEventComponent.cs b/test/testapps/BasicTestApp/KeyPressEventComponent.cs new file mode 100644 index 0000000000..330ade40bc --- /dev/null +++ b/test/testapps/BasicTestApp/KeyPressEventComponent.cs @@ -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 keysPressed = new List(); + + 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); + } + } +}