Aspnetcore: Blazor does not see a change if they are made through JavaScript

Created on 11 Jul 2018  Â·  12Comments  Â·  Source: dotnet/aspnetcore

Hello, if you make changes through JavaScript, then in blazor the data does not change.
Example: https://youtu.be/kHkkLWsMSF8

@page "/testpage"
@layout TestLayout

<div>
    <select bind="@value" style="width: 300px;">
        <option value="None" selected="">None</option>
        <option value="GoogleAuth">Google Authenticator</option>
        <option value="Email">Email</option>
        <option value="Sms">Sms</option>
    </select>
</div>

<div style="position:  absolute; margin-top: 25px;">
    <b>bind</b>: @value
</div>

@functions
{
    string value;
}
area-blazor

Most helpful comment

@RyoukoKonpaku , it works for me.

.cshtml

<select class="form-control" bind="@md.Auth2FA" onchange="selectChange(this)" data-plugin="select2">
        <option value="None" selected="">None</option>
        <option value="Email">Email</option>
        <option value="GoogleAuth">Google Authenticator</option>
        <option value="Sms">Sms</option>
</select>

common.js

var LastElementDOM;
var LastValueDOM;

function mutateDOM(ElementDOM, value) {
    if (LastElementDOM == ElementDOM && LastValueDOM == value)
        return false;

    LastElementDOM = ElementDOM;
    LastValueDOM = value;

    var event = new Event('change');
    ElementDOM.dispatchEvent(event);
}

function selectChange(_this) {
    mutateDOM(_this, _this.value);
}

All 12 comments

It's because Blazor doesn't know that the change occurred, it needs to call StateHasChanged() to check for changes on the DOM. By editing it directly through the console, it didn't cause anything on Blazor's side to trigger a StateHasChanged() for it to start checking and updating.

By default it's called on most events like onchanged which is how bind works internally. This also happens on C# on cases where it doesn't call StateHasChanged() by default.

1) The onchanged event is not called
2) Manual call StateHasChanged() does not change anything

https://youtu.be/BThflyG-KYc

@page "/testpage"
@layout TestLayout

<div>
    <select bind="@value" onchange="@OnChangeValue" style="width: 300px;">
        <option value="None" selected="">None</option>
        <option value="Email">Email</option>
        <option value="Sms">Sms</option>
    </select>
</div>

<div style="position:  absolute; margin-top: 25px;">
    <b>bind</b>: @value
</div>

<div style="position:  absolute; margin-top: 50px;">
    <button type="submit" onclick="@click">StateHasChanged</button>
</div>

@functions
{
    string value;

    protected void OnChangeValue(UIChangeEventArgs e)
    {
        Console.WriteLine("log: OnChangeValue");
    }

    protected void click()
    {
        StateHasChanged();
        Console.WriteLine($"log: StateHasChanged / value: {value}");
    }
}

That won't work either. Since if I remember the dispatch should happen on C# for Blazor to trigger a change detection. So any calls from JS that doesn't go through C# won't get detected.

This is the same when using interop to JS from C#, after the call you need let C# know what to do after the interop. JS alone can't trigger a re-render from Blazor.

I haven't tried it yet, but I don't know if a change detection would be fired when you call a C# method from JS via interop. That's worth testing, I'm on mobile at the moment so I can't confirm.

RyoukoKonpaku, Thanks for answers.
The problem is that you can not connect https://select2.org/ and other similar libraries, because they use the logic above.

Through the interop, can independently get the value of each select by its Id and fill the model in advance before sending it, but i would like to know whether blazor in the future will track changes made through JS or it is technically not possible?

It seems one way I could see to make that work is registering an onchange callback on JS side explicitly which calls interop to C# which invokes StateHasChanged(). It'd be quite messy compared to a full C# solution but that's all I could think of at the moment. This would be the same case when trying to use select2 on react as well.

Basically you'd make a wrapper for Select2 in a way that it makes it friendly to consume via C#. I'll try giving it a shot when myself when I have the time tomorrow.

You're correct though this makes it a bit hard on tracking changes when making wrappers for existing libraries. aspnet/AspNetCore#5455 should track component related stuff, If it isn't there I guess making an issue regarding that would be the way to go.

It’s not enough for your JS to mutate the select box. Your JS must also trigger the “change” event on it. Otherwise Blazor (or any other SPA framework) doesn’t know you’ve mutated the DOM.

You don’t have to call StateHasChanged by the way. You just have to trigger the same DOM events that occur if the user modifies the select box normally.

If I understand you correctly, then this method also does not work for Blazor 0.4
https://youtu.be/8WdCVy8DEVE

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width">
    <title>WebApplication1</title>
    <base href="/" />
</head>
<body>
    <app>Loading...</app>

    <script type="blazor-boot">
    </script>

    <script src="https://code.jquery.com/jquery-3.3.1.min.js"
            integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
            crossorigin="anonymous"></script>

    <script>
        function SubscribeJS() { 
            $("#test").on('change', function () {
                console.log('WASM: onchange JS');
            });
        }
    </script>
</body>
</html>

Blazor

<div>
    <input id="test" onchange="@OnChangeValue" style="width: 300px;" />
</div>

@functions
{
    protected void OnChangeValue()
    {
        Console.WriteLine("onchange C#");
    }
}

Perhaps this does not work only on Blazor 0.4?

The problem is that you're trying to trigger the event using jQuery (i.e., .trigger('change')), which does not trigger real DOM events. jQuery only runs event handlers registered through jQuery, not through the real DOM APIs.

If you use the true DOM APIs for triggering the event instead, then it works fine.

@SteveSandersonMS Ah that makes more sense, this helps a lot on understanding how to make wrappers for this kind of libraries. So if I get this right, the goal here is to register native onchange events so that it bubbles up to C# naturally? Does this work for all events? Thanks for the clarification.

@RyoukoKonpaku , it works for me.

.cshtml

<select class="form-control" bind="@md.Auth2FA" onchange="selectChange(this)" data-plugin="select2">
        <option value="None" selected="">None</option>
        <option value="Email">Email</option>
        <option value="GoogleAuth">Google Authenticator</option>
        <option value="Sms">Sms</option>
</select>

common.js

var LastElementDOM;
var LastValueDOM;

function mutateDOM(ElementDOM, value) {
    if (LastElementDOM == ElementDOM && LastValueDOM == value)
        return false;

    LastElementDOM = ElementDOM;
    LastValueDOM = value;

    var event = new Event('change');
    ElementDOM.dispatchEvent(event);
}

function selectChange(_this) {
    mutateDOM(_this, _this.value);
}

@cores-system Ah that's indeed what I though it would be, nice to know as this would make it easier at least to use 3rd party JS libs to some extent. Thanks.

One of the main reasons why select2 wasn't firing properly when using bind was that it isn't firing native DOM events it seems. It Only fires the jQuery change event.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

guardrex picture guardrex  Â·  3Comments

FourLeafClover picture FourLeafClover  Â·  3Comments

Kevenvz picture Kevenvz  Â·  3Comments

aurokk picture aurokk  Â·  3Comments

ipinak picture ipinak  Â·  3Comments