0

I'm experimenting with Blazor. I want to make a hidden input box visible, then set the focus to the input box.

Here's the test page:

@page "/test"

<div>
    <button class="btn btn-success" @onclick="JumpToInputBox">Jump to Inputbox</button>
    <button class="btn btn-primary" @onclick="@HideInputBox">Hide Inputbox</button>
</div>

<p>&nbsp;</p>

<form>
    <input type="text" id="textinput" class=@InputBoxClass @ref=InputBox @bind="InputValue" />
</form>

<p>&nbsp;</p>

<p>Input: @InputValue</p>

My first, naive, attempt didn't work. Although it rendered the input box visible, it didn't set the focus to it. Here's that version of the code:

@code {

    private string? InputValue { get; set; }
    private ElementReference InputBox;

    private bool InputBoxIsVisible { get; set; } = false;

    private string InputBoxClass => InputBoxIsVisible ? "textbox-large show" : "textbox-large hide";

    private async Task JumpToInputBox()
    {
        InputBoxIsVisible = true;
        await InputBox.FocusAsync();
    }

    private void HideInputBox()
    {
        InputValue = null;
        InputBoxIsVisible = false;
    }
}

where the two CSS classes are:

.show {
    visibility: visible;
}

.hide {
    visibility: hidden;
}

I assume this didn't work because changing the input box class to make the input box visible caused the the page to re-render, and the JumpToInputBox method was trying to set the focus before the page finished rendering.

I came across the following article that describes a way to get around this problem. The fix is to pass actions that have to happen after rendering into the OnAfterRender method to execute:

https://swimburger.net/blog/dotnet/how-to-run-code-after-blazor-component-has-rendered

Based on this, I changed my code:

@code {

    private List<Func<Task>> _asyncActionsToRunAfterRender = new List<Func<Task>>();

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        foreach (var asyncActionToRun in _asyncActionsToRunAfterRender)
        {
            await asyncActionToRun();
        }
        _asyncActionsToRunAfterRender.Clear();

        await base.OnAfterRenderAsync(firstRender);
    }

    private string? InputValue { get; set; }
    private ElementReference InputBox;

    private bool InputBoxIsVisible { get; set; } = false;

    private string InputBoxClass => InputBoxIsVisible ? "textbox-large show" : "textbox-large hide";

    private void JumpToInputBox()
    {
        InputBoxIsVisible = true;
        _asyncActionsToRunAfterRender.Add(() => InputBox.FocusAsync().AsTask());
    }

    private void HideInputBox()
    {
        InputValue = null;
        InputBoxIsVisible = false;
    }
}

This now works but it seems a complex and messy way to wait until the page has rendered before calling await InputBox.FocusAsync(). Is there a cleaner way of waiting in the middle of a method until the page has rendered?

Simon Elms
  • 17,832
  • 21
  • 87
  • 103

0 Answers0