L’idée est de coder une saisie de “tags” comme dans l’animation ci-dessus.
- Un champ texte, dans lequel je rentre des mots.
- Dès que je tape une virgule ou Entrée, la saisie se transforme en “tag”.
- Le tag apparaît à côté du champ avec une petite croix pour le supprimer.
Je récupère la liste des tags dans une structure de données facile d’utilisation : par exemple, un tableau de string.
Pour ce code, je me mets dans le Repl
de Svelte.
Exploration des possibles
Je sais que dans Svelte, on:keydown
déclenche une fonction à chaque appuie sur
le clavier. Je crée donc un petit bout de code pour mettre en évidence ce
comportement :
<script>
function pressed(ev){
console.info(ev.key);
};
</script>
<input on:keydown={pressed}/>
keydown
appelle ma fonction pressed
en lui passant un objet de type event
qui contient plein d’infos. Je m’intéresse seulement à key
qui contient la
touche pressée.
Chaque appuie sur le clavier, avec le curseur dans le champ texte, va provoquer un affichage dans la console.
Donc, je surveille la saisie d’une virgule en faisant un test sur key
:
<script>
function pressed(ev){
if(ev.key === ',') {
console.info("VIRGULE!!!")
}
};
</script>
<input on:keydown={pressed}/>
Rentrons dans le vif du sujet
Quand on appuie sur la virgule, c’est là qu’on a un tag à ajouter : donc je prends le contenu du champ et je l’ajoute à ma liste, un tableau vide au départ.
Pour voir ce que je fais, j’ajoute un ‘{tags}’ qui affichera la liste des tags enregistrés :
<script>
let tags = []; // ici je stock les tags
let value = ""; // la valeur du champ
function pressed(ev){
if(ev.key === ',') { // virgule ?
tags = [...tags, value]; // on ajoute au tableau
value = ""; // ne pas oublier de nettoyer
}
};
</script>
{tags}
<input on:keydown={pressed} bind:value/>
Petit problème : la virgule reste dans le champ malgré le nettoyage.
Le keydown
intercepte l’évènement avant que la touche ne soit prise en compte
par le champ. Ainsi, notre traitement se déclenche avant que la virgule soit
ajoutée dans la ‘value’.
Au lieu de keydown
, on va plutôt prendre keyup
, qui déclenchera notre
traitement après la fin de l’appui. Ainsi, on pourra embarquer la virgule dans
la valeur du champ.
<script>
let tags = [];
let value = "";
function pressed(ev){
if(ev.key === ',') {
tags = [...tags, value];
value = "";
}
};
</script>
{tags}
<input on:keyup={pressed} bind:value/> <!-- keyup à la place de keydown -->
Maintenant, la virgule ne reste plus dans le champ, mais fait partie de la ‘value’.
Je retire la virgule avant de l’ajouter à notre liste de tags avec la ligne :
value = value.replace(',','');
Et cela nous donne :
<script>
let tags = [];
let value = "";
function pressed(ev){
if(ev.key === ',') {
value = value.replace(',',''); // <-- ici on vire la virgule
tags = [...tags, value];
value = "";
}
};
</script>
{tags}
<input on:keyup={pressed} bind:value/>
Avant de continuer, je m’assure un minimum que l’utilisateur ne rentre pas n’importe
quoi dans le champ. Par exemple, une simple virgule ou la touche Entrée
sans aucune
valeur, ne devrait avoir aucun effet, ce qui n’est pas le cas avec le code
actuel.
Donc, une fois que j’ai retiré la virgule, s’il ne me reste rien, c’est que je n’ai rien à faire :
<script>
let tags = [];
let value = "";
function pressed(ev){
if(ev.key === ',') {
value = value.replace(',','');
if(value !== "") { // <-- champ non vide ?
tags = [...tags, value]; // <-- on traite...
value = "";
}
}
};
</script>
{tags}
<input on:keyup={pressed} bind:value/>
Cette imbrication d’imbrication ne me plait pas. Je préfère inverser les tests et sortir tout de suite si les conditions ne sont pas remplies. Ainsi, je me retrouve avec le “bon code” bien aligné à gauche.
Démonstration :
<script>
let tags = [];
let value = "";
function pressed(ev){
if(ev.key !== ',') return;
value = value.replace(',','');
if(value === "") return;
tags = [...tags, value];
value = "";
};
</script>
{tags}
<input on:keyup={pressed} bind:value/>
Pour peaufiner, on va aussi prendre en compte l’appui de la touche Entrée comme déclencheur, en plus de la virgule.
<script>
let tags = [];
let value = "";
function pressed(ev){
if(ev.key !== ',' && ev.key !== 'Enter') return; // <-- Enter aussi
value = value.replace(',','');
tags = [...tags, value];
value = "";
};
</script>
{tags}
<input on:keyup={pressed} bind:value/>
À présent, je vais afficher les tags plus proprement. J’itère sur chaque tag à
l’aide de #each
:
{#each tags as t,i}
...
{/each}
L’instruction #each
va parcourir la liste tags
et me mettre à disposition chaque
valeur dans t
et son index dans i
.
Dans la boucle, j’affiche chaque tag avec un ‘X’ pour pouvoir le supprimer.
J’ai pris le code utf8
correspondant à un X plus stylé 😎.
{#each tags as t,i}
{t} ⨉
{/each}
Pour faire la suppression, je crée un lien autour du X qui va appeler une
fonction del
avec, en paramètre, l’index du tag à retirer.
{#each tags as t,i}
{t} <a href="#del" on:click={()=>del(i)}>⨉</a>
{/each}
La fonction del
se contente d’appeler Splice
.
function del(idx){
tags.splice(idx,1); // <-- supprime un élément en idx
tags = tags; // <-- force la réactivité de Svelte
}
Le tags = tags
sert à déclencher le rafraîchissement de Svelte parce qu’il
ne détecte pas le changement induit par Splice
.
J’entoure chaque tag d’un élément span
et j’ajoute une petite touche de CSS
pour donner une apparence plus tag
:
{#each tags as t,i}
<span class="tag">
{t} <a href="#del" on:click={()=>del(i)}>⨉</a>
</span>
{/each}
<input on:keyup={pressed} bind:value/>
<style>
.tag {font-size: 0.8rem; margin-right:0.33rem; padding:0.15rem 0.25rem; border-radius:1rem; background-color: #5AD; color: white;}
.tag a {text-decoration: none; color: inherit;}
</style>
Cerise sur le gâteau : les suggestions !
Et si je pouvais gérer une liste de suggestion quand je commence à taper dans le champ ? Ce serait top !
En HTML, j’ai la possibilité de suggérer des valeurs à saisir dans un champ, à
partir d’une liste pré-définie : datalist
pour définir les valeurs de
suggestion possibles et la propriété list
pour y faire référence.
Je commence d’abord par créer ma liste de suggestion à l’aide d’une nouvelle
variable tagsugg
:
let tagsugg = ["tag1", "tag2", "tag3"];
Dans la réalité, cette liste pourra être générée à la volée, par exemple en reprenant tous les tags précédemment enregistrés.
Je vais utiliser cette liste pour construire datalist
dans le HTML :
<datalist id="tag_suggestion">
{#each tagsugg as ts}
<option>{ts}</option>
{/each}
</datalist>
Et ensuite, je fais référence à datalist
dans mon champ de saisie grâce à la propriété list
:
<input list="tag_suggestion" on:keyup={pressed} bind:value/>
Et c’est tout (pour le moment) !
J’ai maintenant un champ de saisie de texte qui génère des tags et le tout avec une liste de suggestion !
Une évolution intéressante serait de transformer le code en un composant web pour pouvoir le réutiliser comme une balise HTML.
Retrouvez l’intégralité de ce code, commenté (en anglais), sur le Repl
Svelte