Joindre du code C# à des événements DOM avec des gestionnaires d’événements Blazor
La plupart des éléments HTML exposent des événements déclenchés quand un événement important se produit, par exemple lorsqu’une page a été chargée complètement, quand l’utilisateur a cliqué sur un bouton ou que le contenu d’un élément HTML a changé. Une application peut gérer un événement de plusieurs façons :
- L’application peut ignorer l’événement.
- L’application peut exécuter un gestionnaire d’événements écrit en JavaScript pour traiter l’événement.
- L’application peut exécuter un gestionnaire d’événements Blazor écrit en C# pour traiter l’événement.
Dans cette leçon, vous allez examiner en détail la troisième option : comment créer un gestionnaire d’événements Blazor en C# pour traiter un événement.
Gérer un événement avec Blazor et C#
Chaque élément du balisage HTML d’une application Blazor prend en charge de nombreux événements. La plupart de ces événements correspondent aux événements DOM disponibles dans les applications web standard, mais vous pouvez également créer des événements définis par l’utilisateur qui sont déclenchés par l’écriture de code. Pour capturer un événement avec Blazor, vous écrivez une méthode C# qui gère l’événement, puis vous liez l’événement à la méthode avec une directive Blazor. Pour un événement DOM, la directive Blazor partage le même nom que l’événement HTML équivalent, comme @onkeydown
ou @onfocus
. Par exemple, l’exemple d’application généré à l’aide de l’application Blazor Server contient le code suivant sur la page Counter.razor. Cette page affiche un bouton. Quand l’utilisateur sélectionne le bouton, l’événement @onclick
déclenche la méthode IncrementCount
qui incrémente un compteur indiquant le nombre de clics effectués sur le bouton. La valeur de la variable de compteur est affichée par l’élément <p> dans la page :
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
De nombreuses méthodes de gestionnaire d’événements utilisent un paramètre qui fournit des informations contextuelles supplémentaires. Ce paramètre est connu sous le nom de paramètre EventArgs
. Par exemple, l’événement @onclick
transmet des informations sur le bouton sur lequel l’utilisateur a cliqué, ou indique s’il a appuyé sur une touche comme Ctrl ou Alt en même temps qu’il a cliqué sur le bouton, dans un paramètre MouseEventArgs
. Vous n’avez pas besoin de fournir ce paramètre quand vous appelez la méthode, le runtime Blazor l’ajoute automatiquement. Vous pouvez interroger ce paramètre dans le gestionnaire d’événements. Le code suivant incrémente le compteur affiché dans l’exemple précédent de cinq si l’utilisateur appuie sur la touche Ctrl en même temps qu’il clique sur le bouton :
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount(MouseEventArgs e)
{
if (e.CtrlKey) // Ctrl key pressed as well
{
currentCount += 5;
}
else
{
currentCount++;
}
}
}
D’autres événements fournissent des paramètres EventArgs
différents. Par exemple, l’événement @onkeypress
passe un paramètre KeyboardEventArgs
qui indique la touche sur laquelle l’utilisateur a appuyé. Pour les événements DOM, si vous n’avez pas besoin de ces informations, vous pouvez omettre le paramètre EventArgs
de la méthode de gestion des événements.
Comparaison de la gestion des événements dans JavaScript par rapport à la gestion des événements avec Blazor
Une application web traditionnelle utilise JavaScript pour capturer et traiter des événements. Vous créez une fonction dans le cadre d’un élément <script> HTML, puis vous vous organisez pour appeler cette fonction quand l’événement se produit. Par rapport à l’exemple Blazor précédent, le code suivant montre un fragment d’une page HTML qui incrémente une valeur et affiche le résultat chaque fois que l’utilisateur sélectionne le bouton Cliquez ici !. Le code utilise la bibliothèque jQuery pour accéder au modèle DOM.
<p id="currentCount">Current count: 0</p>
<button class="btn btn-primary" onclick="incrementCount()">Click me</button>
<!-- Omitted for brevity -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
var currentCount = 0;
function incrementCount() {
currentCount++;
$('#currentCount').html('Current count:' + currentCount);
}
</script>
Outre les différences syntaxiques entre les deux versions du gestionnaire d’événements, vous devez noter les différences fonctionnelles suivantes :
- JavaScript ne préfixe pas le nom de l’événement avec un signe
@
. Il ne s’agit pas d’une directive Blazor. - Dans le code Blazor, vous spécifiez le nom de la méthode de gestion des événements quand vous la joignez à un événement. En JavaScript, vous écrivez une instruction qui appelle la méthode de gestion des événements. Vous spécifiez des parenthèses et des paramètres obligatoires.
- Plus important encore, le gestionnaire d’événements JavaScript s’exécute dans le navigateur, sur le client. Si vous créez une application Blazor Server, le gestionnaire d’événements Blazor s’exécute sur le serveur et met à jour le navigateur uniquement avec les modifications apportées à l’interface utilisateur quand le gestionnaire d’événements a été effectué. En outre, le mécanisme Blazor permet à un gestionnaire d’événements d’accéder aux données statiques partagées entre les sessions, ce qui n’est pas le cas du modèle JavaScript. Toutefois, la gestion de certains événements fréquents comme
@onmousemove
peut provoquer un ralentissement de l’interface utilisateur, car elle nécessite un aller-retour réseau vers le serveur. Vous préférez peut-être gérer des événements comme ceux dans le navigateur, à l’aide de JavaScript.
Important
Vous pouvez manipuler le DOM à l’aide du code JavaScript à partir d’un gestionnaire d’événements et en utilisant du code Blazor C#. Toutefois, Blazor gère sa propre copie du DOM qui est utilisée pour actualiser l’interface utilisateur le cas échéant. Si vous utilisez du code JavaScript et Blazor pour modifier les mêmes éléments dans le DOM, vous risquez d’endommager le DOM et éventuellement de compromettre la confidentialité et la sécurité des données dans votre application web.
Gérer les événements de manière asynchrone
Par défaut, les gestionnaires d’événements Blazor sont synchrones. Si un gestionnaire d’événements effectue une opération potentiellement durable, comme l’appel d’un service web, le thread sur lequel le gestionnaire d’événements s’exécute est bloqué jusqu’à ce que l’opération se termine. Cela peut entraîner une mauvaise réponse dans l’interface utilisateur. Pour éviter ce problème, vous pouvez désigner une méthode de gestionnaire d’événements comme asynchrone. Utilisez le mot clé C# async
. La méthode doit retourner un objet Task
. Vous pouvez ensuite utiliser l’opérateur await
à l’intérieur de la méthode de gestionnaire d’événements pour lancer des tâches durables sur un thread distinct et libérer le thread actuel pour un autre travail. Quand une tâche durable se termine, le gestionnaire d’événements reprend. L’exemple de gestionnaire d’événements ci-dessous exécute une méthode longue de manière asynchrone :
<button @onclick="DoWork">Run time-consuming operation</button>
@code {
private async Task DoWork()
{
// Call a method that takes a long time to run and free the current thread
var data = await timeConsumingOperation();
// Omitted for brevity
}
}
Remarque
Pour plus d’informations sur la création de méthodes asynchrones en C#, consultez Scénarios de programmation asynchrone.
Utiliser un événement pour définir le focus sur un élément DOM
Sur une page HTML, l’utilisateur peut passer d’un élément à un autre via la touche de tabulation. Le focus se déplace naturellement dans l’ordre d’apparition des éléments HTML sur la page. Dans certains cas, vous devez peut-être substituer cette séquence et forcer l’utilisateur à consulter un élément spécifique.
La façon la plus simple d’effectuer cette tâche consiste à utiliser la méthode FocusAsync
. Il s’agit d’une méthode d’instance d’un objet ElementReference
. ElementReference
doit référencer l’élément sur lequel vous souhaitez définir le focus. Vous désignez une référence d’élément avec l’attribut @ref
et créez un objet C# portant le même nom dans votre code.
Dans l’exemple suivant, le gestionnaire d’événements @onclick
pour l’élément <bouton> définit le focus sur l’élément <entrée>. Le gestionnaire d’événements @onfocus
de l’élément <input> affiche le message « Received focus » lorsque l’élément obtient l’assistant de concentration. L’élément <input> est référencé par le biais de la variable InputField
dans le code :
<button class="btn btn-primary" @onclick="ChangeFocus">Click me to change focus</button>
<input @ref=InputField @onfocus="HandleFocus" value="@data"/>
@code {
private ElementReference InputField;
private string data;
private async Task ChangeFocus()
{
await InputField.FocusAsync();
}
private async Task HandleFocus()
{
data = "Received focus";
}
L’image suivante montre le résultat obtenu quand l’utilisateur sélectionne le bouton :
Remarque
Une application doit uniquement diriger le focus vers un contrôle spécifique pour une raison spécifique, par exemple pour demander à l’utilisateur de modifier son entrée après une erreur. N’utilisez pas le focus pour forcer l’utilisateur à parcourir les éléments d’une page dans un ordre fixe. Cela peut être très frustrant pour l’utilisateur qui souhaite revenir à certains éléments pour modifier l’entrée.
Écrire des gestionnaires d’événements inline
C# prend en charge les expressions lambda. Une expression lambda vous permet de créer une fonction anonyme. Une expression lambda est utile si vous disposez d’un gestionnaire d’événements simple que vous n’avez pas besoin de réutiliser ailleurs dans une page ou un composant. Dans l’exemple de nombre de clics initial indiqué au début de cette leçon, vous pouvez supprimer la méthode IncrementCount
et remplacer l’appel de la méthode par une expression lambda qui effectue la même tâche :
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="() => currentCount++">Click me</button>
@code {
private int currentCount = 0;
}
Remarque
Pour plus d’informations sur le fonctionnement des expressions lambda, consultez Expressions lambda et fonctions anonymes.
Cette approche est également utile si vous souhaitez fournir d’autres arguments à une méthode de gestion des événements. Dans l’exemple suivant, la méthode HandleClick
prend un paramètre MouseEventArgs
de la même façon qu’un gestionnaire d’événements de clic ordinaires, mais elle accepte également un paramètre de chaîne. La méthode traite l’événement de clic comme auparavant, mais elle affiche également le message dans lequel l’utilisateur a appuyé sur la touche Ctrl. L’expression lambda appelle la méthode HandleCLick
, en passant le paramètre MouseEventArgs
(mouseEvent
) et une chaîne.
@page "/counter"
@inject IJSRuntime JS
<h1>Counter</h1>
<p id="currentCount">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick='mouseEvent => HandleClick(mouseEvent, "Hello")'>Click me</button>
@code {
private int currentCount = 0;
private async Task HandleClick(MouseEventArgs e, string msg)
{
if (e.CtrlKey) // Ctrl key pressed as well
{
await JS.InvokeVoidAsync("alert", msg);
currentCount += 5;
}
else
{
currentCount++;
}
}
}
Remarque
Cet exemple utilise la fonction alert
JavaScript pour afficher le message, car il n’existe aucune fonction équivalente dans Blazor. Vous utilisez l’interopérabilité JavaScript pour appeler JavaScript à partir du code Blazor. Les détails de cette technique sont abordés dans un module distinct.
Substituer les actions DOM par défaut pour les événements
Plusieurs événements DOM ont des actions par défaut qui s’exécutent quand l’événement se produit, qu’un gestionnaire d’événements soit disponible ou pas pour cet événement. Par exemple, l’événement @onkeypress
d’un élément <input> affiche toujours le caractère qui correspond à la touche sur laquelle l’utilisateur a appuyé et gère l’activation de la touche. Dans l’exemple suivant, l’événement @onkeypress
est utilisé pour convertir l’entrée utilisateur en majuscules. En outre, si l’utilisateur tape un caractère @
, le gestionnaire d’événements affiche une alerte :
<input value=@data @onkeypress="ProcessKeyPress"/>
@code {
private string data;
private async Task ProcessKeyPress(KeyboardEventArgs e)
{
if (e.Key == "@")
{
await JS.InvokeVoidAsync("alert", "You pressed @");
}
else
{
data += e.Key.ToUpper();
}
}
}
Si vous exécutez ce code et que vous appuyez sur la touche @
, l’alerte s’affiche, mais le caractère @
est également ajouté à l’entrée. L’ajout du caractère @
est l’action par défaut de l’événement.
Si vous souhaitez empêcher l’affichage de ce caractère dans la zone d’entrée, vous pouvez substituer l’action par défaut par l’attribut preventDefault
de l’événement, comme suit :
<input value=@data @onkeypress="ProcessKeyPress" @onkeypress:preventDefault />
L’événement se déclenche toujours, mais seules les actions définies par le gestionnaire d’événements sont exécutées.
Certains événements dans un élément enfant du DOM peuvent déclencher des événements dans leurs éléments parents. Dans l’exemple suivant, l’élément <div> contient un gestionnaire d’événements @onclick
. Le <bouton> à l’intérieur de <div> a son propre gestionnaire d’événements @onclick
. En outre, <div> contient un élément <input> :
<div @onclick="HandleDivClick">
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
<input value=@data @onkeypress="ProcessKeyPress" @onkeypress:preventDefault />
</div>
@code {
private async Task HandleDivClick()
{
await JS.InvokeVoidAsync("alert", "Div click");
}
private async Task ProcessKeyPress(KeyboardEventArgs e)
{
// Omitted for brevity
}
private int currentCount = 0;
private void IncrementCount(MouseEventArgs e)
{
// Omitted for brevity
}
}
Quand l’application s’exécute, si l’utilisateur clique sur un élément (ou un espace vide) dans la zone occupée par l’élément <div>, la méthode HandleDivClick
s’exécute et affiche un message. Si l’utilisateur sélectionne le bouton Click me
, la méthode IncrementCount
s’exécute, suivie de HandleDivClick
. L’événement @onclick
se propage vers le haut de l’arborescence DOM. Si <div> fait partie d’un autre élément qui gère également l’événement @onclick
, ce gestionnaire d’événements s’exécute également, et ainsi de suite, à la racine de l’arborescence DOM. Vous pouvez réduire cette prolifération ascendante des événements avec l’attribut stopPropagation
d’un événement, comme indiqué ici :
<div @onclick="HandleDivClick">
<button class="btn btn-primary" @onclick="IncrementCount" @onclick:stopPropagation>Click me</button>
<!-- Omitted for brevity -->
</div>
Utiliser un EventCallback suivant pour gérer les événements sur les composants
Une page Blazor peut contenir un ou plusieurs composants Blazor. Les composants peuvent également être imbriqués dans une relation parent-enfant. Un événement dans un composant enfant peut déclencher une méthode de gestionnaire d’événements dans un composant parent à l’aide d’un EventCallback
. Un rappel référence une méthode dans le composant parent. Le composant enfant peut exécuter la méthode en appelant le rappel. Ce mécanisme est semblable à l’utilisation d’un delegate
pour référencer une méthode dans une application C#.
Un rappel peut accepter un seul paramètre. EventCallback
est un type générique. Le paramètre de type spécifie le type de l’argument transmis au rappel.
Examinez par exemple le scénario suivant. Vous souhaitez créer un composant nommé TextDisplay
qui permet à l’utilisateur d’entrer une chaîne d’entrée et de transformer cette chaîne d’une certaine manière. Vous souhaitez peut-être la convertir en majuscules, en minuscules, en casse mixte, filtrer certains caractères ou en effectuer d’autres types de transformations. Toutefois, quand vous écrivez le code pour le composant TextDisplay
, vous ne connaissez pas le processus de transformation et vous souhaitez plutôt différer cette opération vers un autre composant. Le code suivant montre le composant TextDisplay
. Il fournit la chaîne d’entrée sous la forme d’un élément <input> qui permet à l’utilisateur d’entrer une valeur de texte.
@* TextDisplay component *@
@using WebApplication.Data;
<p>Enter text:</p>
<input @onkeypress="HandleKeyPress" value="@data" />
@code {
[Parameter]
public EventCallback<KeyTransformation> OnKeyPressCallback { get; set; }
private string data;
private async Task HandleKeyPress(KeyboardEventArgs e)
{
KeyTransformation t = new KeyTransformation() { Key = e.Key };
await OnKeyPressCallback.InvokeAsync(t);
data += t.TransformedKey;
}
}
Le composant TextDisplay
utilise un objet EventCallback
nommé OnKeyPressCallback
. Le code dans la méthode HandleKeypress
appelle le rappel. Le gestionnaire d’événements @onkeypress
s’exécute chaque fois qu’une touche est enfoncée et appelle la méthode HandleKeypress
. La méthode HandleKeypress
crée un objet KeyTransformation
à l’aide de la touche sur laquelle l’utilisateur a appuyé et transmet cet objet en tant que paramètre au rappel. Le type KeyTransformation
est une classe simple avec deux champs :
namespace WebApplication.Data
{
public class KeyTransformation
{
public string Key { get; set; }
public string TransformedKey { get; set; }
}
}
Le champ key
contient la valeur entrée par l’utilisateur et le champ TransformedKey
contient la valeur transformée de la clé quand elle a été traitée.
Dans cet exemple, l’objet EventCallback
est un paramètre de composant et la valeur est fournie lors de la création du composant. Cette action est effectuée par un autre composant, nommé TextTransformer
:
@page "/texttransformer"
@using WebApplication.Data;
<h1>Text Transformer - Parent</h1>
<TextDisplay OnKeypressCallback="@TransformText" />
@code {
private void TransformText(KeyTransformation k)
{
k.TransformedKey = k.Key.ToUpper();
}
}
Le composant TextTransformer
est une page Blazor qui crée une instance du composant TextDisplay
. Il remplit le paramètre OnKeypressCallback
avec une référence à la méthode TransformText
dans la section de code de la page. La méthode TransformText
prend l’objet KeyTransformation
fourni comme argument et remplit la propriété TransformedKey
avec la valeur trouvée dans la propriété Key
convertie en majuscules. Le diagramme suivant illustre le flux du contrôle quand un utilisateur entre une valeur dans le champ <input> du composant TextDisplay
affiché par la page TextTransformer
:
L’avantage de cette approche est que vous pouvez utiliser le composant TextDisplay
avec n’importe quelle page fournissant un rappel pour le paramètre OnKeypressCallback
. La séparation est totale entre l’affichage et le traitement. Vous pouvez basculer la méthode TransformText
pour tout autre rappel qui correspond à la signature du paramètre EventCallback
dans le composant TextDisplay
.
Vous pouvez connecter directement un rappel à un gestionnaire d’événements sans utiliser de méthode intermédiaire si le rappel est typé avec le paramètre EventArgs
approprié. Par exemple, un composant enfant peut référencer un rappel qui peut gérer ainsi des événements de souris comme @onclick
:
<button @onclick="OnClickCallback">
Click me!
</button>
@code {
[Parameter]
public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}
Dans ce cas, EventCallback
prend un paramètre de type MouseEventArgs
, de sorte qu’il peut être spécifié en tant que gestionnaire pour l’événement @onclick
.