Overblog Suivre ce blog
Editer l'article Administration Créer mon blog
14 février 2011 1 14 /02 /février /2011 11:23

Repartons du  billet  présenté dans le premier article et réfléchissons un bon coup.

Premier souhait, un billet escamotable. Ça, on sait faire depuis le deuxième article : un clic bien placé change la classe du billet et roulez, jeunesse !

Deuxième souhait, un billet pas envahissant : si le visiteur l'a déjà lu on ne le cache pas mais on le présente fermé plutôt qu'ouvert. Il faut donc garder trace du passage du visiteur – forcément un cookie. Ça, on va apprendre.

Troisième souhait, un billet lisible même sans JS. En effet ce billet sera, la plupart du temps, fermé. Mais si le visiteur navigue sans JavaScript ? Il n'aura aucun moyen d'ouvrir (ni de refermer, d'ailleurs) le billet. Mieux vaut donc, dans ce cas, le lui présenter ouvert même s'il l'a déjà lu (ce qu'on ne pourra d'ailleurs pas savoir, faute de Javascript pour lire le cookie). Sans chercher plus loin pour le moment, il nous faut donc aussi détecter la disponibilité de Javascript chez le visiteur. Ça tombe bien, la question est déjà résolue et nous utiliserons la technique présentée dans l'article donné en lien. Apuka…

Note rituelle : si vous n'avez pas l'emploi de ce programme mais que le langage JavaScript vous intéresse, la deuxième partie de l'article peut vous donner des idées. Comme d'habitude, il est inutile de la lire pour installer le programme.

Mise en place

Code HTML

Vous le connaissez déjà, ici un peu modifié. Pas de bouton de commande mais une nouveauté :

 <div id="billet"> <var>10-02-2011</var> <div class="contenu"> Le texte de votre billet, ne vous gênez pas pour y mettre un titre ou des paragraphes ou ce que vous voudrez. </div> </div> 

C'est quoi, ce var ? C'est le  tampon  qui identifie la version du billet. Il faudra penser à changer ce  tampon  à chaque nouvelle version du billet, c'est lui qui sera comparé avec le tampon conservé dans le cookie pour savoir si le billet a déjà été lu ou non. Pourquoi un élément var plutôt qu'un span banal ? D'abord l'emploi d'un élément rarement rencontré facilitera la conception du JavaScript et du CSS, ensuite var signifie tout bonnement "variable" : cet élément sert à présenter une variable "technique", informatique dirons-nous. Ça m'a semblé moins bête que de détourner de son rôle un em ou un strong. Perfectionniste…

Et pourquoi n'y a-t-il pas de boutons de commande ? Le programme JS les créera à la volée, ce qui garantit qu'un visiteur sans JS ne verra pas un bouton totalement inutilisable. C'est plus simple que de s'échiner à cacher lesdits boutons.

Et enfin, pourquoi n'y a-t-il pas de classe ouvert ou ferme sur ce billet ? La réponse viendra dans la suite.

Premier JS : détection de la présence de JS

Éditez le code source de l'en-tête du blog et, au tout début de cet en-tête, ajoutez ceci :

 <div><script type="text/javascript"> document.getElementsByTagName('body')[0].className = 'JS_on'; </script></div> 

L'instruction JS applique la technique décrite dans  [CSS+JS] Détecter en CSS la présence de JavaScript , je vous y renvoie pour les explications.

Petite ruse spéciale OB : cette instruction est emballée dans un div. Sans cela l'éditeur OB déplacerait l'élément script à la fin de l'en-tête. En quoi serait-ce gênant ? Il est essentiel que cette instruction s'exécute au tout début du chargement de la page, pour que le navigateur sache tout de suite qu'il est "avec JS" et applique tout de suite les règles CSS pertinentes. Sans cela, on risque fort de voir le billet s'afficher d'abord ouvert (car le navigateur ne sait pas encore qu'il y a du CSS particulier en mode "avec JS") puis se refermer sitôt affiché, parce que le JS se met en branle. Pas dramatique mais moche, l'inverse sera moins irritant.

Perfectionniste, ça se confirme…

Deuxième JS : traitement du Postit

(et gestion des cookies)

Recopiez le programme suivant sur votre ordinateur :

 // Gestion simplifiée des cookies // http://brendufat.over-blog.com/article-js-cookies-66381726.html // Sur une idée de Peter-Paul Koch - http://www.quirksmode.org/js/cookies.html JMcookie={ create:function(nm,v,d) { // d : durée en jours var dt,xp=''; if (d){ var dt=new Date(); dt.setTime(dt.getTime()+(d*24*3600000)); xp=';expires='+dt.toGMTString() } document.cookie= nm+"="+v+xp+";path=/"; } ,read:function(nm) { var ca=document.cookie.split(';'),i,c; nm+="="; for(i=0;i<ca.length;i++) {c=ca[i]; while (c.charAt(0)==' ') c=c.substring(1,c.length); if (c.indexOf(nm)==0) return c.substring(nm.length,c.length); } return null; } ,remove:function(nm) {this.create(nm,"",-1)} }; // Gestion du Postit (flûte pour la marque déposée !) // http://brendufat.over-blog.com/article-postit3-67165624.html JMpostIt:{ r:undefined ,open:function(){JMpostIt.r.className='ouvert';return false} ,close:function(){JMpostIt.r.className='ferme';return false} ,setup:function(id){ var dp,dc,rC; this.r=document.getElementById(id); if(!this.r) return; dp=this.r.getElementsByTagName('var'); if(dp) dp=dp[0].innerHTML; dc=JMcookie.read('dernierBillet'); if(dp&&dp==dc) this.close() else this.open(); if(dp) JMcookie.create('dernierBillet',dp,14); with((rC=document.createElement('a'))){ className="ouverture";href="#nogo"; innerHTML='[+]';title="Ouvrir le Postit"; } rC.onclick=this.open; this.r.appendChild(rC); with((rC=document.createElement('a'))){ className="fermeture";href="#nogo"; innerHTML='[-]';title="Fermer le Postit"; } rC.onclick=this.close; this.r.appendChild(rC); } } JMpostIt.setup('billet'); 

Remplacez-y l'identifiant billet par celui que vous avez donné au billet. Attention : pas de dièse ! Ensuite chargez ce programme dans la partie  Mes documents  de l'administration de votre blog.

Ajoutez une ligne dans l'en-tête du blog, après le billet :

 <script type="text/javascript" src="URL_du_programme"></script> 

Remplacez bien sûr URL_du_programme par l'url effective du fichier.

CSS

Le CSS est exactement le même, avec juste une règle supplémentaire pour cacher le tampon de la poste et quelques complications de sélecteurs (en gras) pour tenir compte de la navigation sans JS :

 /* ce qui est vrai pour le billet ouvert comme fermé*/ #billet {position:absolute;left:20%;top:50px} #billet var {display:none} /* aspect initial du billet si JS = fermé */ .JS_on #billet, #billet .ferme {background:#ffffb7;border:1px solid gray;width:3em;height:2em} /* aspect initial du billet sans JS = ouvert */ #billet, #billet.ouvert {background-image:url(http:gnagnagna/billet.png);width:218px;height:150px} /* aspect du contenu */ .contenu {padding:20px} /* emplacement des boutons de commande */ .ouverture, .fermeture {position:absolute;right:2px;top:2px} /* masquer/afficher les composants du billet selon son état */ .ouvert .ouverture, .ferme .fermeture, .JS_on .contenu, .ferme .contenu {display:none} .contenu, .ouvert .contenu {display:block} 

Et ça marche.

Tuyaux

  1. en écrivant une nouvelle version du billet n'oubliez pas de changer le tampon de la poste. Pour épargner des ratés à vos lecteurs, il est important de ne jamais utiliser deux fois la même valeur du tampon. La date du jour m'a semblé un moyen simple d'y parvenir, vous pouvez aussi utiliser un numéro d'ordre (augmenté de 1 à chaque nouvelle version) ou, pourquoi pas ? tous les mots du dictionnaire ! De A à Z ou de Z à A, peu importe.
  2. ce programme incorpore la petite bibliothèque de gestion des cookies présentée dans cet article. Si vous avez déjà installé cette bibliothèque il est naturellement inutile de la dupliquer, et vous pouvez supprimer la première partie du programme fourni.
  3. les boutons de commande sont les mêmes que dans la version précédente, avec le texte [+] ou [-]. Si vous prévoyez d'y mettre des images de fond plutôt que ce texte, il faut remplacer innerHTML='[+]'; (et/ou [-]) par innerHTML='' ;

Démontage

Ce gadget, déduction faite de la gestion de cookies, n'est pas très long mais combine plusieurs techniques utiles. Voyons ça.

Pas de Javascript, que faire ?

Il s'agit ici de l'instruction ajoutée dans l'en-tête du blog. Elle donne au body de la page la classe JS_on. Si le visiteur navigue sans JS elle ne s'exécute naturellement pas et le body n'a pas de classe particulière. Dans le CSS du blog, les règles dont le sélecteur commence par .JS_on sont donc les règles qui ne s'appliquent que si le visiteur navigue avec JS.

Revenons à notre billet et demandons-nous comment l'afficher initialement :

  1. visiteur sans JS : billet ouvert
  2. visiteur avec JS, billet non encore lu : billet ouvert
  3. visiteur avec JS, billet déjà lu : billet fermé

Ça semble simple : on indique la classe ouvert dans le HTML et le JS referme le billet s'il faut le replier (et s'il y a du JS, bien sûr). Et nul besoin de détecter quoi que ce soit. Alors ?

On peut partir là-dessus, en effet, et ça marche presque bien… mais un billet déjà lu s'affiche d'abord ouvert avant de se replier plus ou moins vite. Pas dramatique mais pas propre. Du fait que le JS de gestion du billet doit s'exécuter après affichage du billet (sans cela il ne trouvera pas le billet !) il est inutile de chercher de ce côté-là. Il faut donc que le premier affichage du billet ait lieu en sachant déjà si le billet doit être ouvert ou fermé, donc en ayant déjà détecté la présence ou l'absence de JS. Ce qui explique la présence de l'instruction dans l'en-tête.

Réfléchissons jusqu'au bout : avec cette solution, et en présence de JS, un billet non encore lu s'affichera d'abord fermé puis s'ouvrira quand agira le programme. Ce n'est pas encore parfait mais m'a semblé suffisant. Aller plus loin, faire en sorte que le billet s'affiche d'emblée dans l'état voulu, demanderait de faire grossir la partie ajoutée à l'en-tête, avant le billet : lire et interpréter le cookie, ajouter au body non pas la classe JS_on mais une classe billetOuvert ou billetFerme (ces noms ne sont que des exemples) et retoucher le CSS en conséquence. Je ne suis pas allé jusque là : perfectionniste mais un peu cossard tout de même. Mais si vous voulez faire tout très bien, allez-y !

Créer un objet JavaScript

Tel qu'il est construit, ce programme ne se compose que de trois  grosses  instructions Javascript :

  1. JMcookie={...} définit l'objet JMcookie. Cette instruction donne trois propriétés à l'objet : create, read et remove. Il se trouve que toutes les trois sont des fonctions. En jargon de programmation objet, ce sont des méthodes de l'objet 
  2. JMpostIt={...} définit de même l'objet JMpostIt. Celui-là a quatre propriétés : la première est une variable r initialement indéfinie, les trois suivantes sont des méthodes.
  3. JMpostIt.setup('billet');, enfin, provoque l'exécution de la méthode setup de l'objet JMpostIt

Euh M'sieu… c'est quoi, un  objet  dans vot'jargon de malade ? Aîe… faut y passer. Je vais essayer de faire juste et utile, mais approximatif, très incomplet et pas du tout académique – sans quoi on y passe la nuit.

Un  objet  est un coin de mémoire, plus ou moins étendu et doté d'un nom. Ce coin de mémoire peut contenir de tout : des variables simples ou très complexes, des bouts de programme, d'autres objets… L'intention est de rassembler sous un même  chapeau  tout ce qui concerne une même partie du domaine à représenter, de ne rassembler que ça et de ne le rassembler que là. C'est ainsi que l'objet JMcookie regroupe tout ce dont j'ai besoin pour traiter les cookies et que l'objet JMpostIt en fait autant pour la gestion du billet. Il aurait été techniquement possible de créer un seul objet JMmachin mais on aurait perdu en clarté et rien gagné en performance.Utiliser des objets ne sert pas à se griser de technique mais à (tenter de) mettre un peu d'ordre.

Passons aux actes, laissons tomber JMcookie et définissons JMpostIt par

 JMpostIt={/* des trucs */} 

Entrons dans l'accolade :

 JMpostIt={variable_1:valeur_1, variable_2:valeur_2, variable_3:valeur_3 ...} 

La forme est simple : une liste de couples variable:valeur séparés par des virgules. Notez l'emploi du signe : plutôt que =. Et puis ? Et puis valeur peut être une valeur simple,  ordinaire , ou une référence à quelque chose, ou un tableau, ou une fonction, ou un autre objet (sous-objet) si le besoin s'en fait sentir. De tout, je vous l'avais bien dit.

Exemple concret : dans le cas de JMpostIt, les propriétés initialement déclarées sont :

r
recevra la référence du div  billet , ce qui nous permettra d'agir dessus.
open
close
facile à comprendre : deux méthodes pour ouvrir ou fermer le billet. Évidemment elles seront associées aux boutons de commande, mais nous serviront aussi au démarrage
setup
la méthode à exécuter pour activer le système, ce que fera l'instruction qui termine le programme.

Avec open et close vous avez un exemple tout simple d'emploi des propriétés d'un objet : JMpostit désigne l'objet lui-même, JMpostIt.r désigne la propriété r de cet objet. Enfin JMpostIt.r.className désigne la propriété className de la propriété r de l'objet. Clair ?

La méthode setup

C'est bien sûr le gros morceau. Elle doit :

  • trouver le billet dans la page – s'il n'existe pas, inutile d'insister ;
  • trouver le tampon de la poste dans le billet (il peut ne pas exister) ;
  • trouver le dernier tampon sauvegardé en cookie (lui aussi peut ne pas exister) ;
  • si les deux sont identiques fermer le billet, sinon l'ouvrir ;
  • sauvegarder dans le cookie le tampon du billet (en prévision du prochain passage) ;
  • enfin, fabriquer les deux boutons de commande.

Le code ressemble donc à ceci, pour commencer :

 JMpostIt:{ // objet de gestion du postit r:undefined // future référence du billet dans la page ,open:function(){/*......*/} // méthode d'ouverture du billet ,close:function(){/*......*/} // méthode de fermeture du billet ,setup:function(id){ // id = identifiant donné au billet var dp // recevra le tampon trouvé dans le billet ,dc // recevra le tampon trouvé dans le cookie ,rC; // servira lors de la création des deux boutons // trouver le billet dans la page : this.r // trouver le tampon de la poste dans le billet : dp // trouver le dernier tampon sauvegardé en cookie : dc // si les deux sont identiques fermer le billet, sinon l'ouvrir // sauvegarder dans le cookie le tampon du billet // fabriquer les deux boutons de commande } } 

Notez que les trois variables dp, dc et rC ne sont pas des propriétés de l'objet, seulement des variables qu'utilise la méthode setup pour faire sa cuisine. Ces variables sont totalement inconnues dans le reste du monde, et c'est bien ce qu'on désire.

Trouver le billet dans la page - getElementById

Puisque le billet porte un identifiant c'est facile (voir ce mémo) : sa référence est document.getElementById(identifiant_du_billet).
On stocke le résultat dans JMpostIt.r. Comme la méthode en cours d'exécution (setup) appartient à cet objet, le mot réservé this désigne cet objet. On peut donc parler de this.r, plus bref et plus évocateur. Si le billet n'existe pas on arrête tout : if(!this.r) return;.
Traitement très classique que vous retrouverez un peu partout.

Trouver le tampon de la poste dans le billet - getElementsByTagName, innerHTML

La référence du billet est désormais connue : this.r. Le tampon, s'il existe, est stocké dans le premier (et probablement unique) élément var de ce billet. Il faut seulement, avant de foncer, vérifier qu'il y a bien un tampon.
De ce fait on décompose le mouvement en deux temps :

 dp=this.r.getElementsByTagName('var'); // récupère tous les var du billet. En leur absence, dp est 'null' if(dp) dp=dp[0].innerHTML; // SI il y a des var on récupère le contenu du premier, sinon dp reste 'null' 
Encore un traitement classique décrit dans le même mémo.

Trouver le dernier tampon sauvegardé - lecture de cookie

Ah, du nouveau ! L'objet JMcookie tombe à point : dc=JMcookie.read('dernierBillet'); Si le cookie 'dernierBillet' (le nom vous plaît ?) n'existe pas, dc vaudra 'null'

Comparer les deux tampons - test, appel de méthode

Ne pas se faire piéger : si les deux tampons sont absents (null) ils seront égaux tout de même ! La description complète est donc : s'il y a un tampon dans le billet ET si ce tampon est le même que celui du cookie on ferme le billet, dans tous les autres cas on l'ouvre. La traduction en Javascript, en utilisant les deux autres petites méthodes de l'objet, est directe :

 if(dp && dp==dc) this.close() else this.open(); 
Notez l'utilisation de this une nouvelle fois.

Sauvegarder le tampon du billet - écriture de cookie

Deuxième recours à JMcookie, aussi simple que le premier :
if(dp) JMcookie.create('dernierBillet',dp,14); Le '14' veut dire que le cookie se périme en deux semaines, vous pouvez bien sûr modifier ce délai. Vous pouvez aussi changer le nom 'dernierBillet' utilisé pour le cookie, mais quel intérêt ?

Fabriquer les deux boutons de commandes - createElement, appendChild

Il s'agit de créer un lien, un élément a, de lui donner une classe, un titre, etc., et finalement de l'ajouter au billet. Si, comme dans le deuxième article, on écrivait soi-même le HTML nécessaire, on écrirait :

 <a href="#nogo" title="Ouverture du billet" class="ouverture" onclick="JMpostit.open()';">[+]</a> 
Je vous laisse le plaisir du copier/coller pour fabriquer le bouton de fermeture… Hé bien nous allons produire en JavaScript l'équivalent de ce code HTML.

On commence par créer l'élément a, sans rien dedans et sans le placer à un endroit déterminé de la page :

 rC=document.createElement('a'); 

Ensuite on "garnit" cet élément (classe, titre, texte, etc.) :

 rC.className="ouverture"; rC.href="#nogo"; rC.innerHTML='[+]'; rC.title="Ouvrir le Postit"; 

L'ordre importe peu, l'important est de ne rien oublier. On peut aussi  mettre rC en facteur  avec le mot réservé with :

 with(rC) { className="ouverture"; href="#nogo"; innerHTML='[+]'; title="Ouvrir le Postit"; } 

Et le gestionnaire d'événement, le onclick ? La fonction existe déjà, c'est la méthode open de notre objet. Il suffit donc de dire que c'est elle qu'on veut :

 rC.onclick=this.open; 

Attention tout de même à un piège classique : j'ai écrit this.open et pas this.open(). Arf… En effet, la première écriture désigne bien la fonction open (le code Javascript à exécuter plus tard et peut-être, si le visiteur clique sur le lien) alors que la deuxième écriture désigne le résultat de cette fonction. Et alors ? Alors, dans ce deuxième cas, la fonction open s'exécuterait tout de suite (et à coup sûr !), changerait la classe du billet puisque c'est son rôle, et renverrait son résultat d'exécution (d'ailleurs indéfini, dans le cas présent). C'est ce résultat (sans intérêt) qui serait attaché au lien en guise de gestionnaire d'événement.

Inversement, lors de la comparaison du tampon du billet et de celui du cookie, j'ai bien écrit this.open() parce que, à ce moment, je voulais vraiment exécuter la fonction pour changer la classe du billet, sans me soucier de la valeur du résultat d'exécution (tout aussi peu défini).

Inversement toujours, lorsqu'on écrit le HTML à la main, on écrit onclick="JMpostit.open()';" parce qu'on veut bel et bien exécuter cette instruction-là lors du clic. Pour le dire plus rigoureusement : le onclick défini dans le HTML se compose d'une ou plusieurs instructions JavaScript alors que le onclick défini par du JavaScript contient la référence d'une fonction – ladite fonction se composant précisément des instructions qu'on aurait écrites à même le HTML.

Ouf… Dernier point : on met ce truc  dans le with(rC) , lui aussi ? J'ai fait l'essai mais ça ne donne rien. Je pense que la définition d'un gestionnaire d'événement demande des précautions particulières (définition exacte de this, peut-être ?). Bref, mieux vaut laisser l'instruction en-dehors des accolades.

Pour résumer, et en condensant un peu l'écriture, le lien a se fabrique avec ceci :

 with((rC=document.createElement('a'))){ className="ouverture";href="#nogo"; innerHTML='[+]';title="Ouvrir le Postit"; } rC.onclick=this.open; 

Reste à l'insérer dans le billet. Comme ce lien sera en position:absolute, l'endroit exact où on l'ajoute au div du billet, référencé par this.r, n'a guère d'importance. Je choisis donc de l'ajouter en fin de billet parce que c'est plus vite écrit :

 this.r.appendChild(rC); 

Ce qui termine le travail, en dupliquant ce code pour créer le bouton de fermeture.

Petite observation : vous avez sûûûûrement remarqué que j'ajoute le lien après l'avoir complètement construit alors que, après tout, quand on écrit du HTML on le fait progressivement. On écrit d'abord les balises <a> et </a>, ensuite on remplit. Très juste, et l'on pourrait faire la même chose dans le même ordre en JavaScript. Le lien commencerait par apparaître à l'écran, ensuite son aspect évoluerait au fil de l'exécution des instructions JavaScript. Dans le cas de notre minuscule lien de rien du tout il est vrai que ça ne changerait pas grand-chose, mais pour des ajouts plus substantiels, avec des éléments imbriqués les uns dans les autres, cela fait  danser  l'affichage pendant que la page se charge et se modifie. Mieux vaut donc prendre l'habitude de tout fabriquer  hors de la page  puis de l'ajouter en une seule fois dans le document.

Voilà, comme d'habitude c'est plus long à expliquer qu'à comprendre…

par Aïe mes doigts ! - dans La gadgetière
commenter cet article

commentaires

Anna 15/02/2011 13:33


Je n'ai pas l'utilité du gadget, mais j'adore les explications : claires, compréhensibles ET précises, que demande le peuple !


AïmD ! 15/02/2011 15:28



Gaaaaahhh... * smiley extatique *


Sans rire, tu me fais plaisir : expliquer (faire sentir, au moins) ce qu'est un objet sans sombrer dans la philosophie fumeuse, ou expliquer pourquoi il faut tantôt des parenthèses et tantôt non
sans donner dans le "parce que c'est comme ça" n'allait pas de soi. Le reste est beaucoup plus routinier, je l'ai d'ailleurs servi d'autres fois déjà...



Archives