Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Blazor: How to prevent specific key on input like in JS with e.preventDefault()?

Tags:

c#

blazor

The problem seems very simple, but I didn't found any solutions yet. I have a Blazor Input with an onkeydown event:

<input @onkeydown="@(e => KeyWasPressed(e))" 
@onkeydown:preventDefault="@PreventDefault" 
id="@GetId()" 
name="@GetId()" 
@bind-value="@InputValue" 
@bind-value:event="oninput" />

The User should write text, but with the arrow keys the user should navigate within a list (so I try to prevent the cursor to move to the top and the end of the text).

In JavaScript this could be something like this:

function KeyWasPressed(e)
{
    // key down
    if (e.keyCode == 40)
    {
        e.preventDefault();
        // some work...
    }
    // key up
    else if (e.keyCode == 38)
    {
        e.preventDefault();
        // some work...
    }
}

How to do this in Blazor? With @onkeydown:preventDefault you can prevent the whole input. If I set it to a variable (@PreventDefault), I can prevent only the next input (because the first input already happened). Just for understanding what I mean:

  • PreventDefault FALSE > Input "H" > set PreventDefault to FALSE
  • PreventDefault FALSE > Input "ArrowUp" > set PreventDefault to TRUE
  • PreventDefault TRUE > Input "i" > set PreventDefault to FALSE

So Input will be (| = Cursor): H| > |H > |H

Which means the Cursor is wrong and the "i" was prevented.

Any ideas? Thanks in advice.

like image 787
StewieG Avatar asked Feb 22 '20 01:02

StewieG


3 Answers

Unfortunately there is no easy solution for this at the moment. For that scenario you would still need to use a JS event handler and probably some interop if you want to call back into your .NET code.

You will find a very brief comment from Steve Sanderson about the reason (async handlers) here https://github.com/dotnet/aspnetcore/issues/14517#issuecomment-559184498

Another workaround is to bind your input to a variable and update the values manually. (also pointed out here PreventDefault on Blazor input ) But this is probably a bumpy road.

Personally, I would love to see the preventDefault and stopPropagation as part of the EventArgs that are passed into the C# method handlers. I assume that this is technically challenging. However, it would simplify a lot of use cases. You might want to open a issue for this over at aspnetcore.

like image 102
Postlagerkarte Avatar answered Oct 19 '22 02:10

Postlagerkarte


My solution is following.

In JS create following method. It will catch the UP and DOWN keystrokes.

<script>
    function subscribeToChange(componentRef, inputRef) {
        console.log("subscribeToChange!");

        inputRef.onkeydown = function (event) {
            if (event.keyCode == "38") {
                event.preventDefault();

                console.log("Up key pressed");
                componentRef.invokeMethodAsync('invokeFromJS');
            }
            else if (event.keyCode == "40") {
                event.preventDefault();

                console.log("Down key pressed");
                componentRef.invokeMethodAsync('invokeFromJS');
            }
        };

        inputRef.onkeyup = function (event) {
            componentRef.invokeMethodAsync('setValueFromJS', inputRef.value);
        };
    }
</script>

in razor:

<input @ref="Typing3" />
@HelloFromJSStr

in razor.cs:

    public ElementReference Typing3;
    public string HelloFromJSStr = "";

    [JSInvokable("invokeFromJS")]
    public async Task HelloFromJS()
    {
        //HelloFromJSStr += "A";
        //StateHasChanged();
        // do something here on UP or DOWN button presses..
    }

    [JSInvokable("setValueFromJS")]
    public async Task SetValueFromJS(string newValue)
    {
        HelloFromJSStr = newValue;
        StateHasChanged();
    }

Here in my example you have 2 methods. One that is triggered onkeyup could be used to set final the value to the property (HelloFromJSStr ). If any other UI element needs a re-render then call StateHasChanged() method afterwards as in example. Rest is up to your imagination.

The remaining code part is initialization:

public partial class EventsAndRendering
{
    [Inject]
    protected IJSRuntime JSRuntime { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        Console.WriteLine($"{this.GetType().Name}: OnAfterRenderAsync. firstRender: {firstRender}");

        if (firstRender)
        {
            var componentRef = DotNetObjectReference.Create(this);

            await JSRuntime.InvokeAsync<object>("subscribeToChange", new object[] { componentRef, Typing3 });
        }
    }

    // HelloFromJS()
    // SetValueFromJS(string newValue)
}

DotNetObjectReference is what allows us to invoke C# code from JS, without requiring to make the methods statics.

So the final flow is following:

  1. after the page is rendered first time -> we create a reference to our main component as well as the reference to the inpout element.
  2. we then use the above mentioned references to invoke the subscribeToChange JS method, which subscribes to events occurring on our input element.
  3. Upon a keydown event we trigger method HelloFromJS in C#.
  4. Upon a keyup event we trigger method SetValueFromJS in C#.

all above is just an example and must be adjusted per needs :)

inspiration: https://learn.microsoft.com/en-us/aspnet/core/blazor/call-dotnet-from-javascript?view=aspnetcore-5.0#instance-method-call

edit: consider implementing IDisposable interface to dispose of .Net object:

public void Dispose()
{
    componentRef?.Dispose();
}
like image 20
Alex Avatar answered Oct 19 '22 02:10

Alex


(Years late but maybe this will help someone else.)

drocha87 pointed out that this method only works with WebAssembly (wasm).

The onkeydown even fires before the default so we can disable it immediately before using @onkeydown:preventDefault=true. The Idea for this came from here.

Note:

<input @onkeydown="KeyWasPressed"  @onkeydown:preventDefault="preventDefault" @bind-value="@InputValue" />

@code  {
  bool preventDefault = false;
  string InputValue = "";

  private void KeyWasPressed(KeyboardEventArgs e)
  {
      // key down
      if (e.Key == "ArrowDown")
      {
          preventDefault = true;
          // some work...
      }
      // key up
      else if (e.Key == "ArrowUp")
      {
          preventDefault = true;
          // some work...
      }
      else 
      {
          preventDefault = false;
      }
  }  
}
like image 2
SunsetQuest Avatar answered Oct 19 '22 01:10

SunsetQuest