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:
So Input will be (| = Cursor): H| > |H > |H
Which means the Cursor is wrong and the "i" was prevented.
Any ideas? Thanks in advice.
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.
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 static
s.
So the final flow is following:
subscribeToChange
JS method, which subscribes to events occurring on our input element.HelloFromJS
in C#.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();
}
(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;
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With