Bonjour
J'ai créé un formulaire avec plusieurs <input type='text'> où je souhaite proposer de l'autocomplétion grâce à Jquery UI. Certaines autocomplétions sont récupérées à partir d'un tableau statique, mais l'un des champs récupère le tableau dynamiquement à partir d'une base de données, via AJAX (que j'ai commencé à étudier pour cette occasion, donc ma maîtrise sur cette méthode est approximative). J'ai donc un problème sur la réponse XMLHTTPRequest.responseText, qui est apparement vide. Des suggestions ?

autocomplete.php :

<?php

$servername = "localhost";
$username="root";
$password="";
$database = "db";
$conn = mysqli_connect($servername, $username, $password, $database);

if (mysqli_connect_errno()) {
    printf("&Eacute;chec de la connexion : %s\n", mysqli_connect_error());
    exit();
}

$field=$_GET['f'];
$result = [];
switch ($field){
    case 'name' :
        $sql = /*requête SQL*/;
        $r = mysqli_query($conn, $sql);
        $i = 0;
        while ($res = mysqli_fetch_assoc($r)) {
            $result[$i] = $res['data1'];
            $i++;
        }
        break;
    case 'type' :
        $result = [/*data*/];
        break;
    case 'genre' :
        $result = [/*data*/];
        break;
    case 'owned' :
        $result = [/*data*/];
        break;
}
return $result;
?>

script.js :

$('.cells1').focus(function(){
        xmlhttp = getHttpRequest();
        xmlhttp.onreadystatechange = function() {
            if (this.readyState == 4 && this.status == 200) {
                data = this.responseText;
            }
        };
        xmlhttp.open("GET", "autocomplete.php?f="+$(this).attr('id'), true);
        xmlhttp.send();
        $(this).autocomplete ({
            autofocus: true,
            minLength: 0,
            source: data                                     /*data is not defined*/
        })
    });

25 réponses


saibe
Réponse acceptée

yop,
je pense qu'il faut revoir les choses et utiliser jquery jusqu'au bout :

$('.cells1').focus(function(){
var $self = $(this); // pour le scope
params = { // <-- just for fun
    f : $self.attr('id')
};
    $.ajax({
        url : 'autocomplete.php',
        data : params, // just for fun : un simple f= aurait suffit mais c pas élégant :)
        type : 'GET', // il semble que ce soit ta méthode
        success : function(data){ // je sais po trop ce que tu récupères en data puisque ds ton php y'a pas de json_encode
            console.log(data); // just pour vérifier que tu récupères bien une valeur
            /* perso je pense qu'il y a une erreur au niveau php et que tu devrais récupérer un json, mais bon... */
            $self.autocomplete ({
                    autofocus: true,
                    minLength: 0,
                    source: data                                     /*data is defined*/
             })
        }
    });
});

Si tu utilise jquery ui tu as du charher jquery alors pourquoi ne pas l´utiliser dans ton appel ajax?

exact et utiliser $.ajax bcp plus simplement...
il y aurait moins de chance d'avoir une erreur d'asynchrone... :)
il faut que tu mettes ton autocomplete dans le statechange, ici :

var $self = $(this); // pour le scope
xmlhttp.onreadystatechange = function() {
            if (this.readyState == 4 && this.status == 200) {
                data = this.responseText;
                $self.autocomplete ({
                    autofocus: true,
                    minLength: 0,
                    source: data                                     /*data is defined*/
                })
            }
        };
Klayhan
Auteur

Merci pour vos réponses. Néanmoins ce n'est pas ce que je veux, puisque data est alors bien défini, mais vide. En effet, je récupère data depuis le fichier autocomplet.php, auquel je ne fais plus appel dans ta solution saibe

xmlhttp.open("GET", "autocomplete.php?f="+$(this).attr('id'), true);
Klayhan
Auteur

J'ai trois champs de formulaire de type texte : un pour un auteur, un autre pour le type de livre, un pour le genre de livre.
Le champ auteur doit renvoyer une liste en fonction des auteurs présents dans la base de donnée, les autres des simples tableaux 'statiques' puisque ces tableaux ne changent pas. C'est ce que fait mon php, il renvoie soit un tableau créé selon le renvoi de la requête SQL, soit un des tableaux fixes déjà prédéfinis
Je ne doute pas qu'il y a de meilleurs manières, mais je crée ce site surtout pour me faire ma main et la manière qui m'a parut le plus simple ^^ mais je peux me tromper

le problème vient de ton fichier PHP.
avec MySqli je ne sais pas comment ça marche.
mais je crois le problème est au niveau de ta boucle.
qu'est-ce que tu veux faire dans le fichier PHP concretement

et question: d'où te vient xmlhttp = getHttpRequest() surtout d'où sort getHttpRequest()?
parce que si tu veux instancier l'objet XMLHttpRequest tu fais un truc du genre var ajax = XMLHttpRequest pas ajax = getHttpRequest().
Ta requête étant un envoie de formulaire je pense que tu devrais utiliser la méthode POST pour ta requête ajax.
comment recupères-tu les données au final ? Et aussi c'est juste une remarque mais pourquoi tu demarres la requête ajax au focus?
c'est un peu con parce qu'une fois le focus fait la requete est lancée et aucun paramètre n'est envoyé pour lancer la recherche donc ça ne renvoie rien donc c'est normal ce qui se passe.

Bon selon moi voici ce que tu devrais faire:

  1. une requete Ajax qui envoie les données du formulaire.
  2. une requete Ajax qui recupère les données(tout en pensant comment tu vas récuperer les données(txt/xml ou txt/json))
    pour plus d'informations dessus c'est au choix:
    si tu le fais avec JS pur alors suit ce tuto
    si tu le fais avec jQuery alors suit ce tuto
  3. vérifie que ton code php est bon

hello,
est-ce que tu récupères bien une valeur ici :

console.log(data);

sinon ds ton php il y a une petite coquille ici :

if (mysqli_connect_error()) { // <-- à la place de erno
    printf("&Eacute;chec de la connexion : %s\n", mysqli_connect_error());
    exit();
}

mais je te conseille PDO pour la connection à la base de données..

Klayhan
Auteur

@Mich'Kael : XMLHttpRequest ne fonctionne pas sur certains navigateur (toussecertains IEtousse). getHTTPRequest () me permet de créer cet objet si XMLHttpRequest n'est pas pris en charge.
@saibe : mysqli_connect_errno est bien correct ^^, mais je vais regarder le fonctionnement de PDO, merci
http://php.net/manual/fr/mysqli.connect-errno.php

Voici comment j'ai voulu faire :

  1. Vu que j'ai plusieurs champs sur lequel appliquer le JqueryUI autocomplete, j'ai associé le script au moment où l'utilisateur clique sur le champ (d'où le focus)
  2. Le :focus me permet d'envoyer le attr('id') du champ à ma fonction php inclue dans autocomplete.php. Si id=type, id=genre ou id=owned, la fonction renvoie un tableau prédéfini. Si id=name, la fonction envoie une requête SQLi au serveur, qui lui renvoie une liste que je mets dans un tableau, puis la fonction renvoie ce tableau

En l'occurrence, avec votre aide je suis passé du .open()/.send() à l'utilisation du $.ajax() et pour ce que j'en vois ça fonctionne jusqu'à l'appel du .autocomplete()

//jqueryUI autocomplete
    $('.cells1').focus(function(){
        var $self = $(this);
        params = {
            f : $self.attr('id')
        };
        $.ajax({
            url : 'autocomplete.php',
            data : params,
            type : 'POST',
            success : function(data) {
                console.log(data);                  //Renvoie bien les bonnes données mais
                $self.autocomplete({
                    autofocus: true,
                    minLength: 0,
                    source: data                        //Erreur GET 403 ici (je suppose que c'est lors de l'envoi de data à jqueryUI.autocomplete :
                                                                  //127.0.0.1 - - [12/Mar/2017:13:47:17 +0100] "GET /Lbrary/v7/Array(%20%20%20%20[0]%20=%3E%20Oui%20%20%20%20[1]%20=%3E%20Non)?term=o HTTP/1.1" 403 345
                })
            }
        })
    });

J'ai mis mon code en ligne, vous verrez peut-être davantage comment j'ai voulu procéder (si d'autres choses vous paraissent améliorables, n'hésitez pas, mais je voudrais surtout régler ce problème d'autocomplete)

http://codepen.io/Klayhan/pen/PpmdNo

qu'est-ce que te renvoie le console.log(data) ?

Klayhan
Auteur

Array
(
[0] => TOLKIEN J.R.R
[1] => GEMMEL Stella
[2] => BEORN Paul
[3] => WARD J.R
)

Par exemple, si je clique dans le champ 'Auteur', ou

Array
(
[0] => Roman Individuel
[1] => Roman Saga
[2] => BD
[3] => Manga
[4] => Documentaire
[5] => Jeunesse
[6] => Divers
)

Si je clique dans le champ 'Type', ce qui est tout à fait correct, puisque c'est ce que renvoie la fonction dans autocomplet.php

@Klayhan pourquoi faire des arrays sur des trucs statiques?
fait des selects tout simplement.Ne surcharge pas ton javascript pour rien.
l'autocompletion c'est bien mais fais-le vraiment sur le champ dont tu as besoin.
moi j'aurais lancé mon evenement sur le keyup plutot.

hello,
moi je l'aurais fait à l'init, une fois pour toute, car à chaque focus tu refais un autocomplete.... ou bien tu rajoutes un attribut autocompleted et tu testes pour savoir si tu dois le refaire....

cela dit peut-être qu'il y a un soucis avec le format des données transmises ; essaie au niveau de ton php :

echo json_encode($result); // à la place du return

et

source : JSON.parse(data) // ds l'autocomplete
Klayhan
Auteur

@Mike Prime J'ai déjà essayé les select, et ils ont l'inconvénient non négligeable de ne pas pouvoir être personnalisés.
@saibe Ah en effet, je l'avais mis sur le :focus à l'origine parce que j'avais plusieurs champs à autocompléter, mais vu que mon php renvoie désormais un tableau en fonction du champ, c'est inutile. J'avais aussi changé le renvoi de ma fonction, mais apparement mal (j'avais mis return json_encode($result), et sans utiliser JSON.parse dans $.ajax). Je vais essayer merci
source : JSON.parse(data) je le met dans ma fonction ou dans mon script ?

Bonjour.

source : JSON.parse(data) je le met dans ma fonction ou dans mon script ?

Tu as le choix, soit tu fais :

success : function(data) {
    console.log(data);
    $self.autocomplete({
    autofocus: true,
    minLength: 0,
    source: JSON.parse(data) // changement uniquement ici
})

Ou alors :

success : function(data) {
    var results = JSON.parse(data); // modification du format des données
    console.log(results);
    $self.autocomplete({
    autofocus: true,
    minLength: 0,
    source: results // définition des données
})

Dans les deux cas, c'est au niveau du code javascript et uniquement une fois que tu as récupéré des données.
Mais tu as plus simple, si tu définis que tu veux un retour de données parsées au format JSON, ce sera fait automatiquement par jQuery, soit :

$.ajax({
    url : 'autocomplete.php',
    data : params,
    type : 'POST',
    dataType: 'json', // ici ...
    // etc ...
})

dataTypes: If json is specified, the response is parsed using jQuery.parseJSON before being passed, as an object, to the success handler. The parsed JSON object is made available through the responseJSON property of the jqXHR object.

Klayhan
Auteur

Beaucoup trop d'erreurs à mon goût. J'ai voulu me rabattre sur quelque chose de plus simple pour le moment, histoire de peaufiner mon ajax, avec un bouton qui supprime une ligne de BDD. Mais même là, j'ai des erreurs :
Sur ma page select.php, j'affiche le contenu d'une table. À la fin de chaque ligne j'ai un bouton qui déclenche ceci

        $('.select_db tr td:NTH-CHILD(9)').click(function() {
        $toDel = $(this).closest('tr').find('#id').text();                  // stocke l'id (primary key) dans une variable
        $('.alert').show();                                                                 // affiche une div de confirmation avec un confirmer et un annuler
        $('#abort').click(function() {
            $('.alert').hide();
            $('#isOk').empty();
            $('#isOk').slideToggle();
            $('#isOk').append("Suppression annulée");
        });

        $('#confirm').click(function() {
            var params = {
                id : $toDel                                                                 //Récupération de l'id stockée plus haut
        }
        $.ajax({
            url : 'remove.php',
            data : params,
            type : 'GET',
            dataType : 'json',
            success : function(scd_array) {
                console.log(scd_array);
                if (scd_array.scd === 1) { //livre supprimé
                /**/
                }
                else {                                  //succès ajax mais erreur suppression
                /**/
                }
            },
            error : function(request, error) {  //erreur ajax
            /*/
            }
        })
    });
    });

J'ai écrit mon code php selon vos conseils précédents, enfin je crois

 <?php
try {
    $conn = new PDO('mysql:host=localhost;dbname=id652695_livres;charset=utf8', 'root', '', array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
} catch (Exception $e) {
    die('Erreur : '.$e->getMessage());
}

  $id = json_decode($_GET['id']);
  foreach ($conn -> query('SELECT NAME, TITLE FROM Livres WHERE `ID` = '.$id) as $row) {
    $name = $row['NAME'];
    $title = $row['TITLE'];
  }
    $result = $conn -> prepare("DELETE FROM `livres` WHERE `ID` = ?");
    $result->execute(array($id));
  if ($result->rowCount() == 1) {
      $result->closeCursor();
      $conn = null;
    echo json_encode(array('name' => $name, 'title' => $title, 'scd' => 1));
  }
  else {
      $result->closeCursor();
      $conn = null;
    echo json_encode(array('name' => $name, 'title' => $title, 'scd' => 0));
  }
?>

Seulement, j'ai un parseerror, au niveau de l'encodage json je suppose. J'ai testé différentes choses (json.stringify, sans dataType, avec dataType='html') sans comprendre ce qui n'allait pas
J'ai également une erreur au niveau de mon foreach(), dû au fait que $id soit non défini

le dataType c'est text/json.
essaie voir

Perso j'envoie d'abord du texte, et si le texte fonctionne, après je passe par un formatage json...Parce que débugger en envoyant directement du json c'est galère :(
Alors que débugger en envoyant du texte, tu mets l'output dans une div et comme ça tu vois si ton script php bug ou pas.
N'oublies pas d'envoyer les headers en php aussi.

                    header('Access-Control-Allow-Origin: localhost', false);
                    header('Access-Control-Allow-Credentials: true', false);
                    header('Content-Type: application/json');

ça c'est pour du json et controler l'origine du script, par défaut tu ne peux récupérer que sur ton serveur local. (credential: true c'est si tu as besoin des cookies)

hello,
plusieurs choses :
au niveau js, je suis pas fan de définir des events click dans des events click, m'enfin...

$('.select_db tr td:NTH-CHILD(9)').click(function() { // tu ne pourrais pas mettre une class à ton td genre remove
        $toDel = $(this).closest('tr').find('#id').text(); // toutes tes tr on un élément d'id id ? tu ne pourrais pas mettre un dataset

ce qui donnerais pour ton html

<tr data-id="42"> // je mets n'importe quoi...
    <td class="ref">42</td>
    <td class="name">name</td>
    ...
    <td class="remove">Supprimer</td>
</tr>

et le click

$('.select_db tr td.remove').click(function() {
        $toDel = $(this).closest('tr').data('id');

pour ton php tu n'as pas besoin de json_decode ton $_GET

...
$id = $_GET['id'];
$stmt = $db->prepare( $sql );
try {
    $sql = 'SELECT NAME, TITLE FROM `livres` WHERE `ID` = ' . $id;
    $req = $conn->query($sql);
    $livre = $req->fetch(PDO::FETCH_ASSOC);
    $sql = 'DELETE FROM `livres` WHERE `ID` = ' . $id;
    $req = $conn->query($sql);
    echo json_encode($livre);
}
catch (PDOException $e) {
    echo json_encode( array( 
            "erreur" => $e->getMessage()
    ) );
}

et enfin le success de ton ajax

success : function(response) {
                /* response = JSON.parse(response); // si y'a pas de dataType */
                if (response.erreur) { // y'a eu un soucis
                    console.log(response.erreur);
                }
                else { // tout s'est bien passé                     
                    console.log('le livre ' + reponse.title + ' a été supprimé');
                }
            },
Klayhan
Auteur

Le text/json c'est pour le header html je crois non ?
Quant aux header, dans la mesure où je ne récupère pas le script sur un serveur distant, ce n'est pas nécessaire

@saibe Pourquoi rajouter des attributs ? $('.select_db tr td.remove') est la même chose que $('.select_db tr td:NTH-CHILD(9)') non ?
Voilà comment mon tableau est généré dans mon php :

if ($result->fetchColumn() > 0) {                           //si le tableau n'est pas vide
            echo "<table class='select_db'><tr><td>ID</td><td>Auteur</td><td>Titre</td><td>Type</td><td>Num&eacute;ro</td><td>Genre</td><td>Poss&eacute;d&eacute;</td></tr>";
            $sql = "SELECT ID, NAME, TITLE, TYPE, RANK, GENRE, OWNED FROM Livres";
            foreach ($conn->query($sql) as $row) {
                echo "<tr><td id= 'id'>".$row['ID']."</td><td>".$row['NAME']."</td><td>".$row['TITLE']."</td><td>".$row['TYPE']."</td><td>".$row['RANK']."</td><td>".$row['GENRE']."</td><td>".$row['OWNED']."</td><td></td><td><img id='rm' src='img/remove.png' alt='DEL'></img></td></tr>";
            }
            echo "<tr><td></td><td></td><td></td><td></td><td></td><td></td><td>Fin des rayons</td></tr></table>";
        }

Je t'accorde que c'est peu clair cela dit, je devrais le générer autrement ?

Sinon merci beaucoup, le livre est bien supprimé maintenant, mais à l'affichage, il me dit que response.name et response.title sont non définis. Le console.log m'affiche par contre bien Object { NAME: "aze", TITLE:"aze" }

EDIT : j'utilisais response.name au lieu de response.NAME, ça fonctionne maintenant

;) je suis pas fan des majuscules...
difficilement lisible et donc sujet aux erreurs
je réécrirais ton code pour une meilleur lecture et en respectant certaines normes :

$html = "";
if ($result->fetchColumn() > 0) {                           //si le tableau n'est pas vide
    $html.= '<table class="select_db">';
        $html.= '<thead>';
            $html.= '<tr>';
                $html.= '<th>ID</th>';
                $html.= '<th>Auteur</th>';
                ....
            $html.= '</tr>';
        $html.= '</thead>';

        $html.= '<tbody>';
            $sql = "SELECT ID, NAME, TITLE, TYPE, RANK, GENRE, OWNED FROM Livres";
            foreach ($conn->query($sql) as $row) {
                $html.= '<tr data-id="' . $row['ID'] . '">'; // tu peux mettre un dataset facilement récupérable et standard
                    $html.= '<td class="id">' . $row['ID'] . '</td>'; // une class pour tes td (+ facile à styliser)
                    $html.= '<td class="name">' . $row['NAME'] . '</td>';
                    ....
                    $html.= '<td class="remove"><img src="img/remove.png" alt="DEL" /></td>'; // class remove sur td ou sur img
                $html.= '</tr>';
            }
        $html.= '</tbody>';

        $html.= '<tfoot>';
            $html.= '<tr>';
                $html.= '<td colspan="9">Fin des rayons</td>'; // colspan à la place de tes td je sais pas combien il y en a...;)
            $html.= '</tr>';
        $html.= '</tfoot>';
    $html.= '</table>';
}

echo $html;

ainsi tu peux utiliser le code cité plus haut.... à toi de voir...

Pourquoi rajouter des attributs ? $('.select_db tr td.remove') est la même chose que $('.select_db tr td:NTH-CHILD(9)') non ?

a peu de chose prêt que si tu modifies ton table (ajout ou suppression d'une colonne) ton code fonctionne plus...
sans parler d'une lecture plus simple et logique du code....
enfin, c mon avis ;)

Klayhan
Auteur

En effet c'est plus lisible, je vais essayer comme ça. Y aurait-il un moyen d'actualiser le tableau (au moment de la suppression d'un livre) sans rafraîchir la page ? Il faudrait que je génère mon tableau en ajax ?
EDIT : un simple $(this).closest('tr').hide() dans le $.ajax.success suffit

Klayhan
Auteur

Bon et bien tout fonctionne désormais, même mon autocomplete. Une des erreurs corrigées par la suite devait provoquer les erreurs insolubles qui m'embêtaient. Merci à tous, notamment saibe ^^
J'aurais sûrement d'autres requêtes par la suite, mais je recréerai un sujet

avec plaisir ;)