29

Using the example from the Microsoft docs, I'm trying to programmatically set the focus to an input element.

Unfortunately, the example uses a standard <input type="text"> whereas I want to use it for an InputText element.

The Microsoft example uses an extensions method that takes an ElementReference:

public static Task Focus(this ElementReference elementRef, IJSRuntime jsRuntime)
{
    return jsRuntime.InvokeAsync<object>(
        "exampleJsFunctions.focusElement", 
        elementRef);
}

Using an InputText, I see no way of obtaining such an ElementReference.

Providing my own Focus() overload with an InputText instead, compiled but showed no visual result. Therefore I'm clueless.

My question

How can I programmatically set the focus to an InputText element?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
  • To save a soon-to-be deleted answer that should have been a comment instead, here the comment of [Mihajlo Naumovski](https://stackoverflow.com/users/2855167/mihajlo-naumovski): "_Never forget in order to have FocusAsync working in Blazor on HTML element like 'div' you have to assign tabindex to it._". – Uwe Keim Dec 04 '21 at 10:12

10 Answers10

32

In .NET5 it will be much simpler:

<button @onclick="() => textInput.FocusAsync()">Set focus</button>
<input @ref="textInput"/>
@code {
    ElementReference textInput;
}

NOTE: this feature was introduced in .NET5 Preview 8 so might change until the final release!

Also worth mentioning that in .NET5 RC1 JavaScript isolation was introduced via JS Module export/import. So if you still need to use JS interop do not pollute window object.

Update: .NET 5 was released and this feature works unchanged.

Also I have a cool Nuget package which can do some convenient JS tricks for you e.g.: focusing previously active element without having a @ref to it. See docs here.

Major
  • 5,948
  • 2
  • 45
  • 60
  • 6
    It seems that you are the owner of the NuGet package you mention. Correct? – Uwe Keim Dec 17 '20 at 14:17
  • 1
    Please state your affiliation with this nuget package https://meta.stackoverflow.com/questions/308002/is-self-promoting-a-product-on-stack-overflow-allowed – TheGeneral Mar 31 '22 at 05:44
25

For .NET 6 and .NET 7:

In your code, create an InputText variable, either in @code, or behind code:

private InputText inputTextForFocus;

In the Blazor InputText component, reference that variable:

<InputText @ref=inputTextForFocus>

Then, in your code, at the appropriate place, you can access the underlying Element of the InputText and call FocusAsync(). Note Element can be null.

For instance, to set focus on startup:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (inputTextForFocus?.Element != null)
    {
        await inputTextForFocus.Element.Value.FocusAsync();
    }
}

Credit where credit is due: https://www.youtube.com/watch?v=9Q7cXdylU7k

James McCormack
  • 9,217
  • 3
  • 47
  • 57
Mmm
  • 682
  • 9
  • 11
22

You can add id parameter to your InputText and modify your Focus method and JavaScript code.

public async Task Focus(string elementId)
{
    await JSRuntime.InvokeVoidAsync("exampleJsFunctions.focusElement", elementId);
}
focusElement: function (id) {
    const element = document.getElementById(id); 
    element.focus();
}

Note: this is more a workaround than a proper solution, but Blazor doesn't seem to support it directly.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Piotr L
  • 939
  • 1
  • 6
  • 12
5

I was able to accomplish this by entering the JavaScript directly in the Host.chtml file (you can also add a .js file like the walkthrough suggests):

<script type="text/javascript">
    window.exampleJsFunctions = {
        focusElement: function (element) {
            element.focus();
        }
    }
</script>

Next, in an extension file, I've added the extension (NOTE: I renamed Focus to FocusAsync because of naming standards:

public static async Task FocusAsync(this ElementReference elementRef, IJSRuntime jsRuntime)
{
    await jsRuntime.InvokeVoidAsync(
        "exampleJsFunctions.focusElement", elementRef);
}

Then in the razor page (or component), inject the IJSRuntime, Add an ElementReference and tie it to the element you want to focus (Note: Method names were changed to abide to naming standards):

@inject IJSRuntime JSRuntime
@using JsInteropClasses

<input @ref="username" />
<button @onclick="SetFocusAsync">Set focus on username</button>

@code {
    private ElementReference username;

    public async Task SetFocusAsync()
    {
        await username.FocusAsync(JSRuntime);
    }
}
Ryan Thiele
  • 364
  • 2
  • 7
4

Here's a simple way to componentise the solution

Example

@page "/"
Enter your name
<input @ref=ReferenceToInputControl />
<AutoFocus Control=ReferenceToInputControl/>

@code {
  ElementReference ReferenceToInputControl;
}

And here's the code for .NET 7

@code {
    [Parameter]
    public ElementReference Control { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
            await Control.FocusAsync();
    }
}

If you are in .NET 6 or earlier

@inject IJSRuntime JSRuntime
@code {
    [Parameter]
    public ElementReference Control { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
            await JSRuntime.InvokeVoidAsync("BlazorUniversity.setFocus", Control);
    }
}

With the following JavaScript referenced in your page.

var BlazorUniversity = BlazorUniversity || {};
BlazorUniversity.setFocus = function (element) {
    element.focus();
};
Peter Morris
  • 20,174
  • 9
  • 81
  • 146
3

There are a couple of ways to set focus on an element the Blazor native way. Here's one:

Create a class that derives from the InputBase<string> which is the base class of InputText with the same functionality of InputText. In short, copy the code of InputText to your newly created class, and add the necessary functionality. Here's the new class: TextBox.cs

public class TextBox : InputBase<string>
    {

        private ElementReference InputRef;
        protected override void BuildRenderTree(RenderTreeBuilder builder)
    {

            builder.OpenElement(0, "input");
    builder.AddMultipleAttributes(1, AdditionalAttributes);
    builder.AddAttribute(2, "class", CssClass);
    builder.AddAttribute(3, "value", BindConverter.FormatValue(CurrentValue));
    builder.AddAttribute(4, "onchange", EventCallback.Factory.CreateBinder<string>
        (this, __value => CurrentValueAsString = __value, CurrentValueAsString));
    builder.AddElementReferenceCapture(5, (value) => {
                InputRef = value; } );


            builder.CloseElement();


    }
        [Inject] IJSRuntime JSRuntime { get; set; }

        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                await JSRuntime.InvokeVoidAsync("exampleJsFunctions.focusElement", InputRef);
            }
        }

        protected override bool TryParseValueFromString(string value, out string result, out string validationErrorMessage)
        {
        result = value;
        validationErrorMessage = null;
        return true;
        }
        }

   } 

Place this script at the bottom of the _Host.cshtml file, just below

<script src="_framework/blazor.server.js"></script>

<script>

        window.exampleJsFunctions =
        {
            focusElement: function (element) {
               element.focus();
            }
        };
    </script>

Things to note: 1. Define an ElementReference variable to hold reference to the input element. 2. In the BuildRenderTree method I've added code to capture a reference to the input element 3. Call the focusElement JavaScript function from the OnAfterRenderAsync method. This is performed only once. Note that I cannot use the OnInitializedAsync method which is executed only once, but the ElementReference variable may contain null. 4. Note that you cannot run any of the forms components without EditForm...

IMPORTANT: Pressing Ctrl+F5, when your browser is in a minimized state may interfere with seeing the cursor in the text element.

Code for usage:

<EditForm  Model="@employee" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <TextBox @bind-Value="@employee.Name"/>

    <button type="submit">Submit</button>
</EditForm>

@code {
    private Employee employee = new Employee();

    private void HandleValidSubmit()
    {
        Console.WriteLine("OnValidSubmit");
    }

    public class Employee
    {
        public int ID { get; set; } = 1;
        public string Name { get; set; } = "Nancy";
    }
} 
enet
  • 41,195
  • 5
  • 76
  • 113
2

Possibly redundant now .net 5 is out, but if you don't want to hard code an element's Id or derive your own input class, an easy way to achieve this is to just add a CSS class to the element, and then find and set focus to that using getElementsByClassName.

Create a script to contain the helper:

var JSHelpers = JSHelpers || {};

JSHelpers.setFocusByCSSClass = function () {
    var elems = document.getElementsByClassName("autofocus");
    if (elems.length == 0)
        return;
    elems[0].focus();
};

Create a shared 'AutofocusByCSS' component to handle calling the JS at the right time:

@inject IJSRuntime JSRuntime
@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender) JSRuntime.InvokeVoidAsync("JSHelpers.setFocusByCSSClass");
    }
}   

Then in your page:

<InputText @bind-Value="editmodel.someThing" class="form-control autofocus" />

<AutofocusByCSS />

Works on anything that boils down to a standard HTML element and makes it easier to change the autofocus target by changing the class name output.

Daz
  • 2,833
  • 2
  • 23
  • 30
  • There's no such thing as a "CSS class." I think you just mean "element class." Element classes can be used in either CSS or JavaScript. – mfluehr Dec 17 '20 at 14:03
2

i use this one for InputText

 await txtUsername.Element!.Value.FocusAsync();

and this for input

 await txtUsername.FocusAsync();

#ref

hossein sedighian
  • 1,711
  • 1
  • 13
  • 16
1

Similar to @enet's answer, I inherit from InputBase, however not in a .razor file but in a .cs file:

public abstract class MyInputBase<T> : InputBase<T>
{
    /// <summary>
    /// If true, this element will be focused when the page loads
    /// </summary>
    [Parameter] public bool Autofocus { get; set; }

    /// <summary>
    /// Optional. Sets the id on the input element as well as the for attibute on the label element
    /// </summary>
    [Parameter] public string? ElementId { get; set; }


    [Inject] IJSRuntime? Js { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            if (Autofocus) await Js.InvokeVoidAsync("cs.focusElementById", ElementId);
        }
        await base.OnAfterRenderAsync(firstRender);
    }

    protected override void OnInitialized()
    {
        if (string.IsNullOrWhiteSpace(ElementId)) ElementId = Guid.NewGuid().ToString();

        base.OnInitialized();
    }
}

And my .js:

var cs = {

    focusElementById: function (id) {
        setTimeout(function () {
            var el = document.getElementById(id);
            if (el) el.focus();
        }, 500);
    }

}

Couple notes. One, MyInputBase<T> is abstract, which pushes down the TryParseValue to my individual implementations of InputText, InputNumber, etc, which inherit MyInputBase<T> rather than Microsoft's InputBase<T>.

Also, note that I set a timeout in the javascript. At least for me, if I don't do that, focusing the element doesn't work.

MikeT
  • 2,530
  • 25
  • 36
1

easy

@code {
   private ElementReference yourRefID;
}

<button @onclick="async () => await yourRefID.FocusAsync()">Set focus</button>

<input @ref="yourRefID"/>

you may need to do a Task.Delay if you are opening a modal prior.

Xavier
  • 87
  • 1
  • 4