dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.45k stars 10.03k forks source link

[Blazor] Enable custom text in the new (.Net 9.0) reconnect dialog #57453

Open dgoldm opened 2 months ago

dgoldm commented 2 months ago

Is there an existing issue for this?

Is your feature request related to a problem? Please describe the problem.

The new reconnect dialog, displayed by Blazor when the browser loses connection with the server is a wonderful improvement, and I'm tempted to upgrade my project, which is used in production, to .Net 9.0 preview for this reason alone. The problem is that I need to display the text in languages other than English. As far as I can see the English text is hard-coded, e.g.: https://github.com/dotnet/aspnetcore/blob/d65f5c4566decba09ba72379d185797fa632668b/src/Components/Web.JS/src/Platform/Circuits/DefaultReconnectDisplay.ts#L94

Describe the solution you'd like

Could you provide a way to customize the text?

Additional context

No response

javiercn commented 2 months ago

@dgoldm thanks for contacting us.

We don't offer customization of the built-in UI bits other than you providing your own UI.

With that said, you should be able to detect when the element is made visible on to the page using a MutationObserver and change the contents programmatically by navigating to the element.

https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

dgoldm commented 2 months ago

Thanks @javiercn. Judging by the documentation for customization of the old reconnect dialog, (which BTW is still the same in the 9.0 docs - https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/signalr?view=aspnetcore-9.0#reflect-the-server-side-connection-state-in-the-ui), which gives a straightforward, scriptless method to replace the built in UI with a functionally equivalent custom one, I was expecting a similar option for the new dialog. I expect developers to be disappointed when they follow the docs only to discover that their custom UI looks so miserable compared to the built-in one.

Understanding that it now requires javascript, it would be great if MS could provide information on how to create my own UI and hook into the circuit events, Or if indeed the only method is via MutationObserver, have the docs list the relevant element ids and classes, and provide some sample code.

javiercn commented 2 months ago

@dgoldm You can still use the option that you mention, but you won't get the styles, etc. of the new reconnect dialog.

The built-in reconnect UI has never been extensible by other means than you providing your own markup, at which point we simply set some classes and some text on the markup you provide.

In the future we likely want to move this UI into the template itself, there's no need for this UI to be hardcoded inside blazor and only creates problems like this.

If you want to replicate the UI within your app, you can grab the styles and HTML from our default one from here:

dgoldm commented 2 months ago

@javiercn I'm not interested in the styles, only the functionality, namely to display different messages corresponding to the current state of the reconnection mechanism. But for that I would have to be able to implement the various callbacks of ReconnectDisplay. That doesn't seem to be in the scope of the snippets you marked.

Anyway in the meantime I've managed to implement the MutationObserver workaround that you suggested. I'm putting it here, in case anyone comes here with similar needs, or has any suggestion for improvement (I'm no expert at javascript, Blazor has relieved me from pursuing that path 😄)

Add this to App.razor:

<script>
    const rejoiningText = "{your custom text}";
    const retryingText = "{your custom text}";
    const rejoinFailedText = "{your custom text}";
    const buttonCaption = "{your custom text}";

    const observer = new MutationObserver(() => {
        let dialog = document.querySelector('.components-reconnect-dialog');
        if (dialog) {
            let paragraph = dialog.querySelector('p');
            let text = paragraph.innerText;
            if (text.startsWith('Rejoining')) {
                paragraph.innerHTML = rejoiningText;
            } else if (text.startsWith('Rejoin failed')) {
                paragraph.innerHTML = text.replace(/.+ (\d+) seconds?/, retryingText);
            } else if (text.startsWith('Failed to rejoin')) {
                                paragraph.innerHTML = rejoinFailedText;
            }

            let button = dialog.querySelector('button');
            if (button) {
                button.textContent = buttonCaption;
            }
        };
    });

    observer.observe(document.body, { childList: true, subtree: true });
</script>