Cet article explique comment créer un composant Input avec RiotJS, en utilisant le style Material Design de BeerCSS. Avant de commencer, assurez-vous d'avoir une application de base, ou lisez mon article précédent Configurer Riot + Vite et Riot + BeerCSS.
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".
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:
<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, 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 :
<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 :
- Le composant est importé avec import cInput from "../components/c-input.riot";, puis chargé dans l'objet Riot components:{}.
- Dans le HTML, le composant Input est instancié avec
<c-input value={ state.firstname } onchange={ updateValue } onkeydown={ updateValue }></c-input>
. - 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
.
- 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:
<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:
<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 :
<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:
Si l'attribut "helper" est vide, il masquera les balises HTML "span".
L'expression est la même pour un label et une erreur:
<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.
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):
<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:
<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:
{ 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 :
<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 :
<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>
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 🍻