Réaliser un composant Blazor réutilisable : Exemple avec une datagrid (partie 2/3 : le Tri)

Réaliser un composant Blazor réutilisable : Exemple avec une datagrid (partie 2/3 : le Tri)

Dans le précédent article, nous avons exploré la création d’un composant Blazor réutilisable avec l’implémentation concrète d’une datagrid avec pagination. Dans cette seconde partie, je vais reprendre le composant précédent et y ajouter le tri des colonnes. Cela va également permettre de voir les composants imbriqués.

Cet article se base sur le projet réalisé dans l’article précédent. Je vous invite donc à le lire avant de continuer celui-ci.

Ajout du tri

Nous allons maintenant ajouter à notre datagrid la possibilité de trier les colonnes par ordre croissant et par ordre décroissant.

Pour cela, nous allons ajouter un nouveau composant qui sera en charge de la gestion des headers.

Comme pour la datagrid, faîtes un clic droit sur le projet Ajouter > Nouvel élément

Puis choisissez Composant Razor et donnez-lui le nom de DataGridColumn.razor.

Comme pour le premier composant, il y aura plusieurs parties. La première avec les informations de rendu, et la seconde avec les fonctions C#. Commençons par faire les choses simplement.

Remplacez le contenu du fichier par ceci :

<th>@DisplayColumnName</th>

@functions {
    [Parameter]
    public string DisplayColumnName { get; set; } = string.Empty;
}

Ici on indique simplement que l’on passe en paramètre du composant le nom de la colonne à afficher. Et dans le th, on affiche ce nom.

Dans le fichier FetchData.razor de l’application, remplacez le contenu de BlazorDataGridColumn par

<BlazorDataGridColumn>
    <DataGridColumn DisplayColumnName="Date"></DataGridColumn>
    <DataGridColumn DisplayColumnName="Temp. (C)"></DataGridColumn>
    <DataGridColumn DisplayColumnName="Temp. (F)"></DataGridColumn>
    <DataGridColumn DisplayColumnName="Summary"></DataGridColumn>
</BlazorDataGridColumn>

Pour le moment, le rendu est exactement le même qu’avec le <th>..</th>. Mais on voit que l’on a imbriqué un composant dans un autre. Il est maintenant possible d’enrichir le composant DatagridColumn afin de lui faire faire beaucoup plus de chose comme trier nos colonnes par exemple. Et maintenant que l’on a la base du composant, on va justement pouvoir commencer à gérer ce fameux tri.

Comme pour le composant précédent, on va ajouter en haut de la page @typeparam TItem qui va nous permettre de gérer une liste de TItem.

On va ensuite aborder une nouvelle notion qui est les paramètres en cascade. Cela permet à un composant père d’envoyer un paramètre à un composant fils.

Ajouter dans le fichier dans la partie functions :

[CascadingParameter]
public BlazorDataGrid<TItem> BlazorDataTable { get; set; } 

Ici on passe en paramètre le composant père, ce qui va nous permettre de récupérer des informations comme la liste.

Et dans le fichier RazorDatagrid.razor il faut passer le composant en paramètre.

Remplacer


 @BlazorDataGridColumn

par

         <CascadingValue Value="this">
                @BlazorDataGridColumn
            </CascadingValue>

L’utilisation de typeparam nous oblige aussi à passer la liste en paramètre du composant pour récupérer le type. Ajoutons donc également

[Parameter]
public IEnumerable<TItem> Items { get; set; }

Maintenant il va falloir mettre en place le mécanisme permettant de faire le tri des colonnes. Pour cela, nous allons utiliser une fonction qui prend en paramètre le nom réel de la colonne à trier et non pas le nom d’affichage. Par exemple, je peux avoir un objet avec une propriété string Name {get; set;}. Le nom de la colonne sera Name alors que le nom d’affichage peut très bien être Nom.

Nous allons donc devoir ajouter quelques paramètres dans notre composant pour gérer efficacement le tri. Tout d’abord, comme je l’ai déjà dit, il faut gérer le nom de la colonne. Nous allons donc naturellement ajouter un paramètre dans le composant.

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

Puis il faudra savoir si on fait un tri ascendant ou descendant. C’est là qu’intervient une nouvelle variable

private bool IsSortedAscending;

Il faut également savoir si on tri la même colonne ou pas. On va pour cela ajouter une variable au niveau de la datagrid. Dans le fichier BlazorDataGrid.razor, ajoutez

public string CurrentSortColumn;

Nos nouvelles variables sont en place, on peut donc commencer à écrire la fonction permettant le tri.

Retournons dans le fichier DataGridColumn.razor et ajoutons cette nouvelle fonction

private void SortTable(string columnName)
    {
        if (columnName != BlazorDataTable.CurrentSortColumn)
        {
            BlazorDataTable.Items = BlazorDataTable.Items.OrderBy(x => x.GetType().GetProperty(columnName).GetValue(x, null)).ToList();

            BlazorDataTable.CurrentSortColumn = columnName;
            IsSortedAscending = true;

        }
        else
        {
            if (IsSortedAscending)
            {
                BlazorDataTable.Items = BlazorDataTable.Items.OrderByDescending(x => x.GetType().GetProperty(columnName).GetValue(x, null)).ToList();
            else
            {
                BlazorDataTable.Items = BlazorDataTable.Items.OrderBy(x => x.GetType().GetProperty(columnName).GetValue(x, null)).ToList();
            }

            IsSortedAscending = !IsSortedAscending;
        }
    }

Regardons un peu dans le détail ce que fait cette fonction. D’abord on vérifie si on tri une nouvelle colonne ou si c’est la même colonne que l’on tri.

if (columnName != BlazorDataTable.CurrentSortColumn)

Par défaut, le tri sur une nouvelle colonne est un tri ascendant.  On va donc faire une requête orderby sur la liste de notre élément parent (BlazorDataTable.Items) passé en paramètre via l’annotation [CascadingParameter].

Notre fonction est générique. En effet, on ne se sait pas à l’avance quelle colonne on va trier, c’est pour ça que l’on passe le nom de la colonne en paramètre. x.GetType().GetProperty(columnName).GetValue(x, null)) nous permet de requêter sur la bonne colonne.

Si cependant c’est la même colonne que l’on tri, il faut savoir dans quel ordre le faire. On vérifie donc la valeur de IsSortedAscending pour effectuer un OrderBy ou un OrderByDescending. Et on pense ensuite à inverser la valeur de ce booléen pour trier dans le sens inverse si on tri la même colonne. (IsSortedAscending = !IsSortedAscending;) Il faut maintenant adapter le modèle de notre header. Remplacez donc

<th>@DisplayColumnName</th> 

par

<th>
    <span @onclick="@(() => SortTable(ColumnName))">@DisplayColumnName</span>
</th>

Au clic sur le nom de la colonne, on appelle la fonction Sortable qui se charge de faire le tri.

Pour tester, il faut modifier un peu l’appel dans l’application.

Dans FetchData.razor modifiez la datagrid comme ceci :

<BlazorDataGrid Items="@forecasts" PageSize="4">
        <BlazorDataGridColumn>
            <DataGridColumn Items="@forecasts" ColumnName="Date" DisplayColumnName="Date"></DataGridColumn>
            <DataGridColumn Items="@forecasts" ColumnName="TemperatureC" DisplayColumnName="Temp. (C)"></DataGridColumn>
            <DataGridColumn Items="@forecasts" ColumnName="TemperatureF" DisplayColumnName="Temp. (F)"></DataGridColumn>
            <DataGridColumn Items="@forecasts" ColumnName="Summary" DisplayColumnName="Summary"></DataGridColumn>
        </BlazorDataGridColumn>
        <GridRow>
            <td>@context.Date.ToShortDateString()</td>
            <td>@context.TemperatureC</td>
            <td>@context.TemperatureF</td>
            <td>@context.Summary</td>
        </GridRow>
    </BlazorDataGrid> 

Et là quand on clic sur le nom …… Il ne se passe rien. En effet, il va falloir signaler à l’élément parent que la liste a changé et qu’il doit faire un rafraîchissement.

Nous allons utiliser pour cela un service partagé qui sera disponible pour le père et pour le fils.

Ajoutez un nouveau dossier dans le projet du composant. Clic droit sur le projet puis Ajouter > Nouveau dossier.

Appelez ce nouveau dossier Services.

Ajoutez ensuite une nouvelle classe dans le dossier. Clic droit sur le dossier, Ajouter > Classe

Appelez cette classe AppState.cs

Cette classe va contenir un événement et une méthode qui nous permettra de faire un rafraîchissement du parent.

using System;

namespace BlazorComponent.Services
{
    public class AppState
    {
        public event Action RefreshRequested;
        public void CallRequestRefresh()
        {
            RefreshRequested?.Invoke();
        }
    }
}

Nous allons maintenant, par injection de dépendance, ajouter cette classe dans nos deux composants.

Ajoutez en haut des fichiers DatagridColumn.razor et BlazorDataGrid.razor

@using Services
@inject AppState AppState

Dans le parent, c’est-à-dire dans le fichier BlazorDatagrid, il faut créer une fonction de rafraîchissement et s’abonner à l’évènement RefreshRequested avec comme application, la fonction de rafraîchissement.

Commençons par créer la fonction de rafraichissement.

Ajoutez

private void RefreshMe()
    {
        StateHasChanged();
        updateList(curPage);
    }

On indique qu’un changement à eu lieu et on appelle la fonction de mise à jour avec la page courante.

Il faut également s’abonner à l’évènement. Dans la fonction OnInitializedAsync ajoutez :

AppState.RefreshRequested += RefreshMe;

Tout est prêt du côté du père, il faut maintenant se tourner du côté du fils.

A chaque évolution de la de la liste, il faudra appeler la méthode AppState.CallRequestRefresh();

Nous pouvons donc modifier la fonction Sortable de la manière suivante :

private void SortTable(string columnName)
    {
        if (columnName != BlazorDataTable.CurrentSortColumn)
        {
            BlazorDataTable.Items = BlazorDataTable.Items.OrderBy(x => x.GetType().GetProperty(columnName).GetValue(x, null)).ToList();

            BlazorDataTable.CurrentSortColumn = columnName;
            IsSortedAscending = true;
            AppState.CallRequestRefresh();

        }
        else
        {
            if (IsSortedAscending)
            {
                BlazorDataTable.Items = BlazorDataTable.Items.OrderByDescending(x => x.GetType().GetProperty(columnName).GetValue(x, null)).ToList();
            }
            else
            {
                BlazorDataTable.Items = BlazorDataTable.Items.OrderBy(x => x.GetType().GetProperty(columnName).GetValue(x, null)).ToList();
            }
            AppState.CallRequestRefresh();

            IsSortedAscending = !IsSortedAscending;
        }
    }

Pour tester le tri, il faut malgré tout déclarer notre service afin que l’injection de dépendance puisse s’effectuer. Dans le projet de l’application, ouvrez le fichier Startup.cs et ajoutez dans la fonction ConfigureServices :

services.AddScoped<AppState, AppState>();

Il faudra probablement ajouter également en haut de page

using BlazorComponent.Services;

Nous pouvons maintenant tester notre tri.

A ce stade, le tri s’effectue correctement. Seulement, ajouter un indicateur visuel montrant quelle colonne est trié et dans quel sens serait un vrai plus.

Ne tardons pas et ajoutons cette fonctionnalité.

Ouvrez le fichier DatagridColumn.razor et ajoutez une fonction pour gérer le style :

private string GetSortStyle(string columnName)
    {
        if (BlazorDataTable.CurrentSortColumn != columnName)
        {
            return string.Empty;
        }
        if (IsSortedAscending)
        {
            return "oi-arrow-thick-top";
        }
        else
        {
            return "oi-arrow-thick-bottom";
        }
    }

Le premier if (if (BlazorDataTable.CurrentSortColumn != columnName)) permet de savoir si on se trouve sur la colonne que l’on souhaite trier. Si ce n’est pas le cas, on supprime le marqueur.

Sinon on regarde dans quel sens on tri pour afficher le marqueur adéquat.

Il ne reste qu’à afficher ce marqueur sur la colonne et pour cela il suffit d’ajouter <span class= »oi @(GetSortStyle(ColumnName)) »></span> dans notre modèle.

<th>
        <span @onclick="@(() => SortTable(ColumnName))">@DisplayColumnName</span>
        <span class="oi @(GetSortStyle(ColumnName))"></span>
    </th>

Si on lance à nouveau l’application, on peut constater qu’une flèche vient indiquer la colonne triée et le sens de tri.

Conclusion

Voilà notre datagrid peut maintenant être triée et paginée. Nous avons vu ensemble l’utilisation des paramètres cascadés ainsi que les services partagés permettant la communication entre nos composant. Il est facile d’imbriquer des composant et cela permet une utilisation encore plus riche. Dans le prochain article, nous verrons comment ajouter des filtres sur notre datagrid.

Laisser un commentaire