11548 sujets

JavaScript, DOM et API Web HTML5

Bienvenue au concours poletti, waaaaouuuuuu, ses codeeeuuurs, ses hashmaps... hum. Bienvenue à tous.

Aujourd'hui la 3ème énigme est plutôt un défi tout public. Pourquoi ? Parce que chacun, avec son expérience, pourra la trouver difficile. Petite subtilité cependant, je participe. Nous sommes donc tous à la recherche de la meilleure solution.


Objectif moi papa, toi fiston.

Ecrire une fonction (et une seule) capable de traiter un objet dont chaque clé représente un noeud et possède un couple id/parentId. La fonction doit retourner n'importe quel autre objet: element, string, object, ... Mais ce retour doit réprésenter l'arboréscence de l'objet visité.


Mise en situation

Vous écrivez une application et cette application affiche un certains nombre de composants d'arboréscence (comme l'explorer de windows). La particularité de cette application est qu'elle communique avec une base de données et dans cette base de données se trouve, par exemple, une table Categories dont les premières entrées ressemblent à ceci :
[b]id[/b]	[b]parentId[/b]	[b]name[/b]
1	0		Ma catégorie 1
2	0		Ma catégorie 2
...
20	4		Ma sous-catégorie H
Après une requête effectuée par votre application, le serveur vous retourne un objet JavaScript. l'objet Categories représenté ci-dessous est un exemple mais vous pourriez tout aussi bien avoir un Array à la place, question de protocole.

Le but de la manoeuvre est donc de fournir à votre application une fonction capable de traiter cet objet et qui en retourne une vue arborée comme le montre cette image :

http://www.nxstyle.free.fr/strret.gif
Ici, l'objet retourné est de type string



Télécharger l'objet Categories (utf-8)


Comment gagner

1. En écrivant la fonction la plus courte et la plus élégante possible qui ne fait appel qu'à ses propres moyens (variables locales, récursivité, tableau d'arguments possible...)

2. En ne déclarant aucune variable globale si ce n'est l'objet visité, qui est l'unique argument de votre fonction :
function getTree(obj) { ... }

3. En n'opérant aucune modification sur l'objet visité. Donc, pas de drapeau (flag), pas de delete etc...

4. En reunissant la majorité des votants pour votre fonction.


Edit
- Mise en situation ajoutée.
- Fichiers corrigés.

N'hésitez pas à demander plus d'infos ici.
Modifié par Ze Nenex (06 Nov 2007 - 16:48)
J'suis pas sur que ça soit une grande idée d'ouvrir deux topics à concours. Surtout que comme je l'ai écris dans mon topic, il est tout à fait possible de proposer des énigmes Smiley cligne

Sinon, j'ai du mal à saisir la cohérence de ton objet
Mais euh...


var Categories = {
	[...]
	"Ma sous-catégorie A": { id: 6, parentId: 1 },
	"Ma sous-catégorie B": { id: 7, parentId: 1 },
	"Ma sous-catégorie C": { id: 8, parentId: 1 },
	[...]
	"Ma sous-catégorie A": { id: 11, parentId: 3 },
	"Ma sous-catégorie B": { id: 12, parentId: 3 },
	"Ma sous-catégorie C": { id: 13, parentId: 3 },
	[...]
	"Ma sous-sous-sous-catégorie b": { id: 19, parentId: 15 },
	"Ma sous-sous-sous-catégorie b": { id: 20, parentId: 15 },
	[...]
};


Y a pas comme un problème dans cette définition ? Smiley langue

Sinon ça, ça peut marcher :

function classement(obj, parentId) {
		if (parentId==null) parentId = 0;
		var ul = document.createElement("ul");
		var li;
		for (var j in obj) {
			if (obj[j].parentId==parentId) {
				li = document.createElement("li");
				li.innerHTML = j;
				li.appendChild(classement(obj, obj[j].id));
				ul.appendChild(li);
			}
		}
		return ul;
	}

Modifié par Tymlis (06 Nov 2007 - 10:13)
Tymlis a écrit :
Y a pas comme un problème dans cette définition ? Smiley langue
lol... Oui j'ai du allé trop vite. En même je suis au boulot Smiley smile
Merci, c'est corrigé.

QuentinC a écrit :
Heu, tu m'excuses, tu n'as pas un exemple ? Parce que je n'ai rien capté à ce qu'il fallait faire.
La mise en situation devrait t'aider je pense.
Un exemple,
et dans le même temps une proposition:

function getTree(obj) {
	var s = '', o = arguments[1] || obj;
	for(var i in obj) {
		if(s.indexOf(obj[ i ].name) >= 0) return s;
		s += obj[ i ].name + '<br />' + getTree((function(id, o) {
			var a = {};
			for(var i in o)
				if(o[ i ].parentId == id) a[ i ] = o[ i ];
			return a;
		})(obj[ i ].id, o), o);
	}
	return s;
}
Variation sur ce que je proposais avant, réponse sous forme de string au lieu d'HTMLElement cette fois :


function getTree(obj, parentId, indent) {
		parentId = (parentId==null) ? 0 : parentId;
		indent = (indent==null) ? "" : indent;
		var tree = "";
		for (var j in obj) {
			if (obj[j].parentId==parentId) {
				tree+=indent+obj[j].name+"\n"+getTree(obj, obj[j].id, indent+"\t");
			}
		}
		return tree;
	}	


Par contre, je ne sais pas si j'ai le droit de proposer 3 arguments à la fonction, l'intitulé me parait pas clair à ce niveau Smiley cligne
Modifié par Tymlis (06 Nov 2007 - 14:35)
Tymlis a écrit :
Par contre, je ne sais pas si j'ai le droit de proposer 3 arguments à la fonction, l'intitulé me parait pas clair à ce niveau Smiley cligne
Je viens d'éditer le point numéro 2, car effectivement c'est 1 argument seulement: l'objet à visiter.

En ce qui concerne la fonction, j'avoue être arrivé au même point que toi. Je sèche un peu:
function getTree(obj) {
	var finalStr = '', currentId = arguments[1] || 0;
	for(var key in obj) {
		if(finalStr.indexOf(obj[ key ].name) >= 0) return finalStr;
		if(obj[ key ].parentId == currentId)
			finalStr += obj[ key ].name + '<br />' + getTree(obj, obj[ key ].id);
	}
	return finalStr;
}
avec un objet :
function getTree(o) {
	var r = {}, i = arguments[1] || 0;
	for(var k in o)
		if(o[k].parentId == i)
			r[k] = getTree(o, o[k].id);
	return r;
}
J'espere que Shinuza va nous revenir avec un truc en une ligne Smiley lol
Ma proposition n'est pas très courte, mais au moins elle est efficace. Je renvoie un objet DOM.


function getTree () {
if (arguments.length<1) return null;
var obj = arguments[0];
var parentId = (arguments.length>1? arguments[1] : 0);

var ul = document.createElement('ul');
var count = 0;
for (var j in obj) {
var name = obj[j].name;
var id = obj[j].id;
var parent = obj[j].parentId;

if (parent == parentId) {
count++;
var li = document.createElement('li');
var text = document.createTextNode(name);
li.appendChild(text);
var subul = getTree(obj, id);
if (subul!=null) {
var link = document.createElement('a');
link.onclick = function () {
var o = this.parentNode.getElementsByTagName('ul')[0];
o.style.display = (o.style.display=="none"? "block":"none");
this.firstChild.nodeValue = (o.style.display=="none"? "+":"-");
}
link.appendChild(document.createTextNode("-"));
li.insertBefore(link, text);
li.appendChild(subul);
}
ul.appendChild(li);
}

}
return (count>0? ul : null);
}
QuentinC a écrit :
Ma proposition n'est pas très courte
C'est là que je me dis qu'il faudrait crée une classe façade pour le traitement d'objets DOM. Parce qu'à chaque fois qu'on les manipule on se retrouves avec 3 tonnes de codes...

Sinon ta fonction marche nickel Smiley cligne
a écrit :
C'est là que je me dis qu'il faudrait crée une classe façade pour le traitement d'objets DOM. Parce qu'à chaque fois qu'on les manipule on se retrouves
avec 3 tonnes de codes...

Je ne suis pas sûr que produire une string contenant le HTML équivalent soit réellement plus court.
Oui, on peut effectivement faire le lien en une seule ligne, et l'item de liste aussi... mais c'est pas sûr que l'onclick fonctionne. Et même si l'onclick fonctionne, la solution n'est pas très estétique je trouve.
En fait, je ne me voyais pas utiliser un string pour remplacer un objet DOM. Par contre, j'évoquais la possibilité de raccourcir en quelque sorte les méthodes du DOM. Par exemple, pour la création d'un élément, on pourrait avoir ce genre de fonction façade:

function createElement(tag, attrs) {
	var el = document.createElement(tag), i = '', strStyles = '';
	if(attrs) {
		for(i in attrs) switch(i) {
			case 'class':
				el.setAttribute(document.all ? 'className': 'class', attrs[ i ]);
				break;
			case 'style':
				el.setAttribute(i, attrs[ i ]);
				if(document.all) el.style.cssText = attrs[ i ];
				break;
			default:
				el.setAttribute(i, attrs[ i ]);
		}
	}
	return el;
}

document.body.appendChild(createElement(
	'div', {
		'id': 'blop',
		'class': 'reblop',
		'style': 'border: 1px solid red; height: 50px;'
	}
));
Par exemple.

*Edit: mieux, on pourrait avoir une fonction qui traite une chaine telle que :
var mystr = '<div id="blop" class="reblop" style="..."><span>du texte</span></div>';
createElement(mystr);
On pourrait même en faire un petit défi Smiley cligne
Modifié par Ze Nenex (08 Nov 2007 - 10:21)
Salut,
Ze Nenex a écrit :
Edit: mieux, on pourrait avoir une fonction qui traite une chaine telle que :
var mystr = '<div id="blop" class="reblop" style="..."><span>du texte</span></div>';
createElement(mystr);

Je ne vois pas vraiment l'intérêt. Autant utiliser innerHTML dans ce cas. Ce n'est pas la peine de réécrire un parseur qui existe déjà dans le navigateur.
Julien Royer a écrit :
Je ne vois pas vraiment l'intérêt. Autant utiliser innerHTML dans ce cas. Ce n'est pas la peine de réécrire un parseur qui existe déjà dans le navigateur.
Sauf quand on m'envoie une application à raffraichir (et non reécrire) et que celle-ci contient une somme phénoménale de traitements à l'aide d'innerHTML, empêchant ansi toute manipulation programmatique (en générale la raison qui fait que cette application me parvient, d'ailleurs). Alors, disposer d'une telle fonction à un interêt: on ne touche pas au code propriétaire qui de toute façon ne vous paie pas pour ça. C'est en quelque sorte un adaptateur...
Modifié par Ze Nenex (08 Nov 2007 - 10:37)
Ze Nenex a écrit :
Sauf quand on m'envoie une application à raffraichir (et non reécrire) et que celle-ci contient une somme phénoménale de traitements à l'aide d'innerHTML, empêchant ansi toute manipulation programmatique (en générale la raison qui fait que cette application me parvient, d'ailleurs). Alors, disposer d'une telle fonction à un interêt: on ne touche pas au code propriétaire qui de toute façon ne vous paie pas pour ça. C'est en quelque sorte un adaptateur...

Je n'ai pas saisi...
a écrit :
Je ne vois pas vraiment l'intérêt. Autant utiliser innerHTML dans ce cas. Ce n'est pas la peine de réécrire un parseur qui existe déjà dans le navigateur.

+1. Mais dans le cadre d'un défi conçu par des geek pour des geek pourquoi pas