Blazor binding inconsistent behaviour

Viewed 203

I am building a blazor app (WASM) where I use some <input>'s with those inputs. to both I binded a variable, for easy overview I put that in the @code part of the (index)page.

on the second <input> I also put an onclick event, and in the onclick event I fill a third variable (ThirdText) with the first (FirstText).

When I start the application, and I click on the first <input>, and fill in some characters and then click on the second <input> the value of the third variable is still empty. But when I go back to the first, change a bit, and click on the second afterwards, it directly fills the NEW value.

The last bahaviour is what I also hoped for in the first situation.

Here I have my code in index.cs:

@page "/"

<h3>@FirstText</h3> <br/>
<h3>@SecondText</h3> <br/>
<h3>@ThirdText</h3> <br/>

<input @bind="FirstText" style="width:50%" /> <br/>
<input @bind="SecondText" @onclick="OnclickHandler" style="width:50%" /> <br/>

@code{
    private string FirstText { get; set; } = string.Empty;
    private string SecondText { get; set; } = string.Empty;
    private string ThirdText { get; set; } = string.Empty;

    void OnclickHandler(MouseEventArgs mouseEventArgs)
    {
        ThirdText = FirstText;
    }
}

This code is an easy example, the real application has a lot more logic, but this is the base of my problem.

When I make the first box empty, and click on the second box, again it does not work as expected.

I already tried to first fill the 3th variable in oninitialised() , but then the first variable is also empty so, that does not work.

I know in javascript there is a simular problem, that is because the onclick is firstly called, and the onexit or other event you want to use on the exit of the first box is then forgotten. But in that case, it goes wrong al the time. I hope that blazor has a solution for this situation. Because it goes well in Blazor if the first input was already filled.

3 Answers

The reason your code is not working is because of the timing of the click event:

If the button is pressed on one element and the pointer is moved outside the element before the button is released, the event is fired on the most specific ancestor element that contained both elements.

click fires after both the mousedown and mouseup events have fired, in that order.

source

Long story short, by the time mouseup (or even the mousedown for that matter) occurs, the layout has changed and input element has shifted outside of the range of the mouse pointer. For example, change your code to the following and you will see it works now because the elements do not shift:

<h3>AAA @FirstText</h3> <br/>
<h3>AAA @SecondText</h3> <br/>
<h3>AAA @ThirdText</h3> <br/>

The solution to your problem would depend on what you are trying to achieve, but the basic idea here is to not rely on click events when layout changes occur.

To really prove what is happening, in Chrome Dev Tools go to Sources > Event Listener Breakpoints > Mouse > click and make sure the checkbox is checked. After that, type something in the first input and then click the second input element. The click event will fire, however notice the event target in the below screenshot! It is not the input element but the parent element <article>. This proves that the click event was never dispatched to the input element because it is not underneath the mouse pointer when the mouseup event occurs.

enter image description here

#Third Answer:

Here's a test page to capture the problem in it's simplest form.

I've removed most of the original code and made it a simple data entry and save form - without using EditForm.

In the current configuration SaveData isn't called when you enter your name and click on the save button.

Comment out the line <h3>@this.YourName</h3> before the form and it now works. Note there's a second <h3>@this.YourName</h3> below the form that has no impact.

Now change the line to:

<h3>Your name is: @this.YourName</h3>

and it works.

The answer lies in the answer provided by @JesseGood To quote his comment:

When you remove the single line of code, the elements do not shift down anymore so the click event occurs on the button. However when you add the line of code, the element becomes populated causing all the other elements to shift down and by the time you lift your finger off of the mouse the element is no longer under your mouse pointer causing the click event to occur on the parent element.

@page "/"

@using System.Text

<PageTitle>Index</PageTitle>

@*Comment out this line and the event triggers on first click*@
<h3>@this.YourName</h3>

<h3>Your Name is @DisplayName</h3>

<div class="m-2">
    Your Name: <input value="@this.YourName" @onchange=this.NameChange style="width:50%" />
</div>
<div class="m-2">
    <button class="btn btn-primary" @onclick="SaveData">Save</button> <br />
</div>

<h3>@YourName</h3>

<div>
    <pre>
        @log.ToString()
    </pre>
</div>
@code {
    private string YourName { get; set; } = string.Empty;
    private StringBuilder log = new StringBuilder();
    private string DisplayName = string.Empty;
    
    void NameChange(ChangeEventArgs e)
    {
        this.YourName = e.Value?.ToString() ?? String.Empty;
        log.AppendLine("FirstChange Called");
    }

    void SaveData(MouseEventArgs mouseEventArgs)
    {
        this.DisplayName = this.YourName;
        log.AppendLine("Value Saved");
    }
}

Follow on answer:

When you "bind" in Razor, the Razor compiler actually builds out the following code. In the example I've just wired it up manually so I can output some debug code when the onchange event is raised.

__builder.OpenElement(15, "input");
__builder.AddAttribute(16, "style", "width:50%");
__builder.AddAttribute(17, "value", Microsoft.AspNetCore.Components.BindConverter.FormatValue(this.FirstText));
__builder.AddAttribute(18, "onchange", Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => this.FirstText = __value, this.FirstText));
__builder.SetUpdatesAttributeName("value");
__builder.CloseElement();

First and second Answer replaced

You describe the current behaviour but at the same time you are a bit vague about what you expect. As far as I can tell the code does what you ask for.

And what do you expect (want) to happen when a User uses the Tab key to go to the next input? Is it about mouseclicking or about completing an input?

I think you should consider one of these two changes:

<input @bind="FirstText" style="width:50%" /> <br/>
<input @bind="SecondText" @onfocus="OnUpdateHandler" style="width:50%" />

or, and this seems more logical:

<input @bind="FirstText" @onblur="OnUpdateHandler" style="width:50%" />
<input @bind="SecondText"  style="width:50%" />

with

void OnUpdateHandler()
{
    ThirdText = FirstText;
}

If that doesn't work then be more specific: when/why do you want ThirdText to mirror FirstText?

Related