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

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

Un Select fournit un menu d'options : L'objectif est de créer un composant Select avec un design BeerCSS et d'écouter les événements de changement.

Select Elements made with BeerCSS

Base du composant Select

Tout d'abord, créez un nouveau fichier nommé c-select.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 (CSS trouvé dans la documentation BeerCSS) dans ./components/c-select.riot:

class="field suffix 
                {props?.label ? ' label' : null }
                {props?.error ? ' invalid' : null }
            ">
        
             if={ !props?.loading }  each={option in props.options} value={ option } selected={ option === props.value ? true : null }>{ option }
        
         if={ props?.label }>{ props.label }
         if={ !props?.loading && !props?.img  && !props?.icon} >arrow_drop_down
         if={ !props?.loading && !props?.img  && props?.icon} >{ props.icon }
         if={ !props?.loading && props?.img } class="circle" src={ props.img }>
         if={ props?.loading } class="circle">
         class="helper" if={ props?.helper && !props?.error }>{ props.helper }
         class="error" if={ props?.error }>{ props.error }

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. La liste des options passées en tant qu'attributs est un tableau de chaînes de caractères, par exemple ["red", "green", "blue"], et elle est parcourue grâce à l'attribut Riot each comme suit : each={option in props.options}. Une nouvelle balise est créée pour chaque élément de la liste.
  3. Pour chaque élément de la liste, deux attributs importants sont définis sur la balise :
    • L'attribut value obtient la valeur de l'élément de la liste, soit red, green ou blue. L'événement change émettra la valeur si l'option est sélectionnée.
    • L'attribut selected définit si props.value est égal à la valeur de l'option.
  4. Le composant a un état de chargement : si props?.loading existe, les balises d'option, l'icône et l'image sont masquées avec if={ !props?.loading } ; enfin, il affichera une icône de chargement.
  5. Le select a une icône par défaut arrow_drop_down, et il est possible de la remplacer par une autre icône Google Material en passant l'attribut icon. L'icône n'est affichée que s'il n'y a pas d'image et pas d'état de chargement.
  6. Au lieu d'une icône pour l'élément select, une image avec l'attribut img peut être incluse. L'image n'est pas imprimée si la propriété de chargement est activée.
  7. Pour aider l'utilisateur, une propriété helper peut être ajoutée ; elle affichera un message sous l'élément select. Si props?.helper existe, la classe helper est ajoutée au composant.
  8. Si une erreur se produit, il est possible d'afficher un message d'erreur grâce à l'attribut error. Si props.error existe, la classe error est ajoutée au composant.

Enfin, chargez et instanciez le c-select.riot dans une page nommée index.riot:

style="width:600px;padding:20px;">
         style="margin-bottom:20px">Riot + BeerCSS
         label="Color" options={ state.options } value={ state.value } onchange={ changed } />
         img="./favicon.png" helper={ "Color selected: " + state.value } label="Color" options={ state.options } value={ state.value } onchange={ changed }/>
    
    
        import cSelect from "./components/c-select.riot"

        export default {
            components: {
                cSelect
            },
            state: {
                options: [
                    'red',
                    'green',
                    'blue',
                    'purple'
                ],
                value: 'green'
            },
            changed (ev) {
                this.update({ value: ev.target.value })
            }
        }

Détails du code :

  1. Les composants sont importés avec import cSelect from "./components/c-select.riot"; puis chargés dans l'objet Riot components:{}.
  2. Pour les tests, le composant cSelect est instancié deux fois avec dans le HTML. Le second select prend une image avec l'attribut img.
  3. Le composant select prend la liste des options dans l'attribut option avec options={ state.options }.
  4. La valeur sélectionnée est stockée dans l'objet Riot State state: { value: 'green' }. La valeur par défaut sélectionnée est verte.
  5. Si une option est sélectionnée, l'événement change est déclenché, et la fonction changed est exécutée pour mettre à jour state.value.
  6. Pour mettre à jour l'état d'un composant, la fonction Riot this.update() doit être utilisée. Dans notre cas, state.value obtient la valeur de l'événement, comme suit : this.update({ value: ev.target.value }).

Voici le HTML généré :
Two select components made with RiotJS with a list of strings as option

Composant Select Avancé

Un front-end de production obtient souvent une liste d'objets provenant d'une base de données/API, comprenant :

  • Un ID sous forme de nombre ou d'UUID sous forme de chaîne de caractères pour la valeur de l'option.
  • Une chaîne de caractères différente pour le label de l'option.

Par exemple, fournissons une liste de villes au composant select:

export default {
  state: {
    list: [
     { id: 0, city: "Paris" },
     { id: 1, city: "London"},
     { id: 2, city: "Berlin"},
     { id: 3, city: "New York"}
    ]
  }
}

Le composant Select actuel ne prend qu'une liste de chaînes de caractères, et des modifications doivent être apportées pour supporter une liste d'objets. Ajoutons le code HTML suivant au composant c-select.riot :

each={option in props.options} value={ option[props.itemValue] }  key={ option[props.itemValue] } selected={ option[props.itemValue] === props.value ? true : null } if={ props.itemValue && !props?.loading }>{ option[props?.itemLabel || props?.itemValue] }

Détails du code :

  • Pour parcourir tous les objets, l'attribut Riot each est utilisé each={option in props.options}.
  • Pour attribuer une valeur basée sur un objet, la propriété item-value (props.itemValue) définit la clé attribuée à l'attribut value. * Pour décomposer l'expression value={ option[props.itemValue] } :
    • L'option est un élément de la liste
    • Passer la clé props.itemValue entre crochets renvoie la valeur de l'objet.
  • Pour attribuer un label basée sur un objet, la propriété item-label (props?.itemLabel) définit la clé attribuée à l'étiquette de l'option. Si itemLabel n'existe pas, il prend itemValue comme étiquette par défaut.
  • Remarquez key={ option[props.itemValue] } : Ajouter l'attribut key permet à RiotJS d'optimiser la boucle. Si les listes sont immuables, cela améliorera grandement les performances de la boucle.
  • Enfin, l'option n'est imprimée que si la propriété itemValue existe et que le composant n'est pas en cours de chargement.
  • Bonus : une icône Google Material nommée "explore" est passée.

Modifions index.riot et fournissons une liste d'objets au composant Select :

style="width:600px;padding:20px;">
         style="margin-bottom:20px">Riot + BeerCSS
         icon="explore" helper={ "City selected: " + state.city } label="Cities" options={ state.list } value={ state.city } item-value="id" item-label="city" placeholder="Select a city" onchange={ changed }/>
    
    
        import cSelect from "./components/c-select.riot"

        export default {
            components: {
                cSelect
            },
            state: {
                list: [
                    { id: 0, city: "Paris" },
                    { id: 1, city: "London"},
                    { id: 2, city: "Berlin"},
                    { id: 3, city: "New York"}
                ],
                city: 3
            },
            changed (ev) {
                this.update({ city: parseInt(ev.target.value)})
            }
        }

Décomposition du code :

  • La liste des villes est passée au composant Select avec options={ state.list }.
  • L'attribut item-value="id" définit la clé de l'objet utilisée pour la valeur de l'option.
  • L'attribut item-label="city" définit la clé de l'objet utilisée pour le label de l'option.
  • La valeur state.city est mise à jour lorsqu'un événement "change" est déclenché : cela exécutera la fonction changed. Dans notre cas, parseInt est nécessaire car la valeur de l'événement est une chaîne de caractères, et non un nombre.

Le HTML généré :
Select component made with RiotJS with a list of object for options

Test du composant Select

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

Conclusion

Voilà 🎉 Nous avons créé un composant Select avec RiotJS en utilisant BeerCSS. Le code source du select est disponible sur Github : https://github.com/steevepay/riot-beercss/blob/main/components/c-select.riot

N'hésitez pas à commenter si vous avez des questions ou besoin d'aide concernant RiotJS.

Passez une excellente journée ! Santé 🍻