In Blazor, per presentare a video i dati presenti in un modello e salvare ciò che l'utente inserisce all'interno di un input, viene utilizzata una logica definita Two-way data binding. Questi processi permettono di mantenere una sincronia in tempo reale tra modello logico e dati rappresentati: come mostrato nell'esempio che troviamo all'interno di ogni nuova applicazione, se premiamo il bottone, nella pagina Counter, il numero mostrato viene immediatamente incrementato.
Perchè se è una funzionalità così performante, abbiamo bisogno di inserire un debounce, ossia qualcosa che dilazioni il numero di aggiornamenti? Vi sono dei contesti, anche molto comuni, in cui l'elevato numero di aggiornamenti genera del rumore non voluto, rendendo i processi della pagina lenti e volubili ad errori.
Per capire meglio utilizziamo la seconda pagina di esempio, la Weather forecast, e immaginiamo di voler aggiungere un input in cima alla tabella per filtrare i dati. La logica è molto semplice: prevede l'utilizzo di una proprietà di tipo string, che al cambiamento aggiorni una lista che sarà composta da tutti gli elementi della lista originale che hanno superato la scrematura.
<input class="form-control" @bind-value="SearchText" @bind-value:event="oninput"/> <Virtualize Items="@FilteredList" Context="weather"> @weather.Location </Virtualiza> @code{ public string SearchText = ""; IList<WeatherForecast> FilteredList { get => originalList.Where( weather => weather.Location.Contains(SearchText)).ToList(); } IList<WeatherForecast> originalList = new List<WeatherForecast>(); }
Per motivi dimostrativi abbiamo ridotto il codice mostrato, ma nel caso reale ci sarebbe stata una tabella con molte righe e colonne, che vengono create e distrutte ad ogni input dell'utente. Se inserissimo una parola come "MILANO", la logica di aggiornamento ricreerà la tabella 6 volte. Sarebbe dunque più funzionale se, prima di lanciare l'evento che aggiorni la proprietà SearchText, si attendesse del tempo, in modo da lasciare all'utente la possibilità di inserire più lettere.
Per arrivare a ciò non abbiamo proprietà o nulla di automatico, ma possiamo crearci i nostri metodi, ricordandoci che tutto quello che lega ciò che scriviamo all'interno del browser, al codice C#, sono eventi javascript, che possono essere intercettati e gestiti.
Partiamo proprio dalla scrittura di una funzione che ci permetterà di rimandare di un certo numero di secondi l'emissione dell'evento e che allo stesso tempo permetta di annullare l'evento precedente se viene richiamata una seconda volta entro il tempo prefissato. Aggiungiamo un file nella seguente posizione wwwroot/js/events.js
// funzione principale che verrà chiamata da Blazor fornendo // l'elemento html // la tipologia di evento da gestire // l'intervallo di tempo export function debounceEvent(htmlElement, eventName, delay) { registerEvent(htmlElement, eventName, delay, debounce); } function registerEvent(htmlElement, eventName, delay, filterFunction) { let raisingEvent = false; // creazione dell'evento let eventHandler = filterFunction(function (e) { raisingEvent = true; try { // se si supera il tempo pre-impostato, viene eseguito il dispatch dell'evento htmlElement.dispatchEvent(e); } finally { raisingEvent = false; } }, delay); // registrazione dell'evento sull'elemento htmlElement.addEventListener(eventName, e => { if (!raisingEvent) { e.stopImmediatePropagation(); eventHandler(e); } }); } // funzione che permette di incapsulare la funzione all'interno di un timer function debounce(func, wait) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => { func.apply(this, args); }, wait); }; }
Quello che abbiamo creato pocanzi è un modulo javascript, che andrà opportunamente importato alla bisogna, utilizzando l'istanza dell'interfaccia IJSRuntime, come mostrato in seguito
public static class EventExtensions { public static async Task DebounceEvent(this IJSRuntime jsRuntime, ElementReference element, string eventName, TimeSpan delay) { await using var module = await jsRuntime.InvokeAsync<IJSObjectReference>("import", "./js/events.js"); await module.InvokeVoidAsync("debounceEvent", element, eventName, (long)delay.TotalMilliseconds); } }
Uniamo i pezzi del puzzle nella pagina Weather forecast, all'interno della quale occorrerà ottenere una referenza all'input HTML di ricerca e richiamare l'extension method per impostare la logica con cui controllare gli eventi di input
@inject IJSRuntime JSRuntime <input @ref="searchInput" class="form-control" @bind-value="SearchText" @bind-value:event="oninput"/> <Virtualize Items="@FilteredList" Context="weather"> @weather.Location </Virtualiza> @code{ ElementReference searchInput; public string SearchText = ""; IList<WeatherForecast> FilteredList { get => originalList.Where( weather => weather.Location.Contains(SearchText)).ToList(); } IList<WeatherForecast> originalList = new List<WeatherForecast>(); protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await JSRuntime.DebounceEvent(searchInput, "input", TimeSpan.FromMilliseconds(500)); } } }
Grazie alla struttura modulare e all'extension method basato sull'interfaccia JSRuntime, possiamo applicare il debounce a qualsiasi tipologia di evento di un qualsiasi elemento, senza appesantire il codice o l'esecuzione.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Rinnovare il token di una GitHub App durante l'esecuzione di un workflow
Gestire la cancellazione di una richiesta in streaming da Blazor
Migliorare l'organizzazione delle risorse con Azure Policy
Filtrare i dati di una QuickGrid in Blazor con una drop down list
Generare velocemente pagine CRUD in Blazor con QuickGrid
Evitare (o ridurre) il repo-jacking sulle GitHub Actions
Utilizzare Tailwind CSS all'interno di React: installazione
Miglioramenti nelle performance di Angular 16
Referenziare un @layer più alto in CSS
Utilizzare il trigger SQL con le Azure Function
L'evoluzione di Blazor in .NET 8
Usare le collection expression per inizializzare una lista di oggetti in C#