11544 sujets

JavaScript, DOM et API Web HTML5

Je sais, le titre n'est pas très clair mais je m'explique avec ce petit exemple (voir l'implémentation sur http://caractereschinois.free.fr/test3.html ) :

<div id="container"></div>
<div id="value"></div>
<script type="text/JavaScript">
for (var i = 0; i<5; ++i) {
  var newDiv = document.createElement("div");
  newDiv.appendChild(document.createTextNode(i));
  newDiv.onclick = function onclick() {document.getElementById("value").innerHTML = i;};
  document.getElementById("container").appendChild(newDiv);
}
</script>

Je voudrais que quand on appuie sur un numéro, le div 'value' affiche le numéro qui est appuyé. Pour ça je remplis des div avec un numéro comme texte et avec comme fonction onclick une instruction disant d'afficher le numéro en question dans le div 'value'.

Comme on voit ça ne marche pas.

En fait ce qui se passe c'est qu'on garde une référence sur la variable i qui est incrémentée jusqu'à 5. je n'ai pas réussi à trouvé de moyen pour éviter ça.

Des idées ?
Modifié par figo (09 Sep 2007 - 23:25)
Salut,

Moi j'aurais tendance à écrire ceci :

<div id="container"></div>
<div id="value"></div>
<script type="text/JavaScript">
for (var i = 0; i<5; ++i) {
  var newDiv = document.createElement("div");
  newDiv.appendChild(document.createTextNode(i));
  newDiv.onclick = function onclick() {document.getElementById("value").innerHTML = this.innerHTML;};
  document.getElementById("container").appendChild(newDiv);
}
</script>


Je ne suis pas sûr de ce que ça va donner, mais essai toujours Smiley cligne

A+
Merci, ça contourne en effet mon problème.

Malheureusement mon exemple était volontairement simplifié pour exposer la situation afin de savoir s'il y avait une manière propre d'écrire des fonctions onclick sans effet de bord.

Voilà donc un exemple plus près de ce que je veux faire.

  <div id="container"></div>
<script type="text/JavaScript">
function colorierJusquA(indice) {
  for (var i=0; i<indice && i<5; ++i) {
    document.getElementById("box" + i).style.backgroundColor = "black";
  }
}

for (var i=0; i<5; ++i) {
  var newDiv = document.createElement("div");
  newDiv.style.float = "left";
  newDiv.style.display = "inline";
  newDiv.style.backgroundColor = "red";
  newDiv.setAttribute("id", "box" + i);
  newDiv.appendChild(document.createTextNode("_"));
  newDiv.onclick = function onclick() {colorierJusquA(i);};
  document.getElementById("container").appendChild(newDiv);
}
</script>


Vous le retrouverez sur http://caractereschinois.free.fr/test5.html


Ici j'aimerai bien colorier des div jusqu'à l'endroit où l'utilisateur a cliqué. Comme on voit ils se colorient tous d'un coup. L'astuce consistant à extraire l'indice de l'endroit où l'utilisateur a cliqué à partir de this.id ne me semble pas satisfaisante.

Il doit bien y avoir un moyen propre de programmer des fonctions onclick.

Enfin j'ose l'espérer...
Modifié par figo (09 Sep 2007 - 23:52)
Modérateur
Salut,

Tu peux faire ainsi :
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
	<head>
		<meta http-equiv="content-type" content="text/html; charset=utf-8" />
		<title>Exemple</title>
		<style type="text/css"><!--


@media screen, projection
{
	.red { color: red; }
}


		--></style>
		<script type="text/javascript"><!--


var Script, Sp, ColorUntilMe;

Script = function() {};

Sp = Script.prototype =
{
	connect: function(oElem, sEvType, fn, bCapture)
	{
		return document.addEventListener ?
			oElem.addEventListener(sEvType, fn, bCapture):
			oElem.attachEvent ?
				oElem.attachEvent('on' + sEvType, fn):
				false;
	},

	aTag: function(oEl, sTag)
	{
		return oEl.getElementsByTagName(sTag);
	},

	getSource: function(e)
	{
		return e.target || e.srcElement;
	},

	cancelClick: function(e)
	{
		if(e && e.stopPropagation && e.preventDefault)
		{
			e.stopPropagation();
			e.preventDefault();
		}
		else if(e && window.event)
		{
			window.event.cancelBubble = true;
			window.event.returnValue = false;
		}

		return false;
	},

	colorUntilMe: function(e)
	{
		var oSrc, iEl;

		oSrc = Sp.getSource(e);
		iEl = 0;

		while(Sp.aEls[iEl] != oSrc)
			Sp.aEls[iEl++].className = Sp.sColor;

		oSrc.className = Sp.sColor;

		return Sp.cancelClick(e);
	},

	addBehaviour: function()
	{
		var iEl;

		Sp.aEls = Sp.aTag(document, Sp.sTag);
		iEl = Sp.aEls.length;
		
		do Sp.connect(Sp.aEls[--iEl], 'click', Sp.colorUntilMe, false);
		while(iEl > 0);
		
		return true;
	},

	initialize: function(sTag, sColor)
	{
		Sp.sTag = sTag;
		Sp.sColor = sColor;

		return Sp.connect(window, 'load', Sp.addBehaviour, false);
	}
};

ColorUntilMe = new Script;
ColorUntilMe.initialize('a', 'red');


		//--></script>
	</head>
	<body>


<ul>
	<li><a href="#">lien 1</a></li>
	<li><a href="#">lien 2</a></li>
	<li><a href="#">lien 3</a></li>
	<li><a href="#">lien 4</a></li>
	<li><a href="#">lien 5</a></li>
	<li><a href="#">lien 6</a></li>
	<li><a href="#">lien 7</a></li>
	<li><a href="#">lien 8</a></li>
	<li><a href="#">lien 9</a></li>
</ul>


	</body>
</html>

Modifié par koala64 (10 Sep 2007 - 03:15)
Et que penses-tu de cet exemple-ci ?


for (var i = 0; i<5; ++i) { 
  var newDiv = document.createElement("div"); 
  newDiv.appendChild(document.createTextNode(i)); 
newDiv.index=i;
  newDiv.onclick = function onclick() {document.getElementById("value").innerHTML = this.index;}; 
  document.getElementById("container").appendChild(newDiv); 
} 



L'astuce est ici d'attribuer une propriété supplémentaire à ton objet div pourpouvoir la récupérer après coup.
Modérateur
Salut,

Si ce n'est que pour inscrire l'index, je te dirais que c'est la meilleure méthode puisque ça t'évite une boucle supplémentaire... mais si c'est pour colorer tous les éléments jusqu'à celui cliqué, la boucle devient obligatoire et l'élément cliqué suffit (vu qu'on part de 0 jusqu'à ce qu'on le rencontre). L'attribution d'une propriété supplémentaire semble alors inutile non ? Smiley smile
Modifié par koala64 (10 Sep 2007 - 10:18)
Bonjour,

Merci pour vos réponses. La solution de Quentin est facilement adaptable pour répondre à mon problème mais je me demande à quel point elle est généralisable.

La propriété index ne sert-elle pas à autre chose normalement ?
Peut-on avoir plusieurs objets avec le même index ?
Est-ce xhtml1-strict.dtd proof ?
Cela marche-t-il avec tous les navigateurs ?
Et si je veux avoir plusieurs paramètres dans mon onclick ?

Bref ça sent encore un peu la bidouille et j'essaie de faire quelque chose d'aussi propre que possible.

Une remarque en passant, je ne connais pas grand chose à la manière dont le js est géré en interne mais j'ai l'impression qu'une fonction comme

function creerDiv(valeur) {
  var  nouvDiv = document.createElement('div');
  nouvDiv.onclick = 
      function onclick() {faireQuelqueChoseAvec(valeur);};
  return nouvDiv;
}

var valeur0 = "valeur";
creerDiv(valeur0);

va entraîner un pointeur vers la variable value qui ne sera jamais détruit. Celle-ci étant une copie de la variable initValue je n'aurai plus le problème que j'avais avant si je modifie valeur0 aux dépens de la mémoire. Enfin c'est du chipotage parce que de toute façon toute la gestion js dépent du garbage collector donc une variable de plus ne tuera personne.

Je testerai ce soir mais je ne suis toujours pas pleinement satisfait.


Enfin un exemple avec plusieurs paramètres qui mets en défaut la plupart des solutions alternatives (bon là je la pousse peut-être un peu, j'avoue), je n'ai pas encore besoin de faire tout ça mais je ne vois pas a priori pourquoi ça ne marcherait pas :

function creerDiv(valeur0, valeur1, valeur2) {
  var  nouvDiv = document.createElement('div');
  nouvDiv.onclick =
      function onclick() {faireQuelqueChoseAvec(valeur0, valeur1, valeur2);};
  return nouvDiv;
}

var valeur0 = "truc";
var valeur1 = "machin";
var valeur2 = function faireUnTrucGenial() {alert("ho");};
creerDiv(valeur0, valeur1, valeur2);


PS : Merci Koala pour ta solution qui commence à être complexe, je dois dire que j'ai un peu du mal à tout saisir (je trouve tes noms de variables pas toujours très explicites).
Modifié par figo (10 Sep 2007 - 11:38)
Modérateur
Salut,

figo a écrit :
Merci pour vos réponses. La solution de Quentin est facilement adaptable pour répondre à mon problème mais je me demande à quel point elle est généralisable.
J'ai envie de dire oui et non du fait que la fonction ne sert que dans un contexte particulier (avec des balises particulières)... Cela dit, c'était la question donc c'est normal.

a écrit :
La propriété index ne sert-elle pas à autre chose normalement ?
Non, du tout. C'est un ajout "utilitaire" et on y affecte ce qu'on souhaite comme valeur.

a écrit :
Peut-on avoir plusieurs objets avec le même index ?
Oui, pas de problème tant que les objets sont clairement différenciés.

a écrit :
Est-ce xhtml1-strict.dtd proof ?
Pas de problème là aussi du fait que l'index n'influe en rien sur le code xhtml.

a écrit :
Cela marche-t-il avec tous les navigateurs ?
oui. (quoique je m'avance pas trop pour Safari... C'est un capricieux celui-là Smiley lol )

a écrit :
Et si je veux avoir plusieurs paramètres dans mon onclick ?
Là, ça se complique. Smiley murf Soit tu sais à l'avance quelles sont les propriétés soit tu ne le sais pas. Dans le second cas, il faut prévoir une méthode généraliste.

Un bon moyen de transmettre une propriété à un élément est de passer un objet JSON unique en argument au sein duquel tu définies toutes tes propriétés. Par exemple, en reprenant la méthode camelize de la librairie Prototype, on peut s'amuser à affecter n'importe quelle propriété CSS aux différents éléments. (voir les exemples ci-dessous)

a écrit :
Merci Koala pour ta solution qui commence à être complexe, je dois dire que j'ai un peu du mal à tout saisir (je trouve tes noms de variables pas toujours très explicites).
La chose qui devait le plus te perturber était essentiellement la gestion d'événement en DOM-2 non ? Pour simplifier, je repasse avec des onclick. J'aurais du mal à faire mieux vu ta demande. Smiley confused

En ce qui concerne mes noms de variables, j'utilise souvent la même logique :
- Première lettre en minuscule représentant le type de la variable
- puis le nom "camelisé" représentatif du contenu de la variable

Bref, on peut imaginer quelquechose comme ceci :
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
	<head>
		<meta http-equiv="content-type" content="text/html; charset=utf-8" />
		<title>Exemple</title>
		<style type="text/css"><!--


@media screen, projection
{
	div { width: 100px; height: 100px; background-color: red; margin-bottom: 5px; }
}


		--></style>
		<script type="text/javascript"><!--


var Script, Sp, BindPropertiesToLinks, BindPropertiesToDivs;

Script = function() {};

Sp = Script.prototype =
{
	connect: function(oElem, sEvType, fn, bCapture)
	{
		return document.addEventListener ?
			oElem.addEventListener(sEvType, fn, bCapture):
			oElem.attachEvent ?
				oElem.attachEvent('on' + sEvType, fn):
				false;
	},

	aTag: function(oEl, sTag)
	{
		return oEl.getElementsByTagName(sTag);
	},

	camelize: function(sProp)
	{
		var parts, len, camelized, iI;

		parts = sProp.split('-');
		len = parts.length;

		if(len == 1)
			return parts[0];

		camelized = sProp.charAt(0) == '-' ?
			parts[0].charAt(0).toUpperCase() + parts[0].substring(1) :
			parts[0];

		for(iI = 1; iI < len; iI++)
			camelized += parts[iI].charAt(0).toUpperCase() + parts[iI].substring(1);

		return camelized;
	},

	bindProperties: function(oEl, aEl, oProp)
	{
		var iEl, aProp;

		aProp = new Array();

		for(sProp in oProp)
			aProp[Sp.camelize(sProp)] = oProp[sProp];

		iEl = 0;
		
		while(aEl[iEl] != oEl)
		{
			for(sProp in aProp)
				aEl[iEl].style[sProp] = aProp[sProp];
			iEl++;
		}

		for(sProp in aProp)
			oEl.style[sProp] = aProp[sProp];

		return true;
	},

	addBehaviour: function(sTag, oProp)
	{
		var aEl, aEls, iEl;

		aEl = aEls = Sp.aTag(document, sTag);
		iEl = aEls.length;
		
		do
			aEls[--iEl].onclick = function()
						 {
						 	Sp.bindProperties(this, aEl, oProp);
						 	return false;
						 };
		while(iEl > 0);
		
		return true;
	},

	initialize: function(sTag, oProp)
	{
		return Sp.connect(window, 'load', function() { Sp.addBehaviour(sTag, oProp); }, false);
	}
};

BindPropertiesToLinks = new Script;

BindPropertiesToLinks.initialize(
	'a',
	{
		'color': 'red',
		'background-color': 'black',
		'padding-left': '50px'
	}
);

BindPropertiesToDivs = new Script;

BindPropertiesToDivs.initialize(
	'div',
	{
		'background-color': 'black',
		'width': '50px',
		'height': '50px'
	}
);


		//--></script>
	</head>
	<body>

<div></div>
<div></div>
<div></div>

<ul>
	<li><a href="#">lien 1</a></li>
	<li><a href="#">lien 2</a></li>
	<li><a href="#">lien 3</a></li>
	<li><a href="#">lien 4</a></li>
	<li><a href="#">lien 5</a></li>
	<li><a href="#">lien 6</a></li>
	<li><a href="#">lien 7</a></li>
	<li><a href="#">lien 8</a></li>
	<li><a href="#">lien 9</a></li>
</ul>


	</body>
</html>
Dans cet exemple, Script est une fonction vide. De là, on définit au sein du prototype de celle-ci les méthodes utiles au script.

Pour ne pas avoir à retaper systématiquement Script.prototype.maMethode, je crée un raccourci Sp puis je m'en sers à tout va (les méthodes s'écrivent maintenant Sp.maMethode. On pourrait mettre this.maMethode à la place mais je préfère l'éviter car ce n'est jamais parlant). Après, le principe, c'est qu'on peut se resservir du prototype pour créer plusieurs instances.

Ici, j'y crée deux instances, l'une pour styliser les divs et l'autre pour les liens. Je passe les propriétés de style via un objet JSON et pour que les propriétés soient correctement prises en compte, je camélise les labels de l'objet (ex. : background-color devient backgroundColor que j'associe à l'objet style de l'élément)

PS : A mon avis, le principe ne va pas te paraître bien clair malgré mes explications mais bon, je te laisse tester et éplucher le script... désolé Smiley confus
Modifié par koala64 (10 Sep 2007 - 14:58)
Figo > ton exemple avec creerDiv ne devrait pas poser problème, à voir comme ça rapidement.

a écrit :
La propriété index ne sert-elle pas à autre chose normalement ?

Je ne crois pas, mais si tu n'es pas sûr tu peux changer le nom de la propriété.

a écrit :
Peut-on avoir plusieurs objets avec le même index ?

Pas de problème mais il faut bien être sûr de savoir ce que tu fais pour ne pas confondre après coup.

a écrit :
Est-ce xhtml1-strict.dtd proof ?

Aux yeux du W3C oui. Par contre étant donné que j'ajoute des propriétés à un éléement de l'arbre DOM, il se pourrait que si on demande le code HTML réel, il y apparaisse cette fameuse propriété supplémentaire, ou que les navigateurs un peu frileux aient du mal à accepter cette petite modification. A tester.

a écrit :
Cela marche-t-il avec tous les navigateurs ?

Je ne vois pas où il pourrait y avoir un problème.

a écrit :
Et si je veux avoir plusieurs paramètres dans mon onclick ?

Facile : tu utilises plusieurs propriétés supplémentaires ou alors tu encapsules dans un objet.

Il faut dire que, même si j'ai déjà croisé plusieurs fois Koala64 sur alsa, ses scripts me paraissent toujours autant inhabituels et déroutants. Tu n'es donc pas le seul à être dans le flou avec le gourou du javascript.

P.S. Koala64 > Ceci n'est pas une appellation péjorative, bien au contraire. Désolé dans le cas où je t'aurais offensé.
Merci pour toutes ces infos les gars !

Quand je parlais de xhtml1-strict proof je pensais à quelque chose comme ça (obtenu en validant mon code) :
Line 30, Column 15: there is no attribute "index".
    <div index="3">bonjour</div>


Mais bien sûr ça ne perturbe pas le navigateur, on est d'accord, par contre est-ce W3C acceptable je ne sais pas (chipotages...).

Pour ceux qui se posaient la question, dédoubler la variable de boucle par un appel à une fonction externe (entraînant une copie) fonctionne à merveille et est donc généralisable à plusieurs paramètres, par contre il reste le petit inconvénient niveau mémoire dont vous conviendrez on se fiche pas mal.

En tout cas merci pour toutes vos suggestions.

Voilà donc le code qui résout mon problème de coloriage très simplement et disponible sur http://caractereschinois.free.fr/test6.html
  <div id="container"></div>
<script type="text/JavaScript">
function colorierJusquA(indice) {
  for (var i=0; i<indice && i<5; ++i) {
    document.getElementById("box" + i).style.backgroundColor = "black";
  }
}

function getNewDiv(i) {
  var newDiv = document.createElement("div");
  newDiv.style.float = "left";
  newDiv.style.display = "inline";
  newDiv.style.backgroundColor = "red";
  newDiv.setAttribute("id", "box" + i);
  newDiv.appendChild(document.createTextNode("_"));
  newDiv.onclick = function onclick() {colorierJusquA(i);};
  return newDiv;
}

for (var i=0; i<5; ++i) {
  document.getElementById("container").appendChild(getNewDiv(i));
}

Modifié par figo (10 Sep 2007 - 21:59)
Figo > Alors à ce niveau là oui, le W3C n'y verra que du feu. C'est du javascript, pas du HTML.... le W3C se fout pas mal du contenu de ton script lors de la validation.


Par contre tu parles de fuite de mémoire, un peu plus d'infos ne seraient pas de trop là-dessus ?
Bonjour,

En fait il ne s'agit pas vraiment de fuite mémoire mais le comportement tel que je le vois est le suivant :
- dans mon premier exemple, la fonction onclick était enregistrée avec une référence vers la variable i qui manque de pot était modifiée par la suite.
- dans mon dernier exemple, j'appelle une fonction auxilière qui crée une copie de la variable de boucle, cette copie de la variable de boucle est référencée dans la fonction onclick mais comme ce n'est pas la copie qui est référencée et non la variable de boucle je n'ai plus le problème de modification a posteriori.

Quoiqu'il arrive, la fonction onclick garde une référence vers les variables qui y figurent (dans le premier cas c'est la variable de boucle, dans le second c'est la copie de celle ci pour chacune de ses valeurs).

Au final, au lieu d'inclure la valeur de la variable dans la fonction onclick, il semblerait qu'on ait une référence vers la variable qui a été utilisée. Donc pour le garbage collector, cette variable n'est pas détruite tant que l'objet n'est pas lui même détruit (meilleur cas).

Maintenant que j'y réfléchis, je peux comprendre ce comportement. Dans mon cas la variable qui figure dans la fonction onclick est un simple entier donc je trouve dommage qu'il ne copie pas bêtement la valeur, mais si cette variable était un objet compliqué (par exemple un élément div), copier directement sa valeur n'aurait pas de sens (l'initialisation peut être très longue...) et on est bien obligé de garder une référence vers l'objet initial.

Bref, faites attention quand vous utilisez des paramètres dans une fonction onclick définie à la volée en js.

PS : pour mon histoire de xhtml1-strict compatible, il m'est avis qu'idéalement xhtml et js doivent coéxister pacificquement même si en pratique les navigateurs sont très souples à ce niveau. C'est pourquoi je n'utilise pas en js des paramètres qui ne sont pas compatibles dom, qui sait un jour ce sera peut-être même interdit.