Forums : Xul, Xbl, JS...

Aller à la discussion :  Plus récente Plus ancienne

# Petit exemple de code pour tree hiéarachique ?

Envoyé par : Christophe Charron

Date : 27/04/2006 20:57

Bonsoir, ayant lu attentivement les dernières discussions sur la représentation arborescente de données venant d'un fichier RDF résultant d'une requête sur une base Mysql, j'en déduis que je suis particulièrement "pas bon" car je n'arrive pas à cette représentation. De ce que j'ai lu, la source rdf ne dois pas être particulièrement formée, le template pas particulièrement construit ou paramétré, donc je quémande un petit exemple codé pour déterminer si je dois immédiatement arrêter la programmation ou si je dois garder espoir ...

Merci de m'éclairer.

Cordialement, Christophe Charron

# Re: Petit exemple de code pour tree hiéarachique ?

Envoyé par : Utilisateur anonyme

Date : 28/04/2006 10:15

Je suis aussi intéressé par ce sujet. En consulaant xulfr ou xulplanet on n'a que de vagues informations pour Expert et non pas pour des primates débutants. Alban.

# Re: Petit exemple de code pour tree hiéarachique ?

Envoyé par : Eric

Date : 29/04/2006 14:40

Bonjour,

J'ai un exemple qui commence à marcher pas mal mais qui n'est pas encore parfait.

Il se base sur les principes suivants :

1) On a une datasource en mémoire qui reprend les entités à afficher.

2) Pour chaque entité qui doit être affichée, une structure doit être créée qui a comme type :

item {
    uri    : null,
    index  : null,
    chld   : new Array(),
    isOpen : null,
    isCntn : null,
    level  : null,
    parent : null,
    offSet : null 
}

uri : est égal à l'uri de la ressource dans la datasource. Il permettra de la retrouver et d'afficher les bons libellés dans les colonnes correspondantes.

index : est l'indice de la structure dans un tableau (Array) qui reprend tous les éléments à afficher, dans l'ordre où ils doivent être présentés. Ce tableau est un tableau d'indice absolu (variable abslList dans l'exemple). Il ne tient pas compte de la structure hiérarchique.

chld : est un tableau ordonné qui reprend toutes les structures correspondant à des entités qui sont directement filles de l'entité représentée par la structure item.

isCntn : est un boléen qui indique si l'élément est un conténaire.

isOpen : est un boléen qui indique si le conténaire est ouvert.

level : indique le niveau d'indentation dans l'arborescence. Il est incrémenté de 1 chaque fois qu'un élément a des fils.

parent : est l'indice dans la liste d'incices absolus du père de l'élément. Il est null si l'élément n'a pas de père.

offset : est une variable qui indique à tout moment la différence entre l'indice absolu de l'item dans le tableau abslList et l'indice variable de la ligne de l'arborescence qui lui correspond.

Donc, pour présenter quelque chose du style

item 0
    item 0.1
    item 0.2

on devra créé 3 strucutre item

item_0 = {
    uri    : "urn:item:0",
    index  : 0,
    chld   : new Array(),
    isOpen : true,
    isCntn : true,
    level  : 0,
    parent : null,
    offSet : 0 
}

item_0_1 = {
    uri    : "urn:item:0:1",
    index  : 1,
    chld   : new Array(),
    isOpen : false,
    isCntn : false,
    level  : 1,
    parent : 0,
    offSet : 0 
}

item_0_2 = {
    uri    : "urn:item:0:2",
    index  : 2,
    chld   : new Array(),
    isOpen : false,
    isCntn : false,
    level  : 1,
    parent : 0,
    offSet : 0 
}

organisées comme suit dans la liste d'indice absolus :

var abslList = new Array();
abslList[0] = item_0;
abslList[1] = item_0_1;
abslList[2] = item_0_2;

et comme suit entre elles :

item_0.chld[0] = item_0_1;
item_0.chld[1] = item_0_2;

Fonctionnement :

La fonction intl créée une structure de test du style de celle décrite ci-avant.

Les indexes des lignes affichées dans un tree changent dès qu'un élément contenant des fils est ouvert ou fermé. Ce changement est répercuté dans les structures en faisant varier leur offset. Ce sont les fonctions expand et collapse qui s'en chargent. Ells sont appelées au départ de la fonction toggleOpenState.

La fonction getItemAtIndex est chargée de récupérer un élément dans la liste abslList sur base de l'indice de la ligne de l'arborescence qui lui correspond.

Les fonctions getCellText et getCellValue reçoivent l'indice de la ligne de l'arborescence en paramètre. Elles font appel à la fonction getItemAtIndex pour retrouver la structure correspondante. Une fois cette structure trouvée, on peut appeler les fonctionnalités des datasources pour retrouver sur base de l'uri les informations que l'on veut afficher dans chaque colonnes.

Problèmes qui restent à résoudre :

Quand on sélectionne un élément puis qu'on ouvre ou ferme son père, on a parfois un plantage. Je pense qu'il faut faire quelque chose au niveau de la sélection avant d'ouvrir ou fermer un élément (toute idée est la bienvenue).

Manquements :

Je dois encore ajouter les fonctions insertItemAfter(index) et removeItem()

Voici le code XUL :

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window width="500" height="500" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
       onload="intl();">
 
 <script type="application/x-javascript" src="chrome://test/test_view.js"/>
 
 <tree id="treeTest"
       class="plain focusring"
       seltype="single"
       selstyle="primary"
       flex="1">
   <treecols>
     <treecol id="col_0"
              label="Col_0"
              primary="true"
              flex="1"/>
     <splitter class="tree-splitter"/>
     <treecol id="col_1"
              label="Col_1"        
              flex="1"/>
     <splitter class="tree-splitter"/>
     <treecol id="col_2"
              label="Col_2"
              flex="1"/>
   </treecols>
   <treechildren/>
 </tree>
 <button label="Dump abslList"
         oncommand="dumpAbslList();"/>  
</window>

et le JS :

var gView;

function nsTreeView(){
}

nsTreeView.prototype = {

/****************************** View general methods *******************************/
selection : null,
rowCount  : null,
treebox   : null,
widget    : null,

getRowCount : function(){
	return this.abslList.length;
},

getRowProperties : function getRowProperties(index, prop){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".getRowProperties(aRowIndx, aClmnId)\n" );
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".getRowProperties(aRowIndx, aClmnId)\n" );
},
 
getCellProperties : function getCellProperties(index, column, prop){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".getCellProperties(aRowIndx, aClmnId, aPrprList)\n" );
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".getCellProperties(aRowIndx, aClmnId, aPrprList)\n" );
},
 
getColumnProperties : function getColumnProperties(column, elem, prop){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".getColumnProperties(aClmnId, aPrprList)\n" );
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".getColumnProperties(aClmnId, aPrprList)\n" );
},
 
isContainer : function isContainer(index){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".isContainer(aRowIndx)\n" );
	var bool;
	var item = this.getItemAtIndex(index);
	if(item !== null){
		bool = item.isCntn;
	}else{
		throw 0x80004003; // Components.results.NS_ERROR_NULL_POINTER;
	}
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".isContainer(aRowIndx)\n" );
 		return bool;
},
 
isContainerOpen : function isContainerOpen(index){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".isContainerOpen(aRowIndx)\n" );
 		var bool;
	var item = this.getItemAtIndex(index);
	if(item !== null){
		bool = item.isOpen;
	}else{
		throw 0x80004003; // Components.results.NS_ERROR_NULL_POINTER;
	}
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".isContainerOpen(aRowIndx)\n" );
 		return bool;
},
 
isContainerEmpty: function isContainerEmpty(index){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".isContainerEmpty(aRowIndx)\n" );
	var bool;
	var item = this.getItemAtIndex(index);
	if(item !== null){
		if(item.chld.length > 0){
			bool = false;
		}else{
			bool = true;
		}
	}else{
		throw 0x80004003; // Components.results.NS_ERROR_NULL_POINTER;
	}
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".isContainerEmpty(aRowIndx)\n" );
 		return bool;
},
  
isSeparator : function isSeparator(index){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".isSeparator(aRowIndx)\n" );
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".isSeparator(aRowIndx)\n" );
	return false;
},
 
isSorted : function isSorted(){
	return false;
},
/*  
// did not implemented yet!
canDropOn : function canDropOn(index){
	dump( "ENTER FUNCTION ----> " + this.toString() + ".canDrop(aIndx, aOrnt)\n" );
	dump( "EXIT FUNCTION  <---- " + this.toString() + ".canDrop(aIndx, aOrnt)\n" );
	return false;
},
 
canDropBeforeAfter : function canDropBeforeAfter(index, before){
	return false;
},
*/ 
drop : function drop(index, orientation){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".drop(index, orientation)\n" );
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".drop(index, orientation)\n" );
},
 
getParentIndex: function getParentIndex(index){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".getParentIndex(index)\n" );
	var prnt = null;
	var item = this.getItemAtIndex(index);
	if(item !== null){
		var prntItem = this.abslList[item.parent];
		if(prntItem !== null){
			prnt = item.parent - item.offSet;
		}else{
			throw 0x80004003; // Components.results.NS_ERROR_NULL_POINTER;
		}
	}else{
		throw 0x80004003; // Components.results.NS_ERROR_NULL_POINTER;
	}
//		dump( "getParentIndex(" + index + ") == " + prnt + "\n" );
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".getParentIndex(index)\n" );
	return prnt;
},
 
//HasNextSibling is used to determine if the row at rowIndex has a nextSibling
//that occurs *after* the index specified by afterIndex.
hasNextSibling: function hasNextSibling(index, after){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".hasNextSibling(index, after)\n" );
	var bool = false;
		
	var item = this.getItemAtIndex(index);
	if(item !== null){
		if(item.parent !== null){
			var afterItem = this.getItemAtIndex(after);
			var prntItem  = this.abslList[item.parent];
			if(afterItem !== null && prntItem !== null){
				var lastSibling = prntItem.chld[prntItem.chld.length - 1];
				if(lastSibling.index > afterItem.index){
					bool = true;
				} 
			}
		}
	}else{
		throw 0x80004003; // Components.results.NS_ERROR_NULL_POINTER;
	}
	//dump( "hasNextSibling(" + index + "," + after + ")\t== " + bool + "\n" );		
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".hasNextSibling(index, after)\n" );
	return bool;
},
 
getLevel: function getLevel(index) {
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".getLevel(index)\n" );
	var lvl;
	var item = this.getItemAtIndex(index);
	if(item !== null){
		lvl = item.level;
	}else{
		throw 0x80004003; // Components.results.NS_ERROR_NULL_POINTER;
	}
//		dump( "EXIT FUNCTION <---- " + this.toString() + ".getLevel(index)\n" );
	return lvl;
},
 
getImageSrc: function getImageSrc(index, column){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".getImageSrc(aRowIndx, aClmnId)\n" );
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".getImageSrc(aRowIndx, aClmnId)\n" );
},
 
getProgressMode : function getProgressMode(index,column){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".getProgressMode(aRowIndx, aClmnId)\n" );
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".getProgressMode(aRowIndx, aClmnId)\n" );
},
 
getCellValue: function getCellValue(index, column){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".getCellValue(aRowIndx, aClmnId)\n" );
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".getCellValue(aRowIndx, aClmnId)\n" );
},
 
getCellText: function getCellText(index, column){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".getCellText(aRowIndx, aClmnId)\n" );
	var val = null;
	if(column == "col_0"){
		var item = this.getItemAtIndex(index);
		if(item !== null){
			val = item.uri;
		}
	}
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".getCellText(aRowIndx, aClmnId)\n" );
	return val;
},
  
setTree: function setTree(treeBox){
//	  	dump( "ENTER FUNCTION ----> " + this.toString() + ".setCellValue(aTree)\n" );
  	this.treeBox = treeBox;
  	if (!treeBox){
  		this.selection = null;
  	}
//	  	dump( "EXIT FUNCTION  <---- " + this.toString() + ".setCellValue(aTree)\n" );
},
 
cycleHeader : function cycleHeader(col, elem){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".cycleHeader(aClmnId)\n" );
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".cycleHeader(aClmnId)\n" );
},
   
selectionChanged : function selectionChanged(){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".selectionChanged()\n" );
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".selectionChanged()\n" );
},
 
cycleCell: function cycleCell(index, column){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".cycleCell(aRowIndx, aClmnId)\n" );
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".cycleCell(aRowIndx, aClmnId)\n" );
},
 
isEditable: function isEditable(index, column){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".isEditable(aRowIndx, aClmnId)\n" );
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".isEditable(aRowIndx, aClmnId)\n" );
 		return false;
 	},
 
performAction: function performAction(action){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".performAction(aActn)\n" );
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".performAction(aActn)\n" );
},
 
performActionOnCell: function performActionOnCell(action, index, column){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".performActionOnCell(aActn, aRowIndx, aClmnId)\n" );
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".performActionOnCell(aActn, aRowIndx, aClmnId)\n" );
},
 
toggleOpenState: function toggleOpenState(index){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".toggleOpenState(index)\n" );
	var item = this.getItemAtIndex(index);	
	if(item !== null){
		if(item.isOpen === true){
			this.collapse(index, item);
		}else{
			this.expand(index, item);
		}
		item.isOpen = !item.isOpen;
	}else{
		throw 0x80004003; // Components.results.NS_ERROR_NULL_POINTER;
	}
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".toggleOpenState(index)\n" );
},
	
toString : function(){
	return "[TEST View]";
},
 
/****************************** View specific methods *******************************/
abslList : null,
	
intl : function(aAbslList){
	this.abslList = aAbslList;
	this.rowCount = aAbslList.length;
},
	
getItemAtIndex : function(aIndex){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".getItemAtIndex(aIndex)\n" );
	var item = null;
		
	if(aIndex < this.abslList.length){
		var indx;
		var offSet = 0;
		
		item = this.abslList[aIndex];
		while((item.offSet - offSet) !== 0){
			offSet = item.offSet;
			indx   = aIndex + item.offSet;
			item   = this.abslList[indx];
		}
	}		
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".getItemAtIndex(aIndex)\n" );
	return item;
},
	
countCollapsedRows : function(aItem){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".countCollapsedRows(aItem)\n" );

	var chldNmbr = 0;
	if(aItem.isCntn === true && aItem.isOpen === true){
		for(var i = 0; i < aItem.chld.length; ++i){
			++chldNmbr;
			chldNmbr += this.countCollapsedRows(aItem.chld[i]);
		}
	}
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".countCollapsedRows(aItem)\n" );
	return chldNmbr;	
},
	
collapse : function(aIndex, aItem){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".collapse(aIndex, aItem)\n" );
		var indx;
		
		var offSet = this.countCollapsedRows(aItem);
		if(offSet > 0){
			indx = aIndex + aItem.offSet + 1;			
			for(var i = indx; i < this.abslList.length; ++i){
				this.abslList[i].offSet += offSet;
			}

			this.treeBox.rowCountChanged(aIndex, -offSet);
			//this.rowCount -= offSet;
				
		}
			
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".collapse(aIndex, aItem)\n" );			
},

countExpandedRows : function(aItem){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".countExpandedRows(aItem)\n" );

	var chldNmbr = 0;
	for(var i = 0; i < aItem.chld.length; ++i){
		++chldNmbr;
		if(aItem.chld[i].isCntn === true && aItem.chld[i].isOpen === true){ 
			chldNmbr += this.countExpandedRows(aItem.chld[i]);
		}
	}
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".countExpandedRows(aItem)\n" );
	return chldNmbr;	
},
	
expand : function(aIndex, aItem){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".expand(aIndex, aItem)\n" );
		var indx;
		
		var offSet = this.countExpandedRows(aItem);
		if(offSet > 0){
			indx = aIndex + aItem.offSet + 1;			
			for(var i = indx; i < this.abslList.length; ++i){
				this.abslList[i].offSet -= offSet;
			}

			this.treeBox.rowCountChanged(aIndex, offSet);
			//this.rowCount -= offSet;
		}

//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".expand(aIndex, aItem)\n" );	
},
	
dumpAbslList : function(){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".dumpAbslList()\n" );
    
    dump("\n\n");
	    
    for(var i = 0; i < this.abslList.length; ++i){
		dump( i                                  + "\t" +
		      "uri    = " + this.abslList[i].uri    + "\t" +
		      "offset = " + this.abslList[i].offSet + "\t" +
		      "isOpen = " + this.abslList[i].isOpen + "\t" +
		      "parent = " + this.abslList[i].parent + "\n");
	}
		
    dump("\n\n");		
	
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".dumpAbslList()\n" );			
}
	
};

function dumpAbslList(){
//		dump( "ENTER FUNCTION ----> " + this.toString() + ".dumpAbslList()\n" );
gView.dumpAbslList();
//		dump( "EXIT FUNCTION  <---- " + this.toString() + ".dumpAbslList()\n" );
}


function intl() {
dump( "ENTER FUNCTION ----> intl()\n" );

var k = 0;
var rltvList = new Array();
var abslList = new Array();

var prntLvl_1 = 0;	
var prntLvl_2;

for(var i = 0; i < 3; ++i){
	rltvList[i] = {
		uri    : ("item " + i ),
		index  : k,
		chld   : new Array(),
		isOpen : true,
		isCntn : true,
		level  : 0,
		parent : null,
		offSet : 0 
	}
	
	abslList[k] = rltvList[i];
	prntLvl_1 = k;
	++k;
		
	for(var j = 0; j < 3; ++j){
		rltvList[i].chld[j] = {
			uri    : ("item " + i + "." + j),
			index  : k,
			chld   : new Array(),
			isOpen : true,
			isCntn : true,
			level  : 1,
			parent : prntLvl_1,				
			offSet : 0 			
		}
		
		abslList[k] = rltvList[i].chld[j];
	    prntLvl_2 = k;
		++k;

		for(var h = 0; h < 3; ++h){
			rltvList[i].chld[j].chld[h] = {
				uri    : ("item " + i + "." + j + "." + h),
				index  : k,
				chld   : new Array(),
				isOpen : false,
				isCntn : false,
				level  : 2,
				parent : prntLvl_2,				
				offSet : 0 			
			}
				
			abslList[k] = rltvList[i].chld[j].chld[h];
			++k;
		}	
	}					
}
	
gView = new nsTreeView();
gView.intl(abslList);
	
var tree = document.getElementById("treeTest");
tree.view = gView;
	
dump( "EXIT FUNCTION  <---- intl()\n" );
}

# Re: Petit exemple de code pour tree hiéarachique ?

Envoyé par : Christophe Charron

Date : 29/04/2006 20:44

Bonsoir, et merci d'avoir pris la peine de répondre. Je n'imaginais pas que ce fut si "compliqué" ni qu'il faille créer de toute pièce l'arbre. Je vais étudier ce code. Et heureursement que j'avais demandé un "petit" exemple ...

Merci encore.

Cordialement, Christophe Charron

# Re: Petit exemple de code pour tree hiéarachique ?

Envoyé par : laurentj

Date : 02/05/2006 10:31

Bon, c'est sympa ces longs exemples, mais c'est plutôt lourd à lire dans un forum. Et pire, c'est une info interressante qui va vite être oublié. Ce genre d'exemple bien détaillé, c'est mieux de les mettre dans le wiki.

Donc Eric, je te somme de mettre ton exemple dans le wiki en créant cette page : /wiki/RessourcesLibs/TreeViewComplet

(oui, c'est un ordre ;-)

# Re: Petit exemple de code pour tree hiéarachique ?

Envoyé par : laurentj

Date : 03/05/2006 12:58

Merci Eric pour ta contribution au wiki. Par contre, y a eu un souci technique : phpwiki a eu du mal a parser un script aussi long, et causait donc une erreur. J'ai donc mis le source du js dans un fichier à part, téléchargeable.

À propos d'arbre hierarchique, j'ai fait aussi une solution analogue à la tienne dans mon éditeur xml wysiwyg, Etna. Faudrait que je la publie aussi dans le wiki.. :-)

# Re: Petit exemple de code pour tree hiéarachique ?

Envoyé par : Eric

Date : 03/05/2006 14:46

Oui, Laurent j'ai vu.

La différence avec ta solution réside pour moi dans le fait qu'un élément n'a pas besoin de savoir s'il a un nextSibling dès sa construction. Si non, pour le reste, on a sensiblement la même approche.

Il reste cependant un problème à résoudre dans ma solution : quand on sélectionne une ligne avant d'y appliquer un collpase, l'application ne répond plus.

De plus, j'ai trouvé ce lien intéressant http://www.xulplanet.com/tutorials/xulqa(..)

Je continue à chercher pour les problèmes de sélection, mai il y plus dans deux têtes que dans une ... :)

# Re: Petit exemple de code pour tree hiéarachique ?

Envoyé par : Eric

Date : 03/05/2006 18:09

Ca y est, j'ai trouvé d'ou provenait l'erreur lors de la sélection.

Laurent, peux-tu m'indiquer une procédure pour modifier le js publié.

Merci

# Re: Petit exemple de code pour tree hiéarachique ?

Envoyé par : laurentj

Date : 03/05/2006 22:12

envoi moi ton fichier laurent at xulfr.org

Au fait, conçernant la page xulplanet, tu as la même chose sur xulfr, et traduit ;-)

http://xulfr.org/xulplanet/xulqa/q_treebview.html

Il n'est plus possible de poster des messages dans ce forum.


Copyright © 2003-2013 association xulfr, 2013-2016 Laurent Jouanneau - Informations légales.

Mozilla® est une marque déposée de la fondation Mozilla.
Mozilla.org™, Firefox™, Thunderbird™, Mozilla Suite™ et XUL™ sont des marques de la fondation Mozilla.