Utiliser JSON comme format d'échange de données, avec prototype.js
Par Pascal MARTIN le samedi 27 janvier 2007, 13:00 - Développement Web - Lien permanent
Nous avons vu comment réaliser une première requête Ajax en utilisant prototype.js, et comment utiliser JSON comme format d'échanges de données lors d'une requête Ajax utilisant directement l'objet XMLHttpRequest.
A présent, nous allons voir comment nous pouvons mettre ces connaissances "en commun", et utiliser JSON comme format d'échange de données lorsque l'on développe avec le framework Prototype.
Principe de fonctionnement
Un bref rappel sur le principe de fonctionnement :
- Une page HTML contenant, pour notre exemple, un lien appelant une fonction nommée
gestionClic, qui sera chargée de lancer la requête Ajax et gérer son retour. - Quelques lignes de JavaScript, réalisant l'appel Ajax et obtenant la réponse du serveur.
- Et un programme côté serveur (encore une fois, j'utiliserai du PHP), chargé du traitement déclenché par la requête Ajax.
Puisque c'est le but de cet article, le format de données utilisé en sortie par le programme côté serveur sera JSON.
Page HTML
Pour commencer, voici à quoi peut ressembler notre page HTML :
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title>AJAX : Exemple de client</title> <script type="text/javascript" src="prototype-1.5.0.js"></script> <script type="text/javascript" src="test2.js"></script> </head> <body> <p> <a href="" onclick="gestionClic(); return false;"> Cliquez ici ! </a> </p> <div id="resultat"> </div> </body> </html>
Et la fonction gestionClic, appelée lors d'un clic sur le lien, peut être la suivante :
function gestionClic() { var url = './test2.php'; var myAjax = new Ajax.Request( url, { method: 'get', onComplete: gestionReponse }); } // gestionClic()
En cas de clic sur le lien, une requête HTTP est envoyée en GET à la page test2.php.
Une fois la réponse reçue, la fonction gestionReponse est automatiquement appelée pour la traiter.
Par la suite, nous allons travailler au développement de cette fonction.
JSON en responseText
Nous avons vu lors d'un précédent article que l'on peut, avec le programme côté serveur, écrire des données JSON sur la sortie standard ; par exemple, pour renvoyer la liste de trois des noms de trois auteurs de PHP :
<?php header('Content-Type: text/html; charset: UTF-8'); echo '[{"nom": "Lerdorf", "prenom": "Rasmus"}, {"nom": "Gutmans", "prenom": "Andi"}, {"nom": "Suraski", "prenom": "Zeev"}]'; ?>
Ce qui est écrit sur la sortie standard du programme serveur est reçu comme corps de la réponse HTTP, et est accessible via la propriété responseText de l'object XMLHttpRequest.
L'instance d'objet XHR utilisé pour effectuer la requête Ajax est passé par prototype.js en premier paramètre de la fonction appelée à la fin de celle-ci.
En evaluant le contenu de la propriété responseText de cet objet, on reconstitue, côté JavaScript, l'objet renvoyé par le serveur.
Par exemple, pour afficher les noms des trois personnes renvoyées par le programme PHP présenté juste au-dessus :
function gestionReponse(xhr) { var personnes = eval('(' + xhr.responseText + ')'); if (personnes) { var str = ''; for (var i=0 ; i < personnes.length ; i++) { var personne = personnes[i]; str += personne.prenom + ' ' + personne.nom + '<br />'; } $('resultat').innerHTML = str; } }
Nous commençons par evaluer les données au format JSON renvoyée par le serveur, puis, si cette evaluation s'est bien passée - la liste des personnes n'est pas vide - nous parcourons la liste, en vue d'obtenir les noms et prénoms de chacun... Et pour finir, nous affichons le résultat.
Ce qui affichera, comme attendu :
Rasmus LerdorfAndi GutmansZeev Suraski
Mais le framework prototype.js permet d'automatiser ces traitements, afin de diminuer votre charge de travail...
Prototype.js et JSON
Prototype.js permet d'automatiser un peu ceci : si la réponse HTTP contient un en-tête nommé X-JSON, le contenu de celui-ci sera extrait, automatiquement evalué, et sauvegardé comme un objet.
Cet objet sera alors passé en second paramètre à la fonction déclenchée à la réception du résultat - notre fonction gestionReponse.
Code PHP
A titre d'exemple, voici le code d'un programme PHP écrivant notre liste d'auteurs dans un en-tête HTTP nommé X-JSON, plutôt que sur la sortie standard :
<?php header('Content-Type: text/html; charset: UTF-8'); $str = '[{"nom": "Lerdorf", "prenom": "Rasmus"}, {"nom": "Gutmans", "prenom": "Andi"}, {"nom": "Suraski", "prenom": "Zeev"}]'; header('X-JSON: ' . $str); ?>
Récupération et utilisation des données
Et côté JavaScript :
- Le second paramètre reçu par la méthode de gestion de la réponse est l'objet reconstitué.
- Nous pouvons l'utiliser comme si nous avions appelé
evalnous même. - Il ne faut pas appeler
evalnous-même sur les données de l'en-têteX-JSON: cela reviendrait à l'appeler deux fois, puisqu'il est déjà exécuté par prototype.js, ce qui causerait une perte de temps et de performances !
Pour exemple (notez la présence du second paramètre) :
function gestionReponse(xhr, personnes) { if (personnes) { var str = ''; for (var i=0 ; i < personnes.length ; i++) { var personne = personnes[i]; str += personne.prenom + ' ' + personne.nom + '<br />'; } $('resultat').innerHTML = str; } }
Nous commençons par vérifier que l'objet a bien été evalué ; et si oui, nous l'utilisons, exactement comme nous l'avons fait plus haut.
Pour information, plutôt que de parcourir la liste de personnes "à la main", nous pouvons exploiter le fait que, lorsque l'on utilise prototype.js, les listes sont automatiquement étendues en objets Enumerable.
Cela signifie, entre autre, qu'on peut les parcourir à l'aide de la méthode each, qui permet d'exécuter une fonction sur chaque élément de la liste :
function gestionReponse(xhr, personnes) { var str = ''; personnes.each( function (personne) { str += personne.prenom + ' ' + personne.nom + '<br />'; }); $('resultat').innerHTML = str; }
Pour plus d'informations à ce sujet, n'hésitez par à consulter la documentation de l'API de prototype.js !
Évaluation automatique de JavaScript
Il reste encore une fonctionnalité de prototype.js dont je souhaite parler dans cet article :
Si le Content-Type de la réponse renvoyée par le serveur est un de ceux que prototype.js reconnaît comme indiquant du JavaScript, alors, le code retourné sera automatiquement evalué.
Cela signifie deux choses :
- Le code JS renvoyé par le serveur sera exécuté (Je l'ai déjà dit juste en-dessous, mais c'est suffisament important et/ou impactant - ne serait-ce qu'en terme de sécurité - pour que j'insiste).
- Vous n'avez pas besoin de gérer vous-même la réponse à votre requête.
Par exemple, si le code de votre programme côté serveur, appelé par votre requête Ajax, est le suivant :
<?php header('Content-Type: text/javascript; charset: UTF-8'); echo "alert('bouh !');"; ?>
Le Content-Type renvoyé par le serveur sera "text/javascript" - qui est l'un de ceux reconnus par prototype.js.
En admettant que votre appel soit fait de la manière suivante :
function gestionClic() { var url = './test2.php'; var myAjax = new Ajax.Request( url, { method: 'get' }); } // gestionClic()
Le code alert('bouh !'); sera exécuté au retour de la requête... Un message s'affichera à l'utilisateur.
Note : Si tout ce que nous voulons faire est exécuter le code JavaScript généré par le serveur, il n'est même pas nécessaire de définir une fonction gérant le retour de la requête (branchée sur l'événement onComplete, par exemple) ; c'est ce que j'ai fait ici, d'ailleurs.
Liste des Content-Type reconnus par prototype.js
Pour que les données renvoyées par le serveur soient interprétée comme étant du code JavaScript, et que celui-ci soit evalué, le Content-Type de la réponse HTTP doit être un des suivants :
application/ecmascriptapplication/javascriptapplication/x-ecmascriptapplication/x-javascripttext/ecmascripttext/javascripttext/x-ecmascripttext/x-javascript
Je n'ai jusqu'à présent encore jamais réellement utilisé la fonctionnalité d'évaluation automatique de code JavaScript... Mais l'évaluation automatique de données JSON, par contre, elle, est un réel plus ; nous serons sans aucun doute amenés à l'utiliser à l'avenir, au cours de futurs articles.
MAJ 03/04/2008 : Ajout en pièce jointe à l'article d'une archive contenant un exemple complet
Pour être averti lors de la publication de nouvelles entrées, n'hésitez pas à vous abonner au flux RSS ou ATOM des articles de mon blog !
Commentaires
Merci pour ces infos
Salut
ce code m'a été très utile! Cependant j'aimerais savoir s'il est possible de passer une variable en paramètre dans la fonction "gestionclic()" dans le lien pour pouvoir la récupérer et la traiter dans gestionclic? Si oui, comment? j'ai tenter tout bêtement de mettre cette variable dans les parenthèses comme d'habitude mais cela ne fonctionne pas.
merci pour toutes ces infos bien utiles !
super blog, je l'ai trouver par hazard , mé je vais toujours revenir , tres bon travail !!
Merci
(désolé, vos commentaires avaient été classés comme spam... le temps de les publier "à la main"...)
Génial cet article! Il m'a bien tiré d'affaire alors que je m'empétrais dans JQUery
Content que ça serve aussi à des lecteurs utilisant un autre outil !
petite question de néophyte:
j'ai mis en pratique ce tutoriel et cela fonctionne parfaitement, que ce soit en local ou sur le site web.
J'ai fait, par curiosité, un petit essai en appelant la page qui génère l'objet JSON (cette page est localisée sur le site de produciton) depuis ma page de développement (donc en local). Et j'obtiens l'exception suivante : "Permission denied to call method XMLHttpRequest.open". OR j'avais cru comprendre que JSON était notamment apprécié car il évitait tous les problèmes de cross-domain, contrairement à XMLHttpRequest.
Où est le problème?
XmlHttpRequest, vous êtes soumis à la contrainte de sécurité qui va avec : pas de cross-domain.(Ce qui explique l'exception que vous rencontrez)
Le format de données transmises à l'aide de l'objet XHR n'a aucune influence là-dessus.
(Que ce soit du XML, ou n'importe quel type de données "textuelle" (HTML, JSON, ... ) )
Une solution pour ne pas rencontrer la sécurité de same origin policy est de ne pas utiliser l'objet XHR pour effectuer la requête Ajax vers un autre domaine...
Typiquement, cela peut signifier passer par une balise
<script>, plutôt que par une instance d'objetXmlHttpRequest- la balise<script>n'étant pas limitée au domaine courant pour les scripts qu'elle charge.Autrement dit, si vous avez une balise de la forme suivante dans votre page :
<script src="http://mon-autre-domaine.com/blah.php?param=123"></script>Alors, si blah.php sur votre serveur génère du code Javascript, alors ce code JS sera interprété.
Si vous construisez dynamiquement la balise
<script>, en passant en paramètres en GET ce que vous voulez... Voila un moyen d'effectuer des requêtes Ajax en cross-domain !(Par contre, attention aux problématiques de sécurité : ce qui sera renvoyé par le serveur distant sera exécuté sur votre site, exactement comme s'il s'agissait de code JS que vous ayez écrit vous-même ; autrement dit, n'effectuez pas ce genre d'appel vers des serveurs en lesquels vous ne puissiez pas faire pleinement confiance ! )
Pour un exemple, prenons la page HTML suivante :
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>Démo Ajax serveur distant</title>
<script type="text/javascript" src="ajax-distant.js"></script>
</head>
<body>
<form action="">
Nom : <input type="text" maxlength="32" id="nom" />
</form>
<a href="" onclick="testDistant(); return false">clic !</a>
<div id="result"></div>
</body>
</html>
Pour résumer :
Le fichier JS contient les définitions de fonctions suivantes :
var testDistant = function ()
{
var nom = document.getElementById('nom').value;
var script = document.createElement('script');
script.src = 'http://tests/ajax-distant/serveur-distant.php?';
script.src += 'nom=' + nom;
script.type = 'text/javascript';
document.body.appendChild(script);
};
var gestionRetour = function (str)
{
document.getElementById('result').innerHTML = str;
};
var gestionErreur = function ()
{
document.getElementById('result').innerHTML = 'Bouh !';
};
Ce qui donne :
Et le script PHP, sur un autre nom de domaine :
<?phpif (isset($_GET['nom'])
&& !empty($_GET['nom']))
{
echo "gestionRetour('Bonjour, " . addslashes($_GET['nom']) . "');";
}
else
{
echo "gestionErreur();";
}
?>
Pour faire simple (nous ne travaillons que sur un exemple, donc pas besoin de trop long) :
Et voila, une requête Ajax cross-domain !
Bien entendu, vous pouvez faire plus complexe qu'une simple de caractères à la "bonjour Mr X" ; par exemple, en faisant transiter des données sérialisées au format JSON
Note : je n'ai présenté ici que le principe de base ; peut-être écrirai-je un jour un article à ce sujet ?
Note 2 : en effet, il n'y a pas la moindre utilisation de fonctionnalité du framework Javascript Prototype ni d'
Ajax.Requestici... Je n'ai même pas inclu le fichier prototype.js !merci Pascal pour cette réponse détaillée.
J'étais arrivé à la même solution en utilisant la petite librairie "jsr_class.js" trouvée notamment ici : http://www.theurer.cc/blog/2005/12/15/web-services-json-dump-your-proxy/
waou que de commentaores sur votre billet !
(désolé, votre commentaire était passé en spam, ce qui explique sa non-apparition initiale ; le temps de le dé-spammer à la main...)
Bonjour,
Billet très intéressant ! J'ai tenté d'appliquer tout ceci avec une récupération en XMLHttpRequest d'un fichier JSON sous IE.
Je récupère visiblement bien mon fichier et son contenu mais l'eval pose problème. IE (v6) me retourne l'erreur "Identificateur, chaine ou nombre attendu"
Pour info, j'ai mis en content-type de ma page json : text/html; et charset: UTF-8.
et la syntaxe d'eval utilisée est :
@@var chaine = xhr_object.responseText;
var doc = eval('(' + chaine + ')');@@
Auriez-vous une piste ?
Merci d'avance,
(possiblement un peu tard, je l'admet)
Est-ce que la chaîne de caractères obtenue via la requêtes Ajax contient bien du code Javascript valide ?
Que contient la variable "
chaine", dans votre cas, donc ?Bonjour, pas de souci pour le "retard", c'est déjà gentil de répondre
voici ce que je génère en terme de données json :
{
"donnees": { "dateDebut":{ "annee":2008, "mois":05, "jour":14, "heure":11, "minute":45, "seconde":00, }, "conso":[ {valeur:0}, {valeur:0} ], "temp":[ {valeur:26.70}, {valeur:25.60} ] }}
Je tiens à préciser que ce json est correctement interprété par firefox. Il récupère très bien le contenu, sans râler.
Y a-t-il un défaut dans cette chaine ?
Merci d'avance, (et désolé pour le long post)
merci pour ce post qui nous est utile !
j'essaye de faire tourner ca sur un serveur de production et tout fonctionne à merveille.
le dernier point (avec header en x-json) cependant met enormement de temps à repondre (66 sec tésté avec FF et Firebug )
je ne sais pas pourquoi?
je me réponds moi même...
en utilisant une api prototype compréssée ca va beaucoup mieux!
Concernant le bug rencontré par Plantex, il s'agit d'une erreur de syntaxe toute bête, la virgule de
est superflue, étant donné qu'il s'agit de la dernière paire de cet ensemble.
IE voyant cela pense qu'il devrait y avoir une paire suivant celle-ci et, ne la trouvant pas, renvoie l'erreur "Identificateur, chaîne ou nombre attendu".
Amusant de constater qu'une fois n'est pas coutume, Internet Explorer est plus exigeant que Firefox sur la syntaxe
Bonjour,
J'ai exactement le même probleme concernant l'erreur sous internet explorer mais le probleme c'est que j'utilise le zend framework et que le code json est généré automatiquement et pas de "virgule" juste avant une accolade :-/.Quelqu'un aurait la solution pour résoudre un tel probleme? Merci!
Bonjour,
J'ai le même problème avec Internet Explorer : "Identificateur, chaîne ou nombre attendu".
Un eval() d'un JSON ne fonctionne pas et je n'ai pourtant pas de soucis avec des virgules.
Voici mon JSON :
Est-ce que quelqu'un a une solution ?
Merci
Bonsoir,
En essayant d'évaluer cette portion de JSON, je rencontre aussi le type d'erreur que vous décrivez, sous IE uniquement.
Sur la ligne suivante :
(dans la seconde déclaration de module)
=> Pourquoi y-a-t'il cette virgule à la fin de la ligne, après la dernière propriété de l'objet ?
A priori, si vous supprimez cette virgule, l'erreur disparait aussi
En espérant que ça réponde à votre question,
Bon courage,
Pascal M.
merci!
pas si évident de se mettre à jour pour le web 2.0
Bonjour
je suis hébergé sur un serveur apache mais je ne dispose ni de php ni de base de données. j'utilise la bibliothèque prototype et me sert de fichiers au format json pour gérer mes données; je les charge avec la méthode que vous décrivez "eval('(' + xhr.responseText + ')')". j'aurai voulu utiliser l'objet json tel que présenté dans la deuxième méthode, lorsque l'on utilise un entête X-JSON.
Ma question est, comment faire comprendre que le fichier de données est au format X-JSON ? peut on utiliser une forme d'entête html ?
Bonjour,
Si vous n'avez pas la possibilité d'utiliser un langage de programmation côté serveur pour modifier les en-têtes HTTP de la réponse pour ajouter une en-tête
X-JSON, j'ai peur que cela soit difficile.Éventuellement, il serait peut-être possible de faire quelque chose au niveau Apache, avec un module qui va bien, mais ça impliquerait probablement d'être admin du serveur (ce qui ne doit pas être votre cas) -- et je ne sais même pas si c'est possible...
En HTML seul, non, pas possible...
=> Rester sur un
evaldexhr.responseTextme semble être la solution que vous aller devoir conserver...(Note : avec prototype, plutôt que d'utiliser
eval, vous pouvez vous pencher sur la méthode String.evalJSON, qui vérifie si vous lui passez bien du JSON valide -- contrairement àevalqui va exécuter la portion de code JS passée en paramètre, quel que soit ce code)Bon courage !
J'avais un problème avec IE également.
J'ai trouvé la solution ici : http://www.dev411.com/blog/2006/06/...
La taille de l'entête est limitée avec IE, donc faire un header suivi du JSON ne fonctionnera pas si ce dernier est trop volumineux, il faut mettre le JSON dans le corp du message.
Pour ma part j'ai modifié le retour de mon AJAX avec un Content type en "application/json" suivi d'un echo de mon JSON.
Et coté JS, j'ai ce que je veux avec xhr.responseJSON (xhr étant le premier paramètre dans la fonction de retour )