Cet article traite de la création d'un composant Table avec RiotJS, en utilisant BeerCSS. Avant de commencer, assurez-vous d'avoir une application de base RiotJS, ou lisez mes articles précédents.

n'hésitez pas à consulter la documentation de Riot si nécessaire : https://riot.js.org/documentation/

Une Table est un élément complexe et riche avec de nombreux styles et actions. L'article vise à créer un composant Table avec un design BeerCSS, affichant une liste d'objets. Ensuite, personnalisez les cellules avec des éléments spéciaux (chips, cases à cocher, etc.).

Table Component made with RiotJS and BeerCSS

Base du composant Table

Tout d'abord, créez un nouveau fichier nommé c-table.riot dans le dossier des composants. Le préfixe c- signifie "composant", une convention de nommage utile et une bonne pratique.

Écrivez le code HTML suivant dans ./components/c-table.riot (le CSS a été trouvé dans la documentation BeerCSS):

class="
                { props?.leftAlign ? 'left-align ' : null }
                { props?.centerAlign ? 'center-align ' : null }
                { props?.rightAlign ? 'right-align ' : null }
                { props?.stripes ? 'stripes ' : null }
                { props?.outlined ? 'border ' : null }
                { props?.scroll ? 'scroll' : null }"
        >
         class="{props?.scroll ? ' fixed' : null }">
            
                 each={ col in Object.keys(props?.items?.[0])}>{ col }
            
        
        
             each={ item in props?.items}>
                 if={ item } each={ val in Object.values(item) }>
                    { val }
                
            
        
         if={ props?.footer === true } class="{props?.scroll ? ' fixed' : null }">
            
                 each={ header in Object.keys(props?.items?.[0])}>{ header }
            
         
    
    
        thead, tfoot > tr > th::first-letter {
            text-transform:capitalize;
        }

Décomposons le code :

  1. Les balises et définissent une balise racine personnalisée, portant le même nom que le fichier. Vous devez l'écrire ; sinon, cela pourrait créer des résultats inattendus. Utiliser la balise comme balise racine ou redéfinir des balises HTML natives est une mauvaise pratique, donc commencer par c- est un bon nommage.
  2. Le composant peut recevoir une liste d'objets items en tant qu'attribut, et peut être utilisé dans le composant avec props?.items.
  3. Pour créer des cellules, l'expression Riot each est utilisée pour parcourir les éléments :
    • Une première boucle est utilisée pour chaque balise de ligne de tableau avec . L'item est un objet de la liste.
    • Une deuxième boucle enfant est utilisée pour chaque cellule de tableau avec { val }. L'expression Object.values() renvoie un tableau des valeurs d'un objet donné.
    • Pour l'en-tête du tableau, il utilise les noms de key du premier élément de la liste props.items : La fonction Object.keys est utilisée pour renvoyer un tableau de toutes les clés d'objet de l'élément donné, comme { col }.
    • BeerCSS permet la création d'un pied de page, qui suit la même logique que l'en-tête : accéder au premier élément de la liste, obtenir une liste de clés avec Object.keys et créer une boucle avec l'expression Riot each. Le pied de page est affiché conditionnellement si la propriété props.footer existe.
    • La première lettre de chaque en-tête est capitalisée grâce à l'expression CSS : th::first-letter { text-transform:capitalize; }.
    • Enfin, BeerCSS fournit des classes utiles pour changer le style du tableau, par exemple, ajouter des rayures ou des bordures ou changer l'alignement. Les classes sont injectées conditionnellement avec l'expression Riot : { props?.stripes ? 'stripes ' : null } signifiant que si la propriété props.stripes existe, la classe stripes est ajoutée.
    • Enfin, chargez et instanciez le c-table.riot dans une page nommée index.riot :

      style="width:800px;padding:20px;">
               style="margin-bottom:20px">Riot + BeerCSS
               items={ state.animals } />
          
          
              import cTable from "./components/c-table-full.riot"
      
              export default {
                  components: {
                      cTable
                  },
                  state: {
                      animals: [
                          {
                              id: 1,
                              name: 'African Elephant',
                              species: 'Loxodonta africana',
                              diet: 'Herbivore',
                              habitat: 'Savanna, Forests',
                              enabled: false
                          },
                          {
                              id: 2,
                              name: 'Lion',
                              species: 'Panthera leo',
                              diet: 'Carnivore',
                              habitat: 'Savanna, Grassland',
                              enabled: true
                          },
                          {
                              id: 3,
                              name: 'Hippopotamus',
                              species: 'Hippopotamus amphibius',
                              diet: 'Herbivore',
                              habitat: 'Riverbanks, Lakes',
                              enabled: false
                          }
                      ]
                  }
              }

      Détails du code :

      • Les composants sont importés avec import cTable from "./components/c-table.riot"; puis chargés dans l'objet Riot components:{}.
      • Le tableau est instancié avec dans le HTML.
      • Une liste d'animaux est fournie au tableau, grâce à l'attribut items : items={ state.animals } ; le tableau créera automatiquement les en-têtes, les lignes et les cellules ! ✅

      Voici le HTML généré :
      Table component made with RiotJS displaying a list of objects

      Composant Table avec cellules personnalisées

      Afficher des composants personnalisés dans les cellules est souvent nécessaire pour les applications de production, comme des cases à cocher, ou même des boutons.

      Cette capacité est activée grâce aux Slots: La balise injecte des modèles HTML personnalisés dans un composant enfant depuis son parent. Modifions la boucle suivante dans c-table.riot:

      each={ val in Object.values(item) }>
        { val }

      Remplacez-la par l'expression suivante :

      each={ el in Object.entries(item) }>
          name="item" item={ item } column={ el?.[0] } value={ el?.[1] }>
          if={ slots.length === 0 }>
            { el?.[1] }

      Expliquons ce qui se passe ici :

      • Au lieu d'utiliser Object.values, on utilise Object.entries pour convertir un objet en une liste de [key, value]. Par exemple, [{key: 1, name: "blue"}] renvoie [["key", 1], ["name", "blue"]].
      • Un slot est créé pour chaque cellule : .
      • Passer des valeurs dynamiques aux slots s'appelle des Higher Order Components: Tous les attributs définis sur les balises de slot seront disponibles dans leurs modèles HTML injectés. Dans notre cas, quatre attributs sont créés :
        • Le slot a un nom optionnel, une convention de nommage Riot, sans le définir, il aurait le nom default. Il n'est pas possible de définir le nom dynamiquement.
        • L'objet item de la boucle de ligne est passé dans l'attribut item du slot : Il sera accessible sur le composant passé en tant que slots !.
        • La clé de l'élément est passée à l'attribut column du slot grâce à column={ el?.[0] }.
        • La valeur de l'élément est passée à l'attribut value du slot grâce à value={ el?.[1] }.
      • Si le slot n'existe pas, la valeur de la cellule est imprimée avec . Les directives if peuvent être utilisées avec une balise , dans notre cas, on souhaite afficher conditonnellement la valeur de l'élément (en savoir plus sur la documentation Riot).

      Maintenant, dans index.riot, affichons un composant Chip pour la colonne Diet :

      style="width:800px;padding:20px;">
               style="margin-bottom:20px">Riot + BeerCSS
               items={ state.animals }>
                   slot="item">
                       if={ column === 'diet' }>
                          { value }
                      
                       if={ column !== 'diet' }>
                          { value }
                      
                  
              
          
          
              import cTable from "./components/c-table-full.riot"
              import cChip from "./components/c-chip.riot"
      
              export default {
                  components: {
                      cTable,
                      cChip
                  },
                  state: {
                      animals: [
                          {
                              id: 1,
                              name: 'African Elephant',
                              species: 'Loxodonta africana',
                              diet: 'Herbivore',
                              habitat: 'Savanna, Forests',
                              enabled: false
                          },
                          {
                              id: 2,
                              name: 'Lion',
                              species: 'Panthera leo',
                              diet: 'Carnivore',
                              habitat: 'Savanna, Grassland',
                              enabled: true
                          },
                          {
                              id: 3,
                              name: 'Hippopotamus',
                              species: 'Hippopotamus amphibius',
                              diet: 'Herbivore',
                              habitat: 'Riverbanks, Lakes',
                              enabled: false
                          }
                      ]
                  }
              }

      Décomposition du code :

      1. Le composant Chip est définie dans l'objet Riot components:{} puis chargée avec Label .
      2. Dès que nous utilisons slot="item", par exemple { value }, l'élément Chip sera injecté dans toutes les cellules. Utiliser une seule Chip comme Slot est une mauvaise idée : la Chip sera affichée pour toutes les cellules. Ce n'est pas ce que nous voulons !
      3. Grâce aux trois attributs dynamiques (magie des Higher Order Components ✨), nous pouvons contrôler ce qui est affiché :
        • item est l'objet pour la ligne actuelle,
        • column pour la clé d'en-tête actuelle,
        • value pour la valeur de la cellule.
      4. Lorsque plusieurs éléments HTML sont affichés dans un slot, il est préférable de les envelopper dans une balise "template", dans notre cas : .
      5. La partie la plus importante est : { value }. Si la colonne actuelle est diet, la Chip affiche la valeur, sinon, la valeur brute est affichée.

      Voici le résultat HTML :

      Table Riot Component with custom chips components to print cells values

      Composant Table avec des cases à cocher

      Le processus d'insertion de cases à cocher dans le tableau est le même que dans la section précédente : le composant de case à cocher est passé en tant que Slot, et il doit être affiché en fonction d'un nom de colonne.

      Dans notre cas, la case à cocher obtient la valeur booléenne de la colonne enabled.

      Voici le code HTML pour index.riot:

      style="width:800px;padding:20px;">
               style="margin-bottom:20px">Riot + BeerCSS
               items={ state.animals }>
                   slot="item">
                       if={ column === 'enabled' } value={ value } onchange={ (ev) => changed( item.id, ev.target.value ) } />
                       if={ column === 'diet' }>
                          { value }
                      
                       if={ column !== 'diet' && column !== 'enabled' }>
                          { value }
                      
                  
              
          
          
              import cTable from "./components/c-table-full.riot"
              import cChip from "./components/c-chip.riot"
              import cCheckbox from "./components/c-checkbox.riot"
      
              export default {
                  changed (id, value) {
                      const _el = this.state.animals.find(el => el.id === id);
                      if (_el) {
                          _el.enabled = value;
                          this.update();
                      }
                  },
                  components: {
                      cTable,
                      cChip,
                      cCheckbox
                  },
                  state: {
                      animals: [
                          {
                              id: 1,
                              name: 'African Elephant',
                              species: 'Loxodonta africana',
                              diet: 'Herbivore',
                              habitat: 'Savanna, Forests',
                              enabled: false
                          },
                          {
                              id: 2,
                              name: 'Lion',
                              species: 'Panthera leo',
                              diet: 'Carnivore',
                              habitat: 'Savanna, Grassland',
                              enabled: true
                          },
                          {
                              id: 3,
                              name: 'Hippopotamus',
                              species: 'Hippopotamus amphibius',
                              diet: 'Herbivore',
                              habitat: 'Riverbanks, Lakes',
                              enabled: false
                          }
                      ]
                  }
              }

      Décomposition du code :

      1. La case à cocher personnalisée est définie comme un composant dans l'objet Riot components:{} puis chargée avec .
      2. La case à cocher est imprimée uniquement si la colonne actuelle est enabled.
      3. La valeur de la case à cocher est définie grâce à value={ value }.
      4. Si un clic se produit sur la case à cocher, l'événement change est émis, et la fonction changed() est exécutée avec deux attributs onchange={ (ev) => changed( item.id, ev.target.value ) }:
        • La fonction obtient la valeur d'ID de l'élément comme premier argument.
        • En tant que deuxième argument, la fonction obtient la valeur de la case à cocher.
      5. La fonction changed est exécutée : d'abord, l'objet actuel est trouvé grâce à l'ID de l'élément, et enfin la valeur enabled est mise à jour.

      Capture d'écran du tableau généré avec des cases à cocher :
      Image description

      Test du composant Table

      Il existe deux méthodes pour tester le composant Table, et elles sont couvertes dans deux articles différents :

      Conclusion

      Voilà 🎉 Nous avons créé un composant Table Riot en utilisant BeerCSS. Un tableau peut avoir beaucoup plus de fonctionnalités, et de nouveaux articles seraient nécessaires pour:

      • rendre les lignes sélectionnables,
      • créer des en-têtes personnalisés,
      • un défilement virtuel,
      • un tri,
      • un filtrage,
      • et plus encore.

      Le code source du tableau est disponible sur Github : https://github.com/steevepay/riot-beercss/blob/main/components/c-table.riot

      Passez une excellente journée ! Santé 🍻