Bootstrap modal on Blazor stopped working on .NET 8 RC2 (using JSInterop to open/close)

875 views Asked by At

I believe what I'm doing is against the dotnet-8-Blazor-way of doing things, but I'll post anyway as I feel that if more people have done until .NET 7 what I did, this will be useful for them.

Currently (until .NET 8 RC1), I have a page where a bootstrap modal must be opened from C#. To do so, I added two methods (openPOModal, closePOModal) on a JS file and called them from C# using JSInterop:

JS:

var selectPOModal = new bootstrap.Modal('#selectPOModal', {
});

var closePOModal = () => {
    selectPOModal.hide();
}

var openPOModal = () => {
    selectPOModal.show();
}

export { closePOModal, openPOModal };

Razor component:

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            jsModule = await JS.InvokeAsync<IJSObjectReference>(
                "import", "./Pages/MyComponent.razor.js");
        }
    }

    ...
    private async Task SomethingHappensNeedOpenModal()
    {
        await jsModule.InvokeVoidAsync("openPOModal");
    }
}

The modal DOES open even in .NET 8 RC2, but all Blazor controlled elements inside the modal are not responsive anymore.

I have an input inside the modal that is binding to a property and it doesn't get updated anymore. Neither my buttons that call C# methods.

My question is: Is there a simple way to make this work again using JS?

Or is the correct solution to implement bootstrap's open/close logic using Blazor as it's being done in the answers in this linked question?

1

There are 1 answers

3
Kurt Hamilton On BEST ANSWER

The way I have approached this in Blazor .NET 8 RC2 (server) is to add a function to the window that can be called via JS interop.

Firstly, create the function in a JS file that runs on load (in this case using an IIFE).

my-bootstrap.js

(function() {
  // use a custom namespace to avoid polluting the window object
  window.myNamespace = {
    bootstrap: {
      modals: {
        show: el => {
          // I will be passing in the element itself, 
          // but the selector could also be used
          const instance = bootstrap.Modal.getOrCreateInstance(el);
          instance.show();
        }
      }
    }
  };
})();

Create the razor component that contains the modal and an event handler to show the modal via IJSRuntime. In this case I am using a button click.

my-modal.razor

<button @onclick="@OnButtonClick">Show modal</button>

<div class="modal" tabindex="-1" @ref="@ModalElement">
    <!-- modal example omitted for brevity, 
         available from the Bootstrap docs -->
</div>

@inject IJSRuntime JSRuntime
@code {
    // Get a reference to the rendered element using 
    // the @ref="" syntax on the HTML element
    private ElementReference? ModalElement { get; set; }

    private async Task OnButtonClick()
    {
        // call the function with the element reference 
        // as the first and only argument
        await JSRuntime.InvokeVoidAsync(
            "myNamespace.bootstrap.modals.show", 
            ModalElement);
    }
}

I have included a button here simply for demonstration purposes - you can hook into the logic in a way that makes sense for you.

Edit

An example of an event handler working from inside the modal:

<div class="modal" tabindex="-1" @ref="@ModalElement">
    <!-- modal HTML omitted for brevity -->
    <button type="button" class="btn btn-primary" 
            @onclick="@OnActionButtonClick">
      Save changes
    </button>  
    <!-- modal HTML omitted for brevity -->         
</div>

@code {
    public async Task OnActionButtonClick()
    {
        // this is working after the modal is displayed
        await JSRuntime.InvokeVoidAsync("alert", "Hello, World!");
    }
}

It is worth mentioning that I'm using Bootstrap v5.3.2.