Articoli

Interoperabilità con JavaScript

da

Cominciamo ad analizzare qualche aspetto più avanzato di Blazor, partendo da una delle funzionalità essenziali in questa fase di crescita del framework, in cui non abbiamo ancora tutto l’ecosistema di librerie già pronte per risolvere ogni problema di sviluppo front-end.

Blazor e JavaScript

L’utilizzo di Blazor in uno scenario reale, specie se decidiamo di usare Blazor WebAssembly, richiede necessariamente l’integrazione di JavaScript. La buona notizia è che lo standard WASM supporta l’interoperabilità già a livello di specifica e come già spiegato in un articolo precedente, l’esecuzione del codice WebAssembly si colloca nello stesso runtime in cui eseguiamo JavaScript e da cui possiamo accedere alle API del browser.

Questo rende la comunicazione tra i due molto più semplice, permettendo sia l’invocazione di funzioni WebAssembly da JavaScript, che viceversa. In Blazor questo significa poter fare lo stesso tra .NET e JavaScript, cosa molto utile per gestire tutti gli scenari legacy, cruciali nei casi reali.

Comunque l’interoperabilità tra Blazor e JavaScript funziona anche senza WebAssembly, quindi anche in Blazor Server, solo che in questo caso l’esecuzione del codice viene gestita attraverso SignalR.

.NET chiama, JavaScript risponde

Supponiamo di voler chiedere all’utente conferma dell’eliminazione di un elemento dalla lista degli eventi, senza utilizzare la bruttissima confirm del browser, ma usando una modale di Bootstrap per dare all’applicazione un look and feel coerente.

La parte CSS di Bootstrap fa già parte del template, ma per poter usare la modale abbiamo bisogno della parte JavaScript. Scarichiamo i file necessari in locale (se preferite è possibile anche utilizzare le CDN) e aggiungiamo le referenze nel file index.html (oppure _Layout.cshtml nel caso di Blazor Server).

<!DOCTYPE html>
<html>
<head>...</head>
<body>
    ...
    <script src="js/bootstrap.min.js"></script>
    <script>
        var myModal;
        window.mostraConferma = (id) => {
            myModal = new bootstrap.Modal(document.getElementById(id));
            myModal.show();
        };
        window.nascondiConferma = () => {
            myModal.hide();
        };
    </script>
</body>
</html>

Aggiungiamo anche un piccolo script in cui definiamo due funzioni, mostraConferma e nascondiConferma, che utilizzano l’API jQuery di Bootstrap per la visualizzazione di una modale. Selezioniamo la modale attraverso l’id, che prendiamo come parametro della funzione mostraConferma. Per poter rendere visibile queste due funzioni a .NET è necessario che vengano agganciate sull’oggetto window.

Andiamoci adesso a creare un componente Blazor con il markup della modale di Bootstrap e un po’ di codice per rendere personalizzabili l’identificativo, il titolo, il testo della domanda e l’azione da fare nel caso in cui l’utente selezioni il pulsante di conferma. Nella cartella Shared del progetto creiamo un file Conferma.razor, con il seguente codice:

<div class="modal" id="@IdConferma">
    <div class="modal-dialog">
        <div class="modal-content">
        <div class="modal-header">
            <h4 class="modal-title">@Titolo</h4>
            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 
        </div>
        <div class="modal-body">
            @Messaggio
        </div>
        <div class="modal-footer">
            <button type="button" class="btn btn-danger" @onclick="OnSi">Si</button>
            <button type="button" class="btn btn-default" data-bs-dismiss="modal" >No</button>
        </div>
        </div>
    </div>
</div>
@code {
    [Parameter]
    public string IdConferma { get; set; }

    [Parameter]
    public string Titolo { get; set; }

    [Parameter]
    public string Messaggio { get; set; }

    [Parameter]
    public EventCallback OnSi { get; set; }
}

A questo punto possiamo utilizzare il componente appena creato in ListaEventi.razor, fornendo i parametri per l’eliminazione di un evento:

<h2>@Titolo</h2>
<button class="btn btn-primary mb-4" @onclick="OnCrea">Crea Evento</button>
<table class="table">
   ...
</table>

<Conferma
    IdConferma="eliminaEvento"
    Titolo="Elimina Evento"
    Messaggio="@("Eliminare l'evento " + @elementoDaEliminare?.Nome + "?")"
    OnSi="ConfermaEliminazione" />

L’identificativo per la nostra modale sarà quindi eliminaEvento, il titolo sarà “Elimina Evento”, mentre il messaggio sarà una domanda che conterrà il nome dell’elemento che l’utente ha scelto per l’eliminazione. Dobbiamo modificare leggermente il flusso con cui eseguiamo le operazioni nel componente, perchè quando l’utente selezionerà il pulsante Elimina non vogliamo più invocare l’evento OnElimina, ma mostrare la modale di conferma. Per fare questo dobbiamo aggiungere al codice un metodo per mostrare la modale da invocare sull’evento @onclick del pulsante Elimina, chiamiamolo MostraConferma, a cui passeremo l’elemento selezionato per l’eliminazione, che salveremo in una proprietà privata:

<h2>@Titolo</h2>
<button class="btn btn-primary mb-4" @onclick="OnCrea">Crea Evento</button>
<table class="table">
    ...
    @foreach(var evento in ListaElementi)
    {
    <tr>
        ...
        <th>
            <button class="btn btn-warning" @onclick="e => OnModifica.InvokeAsync(evento)">Modifica</button>
            <button class="btn btn-danger" @onclick="e => MostraConferma(evento)">Elimina</button>
        </th>
    </tr>
    }
</table>

...
@code {
    ...

    private ElementoListaEventi elementoDaEliminare;
    
    private async Task MostraConferma(ElementoListaEventi elemento)
    {
        this.elementoDaEliminare = elemento;
    }   
}

E’ arrivato il momento di invocare le funzioni JavaScript che abbiamo definito all’inizio, ma per poterlo fare abbiamo bisogno di iniettare nella pagina l’interfaccia offerta dal framework per l’interoperabilità con JavaScript: IJSRuntime:

@inject IJSRuntime JSRuntime

<h2>@Titolo</h2>
<button class="btn btn-primary mb-4" @onclick="OnCrea">Crea Evento</button>
<table class="table">
    ...
</table>
...

Possiamo utilizzare l’extension method InvokeVoidAsync(this IJSRuntime jsRuntime, string identifier, params object[] args) per invocare sia la funzione che mostra la modale che quella che la nasconde, perchè entrambe non restituiscono un valore:

@code {

    ...

    private ElementoListaEventi elementoDaEliminare;
    
    private async Task MostraConferma(ElementoListaEventi elemento)
    {
        this.elementoDaEliminare = elemento;
        await JSRuntime.InvokeVoidAsync("mostraConferma", "eliminaEvento");
    }
    
    private async Task ConfermaEliminazione()
    {
        await OnElimina.InvokeAsync(this.elementoDaEliminare);
        await JSRuntime.InvokeVoidAsync("nascondiConferma");
    }
}

Nel caso in cui le funzioni JavaScript da richiamare restituiscano un valore, è possibile invece utilizzare l’extension method InvokeAsync<TValue>(this IJSRuntime jsRuntime, string identifier, params object[] args).

Riassumendo, il flusso diventa:

  • L’utente seleziona il pulsante Elimina
  • Viene invocato il metodo MostraConferma, passando l’elemento da eliminare
  • Il metodo salva l’elemento da eliminare in una proprietà privata e mostra la modale
  • L’utente seleziona il pulsante Sì della modale
  • Viene invocato il metodo ConfermaEliminazione che solleva l’evento OnElimina e chiude la modale

Eccolo in azione:

Potete consultare il codice sorgente qui, nella branch 08-interoperabilita-javascript.

Conclusioni

Con questo articolo abbiamo cominciato l’esplorazione degli aspetti un po’ più avanzati di Blazor, quelli che poi nelle applicazioni reali possono fare la differenza in termini di funzionalità e manutenibilità. L’integrazione con JavaScript è fondamentale per l’utilizzo di librerie già pronte che possono renderci più produttivi, fornendoci uno strumento indispensabile in questa prima fase di vita del framework.