Introduction
L’ajout de code HTML peut être réalisé dynamiquement avec la fonction
javascript write
de l’objet document
:
<script type="text/javascript">
document.write("<div id='header'> \n"); document.write(" <p class='certif'>Voici un paragraphe</p> \n"); document.write("</div>\n");
</script>
Si le type de document est HTML Transitional ou Strict, la fonction
document.write
est opérationnelle, en revanche si il s’agit d’un document de
type XHTML, la méthode document.write
n’est pas reconnue.
document.write
est séduisant mais de sérieux problèmes existent avec cette
méthode, méthode qu’il faut éradiquer, parmi ces problèmes :
document.write
ne fonctionne pas si le document est de type XHTML.- le contenu écrit avec
document.write
peut ne pas apparaître dans le DOM (Document Object Model) du document, empêchant ainsi d’accéder et de manipuler ce contenu par programmation. - le traitement des nœuds est séquentiel et immédiat avec
document.write
, ce qui est conceptuellement faux.
SQLPAC est tombé dans ce piège avec un document XHTML : la section des liens de partage AddThis dans le document XHTML n’apparaissaient plus
et la mesure de l’audience Google Analytics était inopérante. Dans chacun de ces cas, la méthode document.write était utilisée.
Après un bref rappel sur le modèle DOM et les outils de débogage
disponibles, cet article présente à travers des cas pratiques la migration des
fonctions document.write
vers les fonctions Javascript compatibles DOM pour
ajouter/supprimer des éléments. Quelques cas particuliers sont également
évoqués : les écueils avec IE 6 et 7 lors de l’utilisation de la méthode
setAttribute
ainsi que l’incompatibilité de Google AdSense avec la norme
XHTML.
L’interface DOM (Document Object Model) et les outils (Chrome, FireFox/FireBug)
Le Document Object Model (ou DOM) est une recommandation du W3C qui décrit une interface indépendante de tout langage de programmation et de toute plate-forme.
DOM permet de construire une arborescence de la structure d’un document et de ses éléments. À partir d’un arbre DOM donné, il est possible de générer des documents dans le langage de balisage voulu (HTML, XML…), qui pourront à leur tour être manipulés par l’interface DOM. DOM est utilisé pour pouvoir modifier facilement des documents XML ou accéder au contenu des pages web HTML.
Deux outils précieux sont disponibles avec Chrome et FireFox (extension FireBug) pour inspecter et déboguer les éléments d’un document.
Dans Chrome : "OutilsOutils de développement" ou Ctrl+Maj+I.
Dans FireFox : "OutilsFirebugOuvrir Firebug" ou F12. Firebug est disponible comme extension : FireBug
L’ancien code de création de la barre d’outils AddThis avec la fonction document.write
La barre d’outils AddThis des fonctions de partage est générée avec la
fonction javascript writeSharh-elinks
dans le script ./js/header.js
, fonction qui
appelle les méthodes document.write
à migrer.
./js/header.js
...
function writeSharh-elinks() {
document.write("<div style='margin-top:5px;margin-bottom:10px;float:right;'>");
document.write("<div class='addthis_toolbox addthis_default_style'>");
document.write("<a class='addthis_button_facebook' rel='nofollow'></a>");
document.write("<a class='addthis_button_linkedin' rel='nofollow'></a>");
document.write("<a class='addthis_button_twitter' rel='nofollow'></a>");
document.write("<a class='addthis_button_delicious' rel='nofollow'></a>");
document.write("<a class='addthis_button_blogger' rel='nofollow'></a>");
document.write("<a class='addthis_button_email' rel='nofollow'></a>");
document.write("<span class='addthis_separator'>|</span>");
document.write("<a class='addthis_button_expanded' href='http://www.addthis.com/bookmark.php?v=250&pub=username' rel='nofollow'>");
document.write("Partager...</a>");
document.write("<script type='text/javascript' src='http://s7.addthis.com/js/250/addthis_widget.js#pub=username'></script>");
document.write("</div>");
document.write("</div>");
}
...
La fonction writeSharh-elinks
est appelée dans le code HTML des articles :
... <body> <script type="text/javascript" src="./js/header.js"></script> ... <script type="text/javascript">
writeSharh-elinks();
</script> ...
Création et insertion d’éléments par l’exemple sans utiliser document.write
Pré-requis d’un élément identifiable
Dans l’ancien code, aucun bloc avec un identifiant (ex. : <div
id="iddiv">
) existe entre la balise <body>
et la fonction
writeSharh-elinks
. La création dynamique d’éléments ou de nœuds HTML peut être réalisée avec des identifiants de bloc grâce à la fonction getElementById
. D’autres méthodes sont possibles (par classe ou par balise) mais ne sont pas abordées ici.
Le squelette des articles est donc modifié en conséquence afin d’introduire
un bloc identifié (id=header
), bloc dans lequel sera inséré dynamiquement la
barre d’outils AddThis autrement qu’avec la fonction document.write
:
<body> <script type="text/javascript" src="js/header.js"></script> <div id="div-header" class="div-header"></div> <script type="text/javascript">
writeSharh-elinks();
</script> </body>
Création d’un élément : createElement et setAttribute
Chaque élément (<div>
, <p>
, <a>
, <img>
,
<script>
, <span>
, etc.) est créé en Javascript avec la méthode
createElement
de l’objet document
. La méthode createElement
ne fait
qu’instancier un objet, cette méthode n’insére pas l’élément immédiatement dans
l’arbre DOM.
Pour instancier un élément div
en javascript :
v_div = document.createElement("div");
La méthode setAttribute
appliquée sur l’élément créé permet d’affecter des
attributs à l’élément (id
, class
, src
, style
, etc.) :
element.setAttribute(attribue,valeur);
Pour définir par exemple l’attribut id="shlinks"
et
style="width:300px;height:200px;background-color:blue;"
à l’élément div
précédemment créé :
v_div = document.createElement("div");
v_div.setAttribute("id","shlinks");
v_div.setAttribute("style","width:700px;height:200px;background-color:blue;");
Voici deux autres exemples qui instancient en javascript un objet de type
img
et script
accompagné de quelques attributs :
|
|
Insertion des éléments : appendChild, insertBefore, nextSibling
Le débogage de l’insertion des éléments se fait à partir des deux outils présentés au paragraphe 2.
Insérer un élément dans un élément avec la méthode appendChild
La méthode appendChild
permet d’ajouter séquentiellement un élément fils
dans un élément parent.
elementparent.appendChild(elementfils);
On désire par exemple obtenir l’arborescence ci-dessous :
<div id="header">
<div id="enfant"></div>
</div>
En Javascript et le modèle DOM :
v_div_parent = document.getElementById("header");
v_div_enfant = document.createElement("div");
v_div_enfant.setAttribute("id","enfant");
v_div_parent.appendChild(v_div_enfant);
- l’élément parent
<div id="header">
est récupéré grâce à son attributid
et la méthodegetElementById
de l’objetdocument
. - l’élément fils
<div id="enfant">
est créé avec la méthodecreateElement
de l’objetdocument
. L’attributid="enfant"
est défini grâce à la méthodesetAttribute
pour cet élément. - l’élément fils
<div id="enfant">
est incorporé séquentiellement dans l’élément parent<div id="parent">
avec la méthodeappendChild
.
Insérer un élément avant un autre élément avec insertBefore
La méthode insertBefore
permet d’insérer un élément fils element1
avant un
autre élément fils element2
dans un élément parent elementparent
.
elementparent.insertBefore(element1,element2);
On désire par exemple obtenir l’arborescence ci-dessous :
<div id="header">
<div id="enfant1"></div>
<div id="enfant2"></div>
</div>
En Javascript, l’élément enfant2
est déjà inséré dans l’élément parent
dans
le modèle DOM avec la méthode appendChild
:
v_div_parent = document.getElementById ("header");
v_div_enfant2 = document.createElement("enfant2");
v_div_before.setAttribute("id","enfant2");
v_div_parent.appendChild(v_div_enfant2);
Ce qui donne dans l’arbre DOM :
<div id="header">
<div id="enfant2"></div>
</div>
Pour insérer l’élément enfant1
avant l’élément enfant2
dans l’élément parent
header
, la méthode insertBefore
est utilisée :
v_div_parent = document.getElementById ("header");
v_div_enfant2 = document.getElementById ("enfant2");
v_div_enfant1 = document.createElement("div");
v_div_enfant1.setAttribute("id","enfant1");
v_div_parent.insertBefore(v_div_enfant1,v_div_enfant2);
- l’élément parent
<div id="header">
est récupéré grâce à son attributid
et la méthodegetElementById
de l’objetdocument
. - l’élément fils
<div id="enfant2">
est récupéré grâce à son attributid
et la méthodegetElementById
de l’objet document. - l’élément
<div id="enfant1">
est créé avec la méthodecreateElement
de l’objetdocument
. L’attributid="enfant1"
est défini avec la méthodesetAttribute
pour cet élément. - l’élément
<div id="enfant1">
est inséré avant l’élément<div id="enfant2">
dans l’élément<div id="header">
avec la méthodeinsertBefore
.
Insérer un élément entre deux éléments avec insertBefore et nextSibling
La méthode insertAfter
n’existe pas pour insérer un élément après un autre
élément.
On désire par exemple obtenir l’arborescence ci-dessous :
<div id="header">
<div id="enfant1"></div>
<div id="enfant2"></div>
<div id="enfant3"></div>
</div>
Les éléments enfant1
et enfant3
existent déjà dans l’arbre DOM :
<div id="header">
<div id="enfant1"></div>
<div id="enfant3"></div>
</div>
En Javascript, la méthode nextSibling
appliquée à l’élément enfant1
renvoie
l’élément enfant3
suivant dans l’arbre DOM. Pour le vérifier, coder une alerte
pour récupérer l’id
de l’élément suivant :
v_div_enfant1 = document.getElementById("enfant1");
alert(v_div_enfant1.nextSibling.id);
=> enfant3
L’insertion d’un élément après un autre élément sans se préoccuper de
l’élément suivant est réalisée tout simplement en combinant les méthodes
insertBefore
et nextSibling
:
elementparent.insertBefore(element2,element1.nextSibling);
v_div_parent = document.getElementById("header");
v_div_enfant1 = document.getElementById("enfant1");
v_div_enfant2 = document.createElement("div");
v_div_enfant2.setAttribute("id","enfant2");
v_div_parent.insertBefore(v_div_enfant2,v_div_enfant1.nextSibling);
Il faut bien entendu vérifier qu’il existe un élément suivant
lorsque la méthode nextSibling
est invoquée sur un élément. Si l’objet
n’existe pas, la méthode appendChild
doit être appliquée.
v_div_parent = document.getElementById("header");
v_div_enfant1 = document.getElementById("enfant1");
v_div_enfant2 = document.createElement("div");
v_div_enfant2.setAttribute("id","enfant2");
if (v_div_enfant1.nextSibling) {
v_div_parent.insertBefore(v_div_enfant2,v_div_enfant1.nextSibling); }
else {
v_div_parent.appendChild(v_div_enfant2);
}
Ajout de nœuds textes : createTextNode et appendChild
La méthode createTextNode
de l’objet document créé des éléments texte avec
Javascript :
myText = document.createTextNode("My Text");
Ces éléments texte peuvent alors être ajoutés à des éléments XML ou HTML
acceptant du texte : <div>
... </div>
, <a>
... </a>
,
<span>
... </span>
, <mabalisexml>
... <mabalisexml1>
, etc.
Exemple :
<div id="header">"My text"</div>
Code :
myText = document.createTextNode("My Text");
v_div_parent = document.getElementById("header");
v_div_parent.appendChild(myText);
Pour éviter l’utilisation de variables intensives dans le code Javascript, le code exemple ci-dessus peut être simplifié :
v_div_parent = document.getElementById("header");
v_div_parent.appendChild(document.createTextNode("My Text"));
Le nouveau code de création de la barre d’outils AddThis compatible HTML/XHTML sans la fonction document.write
Le nouveau code de la fonction javascript writeSharh-elinks
dans le script
./js/header.js
qui génère la barre d’outils AddThis est plus chargé mais il
devient compatible HTML/XHTML avec la suppression des fonctions document.write
:
./js/header.js
...
function writeSharh-elinks() {
// Récupération de l’élément <div id="header">
v_div_header = document.getElementById("header");
// Création de l’élément <div id="addthis">
v_div_addthis = document.createElement("div");
v_div_addthis.setAttribute("id","addthis");
v_div_addthis.setAttribute("class","addthis_toolbox addthis_default_style");
// Définition d’un tableau des réseaux sociaux : facebook, linkedin...
var array_socialnetwork_targets = new Array("facebook","linkedin","twitter","delicious",
"blogger","email");
// Pour tous les éléments du tableau, ajout dynamique des liens <a> </a> dans l’élément <div id="addthis">
for (var i=0; i < array_socialnetwork_targets.length ; i++) {
v_a_addthis_link = document.createElement("a");
v_a_addthis_link.setAttribute("class","addthis_button_"+array_socialnetwork_targets[i]);
v_a_addthis_link.setAttribute("rel","nofollow");
v_div_addthis.appendChild(v_a_addthis_link);
}
// Ajout dynamique de l’élément <span> | </span> dans l’élément <div id="addthis">
v_span_separator_addthis = document.createElement("span");
v_span_separator_addthis.setAttribute("class","addthis_separator");
v_span_separator_addthis.appendChild(document.createTextNode("|"));
v_div_addthis.appendChild(v_span_separator_addthis);
// Ajout dynamique de l’élément <a>Partager...</a> dans l’élément <div id="addthis">
v_a_addthis_share = document.createElement("a");
v_a_addthis_share.setAttribute("class","addthis_button_expanded");
v_a_addthis_share.setAttribute("rel","nofollow");
v_a_addthis_share.setAttribute("onclick","return false");
v_a_addthis_share.setAttribute("href","http://www.addthis.com/bookmark.php?v=250&pub=<username>");
v_a_addthis_share.appendChild(document.createTextNode("Partager..."));
v_div_addthis.appendChild(v_a_addthis_share);
// Ajout dynamique de l’élément <script src=...></script> dans l’élément <div id="addthis">
v_script_addthis = document.createElement ("script");
v_script_addthis.setAttribute("type","text/javascript");
v_script_addthis.setAttribute("src","http://s7.addthis.com/js/250/addthis_widget.js#pub=<username>");
v_div_addthis.appendChild(v_script_addthis);
// Ajout dynamique de l’élément <div id="addthis"></div> dans l’élément <div id="header">
v_div_header.appendChild(v_div_addthis);
}
...
Ce fragment de code est parfait pour Internet Explorer 8, Chrome, FireFox,
Safari et Opera. En revanche dès que l’on démarre la certification pour
Internet Explorer 7 et 6, tout se corse comme d’habitude. Avec Internet
Explorer 6 et 7, la méthode document.createElement("a")
semble ne pas aboutir.
Le paragraphe "Les écueils" dans cet article évoque ce point.
Suppression d’éléments : removeChild
La méthode pour supprimer des éléments est très simple avec la méthode
removeChild
appliquée sur l’élément parent
pour supprimer un élément fils
:
elementparent.removeChild(elementfils);
Exemple :
v_div_parent = document.getElementById("header");
v_div_enfant1 = document.getElementById("enfant1");
v_div_parent.removeChild(v_div_enfant1);
Les écueils
Compatibilités des navigateurs avec les versions de DOM (core et css) : exemple avec IE 6/7 et setAttribute
Lors des phases de validation multi-navigateurs, le code migré précédemment sans les fonctions document.write est inopérant pour les versions Internet Explorer 6 et Internet Explorer 7 : les icônes n’apparaissent pas. Il s’agit de versions anciennes d’Internet Explorer, certes, mais contrairement à FireFox et Google Chrome qui sont régulièrement mis à jour en automatique, de nombreux internautes utilisent encore les navigateurs IE 6 et IE 7, ce qui démontre les difficultés qui existent pour migrer les plateformes Windows, plateformes qui incorporent malheureusement IE dans le noyau.
Il est inenvisageable de négliger ces versions d’Internet Explorer : à titre d’exemple, pour le site SQLPAC en 2010, pour 9667 visites avec Internet Explorer : 41% des internautes utilisent la version Internet Explorer 6 et 32% la version Internet Explorer 7.
Quelques rares sites récapitulent les compatibilités des navigateurs avec
les fonctions Javascript pour manipuler les arbres DOM (createElement
,
setAttribute
…) :
Le site Quirksmode.org est le plus intéressant de tous car il remonte jusqu’aux versions IE 5.5. Une copie au format pdf a toutefois été générée ici car ce site n’a pas été mis à jour depuis Août 2010 : W3C DOM Compatibility- Core (format pdf)
Le débogage a donc été ardu (1 journée perdue) après de multiples tests et
pérégrinations, et la cause fut enfin trouvée : la fonction
setAttribute("class",valeur)
est sans effet avec IE 6 et IE 7. Le site
quirksmode.org confirme ce constat (Quirksmode.org : SetAttribute), tous
les attributs de style (class
, style
…) appliqués sur un élément avec la
méthode setAttribute
sont systématiquement supprimés jusqu’à la version 7
d’Internet Explorer.
Pour contourner ce problème avec IE 6 et 7, l’attribut class
doit être créé
manuellement pour ces versions avec la méthode createAttribute
de l’objet
document
, puis affecté à l’élément.
<div id="header">
...
<a class="addthis_button_expanded"></a>
...
</div>
IE 6 et 7 | IE 8 |
---|---|
|
|
- L’attribut
class
, identifié par la variablev_attr_class
, est créé avec la méthodedocument.createAttribute
. - La valeur "
addthis_button_expanded
" est donnée à l’attribut avec la propriéténodeValue
de l’objet attribut. - L’attribut
v_attr_class
est affecté à l’élémentv_a_addthis_share
avec la méthodesetAttributeNode
.
Une fois de plus, pour ne pas dire comme d’habitude, la version du navigateur Internet Explorer doit être vérifiée en Javascript pour gérer ces cas particuliers. Voici un exemple de vérification :
// agt = mozilla/4.0 (compatible; msie 7.0.... Récupération du navigateur
var agt=navigator.userAgent.toLowerCase();
// is_ie = true => MS Internet Explorer (msie)
var is_ie = ((agt.indexOf("msie") != -1));
// version = 7 => Si is_ie, récupération de la version majeure
if (is_ie) {version = parseFloat(navigator.appVersion.split("MSIE")[1]);}
Lorsque les variables permettant d’identifier le navigateur et la version d’Internet Explorer sont récupérées, les instructions sont alors codées sous conditions :
if ( ! is_ie || (is_ie && version >=8)) {
instructions pour IE >=8 et les autres navigateurs
} else {
instructions pour IE 6 et 7
}
Incompatibilité de Google AdSense avec le format XHTML (document.write)
Inutile de s’escrimer pour l’heure actuelle à vouloir implémenter Google
AdSense proprement avec les méthodes Javascript createElement
et appendChild
pour ajouter le script show_ads.js
, Google AdSense utilise document.write en
mode sérialisé pour afficher la bannière d’annonces :
|
show_ads.js
|
Google AdSense est incompatible avec la norme XHTML/DOM, norme dans laquelle
la fonction document.write
est inactive.
Pour délocaliser et centraliser la création de la bannière Google AdSense dans un script javascript afin de ne pas répéter le bout de code Javascript AdSense dans tous les articles au format HTML, il n’y a pas d’autres alternatives que d’utiliser également la fonction document.write :
document.write("<script type='text/javascript' src='http://pagead2.googlesyndication.com/pagead/show_ads.js'>\n");
document.write("</script>\n");
Pas d’autres alternatives ? Pas tout à fait, une solution existe : elle
consiste à créer une page HTML statique pour les annonces AdSense et à
incorporer celle-ci dynamiquement avec Javascript dans la page HTML parente
grâce à la balise object
. Voici le lien pour cette solution alternative : SQLPAC - Google Adsense, ajout dynamique
avec Javascript et la méthode createElement. Malheureusement cette
solution alternative n’est pas sans conséquences sur les statistiques Google
Analytics, point qui est également abordé dans l’article précédemment cité.
Gageons que les futures APIs de Google AdSense supprimeront cette contrainte.
Un dernier exemple : appel du script Google Analytics ga.js avec insertBefore
Dans un article paru en novembre 2009 au sujet de l’incorporation de Google
Analytics pour mesurer l’audience Web (Mesurer son audience Web et exploiter
efficacement Google Analytics ), l’incorporation du script de Google
Analytics ga.js
est réalisée avec la méthode document.write
:
<script type='text/javascript' src='http://www.google-analytics.com/ga.js'></script>
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
Ce code contenant document.write
empêche le suivi Google Analytics si il
s’agit d’un document de type XHTML.
La fonction document.write peut être supprimée en créant classiquement un
élément de type script
avec la méthode createElement
:
var v_script_ga = document.createElement('script');
v_script_ga.setAttribute("type","text/javascript");
var v_gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
v_script_ga.setAttribute("src",v_gaJsHost + "google-analytics.com/ga.js");
Mais où placer cet élément script ? Une solution simple peut consister à
placer celui-ci juste avant le premier élément <script></script>
de
l’arbre DOM.
var v_script_first = document.getElementsByTagName('script')[0];
var v_parentNode = v_script_first.parentNode;
v_parentNode.insertBefore(v_src_ga, v_script_first);
- La méthode
document.getElementsByTagName('script')
retourne un tableau des éléments<script...></script>
dans l’arbre DOM.document.getElementsByTagName('script')[0]
renvoie donc le premier élément<script...></script>
présent dans l’arbre DOM. v_parentNode
renvoie le nœud (ou élément) parent de ce premier élément<script ...></script>
.- L’élément script
v_src_ga
est inséré juste avant le premier élément<script ...></script>
grâce à la méthodeinsertBefore
appliquée sur son élément parent.
La lisibilité du code en pâtit un peu mais cette opération peut être réalisée en moins de lignes de code:
var v_script_first = document.getElementsByTagName('script')[0]; v_script_first.parentNode.insertBefore(v_src_ga,v_script_first);
Le nouveau code est compatible pour toutes les versions de navigateurs et assure une compatibilité de Google Analytics avec les documents de type XHTML :
var v_src_ga = document.createElement('script');
v_src_ga.setAttribute("type","text/javascript");
var v_gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
v_src_ga.setAttribute("src",v_gaJsHost + "google-analytics.com/ga.js");
var v_script_first = document.getElementsByTagName('script')[0];
v_script_first.parentNode.insertBefore(v_src_ga,v_script_first);