Problématique
Un client peut recevoir d’un serveur un programme à exécuter. Cette auto-programmation du client peut aller jusqu’à un programme qui lui même rappellera le serveur (technologie AJAX, d’ailleurs mal nommée).
Cette problématique pose évidemment des problèmes de portabilité et de sécurité.
La portabilité est assurée par une abstraction des données et de leurs fonctions associées, le DOM étant l’exemple canonique à étudier.
La sécurité est assurée par une limitation volontaire des langages pour clients.
Pour interagir efficacement avec son utilisateur, une application reposant sur l’architecture clients / serveur doit minimiser les appels à celui-ci.
Dans le cas où néanmoins c’est le serveur qui a la connaissance du calcul à effectuer, une solution est qu’il envoie un programme écrit dans un langage connu du client, et que celui-ci devra exécuter.
Le serveur ignorant le processeur utilisé par chacun de ses clients, ces langages sont inévitablement interprétés (directement ou après traduction dans une machine virtuelle). Leur lenteur induite doit restée inférieure à celle du réseau pour que cette solution soit retenue.
Historiquement, le premier langage pour client HTTP est JavaScript, conçu par l’équipe du premier navigateur complet, Netscape, pour sa version 2.0.
Copié, évidemment avec des incompatibilités, sous le nom JScript sous Internet Explorer 3.0 et sous le nom ActionScript par Flash, il a nécessité une normalisation.
L’European Computer Manufacturers Association (qui regroupe à présent des entreprises et des institutions du monde entier) la produite sous le nom EcmaScript, version 3.0, disponible comme document ISO/IEC 16262 et comme document ECMA 262 (ce numéro étant valable pour toutes les versions, ce qui est trompeur).
La version 4 avait été annoncée pour 2007, sans cesse repoussée, puis finalement abandonnée car trop incompatible avec le langage initial.
La version 5 d’EcmaScript, finalement adoptée en Décembre 2009, amendé par la 5.1 en Décembre 2011, se distingue surtout par l’adoption de la notation JSON, qui permet l’envoi entre client et serveur de données structurées décrite de manière concise sous forme de chaînes de caractères.
Il existe plusieurs implémentations de EcmaScript. Celle de Firefox, nommée SpiderMonkey, est une actualisation par Mozilla Foundation (en C++ non standard) du JavaScript historique de Netscape (écrit en C pur) qui au départ aurait dû être un interprète Scheme.
De son côté, Adobe a développé une implémentation qui a remplacé ActionScript dans Flash.
Le projet Tamarin, qui visait la convergence des deux implémentations a été abandonné.
Dans un langage pour client, les opérations de lecture et d’écriture sur périphériques sont à proscrire, et sont remplacées par des accès à la représentation abstraite des données.
Lire tout ou partie des données envoyées par le serveur est en fait pointer sur le nœud de l’arbre fabriqué par le navigateur.
Écrire revient à modifier un nœud de l’arbre, le navigateur réaffichant immédiatement le nouveau document induit.
Par convention, le client donne également accès à ses propres données (fenêtres, caches etc).
C’est une recommandation officielle du W3C, dont la dernière version (3) date d’Avril 2004.
Son but est d’offrir un interface de programmation pour construire, modifier, détruire ou lire tout document HTML ou XML dans tout langage implémentant cette interface.
Cette interface impose seulement à ces langages la disponibilité de certaines fonctions manipulant l’arbre de syntaxe abstraite, mais n’impose rien quant à leur syntaxe et la représentation de leurs structures de données.
Elle est décrite dans le formalisme IDL, et deux exemples d’implémentation sont donnés : EcmaScript et Java.
Le Document Object Model décrit ce qui doit exister (par exemple le fait que l’événement submit soit annulable), l’utilisaton relevant de l’implémentation (en JavaScript, que la fonction associée à l’événement retourne False).
Le DOM se compose de 16 modules (qui ne sont pas tous implémentés par les différents langages), qu’on peut regrouper ainsi :
L’interface de programmation pour la racine comporte essentiellement les outils suivants :
readonly attribute DocumentType doctype ; readonly attribut Element DocumentElement : .... Text createTextNode(in DOMString data) ; Element createElement(in DOMString tagName) raises(DOMException) ; Attr createAttribute(in DOMString name) raises(DOMException) ; Element getElementById(in DOMString elementId) ;
Il possède une interface composée essentiellement de :
readonly attribute unsigned short nodeType ; readonly attribute Node parentNode ; readonly attribute NodeList childNodes ; readonly attribute Node firstChild ; readonly attribute Node lastChild ; readonly attribute Node previousSibling ; readonly attribute Node nextSibling ; readonly attribute NamedNodeMap attributes ; boolean hasChildNodes() ; boolean hasAttributes() ; Node insertBefore(in Node new, in Node ref) raises(DOMException) ; Node replaceChild(in Node new, in Node old) raises(DOMException) ; Node removeChild(in Node old) raises(DOMException) ; Node appendChild(in Node new) raises(DOMException) ;
Un Element est un nœud HTML ou XML ; il possède l’interface commune à tous les nœuds, plus quelques autres fonctionnalités :
readonly attribute DOMString tagName ; DOMString getAttribute(in DOMString name) ; void setAttribute(in DOMString name, in DOMString value) raises(DOMException) ; void removeAttribute(in DOMString name) raises(DOMException) ; ....
Il existe deux moyens pour insérer des scripts dans une page décrite par le DOM :
Tous les scripts d’une même page partagent le même espace de noms (fonctions et variables peuvent être définis dans un script et utilisés dans un autre), mais pas les scripts de deux frames pour des raisons de sécurité (vol de cookie notamment).
| Nom | Moment | Lieu |
|---|---|---|
| onkeypress onkeydown onkeyup | action sur le clavier | Toute balise |
| ontouch | action sur écran tactile | Toute balise |
| onclick ondblclick onmousedown onmouseup onmouseover onmouseout onmousemove |
action sur la souris | Toute balise |
| onselect | A la copie | input textarea |
| onfocus onblur | Au changement de focus | a area label input select textarea button |
| onchange | A la modification | input select textarea |
| onload | Au chargement | body |
| onunload | Au remplacement | body |
| onsubmit | A la soumission | form |
| onreset | A la Remise à zéro | form |
L’arbre de syntaxe abstraite d’un document HTML est disponible en JavaScript par un objet accueilli nommé document.
Il implémente sous forme de méthode les fonctions mentionnées dans l’interface IDL :
document.getElementByID(’formulaire’) retourne le nœud de l’arbre dont l’attribut id vaut formulaire.
alert(document.getElementById(’formulaire’).hasChildNodes()) affichera Vrai ou Faux dans une fenêtre d’alerte, selon que ce nœud a des fils ou non.
Pour modifier un attribut, on affecte la feuille correspondante, avec la méthode setAttribute
Pour modifier un nœud, il faut utiliser la fonction replaceChild au niveau de son parent, et créer un nouveau nœud avec createElement pour une balise et createTextNode pour un texte. Si x et y sont des nœuds, alors les deux instructions :
x.replaceChild(document.createTextNode(100), x.firstChild)
y.replaceChild(document.createElement(’br’), y.firstChild)
remplaceront leur premier sous-arbre par du texte et une balise br respectivement.
La modification dynamique d’un attribut style a une signification évidente.
Celle d’un attribut dont la valeur est une URL est plus sujette à interprétation, et les navigateurs divergent souvent sur ce point.
Le W3C a tardivement abordé cette question dans la Load and Save Specification.
La tendance générale est de considérer que modifier un tel attribut revient à accéder à cette URL, mais en tâche de fond : la page courante reste visible.
Le traitement de la réponse, et le moment où interviendra ce traitement, est très hétérogène selon les balises ayant de tels attributs et les navigateurs employés.
Un site intéressant recense trois cas de ces appels asynchrones au serveur :
Dans tous les cas, il faut mettre en place un réveil JavaScript qui ira périodiquement regarder si la réponse est arrivée.
Celle-ci peut-etre transmise dans le corps de la réponse ou dans les en-têtes HTTP (par exemple sous forme de cookie).
Des trois méthodes proposées, la modification d’une balise script est la plus légitime (puisque de toutes façons on exécute un script) ; voici son synopsis :
Les navigateurs peuvent croire que l’URL est déjà chargée : non seulement il faut utiliser les en-têtes HTTP d’interdiction de mise en cache, mais certains navigateurs associent l’information "déjà chargée" à la balise script elle-même, ce qui impose d’en recréer une par CreateElement.
Le réglage du minuteur est délicat : des appels trop fréquents grèvent la disponibilité du processeur, des appels trop rares impatientent l’utilisateur.
Quant aux solutions par envoi d’une image (jpg etc) ou d’une css, elles imposent une analyse syntaxique des réponses écrites dans ces formats pour transcoder dans le format du DOM.
Le W3C a fini par fournir une recommandation sur l’objet XMLHttpRequest (bientôt en version 2) lequel comporte principalement :
La technologie dite Asynchronous Javascript And XML repose sur cette spécification.
Nommé XMLHttpRequest dans le monde du logiciel libre, et ActiveXObject dans un logiciel emprisonné, cet objet se construit par appel à la méthode new et test préalable de disponibilité comme fils du nœud window.
La fonction zéro-aire trouvera la réponse dans l’objet alloué, qui doit donc être affecté soit à une variable globale, soit à une variable rémanente de la fonction zéro-aire : on voit tout l’intérêt des fermetures de JavaScript.
function ajax(method, url, flux, rappel){
var r = window.XMLHttpRequest ? new XMLHttpRequest() :
(window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : '');
if (!r) return false;
r.onreadystatechange = function () {rappel(r);}
r.open(method, url, true);
if (flux)
r.setRequestHeader("Content-Type",
"application/x-www-form-urlencoded; ");
r.send(flux);
return true
}La fonction de rappel est invoquée à chaque changement d’un champ de l’objet nommé readyState, qui prend successivement les valeurs :
L’exemple suivant, qui charge au préalable la fonction définie précédemment, montre ces étapes lorsqu’il va chercher la page d’accueil de son serveur :
<script type='text/javascript' src='ajax.js'></script>
<script type='text/javascript'>
function isReady(xmlhttp) {
alert("readyState = " + xmlhttp.readyState);
if ( xmlhttp.readyState == 4)
{alert('status: ' + xmlhttp.status + xmlhttp.responseText);}
}<script>
<body onload="ajax('GET', '/', null, isReady)">
.....
function AjaxSqueezeNode(trig, noeud, f) {
var i, s, g, u;
if (!f) f = function(r) { noeud.innerHTML = r.responseText;}
if (typeof(trig) == 'string') {return !ajax('GET', trig, null, f); }
for (i=0,u='';i < trig.elements.length;i++) {
n = trig.elements[i];
s = ((n.type != 'checkbox')&&(n.type != 'radio')) ? n.name : n.checked;
if (s) {u += n.name+"="+ encodeURIComponent(n.value) + '&'; }
}
s = trig.getAttribute('action');
if (typeof(s)!='string') s = trig.attributes.action.value;
return !ajax('POST', s,u,f);
}
à appeler dans le onclick d’une balise a avec this.href, this.parentNode comme arguments, ou le onsubmit d’une balise form.
La création en JS de la requête ne permet pas d’envoyer un fichier par ce mode, sauf à programmer à nouveau un mécanisme de TimeOut.
Le bouton "Back" des navigateurs n’a plus la même sémantique.
Il est à noter qu’en fait rien de XML n’est apparu dans tout ceci : il s’agit plus d’Asynchronous JavaScript Applications que d’Asynchronous JavaScript And XML.
Une fois de plus, un outil nécessaire a dû emprunter du vocabulaire à la mode pour faire parler de lui, alors qu’il n’en avait fondamentalement pas besoin.