17
votes

I try to write a simple snake game on Blazor, but I can't find any way to bind my events on document.
There is an opportunity to bind events on different elements such as div or input.
Like for instance: <input onkeypress="@KeyPressInDiv"/>
Where public void KeyPressInDiv(UIKeyboardEventArgs ev) {....}
I suppose, that there should be some analogy for a javascript method document.onkeydown = function (evt) {} I have found several ways for working around this problem:
1) Use JS for binding and invoke blazor code(taken from https://github.com/aesalazar/AsteroidsWasm )

document.onkeydown = function (evt) {
  evt = evt || window.event;
  DotNet.invokeMethodAsync('Test.ClientSide', 'JsKeyDown', evt.keyCode);

  //Prevent all but F5 and F12
  if (evt.keyCode !== 116 && evt.keyCode !== 123)
      evt.preventDefault();
};

document.onkeyup = function (evt) {
  evt = evt || window.event;
  DotNet.invokeMethodAsync('Test.ClientSide', 'JsKeyUp', evt.keyCode);

  //Prevent all but F5 and F12
  if (evt.keyCode !== 116 && evt.keyCode !== 123)
      evt.preventDefault();
};

and in c# implement a static class with methods marked by [JSInvokable] and events.
This work, but lead to an awful delay on every press
2) This is possible to add input tag and bind event on it.
This will work really faster than the previous, but:
- it mainly looks like a huck then a solution
- we are not able to catch a number of actions such as Up/DownArrow

Is there a direct way for binding on documents events from blazor?
Update 1: I create simple project for better explanation what I try to achive. https://github.com/XelMed/BlazorSnake
There is 3 implementation of Snake:
1) pure js - this is an expected behavior
2) Using js with blazor - invoked blazor function from JS code with JsInterop
3) Using input tag - binding events on input tag for control Snake

3
How much of a delay are you seeing? When I do logging of keypress in JS and in the C# method that receives it, I do not see more then 1-2 millisecond difference. And that is with the logging statements - probably less without them.Ernie S

3 Answers

3
votes

Perhaps add a event listener to the document using JsInterop and assign a anonymus function to the event which calls your C# method with the even parameters.

For example your JsInterop.js:

document.addEventListener('onkeypress', function (e) {
    DotNet.invokeMethodAsync('Snake', 'OnKeyPress', serializeEvent(e))
});

with serializeEvent being as follos to avoid some quirkiness:

var serializeEvent = function (e) {
    if (e) {
        var o = {
            altKey: e.altKey,
            button: e.button,
            buttons: e.buttons,
            clientX: e.clientX,
            clientY: e.clientY,
            ctrlKey: e.ctrlKey,
            metaKey: e.metaKey,
            movementX: e.movementX,
            movementY: e.movementY,
            offsetX: e.offsetX,
            offsetY: e.offsetY,
            pageX: e.pageX,
            pageY: e.pageY,
            screenX: e.screenX,
            screenY: e.screenY,
            shiftKey: e.shiftKey
        };
        return o;
    }
};

in your C# code you would have:

[JSInvokable]
public static async Task OnMouseDown(UIMouseEventArgs e){
    // Do some stuff here
}
1
votes

I had the same requirement as you, and I managed to wire up document events (e.g. keydown) to my Razor methods using invokeMethodAsync, but then I found that I missed out on the automatic DOM diffing and updating that Blazor provides if the Razor method changes some state which is bound to an HTML element (e.g. controls the visibility of a div element).

It seems (from my limited understanding of Blazor) that normally Blazor returns whatever state data is necessary to update the DOM, in the return data of a WebAssembly method. But if you use invokeMethod or invokeMethodAsync from JavaScript, you have to manage returning and consuming this data yourself, but that might be problematic because your updates might conflict with Blazor's view of the DOM state.

So I came up with a hacky approach of generating a hidden button in my Razor view, e.g.:

<button id="arrow-left-button" @onclick="HandleArrowLeftPress"></button>

And then on the JavaScript side, I wired up a document event which finds that button by Id and calls .click() on it:

document.getElementById('arrow-left-button').click();

I know this seems truly awful, but it worked for what I was doing, which was using the arrow keys to move an absolutely positioned element on screen.

If anyone knows a cleaner way (like how to force an update of a Razor view by its name from the JavaScript side, in the handler callback), please let me know.

-1
votes

You can bind the event directly to a C# method, just using the event tag that you need (onkeyup/onmousemove ....) .

@page "/"

<div>
    <input type="text" onkeyup=@KeyUp />
</div>
@functions {
    void KeyUp(UIKeyboardEventArgs e)
    {
        Console.WriteLine(e.Key);
    }

    protected override Task OnInitAsync()
    {
        return Task.CompletedTask;
    }
}