Script d'auto-complétion de mots

Le problème exposé dans ce sujet a été résolu.

Bonjour amis agrumes,

je suis entrain de réaliser un tp sur l'auto-complétion (en suivant un excellent tutoriel sur le javascript réalisé par un agrume de ce site) qui ressemble à celui de la barre d'adresse des navigateurs. Mon problème réside dans le fait que lorsque je choisis un mot qui apparaît dans la liste des résultats proposés par mon auto-complétion, et bien cette dernière (l'auto-complétion) ne fonctionne plus jusqu'à ce que je vide le mot qui se trouve dans mon champ texte. Voici la structure HTML de mon tp.

1
2
3
4
5
<form class="col-md-4" method="post" action="traitement.php" id="myForm">
    <input type="text" autocomplete="off" class="form-control" name="ville" id="ville">
    <div class="panel panel-default" id="resultat">
    </div>
</form>

C'est dans le <div class="panel"..> que je mets mes résultats. Niveau javascript voici ce que je fais.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//on récupère les champ de saisie et de résultat, ainsi que le formulaire
    var ville = document.getElementById('ville'),
        resultat = document.getElementById('resultat')
        myForm = document.getElementById('myForm');

    //on instancie l'objet qui sert à effectuer requête ajax
    var xhr = new XMLHttpRequest();

    //on rajoute les évènements qu'il faut
    myForm.addEventListener('submit', function(e){
        e.preventDefault();
    }, false);

    ville.addEventListener('keyup', function(e){
        //on vide les précédents résultats de la recherche
        resultat.innerHTML = '';
        resultat.style.display = 'none';

        //Si l'utilisateur tape sur entrée, on efface les résultats de recherche
        if(e.keyCode == 13)
        {
            e.target.nextElementSibling.innerHTML = '';
            e.target.nextElementSibling.style.display = 'none';
        }
        if (xhr.readyState !== xhr.UNSENT) //S'il y a une requête en cours, on l'annule
            xhr.abort();
        console.log(xhr);
        //préparation de la requête
        xhr.open('POST', 'traitement.php');
        //on récupère le formulaire
        var formulaire = document.getElementById('myForm'),
            form = new FormData(formulaire);
        //envoi du formulaire
        xhr.send(form);
    }, false);
//traitement de la réponse
    xhr.addEventListener('loadend', function(){
        if(xhr.status == 200) //si la requête s'est bien déroulée
        {
            //on récupère les résultats
            var results = xhr.responseText.split('|');
            //S'il y a des résultats, alors on construit le bloc de résultats
            if(xhr.responseText != "")
            {
                //on rend visible le bloc des résultats
                resultat.style.display = 'block';
                for (var i = 0, c = results.length; i < c; i++)
                {
                    var div = document.createElement('div');
                    div.classList.add('supposition');
                    div.appendChild(document.createTextNode(results[i]));
                    resultat.appendChild(div);
                }
            }
        }
    }, false);

je n'ai pas mis toutes mes gestions d'évènements car je me dis que ça n'influence pas le résultat.

Aussi, par moment, je me retrouve avec une réponse de ce genre dans mon champ texte

1
<div class="supposition"></div>

En gros, j'ai le HTML d'un résultat vide qui s'incruste dans mon champ texte, je ne sais pas trop pourquoi. Si quelqu'un peut bien regarder ce qui ne va pas dans mon code, ce serait sympa.

Cordialement.

Edit Ymox

Correction du tag dans le titre

+0 -0

jusqu'à ce que je vide le mot qui se trouve dans mon champ texte.

Pourrait tu être plus clair sur cette phrase avant que je commence a chercher?

Et a tu essayer de débugger avec ton explorateur ou avec des alert(), car bien souvent ça permet de localiser pas mal le problème en JS.

+0 -0

A mon avis il faut aussi poster le code php, il y a peut-être une partie de la réponse de l'autre côté.

La première chose qui m'intrigue un peu ici, c'est que tu réutilises xhr pour faire plusieurs requêtes. Je ne suis pas sûr que ce soit sans influence. Dans la quasi totalité des codes utilisant XMLHttpRequest que j'ai déjà vu passer depuis 7 ou 8 ans, on ne réutilisait pas l'objet après la fin de la requête. Ce n'est peut-être plus d'actualité mais on ne sait jamais…

Suggestion sinon, tu pourrais regarder l'élément HTML5 <datalist>. Ca te permettrait d'avoir une liste où les compétions sont cliquables et où il y a une gestion de la sélection. Compatible IE10+ mais vu que tu utilises déjà FormData qui n'est compatible qu'avec ce dernier, ça ne changerait pas grand chose.

+0 -1

Suggestion sinon, tu pourrais regarder l'élément HTML5 <datalist>. Ca te permettrait d'avoir une liste où les compétions sont cliquables et où il y a une gestion de la sélection. Compatible IE10+ mais vu que tu utilises déjà FormData qui n'est compatible qu'avec ce dernier, ça ne changerait pas grand chose.

QuentinC

Le problème de <datalist> c'est que c'est encore mal supporté et source de bugs.

Pour ce qui est de xhr c'est vrai que c'est pas forcément une bonne idée, mais il y a un .abort() qui l'annule avant de la relancer. Et comme c'est toujours la même page qui est utilisée, ça devrait pas être trop problématique.

Cela peut ne pas être exactement ce que tu recherches, mais est-ce que quelque chose comme select2 pourrait t'aider ?

Sinon, il y a quelque chose qui m'étonne dans ton JavaScript, pour le peu que j'ai regardé : les lignes 20 à 24. Pour effacer les résultats, je viderais directement le div#result, je ne comprends pas pourquoi tenter de passer au nœud suivant celui d'où l'événement a été lancé.

+0 -0

Sinon, il y a quelque chose qui m'étonne dans ton JavaScript, pour le peu que j'ai regardé : les lignes 20 à 24. Pour effacer les résultats, je viderais directement le div#result, je ne comprends pas pourquoi tenter de passer au nœud suivant celui d'où l'événement a été lancé.

Ca dépend, s'il a plusieurs champs comme celui-là, mieux vaut éviter les références absolues à un quelconque élément du DOM.

+0 -0

Bonjour à vous, désolé d'avoir mis si long à répondre, j'étais un peu occupé ses derniers jours. Sanoc, en fait je me rends compte que si je sélectionne un mot comme "Aubagne". Si je supprime le "e" à la fin,je n'obtiens même pas "Aubagne" parmi mes résultats. Toujours sur le même mot, si je supprime les 4 derniers caractères, j'obtiens trois mots qui se trouvent dans ma liste de ville mais "Aubagne" n'y figure pas. Voici les mots que j'obtiens "Eaubonne", "Fleury-les-Aubrais", "Maubeuge".

C'est bien possible que le problème vienne du PHP. Voici mon code PHP:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
//on récupère les noms des villes dans un tableau
$file = file_get_contents('./towns.txt', FILE_USE_INCLUDE_PATH);
$tableau = unserialize($file);

//on récupère la saisie utilisateur
$saisie = $_POST['ville'];

//tableau qui sera retourné et qui contiendra les occurences correspondant à la recherche
$resultat = array();

//on parcourt le tableau pour chercher les éléments correspondant à la recherche
for($i = 0; $i < count($tableau); $i++)
{
    if(stripos($tableau[$i], $saisie)) //on vérifie la présence de la saisie sur chaque valeur, si c'est le cas, on l'ajoute au tableau résultat
        $resultat[] = $tableau[$i];
}
//on trie les résultats par ordre lexico-grammatical croissant
sort($resultat);

//on affiche le résultat sous forme de chaîne formattée
echo implode('|',$resultat);
?>

Je vous remercie d'avance pour de nouvelles propositions

+0 -0

Bonjour viki53, oui je me suis rendu compte que c'est la méthode strpos qui causait problème :p Par contre, j'ai pas fait attention au warning de la doc. J'ai utilisé à la place substr_count qui marche très bien.

Par contre, j'ai un autre problème. Je voudrais utilisé les touches fléchées pour pouvoir me déplacer dans les résultats des recherches et qu'il remplissent mon champ texte, exactement comme avec le champ de recherche de google par exemple. Voici l'algorithme auquel je pense

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
ville.addEventListener('keyup', function(e){
        //on vide les précédents résultats de la recherche
        resultat.innerHTML = '';
        resultat.style.display = 'none';

        /*Si l'utilisateur tape sur la touche de direction inférieure
        on défile les résultats disponibles vers le bas*/
        if(e.keyCode == 40)
        {
            //Si le premier élément résultat a le focus (ou simulé un hover car j'ai un css adapté quand un élément est survolé)
                //Je retire le focus
                //Je passe le focus à l'élément suivant
            //Sinon 
                //Je donne le focus au premier élément (ou simule le survol)
        }
...

Mais je ne sais pas comment m'y prendre car les élément div ne prennent pas de focus. A nouveau besoin d'aide.

+0 -0

Voici ce que j'ai essayé de faire sans succès. Je regarde les propriétés css de mon div résultats, si aucun n'a les propriétés que j'ai choisi pour le survol, alors je l'ajoute au premier, sinon je l'ajoute à l'élément suivant.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
ville.addEventListener('keyup', function(e){
        //on vide les précédents résultats de la recherche
        resultat.innerHTML = '';
        resultat.style.display = 'none';

        /*Si l'utilisateur tape sur la touche de direction inférieure
        on défile les résultats disponibles vers le bas*/
        if(e.keyCode == 40)
        {
            controle(e.target.nextElementSibling, "bas");
        }
function controle(resultat, sens)
    {
        var elementSurvole = false,
            div = 0,
            tableau = resultat.childNodes;

        /*function deplacement(element)
        {

        }*/

        for(var i = 0, c = tableau.length; i < c; i++)
        {
            if(tableau[i].nodeType == 1)
            {
                div = tableau[i];
                if(getComputerStyle(div).color == 'white')
                {
                    elementSurvole = true;
                    break;
                }
            }
        }

        if(elementSurvole)
        {
            if(sens == 'bas')
            {
                if(div.nextElementSibling)
                {
                    div.style.backgroundColor = 'white';
                    div.style.color = 'black';
                    var elementSuivant = div.nextElementSibling;
                    elementSuivant.style.backgroundColor = 'rgb(0,127,255)';
                    elementSuivant.style.color = 'white';
                }
            }
        }
        else
        {
            var premier = resultat.firstElementChild;
            premier.style.backgroundColor = 'rgb(0,127,255)';
            elementSuivant.style.color = 'white';
        }
    }

Je me rends compte que mon paramètre resultat ne contient pas d'élément enfant à chaque fois. Pourquoi?

Personnellement je l'ai avait lu, mais je ne suis pas vraiment le plus doué dans cette affaire. Il faut être patient et continuer de chercher en attendant, le forum est la pour aider pas pour tout résoudre.

+0 -0

J'avais justement noté ton message comme à regarder plus en détail plus tard, donc patience  ;)

Edit

Est-ce que tu as vérifié que resultat contenait, dans controle, bien ce à quoi tu t'attendais ? Il n'est pas impossible que tu tombes sur un nœud texte s'il n'y a ne serait-ce qu'un espace entre la source de l'événement et la cible de ton action…

+0 -0

Salut, oui, effectivement j'ai résolu mon problème. Désolé d'avoir mis si long à répondre :p En fait, j'ai réécris mon script en utilisant une variable pour stocker l'indice de l'élément actuellement survolé dans ma liste des résultats. Ensuite en fonction de la touche appuyée, je faisais les vérifications sur cette variable. Voici un extrait de code utilisé

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//on récupère les champ de saisie et de résultat, ainsi que le formulaire
    var ville = document.getElementById('ville'),
        resultat = document.getElementById('resultat')
        myForm = document.getElementById('myForm')
        selectedIndex = -1;

function obtenirDivResultat(resultat)
    {
        var enfants = resultat.childNodes,
            length = resultat.childNodes.length
            divs = [];
            console.log(enfants);
        for(var i = 0; i < length; i++)
        {
            (function(i){
                if(enfants[i].classList.contains("supposition"))
                    divs.push(enfants[i]);
            })(i);
        }
        //console.log(divs);
        return divs;
    }
    ville.addEventListener('keyup', function(e){
        /*Si l'utilisateur tape sur la touche de direction du bas
        on défile les résultats disponibles vers le bas*/
        if(e.keyCode == 40)
        {
            //console.log("touche bas");
            var divs = obtenirDivResultat(resultat);
            if(divs.length > 0 && selectedIndex + 1 < divs.length)
            {
                console.log(selectedIndex);
                if(selectedIndex > - 1) //Si un élément a déjà été selectionné
                    //on retire la classe hover
                    divs[selectedIndex].classList.remove("hover");
                divs[++selectedIndex].classList.add("hover");
            }
        }
...

Après ça fait un moment que j'ai laissé le script ^^ mais je crois que j'avais résolu le problème ainsi. Merci pour votre aide.:)

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte