🇫🇷 Input Component avec RiotJS
Tags: french, javascript, riotjs, frontend
Published: 2024-05-21 11:31:05

Cet article explique comment créer un composant Input avec [RiotJS](https://riot.js.org/), en utilisant le style Material Design de [BeerCSS](https://www.beercss.com/). Avant de commencer, assurez-vous d'avoir une application de base, ou lisez mon article précédent Configurer [Riot + Vite](https://dev.to/steeve/base-dune-application-riotjs-avec-vite-1dkb) et [Riot + BeerCSS](https://dev.to/steeve/utiliser-riotjs-avec-beercss-material-design-3-2b83).

> Ceci est une série d'articles sur RiotJS, pour créer des composants et apprendre les meilleures pratiques pour déployer une application en production. Je pars du principe que vous avez une compréhension de base de Riot ; cependant, n'hésitez pas à consulter la documentation Riot si nécessaire : https://riot.js.org/documentation/

BeerCSS fournit de nombreux états d'entrée, tels que "error", "icons" ou "loading", et il prend en charge différents styles, tels que "round", "small" et "medium". L'objectif est de créer un composant Input qui prend en charge dynamiquement tous les styles et écoute les évènements "input", "change", "onkeyup".

![Capture d'écran des input BeerCSS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wavds13q0hn4hgazbiak.png)

## Base du Component Input

Tout d'abord, créez un nouveau fichier nommé **c-input.riot** dans le dossier **components**. Le `c-` signifie "composant", une convention de nommage pratique:

Dans **./components/c-input.riot**, écrivez le HTML et Javascript suivant pour faire un input:
```html
<c-input>
    <div class="field border">
        <input type="text" value="{ props?.value }">
    </div>
</c-input>
```
Décomposons le code :
* Pour injecter une valeur personnalisée dans l'input, vous devez utiliser **value="{ props?.value }"** : La valeur est passée en tant que `props`. Avec les [propriétées Riot](https://riot.js.org/documentation/#properties), nous pouvons transmettre des données aux composants via des attributs personnalisés.
* Les balises `<c-input>` et `</c-input>` définissent une balise racine personnalisée, portant le même nom que le fichier. Vous devez les écrire ; sinon, cela pourrait créer des résultats inattendus. Utiliser uniquement `<input>` ou redéfinir des balises HTML natives est une mauvaise pratique.
* La balise enfant `div` est copiée des exemples d'input de BeerCSS.

Ensuite, nous pouvons charger et instancier le **c-input.riot** dans une page d'accueil :
```html
<index-riot>
    <div style="width:500px;padding:20px;">
        <h4 style="margin-bottom:20px">Riot + BeerCSS</h4>
        <c-input value={ state.firstname } onchange={ updateValue } onkeydown={ updateValue }></c-input>
    </div>
    <script>
        import cInput from "../components/c-input.riot";

        export default {
            components: {
                cInput
            },
            state: {
                firstname: 'Default name'
            },
            updateValue (ev) {
                this.update({
                    firstname: ev.target.value
                })
            }
        }
    </script>
</index-riot>
```
Analyse du code :
1. Le composant est importé avec **import cInput from "../components/c-input.riot";**, puis chargé dans l'objet Riot **components:{}**.
2. Dans le HTML, le composant Input est instancié avec `<c-input value={ state.firstname } onchange={ updateValue } onkeydown={ updateValue }></c-input>`.
3. Trois attributs sont transmis :
   * **value** : ce sera la valeur de l'input.
   * **onchange** et **onkeydown** : Ce sont des événements émis si la valeur de l'input change ou si une touche est pressée. Lorsqu'un événement est déclenché, il exécute une fonction, dans notre cas `updateValue`.
4. La valeur par défaut du `firstname` est "Default name".


Pour stocker la valeur de l'input, un état nommé firstname est créé: `state.firstname`, et il doit être mis à jour grâce à la fonction nommée `updateValue`. Si la valeur de l'input change, le `firstname` prendra la nouvelle valeur de l'input, puis la propagera dans props.

Écrire une fonction dédiée pour mettre à jour `state.firstname` n'est pas nécessaire, cela peut être simplifié en une seule ligne:
```html
<c-input value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeydown={ (ev) => update({ firstname: ev.target.value }) }></c-input>
```

## Supporter les Styles du Composant Input

Un input peut afficher plusieurs styles : normal, helper, errors, icon, chargement, ou tous combinés.

Ajoutons le "helper", un message d'aide affiché sous l'Input: 
```html
<c-input>
    <div class="field border">
        <input type="text" value={ props?.value ?? '' }>
        <span class="helper" if={ props?.helper }>{ props.helper }</span>
    </div>
</c-input>
```
Le HTML provient de BeerCSS, puis j'ai simplement ajouté deux éléments :
* **{ props.helper }** Il affichera l'aide via les props (attribut HTML).
* **if={ props?.helper }** condition pour afficher ou masquer le message d'aide si une valeur existe.

Essayons l'aide en définissant l'attribut "helper" avec une chaîne, par exemple :
```html
<c-input helper="This is a helper" label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeydown={ (ev) => update({ firstname: ev.target.value }) }></c-input>        
```
Le résultat:

![Riot input component with a helper as attribute](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9n2xwx66zft4hqt6wj9o.png)

Si l'attribut "helper" est vide, il masquera les balises HTML "span".

L'expression est la même pour un label et une erreur:
```html
<c-input>
    <div class="
        field border
        { props?.label ? ' label' : '' }
    ">
        <input type="text" value={ props?.value }>
        <label if={ props?.label }>{ props.label }</label>
        <span class="helper" if={ props?.helper && !props?.error }>{ props.helper }</span>
        <span class="error" if={ props?.error }>{ props.error }</span>
    </div>
</c-input>
```
Analyse du code :
* Riot supporte les conditions Javascripts dans les valeurs des attributs, dans notre cas nous ajoutons la classe **label** si `props.label` existe.
* La balise et la valeur du **label** sont affichées si `props.label` existe.
* L'erreur est imprimée uniquement si `props.error` existe. En même temps, la condition masque "props.helper" parce que c'est plus important d'afficher une erreur.

![Riot Input Component with an error state](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/290cp4dr109idqfxb57m.png)

Nous pouvons suivre la même logique pour prendre en charge l'icone de chargement, les icônes en préfixe (props.input) et suffixe (props.inputend):
```html
<c-input>
    <div class="
        field border
        { props?.error ? " invalid" : '' }
        { props?.label ? " label" : '' }
        { props?.icon || props?.loading ? " prefix" : '' }
        { props?.iconend || props?.loadingend  ? " suffix" : '' }
    ">
        <progress if={ props?.loading } class="circle"></progress>
        <i if={ props?.icon && !props?.loading }>{ props?.icon }</i>
        <input type="text" value={ props?.value }>
        <label if={ props?.label }>{ props.label }</label>
        <i if={ props?.iconend && !props?.loadingend }>{ props?.iconend }</i>
        <progress if={ props?.loadingend } class="circle"></progress>
        <span class="helper" if={ props?.helper && !props?.error }>{ props.helper }</span>
        <span class="error" if={ props?.error }>{ props.error }</span>
    </div>
</c-input>
```
De nombreuses applications ont besoin de différents types d'Input, tels que des nombres, des mots de passe, et plus encore. Créons une prop pour prendre en charge tous les types:
```html
<input type={props?.type ?? 'text'} value={ props?.value }> 
```
Le type d'Input par défaut est *text*, et il peut également prendre en charge : **number**, **password**, **file**, **color**, **date**, **time**.

Le composant est presque terminé: Ajoutons maintenant la prise en charge de plusieurs tailles et formes (comme rond, remplissage gris, petit, etc.). Nous devons ajouter des classes conditionnellement:
```js
        { props?.round ? " round" : ''}
        { props?.fill ? " fill" : ''}
        { props?.small ? " small" : ''}
        { props?.medium ? " medium" : ''}
        { props?.large ? " large" : ''}
        { props?.extra ? " extra" : ''}
```
Voici le code final du composant Input :
```html
<c-input>
    <div class="
        field border
        { props?.round ? " round" : ''}
        { props?.fill ? " fill" : ''}
        { props?.small ? " small" : ''}
        { props?.medium ? " medium" : ''}
        { props?.large ? " large" : ''}
        { props?.extra ? " extra" : ''}
        { props?.error ? " invalid" : '' }
        { props?.label ? " label" : '' }
        { props?.icon || props?.loading ? " prefix" : '' }
        { props?.iconend || props?.loadingend  ? " suffix" : '' }
    ">
        <progress if={ props?.loading } class="circle"></progress>
        <i if={ props?.icon && !props?.loading }>{ props?.icon }</i>
        <input type={props?.type ?? 'text'} value={ props?.value }>
        <label if={ props?.label }>{ props.label }</label>
        <i if={ props?.iconend && !props?.loadingend }>{ props?.iconend }</i>
        <progress if={ props?.loadingend } class="circle"></progress>
        <span class="helper" if={ props?.helper && !props?.error }>{ props.helper }</span>
        <span class="error" if={ props?.error }>{ props.error }</span>
    </div>
</c-input>
```
Enfin, nous pouvons instancier dans le fichier `index.riot` plusieurs `<c-inputs>` pour essayer tous les attributs :

```html
<c-input label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>
        
<c-input helper="This is a helper" label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>        

<c-input error="Something is wrong" helper="This is a helper" label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>        
        
<c-input icon="edit" label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>
        
<c-input iconend="edit" label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>
        
<c-input icon="search" iconend="edit" label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>

<c-input loading="true" icon="edit" label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>
        
<c-input loadingend="true" iconend="edit" label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>

<c-input fill="true" round="true" small="true" label="Test" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>
```
Voici le résutat:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/18uzqr7m14n4f6hkkxcv.png)

Le code source de l'Input est disponible sur Github:
https://github.com/steevepay/riot-beercss/blob/main/components/c-input.riot

## Conclusion

Et voilà 🎉 Nous avons créé un composant Input avec RiotJS en utilisant un style Material Design avec BeerCSS.

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

Bonne journée ! Cheers 🍻