Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bind on document events with blazor

I am writing a simple snake game with Blazor, but I can't figure out how to bind to document events. I know that it is possible to bind events on different elements such as div or input. Example: <input onkeypress="@KeyPressInDiv"/>, where the handler is public void KeyPressInDiv(UIKeyboardEventArgs ev) {...}.

I suppose that there should be some equivalent to the JavaScript method document.onkeydown = function (evt) {}. I have found two approaches for working around this problem:

  1. Use JavaScript 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 works, but leads to an extreme delay on every key press.

  1. It is possible to add an input tag and bind to its events. This works much faster than the previous approach, but it seems like a hack rather then a solution. Also, we are not able to listen for some actions, such as Up/Down Arrow.

Is there a direct way to bind to document events from Blazor?

Update 1: I created a simple project to better explain what I want to achieve: https://github.com/XelMed/BlazorSnake There are 3 implementations of Snake:

  1. Pure JS - this has the expected behavior
  2. Using JS with Blazor - invoke a Blazor function from JS code with a JsInterop
  3. Using input tag - bind to events on an input tag to control the snake
like image 530
XelMed Avatar asked Mar 26 '19 10:03

XelMed


People also ask

How do you bind a value in Blazor?

Bind a property or field on other Document Object Model (DOM) events by including an @bind:event="{EVENT}" attribute with a DOM event for the {EVENT} placeholder. The following example binds the InputValue property to the <input> element's value when the element's oninput event ( input ) is triggered.

How do you get the target element onclick event in Blazor?

Blazor does not have support to manipulate DOM elements directly, but we can still achieve it by using JavaScript interop. By creating a reference to an element, we can send it as a parameter to the JavaScript method via interop.

Is Blazor two-way binding?

Blazor also supports two-way data binding by using bind attribute. Currently, Blazor supports only the following data types for two-way data binding. If you need other types (e.g. decimal), you need to provide getter and setter from/to a supported type.


2 Answers

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
}
like image 180
Blightbuster Avatar answered Oct 19 '22 08:10

Blightbuster


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.

like image 40
Tom W Hall Avatar answered Oct 19 '22 08:10

Tom W Hall