XMLHttpRequest et le formatage des données
Dans la plupart des tutoriels de ce site qui porte sur le DOM scripting, nous nous focalisons toujours et uniquement sur la manipulation des données reçues par le navigateur.
Au court du présent billet, au contraire, nous allons voir comment alimenter le document par des données fraîchement cueillies sur le serveur et nous en décortiquerons les diverses phases et étapes.
Préambule
La mise en place d’une mécanique AJAX nécessite l’emploi d’un serveur web, Apache par exemple. Pour suivre confortablement la suite de ce billet cela sous entend que vous utilisez soit un serveur local (Xampp, Wampserver, Mamp, EasyPHP…) soit que vous disposez d’un hébergement en ligne sur lequel vous pouvez teser vos fichiers.
Instanciation de l’objet
Quelques soit le navigateur utilisé, les méthodes et les propriétés invoquées, ainsi que la logique de fonctionnement mise en place fonctionnent strictement de la même manière, à ceci prêt, qu’en fonction de la version d’Internet Explorer il faudra employer un ActiveX XMLHttp
, au lieu d’un objet XMLHttpRequest
. Seul Internet Explorer agît de la sorte et peut utiliser deux versions différentes de l’activeX : avant IE 7 on utilise Microsoft.XMLHTTP
et à partir de IE 7 Msxml2.XMLHTTP
.
Pour rendre la chose plus sympa, il existe également plusieurs versions de Msxml2.XMLHTTP
. Jeremy Keith dans son ouvrage Bulletproof AJAX, propose une approche basée sur une série de try / catch
imbriqués qui apporte une solution à cette problématique. Vous trouverez d’autres pistes et approches sur les liens suivants :
Décortiquons le processus d’instanciation
Le principe de base du filtrage de navigateur consiste à essayer d’instancier un objet XMLHttpRequest
, et si cela ne fonctionne pas alors de passer aux diverses solutions alternatives. Bien sur il faut partir de la meilleur configuration possible et décliner vers des solutions plus anciennes. Enfin et afin de ne pas générer une erreur de script, nous allons utiliser une instruction try / catch
.
Commençons pas initialiser une variable témoin, xhr, à false. Ensuite, vérifions si le navigateur interprète bien l’objet XMLHttpRequest
(y compris les dernières version d’Internet Expolorer), dans ce cas donnons à la variable xhr la valeur de l’instanciation de l’objet XMLHttpRequest
. Sinon, nous allons devoir user d’un ActiveX. Le problème alors est qu’il existe deux familles d’ActiveX possible Msxml et Msxml2, et là rien ne peut nous indiquer le type que le navigateur prend en charge, la seule solution reste de tester une à une les solutions en partant de la meilleure configuration et en déclinant vers la plus ancienne. Afin de ne pas générer d’erreur le test peut s’éffectuer au travers d’une instruction try / catch
. Le code final qui en découle peut ressembler à ceci :
function creeObjetHTTP(){ var xhr = false if (window.XMLHttpRequest){ xhr = new XMLHttpRequest(); } else if (window.ActiveXObject){ try { xhr = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) { try { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) { xhr = false; } } } return xhr }
Allons plus loin dans le filtrage
Divers scripts que l’on peut trouver sur le Web utilisent une syntaxe particulière englobant certaines parties du code par /*@ et @*/
. En fait, il s’agit d’une compilation conditionnelle introduite par Internet Explorer 5, et donc seules les versions égales ou supérieures à Internet Explorer 5 l’interprètent. Cela permet de ne pas injecter la structure try / catch
à Internet Explorer version 4 qui ne la ne prenait pas encore en compte.
Bon, avouons que tomber sur IE4 serait quand même pas mal délirant… :).
Un script plus complet peut prendre en charge les diverses versions de Msxml, permettant ainsi au navigateur d’utiliser l’ActiveX le plus récnet qu’il puisse gérer. Si vous souhaitez creuser ce sujet rapprochez vous de l’article Using the right version of MSXML in Internet Explorer. L’idée est alors d’utiliser un tableau (array) contenant chacun de ces objets et au travers d’une boucle, de les essayer de manière descendantes un par un, jusqu’à trouver la version ad hoc.
Attention cette boucle cause une erreur sur Internet Explorer 4, vu que les compilation conditionnelle n’ont pas été employées :
function creeObjetHTTP(){ var xhr = false if (window.XMLHttpRequest){ xhr = new XMLHttpRequest(); } else if (window.ActiveXObject) { var success = false; var ieXHR = new Array( "Msxml2.XMLHTTP.7.0","Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.5.0","Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP"); for (var I=0; I<ieXHR.length && !success; I++) { try{ xhr = new ActiveXObject(ieXHR[I]); success = true; //break; }catch(e){ } } } return xhr; }
Instance ou pas, il faut alors passer à la suite…
En résumé, en appelant la fonction creeObjetHTTP()
nous devons obtenir en retour soit une instance d’objet HTTP soit false. Un simple test sur la valeur alors renvoyé suffit amplement. Dans le cas où une instance est retournée, peu nous importe alors qu’il s’agisse d’un ActiveX
ou d’un objet XMLHttpRequest
, tout ce qui va suivre s’applique autant à ‘lun qu’à l’autre. Afin d’en simplifier le processus plaçons l’appel à la fonction dans une variable.
requete = creeObjetHTTP() if (requete != false) { // il existe bien une instance }
Envoyer une requête
Voilà, nous avons instancié un objet nous permettant d’invoquer des requêtes HTTP, cependant qu’il s’agisse d’un ActiveX
ou d’un objet XMLHttpRequest
, nous ferons dorénavant référence uniquement à un objet XMLHttpRequest
. Mettons en place un échange client / serveur et envoyons notre première requête. L’envoi d’une requête se fait en deux temps, d’une part la préparation et l’ouverture de la requête au travers de l’instruction open(), et d’autre part, l’envoi même de la requête par l’instruction send().
Préparation et ouverture de la requête, open()
requete.open(type de requête,fichierACharger.extensio",Asynchrone ou Synchrone ?,login,password)
Comme nous le voyons dans le code ci-dessus, l’instruction open(a,b,c,d,e) possède 5 paramètres, dont deux sont facultatifs (d et e). Cette instruction s’applique directement sur l’instance de l’objet XMLHttpRequest
.
paramètre | description |
---|---|
a | Le premier paramètre représente le type de requête utilisée pour envoyer les données. Le protocole HTTP permet divers types de requêtes mais le plus souvent les échanges se focalisent autour des méthodes GET ou POST . Généralement, la méthode GET est utilisée lorsqu’aucun paramètre n’est envoyé et que seulement une réponse est attendue.
La méthode |
b | Ce deuxième paramètre contient l’adresse ciblée par la requête. Il peut s’agir d’une adresse absolue, relative au document ou relative à la racine du site. Cette adresse peut pointer vers tout type de fichier PHP, ASP, HTML, XML, jSON, js, TXT… |
c | Le troisième paramètre permet de gérer l’aspect asynchrone de la requête. Soit on décide de bloquer la suite du programme en attendant la réception des données (on affecte alors true au paramètre), soit on continue la suite du processus, en attendant que l’ensemble des données soit parvenues (on affecte false au paramètre). Généralement on initie ce paramètre sur true. |
d et e | Bien que peu utilisées, car n’importe qui peut accéder très facilement à ces paramètres, il est possible de passer deux valeurs pour login et password. Cela peut être utile pour authentifier la provenance d’un formulaire ou d’une requête particulière. |
requete.open( « POST » , « fichier.xml » , true ) |
Envoi de la requête, méthode send()
La nature de l’unique paramètre de cette méthode dépend du fait qu’il y ait ou pas des paramètres à envoyer. Si aucun paramètre n’est envoyé, il faut passer null.
requete.send(null)
Si des paramètres sont envoyés, il va falloir au préalable définir l’ensemble des headers nécessaires au protocole d’envoi. La méthode setRequestHeader()
est là pour remplir cette tâche. Vous trouverez une liste assez complète, ainsi que la description des divers entête HTTP disponibles sur le site du w3c dans la section Header Field Definitions.
Toutefois, seule la définition d’encodage est au minimum requise, l’encodage par défaut en encodage URL peut être définie. Vous pouvez en savoir plus en vous rendant encore une fois sur le site du w3c section Form content Types ou sur le site de yoyo design.
requete.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
Attention, ce n’est pas le tout que de définir le type d’encodage de la requête, il faut également encoder les données transmises. C’est à dire remplacer les caractères spéciaux par des caractères de type URL. Rappelez vous de certains URL contenant les caractères %20
par exemple…. Voici dans le tableaux ci-dessous les principaux caractères. Vous trouverez de plus amples informations sur le site de permadi.com Introduction to URL Encoding. Vous trouverez également beaucoup d’informations sur le site de lab.artlung.com, How to encode URLs.
caractères | caractères encodés |
---|---|
espace | + |
& | %26 |
« | %27 |
‘ | %22 |
% | %25 |
( | %28 |
) | %29 |
~ | %7E |
Affectation de la fonction de récupération.
Attention dès le départ et de préférence avant d’envoyer la requête, il faut pointer vers une fonction qui va se charger de récupérer les information transmises par le serveur. Il suffit simplement d’affecter à la propriété onreadystatechange
de l’objet requête, une fonction de réception. Attention à ne pas utiliser de parenthèse à ce moment-là, il s’agit uniquement d’un pointeur. Cette fonction sera donc à l’écoute de tout mouvement du serveur.
requete.onreadystatechange = fonction_reception
Récapitulatifs de l’envoi d’une requête.
xhr.open("GET","fichierACharger.extension",true) xhr.onreadystatechange = fonction_reception xhr.send(null)
Récupération d’une requête
Vérification de l’état de récupération des données
La méthode est envoyée nous avons plus qu’à écouter le serveur… La différence entre HTTP,
et d’autres protocoles de communication comme par exemple UDP
, c’est qu’HTTP
est extrèmement bavard et nous confirme sans cesse sa position, ce qu’il fait, ce qu’il se passe… Bref, cela se traduit par le fait qu’à chaque changement d’état de la requête, le serveur renvoit une information au client, enfin disons à l’instance XMLHttpRequest
qui a invoquée la requête initiale. Cette réponse se fait sous forme d’un changement d’état de la propriété readyState
de l’instance.
Cette propriété readyState
reflète le changement d’état de la requête AJAX en cours. La valeur est numérique et correspond généralement, en fonction des navigateurs, aux valeurs énumérées dans le tableau ci-dessous. Si vous voulez creuser plus loin le comportement de cette propriété, il existe deux articles très intéressants, Digging deeper into HTTP ready states et XMLHTTP notes; readyState and the events :
valeur de readyState | état | interprétation |
---|---|---|
0 | Uninitialized | La méthode open() n’a pas encore été interpellée |
1 | Loading | La méthode open() a été interpellée mais pas la méthode send() |
2 | Loaded | La méthode send() a été interpellée |
3 | Interactive | Le server a commencé à envoyer une réponse |
4 | Complete | Le serveur a terminé l’envoi de la réponse |
Bien que certains navigateurs peuvent diffèrer sur la manière d’interpréter les états de la requête AJAX, tous s’accordent à ce que la propriété readyState
vale 4, lorsque le serveur a terminé d’envoyer sa réponse. De ce fait, il suffit de vérifier si cette propriété équivaut 4, pour s’assurer de la bonne réception des informations attendues.
function fonction_reception(){ if (requete.readyState == 4){ // les informations ont été renvoyées par le serveur } }
Status de réception
Ok, le serveur atteste de l’envoi des données, certes c’est une chose, mais en fait rien ne nous garantit que ces données reçues sont les données que nous attendions. Noublions pas que HTTP nous informe de chaque état, et valide toujours ses opérations. Il faut donc s’assurer du status contenu dans les entêtes envoyées par le serveur, nous avons pour cela la propriété status
à disposition.
Vous connaissez les classiques messages 404 ou 500 du protocole HTTP… et après tout AJAX n’utilise qu’une partie de ce protocole, donc il en utilise les mêmes messages d’erreurs. N’hésitez pas à consulter la liste complète sur le site du w3c, Status Code Définitions.
Nous devons donc nous assurer que le status du retour de notre requête soit bien le bon, c’est à dire 200.
Attention, certaines relations clients serveurs peuvent renvoyer un statut 304, qui correspond à une version de document qui n’a pas été modifié depuis la dernière utilisation et donc, qui peut être récupéré dans le cache.
Voici ci-dessous un tableau reflétant les principaux statuts HTTP.
status | interprétation |
---|---|
200 | ok |
304 | Not Modified |
403 | Forbidden |
404 | Not Found |
500 | Internal Server Error |
Nous pouvons donc compléter notre fonction avec un filtre supplémentaire qui s’assurera que le statut renvoyé soit bien 200 ou éventuellement 304, avant de poursuivre :
function fonction_reception(){ if (requete.readyState == 4){ if (requete.status == 200 || requete.status == 304){ // les informations ont été renvoyées par le serveur et le status est correct } } }
Exploitation des données reçues
Il existe deux formats de réception des données, et donc deux méthodes de réception qui vont de pair. Ne pas confondre les formats de réception qui sont définis en fonction du type MIME utilisé par le serveur avec la structure des données renvoyées qui peuvent, quelque soit le format, structurer les données. En clair il est possible de recevoir un document de type XML envoyé comme du format TXT…
Pour vous en assurer, vous pouvez utiliser la méthode getResponseHeader("Content-Type")
appliquée à l’objet de requête.
Vous obtiendrez alors : soit text/plain
, application/javascript
auquel cas il faut utiliser la propriété objet.responseText
, soit un autre type de contenu comme text/xml application/xml, text/html
… et là il faut faire appel à la propriété objet.responseXML
. Pour notre exemple, ajoutez simplement la ligne de code suivante à notre filtre précédent :
if (requete.getResponseHeader("Content-Type") == "text/plain"){ alert(requete.responseText) }
responseText vs responseXML
Lequel utiliser ? et bien tout dépend du traitement des informations. Si vous avez besoin du texte brut (TXT, HTML, JSON, JS ...
) vous pouvez avoir recours à responseText
. Attention pour JSON, il faudra quand même utiliser soit un simple eval()
si vous faites confiance à l’expéditeur, soit une fonction plus sécurisée, afin que Javascript puisse évaluer le contenu de la chaine.
Dans le cas d’un traitement d’un document structuré (xHTML, XML
) utilisant un type MIME application/xml
, pour accéder à la structure de l’arborescence, il faut user d’un responseXML
.
Formatage des données
Bien souvent, les données sont reçues sous forme de chaine de caractères et donc accessibles via la propriété objet.responseText
. Il est cependant possible de structurer ces données suivant diverses solutions parmi lesquelles : TXT
, HTML
, XML
ou JSON
. Nous allons explorer ces diverses possibilités.
TXT
Les données sont transmises à l’état brut et peuvent tel l’exemple précédent, se composer d’une simple phrase. Il est vrai que cela n’est pas bien complet ni structuré, mais cela à le mérite d’exister et d’être utilisable dans certains cas.
Cependant nous avons en général besoin de diverses informations qui puissent être facilement identifiables. Dans ce cas, nous aurons de préférence recours à une structuration plus élaborée, comme HTML
, XML
ou JSON
.
element.firstChild.nodeValue = requete.responseText;
HTML
Il est possible de recevoir les données directement au format HTML
. Dans ce cas, au moment de l’intégration dans le document, l’utilisation de la propriété objet.responseText
peut être simplifiée au maximum.
Il existe la propriété innerHTML
qui ne fait pas partie du DOM, ni d’aucun autre standard. C’est un ajout de Microsoft que l’ensemble des éditeurs ont repris, et maintenant tous les navigateurs interprètent correctement cette propriété. Elle s’utilise simplement de la manière suivante :
element.innerHTML = requete.responseText;
Ceci dit, il faut faire attention, le contenu actuel de l’élément est intégralement remplacé par le contenu chargé. Il ne s’agit pas d’une méthode similaire à appendChild()
.
Bien que l’utilisation de la structure HTML semble intéressante dans bien des cas, s’il est très simple de mettre à jour une partie contigüe du document, il n’est pas facile de modifier diverses parties du document en simultané. Il est préférable pour cela d’avoir recours à une structure XML
ou JSON
.
XML
Si l’on souhaite parser le document XML
en utilisant le DOM, il faut que les données soient renvoyées sous application/xml
. De ce fait, tout ce que nous avons vu au préalable sur l’utilisation du DOM reste pleinement exploitable. Vous allez pouvoir le constater sur l’exemple utilisé.
var root = requete.responseXML;
JSON
JSON
, prononcer Jason, est en fait une utilisation d’un objet Javascript pour contenir et parser les données (JavaScript Object Notation). De ce fait, l’ensemble des données peut être écrit de manière littérale, et manipulé comme une simple chaine de caractères.
Une fois chargées, les données seront alors évaluées par Javascript et deviendront du coup complètement manipulables par le code.
Simple non ? L’ensemble des acteurs du web et les concepteurs de web services, commencent à fournir des données JSON
en parallèle de données XML
. Voir à ce sujet la proposition de Yahoo, Using JSON with Yahoo! Web Services.
var data = eval('('+requete.responseText+')');
Récapitulations
Méthodes et propriétés de l’objet
L’objet XMLHttpRequest
, comme l’activeX
, possèdent une série de méthodes et propriétés nous permettant de dialoguer avec le serveur et d’analyser l’ensemble de ces échanges.
Méthodes | Propriétés |
---|---|
open() | status |
send() | statusText |
abort() | readyState |
setRequestHeader() | onreadystatechange |
getResponseHeader() | responseText |
getAllResponseHeader() | respondeXML |
Requête de base
Afin d’obtenir le service minimum pour une requête AJAX, voici donc la fonction complète :
function requeteAJAX(){ var xhr = creeObjetHTTP() if (xhr != false){ xhr.open("GET","fichierACharger.extension",true) xhr.send(null) xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ if (xhr.status == 200 || xhr.status == 304){ //traite_datas(xhr.responseText) //traite_datas(xhr.responseXML) } } } } } function traite_datas(arg){ // traitement des données reçues }
Conclusion
Aller plus loin
Ajax par la Pratique
Justin Gehtland Ben Galbraith Dion Almaer
Vous apprendrez bien sûr à maîtriser le comportement de l'objet XMLHttpRequest sur toutes les plates-formes, mais vous vous familiariserez surtout avec les bibliothèques et APIs les plus intéressantes comme Script.aculo.us, Prototype, Dojo ou E4X....
Bulletproof Ajax
Jeremy Keith
In Bulletproof Ajax, author Jeremy Keith demonstrates how developers comfortable with CSS and (X)HTML can build Ajax functionality without frameworks, using the ideas of graceful degradation and progressive enhancement to ensure that the pages work for all users....
Développez en Ajax
Michel Plasse et Olivier Salvatori
Après avoir présenté des exemples typiques d'utilisation d'Ajax, cet ouvrage étudie en profondeur les techniques sous-jacentes (CSS et DOM, JavaScript objet, XMLHttpRequest, JSON, XML et XSLT) en les illustrant d'exemples d'applications variées et de complexité croissante. Il présente également plusieurs des frameworks qui facilitent le développement d'applications Ajax, notamment prototype, dojo et script.aculo.us....
Les fondamentaux d'Ajax par la Pratique
Bruno Sébarte
Lors de l´écriture de cette formation, deux solutions s´offraient à nous : aborder AJAX en le saucissonnant et en expliquant de manière indépendante, en détail, et en profondeur chacune de ses particularités, ou bien présenter AJAX par la pratique en se lançant directement dans le développement d´une mini-application. Telle est la solution qui nous a semblé être la plus adaptée pour une telle entreprise. Découvrez ci-dessous pourquoi !...