Javascript : réaliser un champ de texte avec auto-suggestions
Vous aimeriez pouvoir réaliser une barre de recherche avec des propositions dynamiques à la manière de Google, mais vous ne savez pas comment vous y prendre ?
Alors suivez-moi ! Nous allons voir ensemble tout du long de ce tutoriel comment réaliser ce type de champ de texte !
Sommaire
Présentation
Le principe est simple : nous voulons réaliser un champ de texte qui, lorsque le visiteur saisira une lettre, ira chercher dans une liste de mots clés ceux qui commencent (ou contiennent) ces lettres, pour les afficher au visiteur.
Préparation
Bon, nous n'allons pas du tout nous occuper du design du champ de texte dans ce tutoriel, donc je vous donne la structure HTML qu'aura le champ de texte avec les suggestions, et un design que j'ai intégré à partir de ce PSD :
<form id="auto-suggest" action="#" method="post">
<input type="text" class="search" name="search" value="Rechercher..." onfocus="if(this.value=='Rechercher...')this.value=''" onblur="if(this.value=='')this.value='Rechercher...'" autocomplete="off"/>
<ul class="suggestions">
<li>Suggestion 1</li>
<li>Suggestion 2</li>
<li>Suggestion 3</li>
<li>Suggestion 4</li>
<li>Suggestion 5</li>
</ul>
</form>
le CSS :
html, body {background:url(images/noise.png) left top repeat}
#auto-suggest {width:358px;margin:0 auto}
#auto-suggest .search {
width:322px;height:33px;margin:4px;padding:0 13px;border:1px solid #cdcdcd;
color:#ccc;font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;font-size:14px;font-weight:bold;
-moz-border-radius:2px;
-webkit-border-radius:2px;
border-radius:2px;
-moz-box-shadow:inset 0 1px 4px rgba(0,0,0,0.15), 4px 4px 0 #f1f1f1,-4px 4px 0 #f1f1f1,-4px -4px 0 #f1f1f1,4px -4px 0 #f1f1f1;
-webkit-box-shadow:inset 0 1px 4px rgba(0,0,0,0.15), 4px 4px 0 #f1f1f1,-4px 4px 0 #f1f1f1,-4px -4px 0 #f1f1f1,4px -4px 0 #f1f1f1;
box-shadow:inset 0 1px 4px rgba(0,0,0,0.15), 4px 4px 0 #f1f1f1,-4px 4px 0 #f1f1f1,-4px -4px 0 #f1f1f1,4px -4px 0 #f1f1f1
}
#auto-suggest .search:focus {
color:#555;border-color:#c8c8c8;
-moz-box-shadow:inset 0 1px 4px rgba(0,0,0,0.15), 4px 4px 0 #ececec,-4px 4px 0 #ececec,-4px -4px 0 #ececec,4px -4px 0 #ececec;
-webkit-box-shadow:inset 0 1px 4px rgba(0,0,0,0.15), 4px 4px 0 #ececec,-4px 4px 0 #ececec,-4px -4px 0 #ececec,4px -4px 0 #ececec;
box-shadow:inset 0 1px 4px rgba(0,0,0,0.15), 4px 4px 0 #ececec,-4px 4px 0 #ececec,-4px -4px 0 #ececec,4px -4px 0 #ececec
}
#auto-suggest .suggestions {
width:342px;position:relative;margin:-6px auto 0;padding:0;list-style-type:none;border:1px solid #d5d4d4;background:#fff;
font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;font-size:13px;color:#555;
-moz-border-radius:2px;
-webkit-border-radius:2px;
border-radius:2px;
-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);
-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);
box-shadow:0 1px 3px rgba(0,0,0,0.1)
}
#auto-suggest .suggestions li {height:25px;padding:0 10px;line-height:25px;cursorointer;border-top:1px solid #f5f5f5}
#auto-suggest .suggestions li:hover {background:url(images/tick.png) no-repeat #fffac2;background-position:320px center;border-top-color:#fffac2}
#auto-suggest .suggestions li:first-child {border:none;}
et les images placées dans un dossier "images" :
tick.png
noise.png
Bon, comme vous pouvez le constater, l'intégration n'est pas parfaite, mais ça suffira amplement .
N'oubliez pas la ligne
autocomplete="off"
dans le champ de texte qui empêche les suggestions du navigateur, sinon c'est pas beau
Permettez-moi de revenir sur une ligne de code donnée dans le HTML :
<input type="text" class="search" name="search" value="Rechercher..." onfocus="if(this.value=='Rechercher...')this.value=''" onblur="if(this.value=='')this.value='Rechercher...'"/>
Comme vous pouvez le constater, ce champ de texte contient du javascript.
En fait lorsque l'utilisateur clique sur le champ de texte, la page appelle l'évènement "onfocus" du champ, qui contient :
if(this.value=='Rechercher...')this.value=''
En gros, si la valeur du champ est égale à 'Rechercher...' (la valeur par défaut), on vide le champ de texte, ce qui évite à l'utilisateur de devoir lui-même effacer le mot, qui compte comme du texte entré par le visiteur.
Pareillement, lorsque le visiteur clique en dehors de la page pour enlever son curseur du champ, l'évènement "onblur" est appelé :
if(this.value=='')this.value='Rechercher...'
On regarde si le contenu du champ est vide, auquel cas on modifie la valeur en 'Rechercher...', pour que le visiteur, qui finalement n'a rien inscrit dans le champ, se souvienne de son utilité .
Bien, et maintenant la touche finale :
créez un fichier script.js et ajoutez cette ligne entre les balises <head></head> de votre page HTML :
<script type="text/javascript" src="script.js"></script>
Codage !!
Allez zou ! c'est parti !
Bon, rappelez-vous de ce que j'avais dit dans le précédent tutoriel Permettre les tabulations dans une zone de texte : pour récupérer des infos sur un élément de la page en Javascript, il faut impérativement qu'elle ai fini de charger.
Donc pour exécuter le code après le chargement de la page 2 solutions :
- Mettre la balise <script> à la fin du BODY (sâle)
- Mettre le code javascript dans un window.onload (clean )
Nous allons donc utiliser l'évènement window.onload
Donc, nous en sommes à ce stade :
window.onload = function(){
};
c'est parti pour le code :
// Tout d'abord créons un tableau de mots clefs :
var motsClefs = [
'Kommunauty',
'Kommunauté',
'K',
'Krousty',
'Bob',
'Boby',
'Bobu'
// ...
];
// On récupère le formulaire à l'aide de son ID
var form = document.getElementById("auto-suggest");
// On récupère le champ de texte
var input = form.search;
Bon, je me dois de revenir sur cette ligne :
var input = form.search;
Sachez qu'en javascript, on peut obtenir un élément du HTML grace à son attribut "name" en faisant :
nomDuConteneur(ici le formulaire).attributNameDeLElement
Ensuite, vous pouvez supprimer la liste de suggestions de votre Code HTML car nous allons la créer nous-même dynamiquement en javascript  :
// On créer un "UL"
var list = document.createElement("ul");
// On lui ajoute la classe "suggestions"
list.className = "suggestions";
// On le cache pour qu'il ne soit pas visible
list.style.display = "none";
// On l'ajoute à l'intérieur du formulaire
form.appendChild(list);
Maintenant, si vous regardez le code source de votre page avec Firebug, vous devriez voir un "UL" ayant la classe "suggestions" dissimulé dans le formulaire.
Bon, nous voulons que les suggestions apparaissent uniquement si l'utilisateur tape dans le champ de texte, et uniquement s'il n'est pas vide.
Nous allons donc utiliser l'évènement onkeyup du champ de texte.
Pourquoi onkeyup (Quand on relâche la touche) plutôt que onkeydown (Quand on enfonce la touche du clavier) ?
Et bien tout simplement parce que onkeydown est appelé juste au moment où la touche est enfoncée, et donc juste AVANT que la lettre soit ajoutée au champ de texte.
c'est parti !!
input.onkeyup = function(){
// On récupère le texte
var txt = this.value;
// Si le texte est vide, alors on arrète la la fonction, et on cache la liste
if(!txt){
list.style.display = "none";
return;
}
// variable qui indiquera le nombre de suggestions correspondantes
var suggestions = 0;
// On créer une variable qui contiendra toute les suggestions qui seront affichées
var frag = document.createDocumentFragment();
for(var i = 0, c = motsClefs.length; i < c; i++){
// le code qui va venir après
}
// Si il y a des suggestions qui peuvent être affichées
if(suggestions){
// On vide le contenu de la liste
list.innerHTML = "";
// On lui ajoute les différentes suggestions
list.appendChild(frag);
// On affiche la liste
list.style.display = "block";
} // Sinon s'il n'y en a pas
else {
// On cache la liste
list.style.display = "none";
}
};
// Quand l'utilisateur dé-sélectionne le champ
// (Quand il clique ailleurs sur la page)
input.onblur = function(){
// On cache la liste
list.style.display = "none";
// Si le contenu est vide on le remplace par le mot par défaut
if(this.value=="")
this.value = "Rechercher...";
};
Je vais à présent revenir sur cette ligne :
var frag = document.createDocumentFragment();
En fait, nous créons un "Fragment", qui contiendra les suggestions.
C'est ce... cette... ce "truc" qui sera lui-même ajouté dans la liste.
Son intérêt : plutôt que d'ajouter dans la boucle "for" les suggestions une à une dans la liste, ce qui consomme plus de ressources, on les stocke toutes dans une seule variable, pour les afficher toutes en même temps
--> Plus rapide
Notez qu'il faut supprimer l'évènement onBlur qu'on avait ajouté au champ de texte dans le HTML, puisqu'il est "écrasé" par l'évènement onBlur du script :
input.onblur = function(){
// On cache la liste
list.style.display = "none";
// Si le contenu est vide on le remplace par le mot par défaut
if(this.value=="")
this.value = "Rechercher...";
};
Bon, attaquons nous maintenant à la boucle for, qui parcourt tous les mots-clefs.
Nous allons utiliser les RegEx, les Expressions régulières qui permettent de tester des expressions dans un texte.
Les Regex peuvent être créée de 2 manières différentes :
- La Longue :
var reg = new RegExp("l'expression régulière");
- La Courte :
var reg = /l'expression régulière/;
Nous seront néamoins contraint pour notre utilisation à la Regex longue, parce que l'on pourra insérer des variable dans l'expression :
var reg = new RegExp("debut"+masupervariable+"fin");
Bien ! Avant de coder, construisons ensemble la Regex :
On veut savoir si le mot clef commence par les lettres entrées, sans respecter la casse, donc :
^lesLettresEntrées
pour ne pas respecter la casse, sachez que l'objet Regex prend un 2ème paramètre, donc :
var reg = new RegExp("^"+txt, "i");
("i" signifie Insensitive)
Voilà ! je crois que tout est dit, on ajoute ça au code dans la boucle  :
// Si l'expression régulière correspond au mot clef
if(new RegExp("^"+txt,"i").test(motsClefs[i])){
// On créé l'élément HTML "LI"
var word = document.createElement("li");
// On l'ajoute au fragment
frag.appendChild(word);
// On lui mets comme contenu le mot clef avec en gras les lettres entrées
// (grâce à une RegEx et à la fonction replace() ^^ )
word.innerHTML = motsClefs[i].replace(new RegExp("^("+txt+")","i"),"<strong>$1</strong>");
// On ajoute le mot clef à l'élément HTML "LI", pour pouvoir le récupérer plus tard
word.mot = motsClefs[i];
// Lorsque le visiteur clique sur le lien
word.onmousedown = function(){
// On re-sélectionne le champ de texte
input.focus();
// On remplace sa valeur par le mot clef
input.value = this.mot;
// On cache la liste
list.style.display = "none";
// On empêche la dé-sélection du champ
return false;
};
// On indique qu'une suggestion a été ajoutée
suggestions++;
}
Donc :
l'utilisateur clique sur un lien, d'abord la div est cachée avec le onblur, et du coup le clic sur le mot n'est pas comptabilisé. Bref, ça ne marche qu'avec onmousedown.
Mais bon, inutile pour vous de savoir tout ça pour le moment. Effacer ceci de votre mémoire, et continuez gentiment à copier le code plus haut
Améliorations
Bon, les enfants, histoire que vous ne chaumiez pas pendant votre temps libre, essayez d'améliorer le script.
- Quand il y a une petite liste de mots clefs, c'est fluide, mais quand on en a beaucoup, ça devient vite galère, donc essayez de faire un système qui une fois la première lettre entrée stocke dans un deuxième tableau seulement les mots commençant par cette lettre, histoire d'alléger la boucle for quand juste une autre lettre est tapée.
Voilà ! ce tutoriel arrive à son terme ! j'espère que vous avez appris de nouvelles choses à propos du javascript et que ce tutoriel vous aura été utile !
N'hésitez pas à commenter.
Ah oui !
le récapitulatif pour les fainéants : (j'oubliais que même sur K y a encore des gens qui copient tout )
window.onload = function(){
var motsClefs = [
'Kommunauty',
'Kommunauté',
'K',
'Krousty',
'Bob',
'Boby',
'Bobu'
];
var form = document.getElementById("auto-suggest");
var input = form.search;
var list = document.createElement("ul");
list.className = "suggestions";
list.style.display = "none";
form.appendChild(list);
input.onkeyup = function(){
var txt = this.value;
if(!txt){
list.style.display = "none";
return;
}
var suggestions = 0;
var frag = document.createDocumentFragment();
for(var i = 0, c = motsClefs.length; i < c; i++){
if(new RegExp("^"+txt,"i").test(motsClefs[i])){
var word = document.createElement("li");
frag.appendChild(word);
word.innerHTML = motsClefs[i].replace(new RegExp("^("+txt+")","i"),"<strong>$1</strong>");
word.mot = motsClefs[i];
word.onmousedown = function(){
input.focus();
input.value = this.mot;
list.style.display = "none";
return false;
};
suggestions++;
}
}
if(suggestions){
list.innerHTML = "";
list.appendChild(frag);
list.style.display = "block";
}
else {
list.style.display = "none";
}
};
input.onblur = function(){
list.style.display = "none";
if(this.value=="")
this.value = "Rechercher...";
};
};
Lucas.
Ps : nous sommes TOUJOURS des rebelles
Ajoute un commentaire !