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 met 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. Et donc notre traitement se déclenche avant que la virgule soit ajoutée dans la ‘value’.
Au lieu de keydown, on va plutot prendre keyup, qui déclenchera notre traitement après la fin de l’appuie. 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 celà 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 champs. Par exemple, une simple virgule ou un 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/>
Maintenant, 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 à dispo 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 raffraî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 la datalist
dans le html :
<datalist id="tag_suggestion">
{#each tagsugg as ts}
<option>{ts}</option>
{/each}
</datalist>
Et ensuite, je fais référence à cette ‘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é (in english), sur le Repl Svelte