Search code examples
blazorblazor-webassembly

Blazor requires double function calls to select parts of text - why?


I try to create custom component for DateOnly. I would like to select a part of the date that is active like belowe:

enter image description here

Now i want to change my CurrentValue (date) by 1 day if I press the button ArrowUp or ArrowDown and than i want to select some text.

private async Task OnKeyDownEvent(KeyboardEventArgs e)
        {
        preventDefault = true;

        switch(e.Code)
            {
            case "ArrowUp":
                {
                if(CurrentValue != null)
                    {
                    CurrentValue = CurrentValue.Value.AddDays(1);
                    }
                break;
                }
            case "ArrowDown":
                {
                if(CurrentValue != null)
                    {
                    CurrentValue = CurrentValue.Value.AddDays(-1);
                    }
                break;
                }
            }
        await JsRuntime.InvokeVoidAsync("SelectionPartOfText", ReferenceToInputDate, 0, 2);
        await JsRuntime.InvokeVoidAsync("SelectionPartOfText", ReferenceToInputDate, 0, 2); //<- WHY I HAVE TO INVOKE THIS DOUBLE TIMES??
        }

The problem is that if I don't call SelectionPartOfText twice as above, the date part will not be selected. Could You tell me why?

If I call Select Part Of Date at the beginning and then at the end of the OnKeyDownEvent function, it will be work too, but why i have to invoke twice?

Code belowe works too:

private async Task OnKeyDownEvent(KeyboardEventArgs e)
        {
        preventDefault = true;

        await JsRuntime.InvokeVoidAsync("SelectionPartOfText", ReferenceToInputDate, 0, 2); //<-- FIRST (if I remove this part the function will stop working!)

        switch(e.Code)
            {
            case "ArrowUp":
                {
                if(CurrentValue != null)
                    {
                    CurrentValue = CurrentValue.Value.AddDays(1);
                    }
                break;
                }
            case "ArrowDown":
                {
                if(CurrentValue != null)
                    {
                    CurrentValue = CurrentValue.Value.AddDays(-1);
                
                    }
                break;
                }
            }
        await JsRuntime.InvokeVoidAsync("SelectionPartOfText", ReferenceToInputDate, 0, 2); //<- SECOUND (WHY I HAVE TO INVOKE THIS DOUBLE TIMES??)
        }

Solution

  • When CurrentValue changes, the input field content may be re-rendered, and the text selection will be reset, so it needs to be called twice. You can try to use await Task.Delay(50); in the KeyboardEvent to add a small delay to ensure that the date is updated before executing:

     private async Task OnKeyDownEvent(KeyboardEventArgs e)
     {
         if (e.Key == "ArrowUp")
         {
             CurrentValue = CurrentValue.AddDays(1);
         }
         else if (e.Key == "ArrowDown")
         {
             CurrentValue = CurrentValue.AddDays(-1);
         }
         await Task.Delay(50);
        
         await JsRuntime.InvokeVoidAsync("selectDatePart", inputDateElement, 0, 2);
     }
    

    You can also do this with dateInput, so that by default you can use the up and down keys to change the date. Then you can bind the onkeydown event to select the date in the input box when it is triggered. Here is an example for your reference:

    <input type="date" @ref="dateInput" @onkeydown="OnKeyDown" value="@currentDate.ToString("yyyy-MM-dd")" />
    
    @code {
        private ElementReference dateInput;
        private DateTime currentDate;
    
        protected override async Task OnInitializedAsync()
        {
          
            currentDate = DateTime.Now.Date;
        }
    
        private async Task OnKeyDown(KeyboardEventArgs e)
        {
           
            await JsRuntime.InvokeVoidAsync("selectDate", dateInput.Id);
        }
    
        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                
                await JsRuntime.InvokeVoidAsync("initializeDateSelection");
            }
        }
    }
    

    JS:

    function initializeDateSelection() {
        const dateInputs = document.querySelectorAll('input[type="date"]');
        dateInputs.forEach(input => {
            input.addEventListener('keydown', function (event) {
               
                if (event.key === "Enter" || event.key === " ") {
                    selectDate(input.id);
                }
            });
        });
    }
    
    function selectDate(inputId) {
        const input = document.getElementById(inputId);
    
        if (input) {
    
            input.select();
        }
    }
    

    When I change the date:

    enter image description here