PDA

View Full Version : TreeLoader extension to update a TreePanel



mabello
15 Feb 2008, 7:42 AM
I keep updating the extension in this first post

Extension goal:
The main goal of the extension is to extend the Ext.tree.TreeLoader so that you can update a TreePanel by manipulating normal "data object" that you can have loaded by an ajax call or in a different way. So you do not have to create an ad-hoc data to populate a TreePanel; it also help to understand better the public interface that a TreeLoader need to implement to be intagrated in the framework. (Attachment with code and examples at the bottom.)

Development history:

************** (24 April 2008)
* Version 0.2.3 *
**************
===== Changed =====

Ext.Tree.MyTreeLoader
-Internal refactoring

===== Changed =====

****************


**************
* Version 0.2.2 *
**************

===== Added =====

Ext.tree.TreeNodeProvider class
-Added new class Ext.tree.TreeNodeProvider as a better alternative to the prevoius treeNodeProvider object

===== Added =====

===== Fixed =====

Ext.Tree.MyTreeLoader
-Fixed load event, now it is correctly fired (if loadexception is not fired and beforeload does not return false of course, following the standard TreeLoader behaviour), thanks to tford for pointing out the bug

===== Fixed =====

****************


**************
* Version 0.2.1 *
**************

===== Fixed =====
Ext.Tree.MyTreeLoader
-Fixed load function, now call node.destroy(), thanks to mykes for his/her kind help, more info here http://extjs.com/forum/showthread.php?t=14993
===== Fixed =====

****************



**************
* Version 0.2 *
************

===== Added =====

Ext.Tree.MyTreeLoader
-Added getTreeNodeProvider: function()-> get the treeNodeProvider
-Added setTreeNodeProvider: function(newTreeNodeProvider)-> set a new treeNodeProvider

===== Added =====

****************


************
* Version 0.1 *
************

Ext.Tree.MyTreeLoader
-Initial release

****************


Tutorial end previous explanations

Version 0.2.2 (examples are based on version 0.2.1)
----------------------------------------------------------------------------------
FIX: load event is correctly fired (if loadexception is not fired and beforeload does not return false of course, following the standard TreeLoader behaviour), thanks to tford for pointing out the bug
ADDED: new class Ext.tree.TreeNodeProvider as a better alternative to the prevoius treeNodeProvider object.
You need to pass to the constructor the following configuration object:


cfg = {
data: object //@optional, to preload data inside your TreeNodeProvider
getNodes: function(dataToElaborate) //@mandatory
}

Example of use:


var treeNodeProvider = new Ext.tree.TreeNodeProvider({

getNodes: function(dataToElaborate) { //Here you process your data
var nodeArray = [];//The tree structure used to refresh the TreePanel
for(var i = 0; i < dataToElaborate.length; i++){
var folder = dataToElaborate[i];
var filesInFolder = folder.files;
//Create the parent node
var node = {
text: folder.name,
leaf: false,
children: []
}
//Create the children node
for(var j = 0; j < filesInFolder.length; j++) {
var fileName = filesInFolder[j];
var childNode = {
text: fileName,
leaf: true
}
//Set the children to the parent node
node.children.push(childNode);
}
//Add the parent node to the nodeArray
nodeArray.push(node);
}
//throw 'exception';
//return the tree structure here
return nodeArray;
}
});


----------------------------------------------------------------------------------
Version 0.2.1
----------------------------------------------------------------------------------
FIX: load function now call node.destroy(), thanks to mykes for his/her kind help, more info here http://extjs.com/forum/showthread.php?t=14993

----------------------------------------------------------------------------------
Version 0.2
----------------------------------------------------------------------------------
Added getTreeNodeProvider: function()-> get the treeNodeProvider
Added setTreeNodeProvider: function(newTreeNodeProvider)-> set a new treeNodeProvider
----------------------------------------------------------------------------------
Version 0.1
-------------
-------------

The main goal of the extension is to extend the Ext.tree.TreeLoader so that you can update a TreePanel by manipulating normal "data object" that you can have loaded by an ajax call or in a different way (for example if you want to create a menu with the TreePanel, you can "hardcode" the data in your javascript and display them in the TreePanel without any server side call)

So for example, you can update a treeView manipulating an object like this one:


var treeData1 = [
{
name: "Folder1",
files:['File1', 'File2', 'File3']
},{
name: "Folder2",
files:['Picture1', 'Picture2']
}
];


The core part you are interested in is actually the following part



//Create your treeNodeProvider; it is an object that must implement these 2 methods (must "honour" this interface methods) :
// setData(value)
//getNodes(), returning the right structure that the treePanel can elaborate to refresh itself
var treeNodeProvider = {
data: [],//Property in which are set the data to elaborate

getNodes: function() { //Here you process your data

var nodeArray = [];//The tree structure used to refresh the TreePanel
for(var i = 0; i < this.data.length; i++){
var folder = this.data[i];
var filesInFolder = folder.files;
//Create the parent node
var node = {
text: folder.name,
leaf: false,
children: []
}
//Create the children node
for(var j = 0; j < filesInFolder.length; j++) {
var fileName = filesInFolder[j];
var childNode = {
text: fileName,
leaf: true
}
//Set the children to the parent node
node.children.push(childNode);
}
//Add the parent node to the nodeArray
nodeArray.push(node);
}
//return the tree structure here
return nodeArray;
},
setData: function(data) {//Called internally by Ext.tree.MyTreeLoader by the method updateTreeNodeProvider
this.data = data;
},
scope: this//Could be useful to use when you elaborates data to switch the context...not used in this example and it's not required
};


Every time you click to the refresh button in the example, I simulate to load different data, in our case the data we want to elaborate is a structure like the following:



//Data to load in the tree
var treeData1 = [
{
name: "Folder1",
files:['File1', 'File2', 'File3']
},{
name: "Folder2",
files:['Picture1', 'Picture2']
}
];
//Data to load in the tree
var treeData2 = [
{
name: "Pictures",
files:['Doc1.txt', 'Doc2.txt']
},{
name: "Documents",
files:['xxx.txt', 'yyy.txt']
}
];


In this case, at the beginning the tree is empty, but you can preload the data in the treeLoader if you remove the comment of this line in the example.



myTreeLoader.updateTreeNodeProvider(treeData1);


The rendering part is the same of a normal TreePanel, but the way you load the data is different and the work to refresh the tree is this:



var rootNode = treePanel.getRootNode();//get the root node
var loader = treePanel.getLoader();//Get the loader, note that is of type MyTreeLoader
loader.updateTreeNodeProvider(treeData2);//update the treeNodeProvider associated to the loder, but you DON't reload the TreePanel yet!!!
loader.load(rootNode);//Actually refresh the TreePanel UI


In this way you can load the data in the way you prefer and elaborate the data in getNodes function of the treeNodeProvider;
the data can be in the format you like but in getNodes method you need to return a structure that the treePanel can udertend to render itself.

Please feel free to contact me if something is not clear and give me suggestions and feedback so I can improve the code and the example.

I have found it very usefull in my application because you can load the data in the way you want (ajax call or in other way) and then elaborate the data in getNodes.

NEW EXAMPLE 2:

Now, I have updated the extension with these two methods:


getTreeNodeProvider: function(){
return this.treeNodeProvider;
},

setTreeNodeProvider: function(newTreeNodeProvider) {
if(newTreeNodeProvider == null || (typeof newTreeNodeProvider =='undefined'))
throw 'setTreeNodeProvider, newTreeNodeProvider == null || (typeof newTreeNodeProvider == undefined)';
this.treeNodeProvider = newTreeNodeProvider;
}


Now, I have created an example in which I'm simulating some ajax calls and I'm actually using two different treeNodeProvider to reload my treePanel in different way (I really like it).

So here we are, first treeNodeProvider:


var treeNodeProvider = {
data: [],//Property in which are set the data to elaborate

getNodes: function() { //Here you process your data

var nodeArray = [];//The tree structure used to refresh the TreePanel
for(var i = 0; i < this.data.length; i++){
var folder = this.data[i];
var filesInFolder = folder.files;
//Create the parent node
var node = {
text: folder.name,
leaf: false,
children: []
}
//Create the children node
for(var j = 0; j < filesInFolder.length; j++) {
var fileName = filesInFolder[j];
var childNode = {
text: fileName,
leaf: true
}
//Set the children to the parent node
node.children.push(childNode);
}
//Add the parent node to the nodeArray
nodeArray.push(node);
}
//return the tree structure here
return nodeArray;
},
setData: function(data) {//Called internally by Ext.tree.MyTreeLoader by the method updateTreeNodeProvider
this.data = data;
},
scope: this//Could be useful to use when you elaborates data to switch the context...not used in this example and it's not required
};


The second treeNodeProvider


var treeNodeProvider2 = {
data: [],//Property in which are set the data to elaborate

getNodes: function() { //Here you process your data
return Ext.decode(this.data);
},
setData: function(data) {//Called internally by Ext.tree.MyTreeLoader by the method updateTreeNodeProvider
this.data = data;
},
scope: this//Could be useful to use when you elaborates data to switch the context...not used in this example and it's not required
};


Simulating a first AjaxCall in which I'm using the first treeNodeProvider to refresh the tree with nodes:


var ajaxCallGetDataForTree = function(inputParameters){
treePanel.body.mask("Loading data...");
setTimeout(ajaxCallbackGetDataForTree, 5000);
}

var ajaxCallbackGetDataForTree = function(){
treePanel.body.unmask();
treePanel.body.highlight('#c3daf9', {block:true});
//Simulating that I have received the response that is treeData2 from the callback of the ajaxCall
var rootNode = treePanel.getRootNode();//get the rootnode
var loader = treePanel.getLoader();//Get the loader, note that is of type MyTreeLoader
loader.updateTreeNodeProvider(treeData2);
loader.load(rootNode);
}


The second Ajax call, you probably are interested on this one, because I'm simulating to receive a Json string from the server (in the callback of the ajax call):
The response form the server is the following indeed:



var serverResponseString = '[{"text":"Ray Abad","id":"contacts\/11","cls":"file","leaf":"true","listeners": { "click": function(node, eventObject){ alert(node);}} },{"text":"Kibbles Bits","id":"contacts\/9","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Johnny Bravo","id":"contacts\/18","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Mike Bridge","id":"contacts\/13","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Jane Brown","id":"contacts\/2","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Jim Brown","id":"contacts\/19","cls":"file","leaf":"true","listeners":"userNodeInterceptor"}]';


The ajax call is:


var ajaxCallGetDataForTree2 = function(inputParameters){
treePanel.body.mask("Loading data...");
setTimeout(ajaxCallbackGetDataForTree2, 5000);
}

var ajaxCallbackGetDataForTree2 = function(){
treePanel.body.unmask();
treePanel.body.highlight('#c3daf9', {block:true});
//Simulating that I have received the response that is treeData2 from the callback of the ajaxCall
var rootNode = treePanel.getRootNode();//get the rootnode
var loader = treePanel.getLoader();//Get the loader, note that is of type MyTreeLoader
loader.updateTreeNodeProvider(serverResponseString);
loader.load(rootNode);
}



Now, please take a look at the ttbar items, I'm using the following code:


tbar:[{
text: "Click here to Refresh using treeNodeProvider",
handler: function(){
//Simulating change the treeNodeProvider of the loader
var rootNode = treePanel.getRootNode();//get the rootnode
var loader = treePanel.getLoader();//Get the loader, note that is of type MyTreeLoader
loader.setTreeNodeProvider(treeNodeProvider);
//Simulating an ajax call
ajaxCallGetDataForTree();
}
},{
text: "Click here to Refresh using treeNodeProvider2",
handler: function(){
//Simulating change the treeNodeProvider2 of the loader
var rootNode = treePanel.getRootNode();//get the rootnode
var loader = treePanel.getLoader();//Get the loader, note that is of type MyTreeLoader
loader.setTreeNodeProvider(treeNodeProvider2);
//Simulating a different ajax call
ajaxCallGetDataForTree2();
}
}]

In the handler, before I simulate the ajax call, I set a different treeNodeProvider in the loader:


loader.setTreeNodeProvider(treeNodeProvider2);


Of course, you can do that in the ajaxcall or in the ajaxCallback that could be better I think, but mine is only an example

If you want to preolad your tree, you need to use this line of code before creating the treeLoader (as you can see in the example):


myTreeLoader.updateTreeNodeProvider(treeData1);//if you want to "preload" the TreePanel with this data


Thanks and keep up the good work with Ext!!!

Capi666
6 Mar 2008, 4:41 AM
Hw can render it?

I

Capi666
6 Mar 2008, 8:23 AM
ok, perfect!!!

My code html:



...
...
<div id="container">
<div id="toolbar"></div>
<div id="divtree"></div>
</div>
...
...



And the js:



Ext.tree.MyTreeLoader = function(config){

Ext.apply(this, config);
Ext.tree.MyTreeLoader.superclass.constructor.call(this, config);

};

Ext.tree.MyTreeLoader = Ext.extend(Ext.tree.MyTreeLoader, Ext.tree.TreeLoader, {

treeNodeProvider: null,

loading: false,

load : function(node, callback){
if(this.clearOnLoad){
while(node.firstChild){
node.removeChild(node.firstChild);
}
}
if(this.doPreload(node)){ // preloaded json children
if(typeof callback == "function"){
callback();
}
}else if(this.treeNodeProvider){
this.requestData(node, callback);
}
},

requestData : function(node, callback){
if(this.fireEvent("beforeload", this, node, callback) !== false){
this.loading = true;
var nodesToAdd = this.treeNodeProvider.getNodes();
if(nodesToAdd)
this.processResponse(nodesToAdd, node, callback);
this.loading = false;
} else {
// if the load is cancelled, make sure we notify
// the node that we are done
if(typeof callback == "function"){
callback();
}
}
},

isLoading : function(){
return this.loading;
},

abort : function(){
},

processResponse : function(o, node, callback){
try {
node.beginUpdate();
for(var i = 0, len = o.length; i < len; i++){
var n = this.createNode(o[i]);
if(n){
node.appendChild(n);
}
}
node.endUpdate();
if(typeof callback == "function"){
callback(this, node);
}
}catch(e){
this.fireEvent("loadexception", this, node, e);
if(typeof callback == "function"){
callback(this, node);
}
}
},

updateTreeNodeProvider: function(obj){
if(this.treeNodeProvider)
this.treeNodeProvider.setData(obj);
}
});

Ext.onReady(function(){

var treeNodeProvider = {
data: {},

getNodes: function() {//Usually you will use this.data, which contains the dataObject to create your tree structure
var nodeArray = [];
var NombresArray = ['Nombre Jefe','1','2','3','4','5','6'];

var node = {
text: NombresArray[0],
leaf: false,
children: []
}
for (i=1;i<NombresArray.length; i++){

var childNode = {
text: NombresArray[i],
leaf: true
}

node.children.push(childNode);
}

nodeArray.push(node);

return nodeArray;
},
setData: function(data) {
this.data = data;
},
scope: this
};

var myTreeLoader = new Ext.tree.MyTreeLoader({
treeNodeProvider: treeNodeProvider
});

myTreeLoader.updateTreeNodeProvider({});//Usually you need to pass a dataObject

var basePanelCfg = {
preloadChildren: true,
lines: false,
clearOnLoad: true,
rootVisible: false,
containerScroll: true,
frame: false,
collapsible: false,
animate: true,
loader: myTreeLoader,
};

//var treePanel = new Ext.tree.TreePanel(basePanelCfg);
var treePanel = new Ext.tree.TreePanel(
{
preloadChildren: true,
lines: false,
clearOnLoad: true,
rootVisible: false,
containerScroll: true,
frame: false,
collapsible: false,
animate: true,
loader: myTreeLoader,
});

var root = new Ext.tree.AsyncTreeNode({
text: 'Root',
draggable: false
});

treePanel.setRootNode(root);
treePanel.render('divtree');

});


Lot of thanks,

PD: I add reputation for you;);)

mabello
6 Mar 2008, 8:42 AM
Hey thank you very much, so kind :">

And your example is better than mine, so thanks :)

Only one thing, if you want to run your example in IE6 you need to remove 2 extra comma you have in your code:



var basePanelCfg = {
preloadChildren: true,
lines: false,
clearOnLoad: true,
rootVisible: false,
containerScroll: true,
frame: false,
collapsible: false,
animate: true,
loader: myTreeLoader,<-----------------
};

//var treePanel = new Ext.tree.TreePanel(basePanelCfg);
var treePanel = new Ext.tree.TreePanel(
{
preloadChildren: true,
lines: false,
clearOnLoad: true,
rootVisible: false,
containerScroll: true,
frame: false,
collapsible: false,
animate: true,
loader: myTreeLoader,<----------------
});

Of course FF is Ok, but IE6 will complain...

Thanks again

Capi666
6 Mar 2008, 9:11 AM
Yes, the last commas always deleted it... But, now forget it...

My sample better? I don

Capi666
7 Mar 2008, 2:20 AM
How can expand automatically the tree the first time when acced to the page?

Thanks,

mabello
7 Mar 2008, 2:45 AM
You can use:


treePanel.expandAll();

Of course the tree must be already rendered.

Capi666
7 Mar 2008, 3:46 AM
Yes, lot of thanks!!

Capi666
13 Mar 2008, 7:28 AM
How can know the position that i select?

Thanks,

Capi666
13 Mar 2008, 8:21 AM
Sorry, it

mabello
14 Mar 2008, 4:44 AM
Goal: update the tree with a response coming from the server that is a json string (like for a normal TreeLoader).
Add custom listeners for each node (you could want a different listener foreach node, in this case I've used always the same listener)


Core part:



var serverResponseString = '[{"text":"Ray Abad","id":"contacts\/11","cls":"file","leaf":"true","listeners": { "click": function(node, eventObject){ alert(node);}} },{"text":"Kibbles Bits","id":"contacts\/9","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Johnny Bravo","id":"contacts\/18","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Mike Bridge","id":"contacts\/13","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Jane Brown","id":"contacts\/2","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Jim Brown","id":"contacts\/19","cls":"file","leaf":"true","listeners":"userNodeInterceptor"}]';

var serverResponseObject = Ext.decode(serverResponseString);


The treeLoader is simple like this:


var treeNodeProvider = {
data: [],//Property in which are set the data to elaborate

getNodes: function() { //Here you process your data
return serverResponseObject;//Ext.decode(serverResponseString);
},
setData: function(data) {//Called internally by Ext.tree.MyTreeLoader by the method updateTreeNodeProvider
this.data = data;
},
scope: this//Could be useful to use when you elaborates data to switch the context...not used in this example and it's not required
};

var myTreeLoader = new Ext.tree.MyTreeLoader({
treeNodeProvider: treeNodeProvider
});

And if you want to add different event listeners foreach node, you can use something simple like this:


function updateListeners(serverResponseObject, interceptor){
for(var i = 0; i < serverResponseObject.length; i++){
var node = serverResponseObject[i];
node.listeners = interceptor;
}
}


Complete example


<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Product View</title>

<link rel="stylesheet" type="text/css" href="../Styles/Page.css" />
<link rel="stylesheet" type="text/css" href="../resources2.0.2/css/ext-all.css" />
<link rel="stylesheet" type="text/css" href="../Styles/Action.css" />
<link rel="stylesheet" type="text/css" href="../Styles/FilterPanel.css" />
<link rel="stylesheet" type="text/css" href="../Styles/Tools.css" />
<script type="text/javascript">
//IE Bug Fix (caching of the images, don't flicker please!!!
try {
document.execCommand("BackgroundImageCache", false, true);
}
catch(err) {}
</script>


<script type="text/javascript" src="../scripts/Ext2.0.2/ext-base.js"></script>
<script type="text/javascript" src="../scripts/Ext2.0.2/ext-all-debug.js"></script>
<style type="text/css">
.ext-ie .x-form-item label.x-form-item-label{position:absolute;}
.ext-ie .x-form-label-top .x-form-item label.x-form-item-label{position:static;}
</style>
<%-- favicon --%>
<link rel="shortcut icon" href="~/Images/favicon.ico" />
</head>
<body>
<div id="tree-ct"></div>
</body>
<script type="text/javascript">

Ext.tree.MyTreeLoader = function(config){

Ext.apply(this, config);
Ext.tree.MyTreeLoader.superclass.constructor.call(this, config);

};

Ext.tree.MyTreeLoader = Ext.extend(Ext.tree.MyTreeLoader, Ext.tree.TreeLoader, {

treeNodeProvider: null,

loading: false,

load : function(node, callback){
if(this.clearOnLoad){
while(node.firstChild){
node.removeChild(node.firstChild);
}
}
if(this.doPreload(node)){ // preloaded json children
if(typeof callback == "function"){
callback();
}
}else if(this.treeNodeProvider){
this.requestData(node, callback);
}
},

requestData : function(node, callback){
if(this.fireEvent("beforeload", this, node, callback) !== false){
this.loading = true;
var nodesToAdd = this.treeNodeProvider.getNodes();
if(nodesToAdd)
this.processResponse(nodesToAdd, node, callback);
this.loading = false;
} else {
// if the load is cancelled, make sure we notify
// the node that we are done
if(typeof callback == "function"){
callback();
}
}
},

isLoading : function(){
return this.loading;
},

abort : function(){
},

processResponse : function(o, node, callback){
try {
node.beginUpdate();
for(var i = 0, len = o.length; i < len; i++){
var n = this.createNode(o[i]);
if(n){
node.appendChild(n);
}
}
node.endUpdate();
if(typeof callback == "function"){
callback(this, node);
}
}catch(e){
this.fireEvent("loadexception", this, node, e);
if(typeof callback == "function"){
callback(this, node);
}
}
},

updateTreeNodeProvider: function(obj){
if(this.treeNodeProvider)
this.treeNodeProvider.setData(obj);
}
});

var flag = true;//To toggle the refresh



Ext.onReady(function(){

var userNodeInterceptor = {
click: function(node, eventObject){
var nodeId = node.attributes.id;
alert("Click on node of id->" + nodeId);
},

contextmenu : function(node, e){
if(!this.menu) // create context menu on first right click
{
this.menu = new Ext.menu.Menu({
id:'feeds-ctx',
items: [saveAction]
});
this.menu.on('hide', onContextHide, this);
}
if(this.ctxNode)
{
this.ctxNode.ui.removeClass('x-node-ctx');
this.ctxNode = null;
}
//if(node.isLeaf()){
this.ctxNode = node;
this.ctxNode.ui.addClass('x-node-ctx');
this.ctxNode.select();//Select the node
saveAction.selectedNode = node;
this.menu.showAt(e.getXY());
//}
}
};

var serverResponseString = '[{"text":"Ray Abad","id":"contacts\/11","cls":"file","leaf":"true","listeners": { "click": function(node, eventObject){ alert(node);}} },{"text":"Kibbles Bits","id":"contacts\/9","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Johnny Bravo","id":"contacts\/18","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Mike Bridge","id":"contacts\/13","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Jane Brown","id":"contacts\/2","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Jim Brown","id":"contacts\/19","cls":"file","leaf":"true","listeners":"userNodeInterceptor"}]';
var serverResponseObject = Ext.decode(serverResponseString);

function updateListeners(serverResponseObject, interceptor){
for(var i = 0; i < serverResponseObject.length; i++){
var node = serverResponseObject[i];
node.listeners = interceptor;
}
}

updateListeners(serverResponseObject, userNodeInterceptor);

var onContextHide = function(scope){
if(this.ctxNode)
{
this.ctxNode.ui.removeClass('x-node-ctx');
this.ctxNode = null;
}
}


// Right Click Code
var saveAction = new Ext.Action({
text: "Save",
handler: function(){
alert("Saving node of id->" + saveAction.selectedNode.attributes.id);
}
});

function getInterceptor(){
return userNodeInterceptor;
}

//Create your treeNodeProvider; it is an object that need to have this properties:
// data
var treeNodeProvider = {
data: [],//Property in which are set the data to elaborate

getNodes: function() { //Here you process your data
return serverResponseObject;
},
setData: function(data) {//Called internally by Ext.tree.MyTreeLoader by the method updateTreeNodeProvider
this.data = data;
},
scope: this//Could be useful to use when you elaborates data to switch the context...not used in this example and it's not required
};

var myTreeLoader = new Ext.tree.MyTreeLoader({
treeNodeProvider: treeNodeProvider
});



var basePanelCfg = {
title: 'TreePanel',
preloadChildren: true,
lines: false,
clearOnLoad: true,
rootVisible: false,
containerScroll: true,
frame: false,
collapsible: false,
animate: true,
loader: myTreeLoader
};

var treePanel = new Ext.tree.TreePanel(basePanelCfg);

var root = new Ext.tree.AsyncTreeNode({
text: 'Root',
draggable: false
});

treePanel.setRootNode(root);

// var innerPanel = {
// frame: true,
// title: "Details",
// items: [treePanel]
// };
treePanel.render('tree-ct');
treePanel.expandAll();

// new Ext.Viewport({
// layout:'fit',
// items:[treePanel]
// });
});
</script>
</html>

Capi666
17 Mar 2008, 2:00 AM
Another problem... Can i have variuos node with leaf: false?



NombresArray = [['Jefe01', 'Uno01', 'Dos01'],['Jefe02', 'Uno02', 'Dos02', 'Tres02'],['Jefe03', 'Uno03', 'Dos03']]

for (i=0;i<NombresArray.length;i++){
alert(NombresArray.length)

var node = {
text: NombresArray[i][0],
leaf: false,
id: '0',
children: []
}

for (j=1;j<NombresArray[i].length;j++){
var childNode = {
text: NombresArray[i][j],
leaf: true,
id: i
}

node.children.push(childNode);
}
nodeArray.push(node);
}


I create this code, but when i execute, don

mabello
17 Mar 2008, 2:19 AM
Dear Capi666,
the id of each node need to be UNIQUE, it is a precondition of the tree panel otherwise it does not work.
So, when you put your id for each node, you need to be sure it's unique.

In your code, each not-leaf node has the same id ('0'), so that's the problem.

Besides, in your code, remember to put the var keyword before i and j, or at least make sure you define i and j variable before the for with the var keyword; it's really important, otherwise it means that i and j are global variable and you could have a problem if you use somewhere the variable i or j without reinitialize (i=0) the variable before you use it


getNodes: function() {
//var i = 0;
//var j = 0;
//Otherwise in the for cycle you need to use var declaring your i variable
var nodeArray = [];
for (var i=0;i<NombresArray.length;i++){
alert(NombresArray.length)

var node = {
text: NombresArray[i][0],
leaf: false,
children: []
}

for (var j=1;j<NombresArray[i].length;j++){
var childNode = {
text: NombresArray[i][j],
leaf: true
}

node.children.push(childNode);
}
nodeArray.push(node);
}
return nodeArray;
}

Hope this helps

Capi666
17 Mar 2008, 2:27 AM
Yes, lot of thanks...

Capi666
18 Mar 2008, 4:25 AM
How can select the first row when load the tree?

http://extjs.com/forum/showthread.php?p=139672#post139672

Thanks,

mabello
18 Mar 2008, 4:41 AM
For me this example works, and in FF the node is selected.

This is the example code:


<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Product View</title>

<link rel="stylesheet" type="text/css" href="../Styles/Page.css" />
<link rel="stylesheet" type="text/css" href="../resources2.0.2/css/ext-all.css" />
<link rel="stylesheet" type="text/css" href="../Styles/Action.css" />
<link rel="stylesheet" type="text/css" href="../Styles/FilterPanel.css" />
<link rel="stylesheet" type="text/css" href="../Styles/Tools.css" />
<script type="text/javascript">
//IE Bug Fix (caching of the images, don't flicker please!!!
try {
document.execCommand("BackgroundImageCache", false, true);
}
catch(err) {}
</script>


<script type="text/javascript" src="../scripts/Ext2.0.2/ext-base.js"></script>
<script type="text/javascript" src="../scripts/Ext2.0.2/ext-all-debug.js"></script>
<style type="text/css">
.ext-ie .x-form-item label.x-form-item-label{position:absolute;}
.ext-ie .x-form-label-top .x-form-item label.x-form-item-label{position:static;}
</style>
<%-- favicon --%>
<link rel="shortcut icon" href="~/Images/favicon.ico" />
</head>
<body>
<div id="tree-ct"></div>
</body>
<script type="text/javascript">

Ext.tree.MyTreeLoader = function(config){

Ext.apply(this, config);
Ext.tree.MyTreeLoader.superclass.constructor.call(this, config);

};

Ext.tree.MyTreeLoader = Ext.extend(Ext.tree.MyTreeLoader, Ext.tree.TreeLoader, {

treeNodeProvider: null,

loading: false,

load : function(node, callback){
if(this.clearOnLoad){
while(node.firstChild){
node.removeChild(node.firstChild);
}
}
if(this.doPreload(node)){ // preloaded json children
if(typeof callback == "function"){
callback();
}
}else if(this.treeNodeProvider){
this.requestData(node, callback);
}
},

requestData : function(node, callback){
if(this.fireEvent("beforeload", this, node, callback) !== false){
this.loading = true;
var nodesToAdd = this.treeNodeProvider.getNodes();
if(nodesToAdd)
this.processResponse(nodesToAdd, node, callback);
this.loading = false;
} else {
// if the load is cancelled, make sure we notify
// the node that we are done
if(typeof callback == "function"){
callback();
}
}
},

isLoading : function(){
return this.loading;
},

abort : function(){
},

processResponse : function(o, node, callback){
try {
node.beginUpdate();
for(var i = 0, len = o.length; i < len; i++){
var n = this.createNode(o[i]);
if(n){
node.appendChild(n);
}
}
node.endUpdate();
if(typeof callback == "function"){
callback(this, node);
}
}catch(e){
this.fireEvent("loadexception", this, node, e);
if(typeof callback == "function"){
callback(this, node);
}
}
},

updateTreeNodeProvider: function(obj){
if(this.treeNodeProvider)
this.treeNodeProvider.setData(obj);
}
});

var flag = true;//To toggle the refresh

var NombresArray = [['Jefe01', 'Uno01', 'Dos01'],['Jefe02', 'Uno02', 'Dos02', 'Tres02'],['Jefe03', 'Uno03', 'Dos03']];



Ext.onReady(function(){

var userNodeInterceptor = {
click: function(node, eventObject){
var nodeId = node.attributes.id;
alert("Click on node of id->" + nodeId);
},

contextmenu : function(node, e){
if(!this.menu) // create context menu on first right click
{
this.menu = new Ext.menu.Menu({
id:'feeds-ctx',
items: [saveAction]
});
this.menu.on('hide', onContextHide, this);
}
if(this.ctxNode)
{
this.ctxNode.ui.removeClass('x-node-ctx');
this.ctxNode = null;
}
//if(node.isLeaf()){
this.ctxNode = node;
this.ctxNode.ui.addClass('x-node-ctx');
this.ctxNode.select();//Select the node
saveAction.selectedNode = node;
this.menu.showAt(e.getXY());
//}
}
};

var serverResponseString = '[{"text":"Ray Abad","id":"contacts\/11","cls":"file","leaf":"true","listeners": { "click": function(node, eventObject){ alert(node);}} },{"text":"Kibbles Bits","id":"contacts\/9","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Johnny Bravo","id":"contacts\/18","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Mike Bridge","id":"contacts\/13","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Jane Brown","id":"contacts\/2","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Jim Brown","id":"contacts\/19","cls":"file","leaf":"true","listeners":"userNodeInterceptor"}]';
var serverResponseObject = Ext.decode(serverResponseString);

function updateListeners(serverResponseObject, interceptor){
for(var i = 0; i < serverResponseObject.length; i++){
var node = serverResponseObject[i];
node.listeners = interceptor;
}
}

updateListeners(serverResponseObject, userNodeInterceptor);

var onContextHide = function(scope){
if(this.ctxNode)
{
this.ctxNode.ui.removeClass('x-node-ctx');
this.ctxNode = null;
}
}


// Right Click Code
var saveAction = new Ext.Action({
text: "Save",
handler: function(){
alert("Saving node of id->" + saveAction.selectedNode.attributes.id);
}
});

function getInterceptor(){
return userNodeInterceptor;
}

//Create your treeNodeProvider; it is an object that need to have this properties:
// data
var treeNodeProvider = {
data: [],//Property in which are set the data to elaborate

getNodes: function() {
var nodeArray = [];
for (var i=0;i<NombresArray.length;i++){

var node = {
text: NombresArray[i][0],
leaf: false,
id: '' + i,
children: []
}

for (var j=1;j<NombresArray[i].length;j++){
var childNode = {
text: NombresArray[i][j],
leaf: true
}

node.children.push(childNode);
}
nodeArray.push(node);
}
return nodeArray;
},
setData: function(data) {//Called internally by Ext.tree.MyTreeLoader by the method updateTreeNodeProvider
this.data = data;
},
scope: this//Could be useful to use when you elaborates data to switch the context...not used in this example and it's not required
};

var myTreeLoader = new Ext.tree.MyTreeLoader({
treeNodeProvider: treeNodeProvider
});



var basePanelCfg = {
title: 'TreePanel',
preloadChildren: true,
lines: false,
clearOnLoad: true,
rootVisible: false,
containerScroll: true,
frame: false,
collapsible: false,
animate: true,
loader: myTreeLoader
};

var treePanel = new Ext.tree.TreePanel(basePanelCfg);

var root = new Ext.tree.AsyncTreeNode({
text: 'Root',
draggable: false
});

treePanel.setRootNode(root);

// var innerPanel = {
// frame: true,
// title: "Details",
// items: [treePanel]
// };
treePanel.render('tree-ct');
treePanel.expandAll();

var firstTreeNode = treePanel.getNodeById('0');
treePanel.selectPath(firstTreeNode.getPath());

// new Ext.Viewport({
// layout:'fit',
// items:[treePanel]
// });
});
</script>
</html>




The part you are interested is this:


var firstTreeNode = treePanel.getNodeById('0');
treePanel.selectPath(firstTreeNode.getPath());


I have put in the code the id '0', only to have an example.

Now while in FF it works and you can see your node is selected, you can have problem in IE6, in which you need some css fix because the selection is a little bit buggie.

For more details check this thread out:

http://extjs.com/forum/showthread.php?t=27460&highlight=Tree+Select

Hope this helps

obbakilla
18 Mar 2008, 4:48 AM
hej mabello,

maybe you have a solution for this problem, too?

;)

http://extjs.com/forum/showthread.php?t=29765

Capi666
18 Mar 2008, 6:00 AM
The problem was the location of the code... Sorry.

Thanks,

EmilPalm
18 Mar 2008, 9:17 AM
Right now i have:


loader: new Ext.tree.TreeLoader({
url:'/fakeresults.txt', //DEBUG
requestMethod:'GET',
uiProviders:{
'col': Ext.tree.ColumnNodeUI
}
}),
But i want it to be preloaded on every refresh is this thread something in that direction?

Best Regards
Emil

mabello
18 Mar 2008, 3:33 PM
Yes, I think you can find something that suite your needs.

Now, I have updated the extension (I have updated the first post I've made) with this two method:



getTreeNodeProvider: function(){
return this.treeNodeProvider;
},

setTreeNodeProvider: function(newTreeNodeProvider) {
if(newTreeNodeProvider == null || (typeof newTreeNodeProvider =='undefined'))
throw 'setTreeNodeProvider, newTreeNodeProvider == null || (typeof newTreeNodeProvider == undefined)';
this.treeNodeProvider = newTreeNodeProvider;
}


Now, I have created an example in which I'm simulating an ajax call and I'm actually using two different treeNodeProvider to reload my treePanel in actually different way (I really like it).

So here we are, first treeNodeProvider:


var treeNodeProvider = {
data: [],//Property in which are set the data to elaborate

getNodes: function() { //Here you process your data

var nodeArray = [];//The tree structure used to refresh the TreePanel
for(var i = 0; i < this.data.length; i++){
var folder = this.data[i];
var filesInFolder = folder.files;
//Create the parent node
var node = {
text: folder.name,
leaf: false,
children: []
}
//Create the children node
for(var j = 0; j < filesInFolder.length; j++) {
var fileName = filesInFolder[j];
var childNode = {
text: fileName,
leaf: true
}
//Set the children to the parent node
node.children.push(childNode);
}
//Add the parent node to the nodeArray
nodeArray.push(node);
}
//return the tree structure here
return nodeArray;
},
setData: function(data) {//Called internally by Ext.tree.MyTreeLoader by the method updateTreeNodeProvider
this.data = data;
},
scope: this//Could be useful to use when you elaborates data to switch the context...not used in this example and it's not required
};


The second treeNodeProvider


var treeNodeProvider2 = {
data: [],//Property in which are set the data to elaborate

getNodes: function() { //Here you process your data
return Ext.decode(this.data);
},
setData: function(data) {//Called internally by Ext.tree.MyTreeLoader by the method updateTreeNodeProvider
this.data = data;
},
scope: this//Could be useful to use when you elaborates data to switch the context...not used in this example and it's not required
};


Simulating a first AjaxCall in which I'm using the first treeNodeProvider to refresh the tree with nodes:


var ajaxCallGetDataForTree = function(inputParameters){
treePanel.body.mask("Loading data...");
setTimeout(ajaxCallbackGetDataForTree, 5000);
}

var ajaxCallbackGetDataForTree = function(){
treePanel.body.unmask();
treePanel.body.highlight('#c3daf9', {block:true});
//Simulating that I have received the response that is treeData2 from the callback of the ajaxCall
var rootNode = treePanel.getRootNode();//get the rootnode
var loader = treePanel.getLoader();//Get the loader, note that is of type MyTreeLoader
loader.updateTreeNodeProvider(treeData2);
loader.load(rootNode);
}


The second Ajax call, you probably are interested on this one, because I'm simulating to receive a Json string from the server (in the callback of the ajax call):
The response form the server is the following indeed:



var serverResponseString = '[{"text":"Ray Abad","id":"contacts\/11","cls":"file","leaf":"true","listeners": { "click": function(node, eventObject){ alert(node);}} },{"text":"Kibbles Bits","id":"contacts\/9","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Johnny Bravo","id":"contacts\/18","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Mike Bridge","id":"contacts\/13","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Jane Brown","id":"contacts\/2","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Jim Brown","id":"contacts\/19","cls":"file","leaf":"true","listeners":"userNodeInterceptor"}]';


The ajax call is:


var ajaxCallGetDataForTree2 = function(inputParameters){
treePanel.body.mask("Loading data...");
setTimeout(ajaxCallbackGetDataForTree2, 5000);
}

var ajaxCallbackGetDataForTree2 = function(){
treePanel.body.unmask();
treePanel.body.highlight('#c3daf9', {block:true});
//Simulating that I have received the response that is treeData2 from the callback of the ajaxCall
var rootNode = treePanel.getRootNode();//get the rootnode
var loader = treePanel.getLoader();//Get the loader, note that is of type MyTreeLoader
loader.updateTreeNodeProvider(serverResponseString);
loader.load(rootNode);
}



Now, hif you check to the ttbar items, I'm using the following code:


tbar:[{
text: "Click here to Refresh using treeNodeProvider",
handler: function(){
//Simulating change the treeNodeProvider of the loader
var rootNode = treePanel.getRootNode();//get the rootnode
var loader = treePanel.getLoader();//Get the loader, note that is of type MyTreeLoader
loader.setTreeNodeProvider(treeNodeProvider);
//Simulating an ajax call
ajaxCallGetDataForTree();
}
},{
text: "Click here to Refresh using treeNodeProvider2",
handler: function(){
//Simulating change the treeNodeProvider2 of the loader
var rootNode = treePanel.getRootNode();//get the rootnode
var loader = treePanel.getLoader();//Get the loader, note that is of type MyTreeLoader
loader.setTreeNodeProvider(treeNodeProvider2);
//Simulating a different ajax call
ajaxCallGetDataForTree2();
}
}]

so in the handler, before I simulate the ajax call, I set a new treeNodeProvider in the loader.
Of course, you can do that in the ajaxcall or in the ajaxCallback that could be better I think, but mine is only an example

If you want to preolad your tree, you need to use this line of code before creating the treeLoader (as you can see in the example):


myTreeLoader.updateTreeNodeProvider(treeData1);//if you want to "preload" the TreePanel with this data


Now, the complete example:


<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Product View</title>

<link rel="stylesheet" type="text/css" href="../../resources/css/ext-all.css" />

<!-- GC -->
<!-- LIBS -->
<script type="text/javascript" src="../../adapter/ext/ext-base.js"></script>
<!-- ENDLIBS -->

<script type="text/javascript" src="../../ext-all.js"></script>
<link rel="shortcut icon" href="~/Images/favicon.ico" />
</head>
<body>
<div id="tree-ct"></div>
</body>
<script type="text/javascript">

Ext.tree.MyTreeLoader = function(config){

Ext.apply(this, config);
Ext.tree.MyTreeLoader.superclass.constructor.call(this, config);

};

Ext.tree.MyTreeLoader = Ext.extend(Ext.tree.MyTreeLoader, Ext.tree.TreeLoader, {

treeNodeProvider: null,

loading: false,

load : function(node, callback){
if(this.clearOnLoad){
while(node.firstChild){
node.removeChild(node.firstChild);
}
}
if(this.doPreload(node)){ // preloaded json children
if(typeof callback == "function"){
callback();
}
}else if(this.treeNodeProvider){
this.requestData(node, callback);
}
},

requestData : function(node, callback){
if(this.fireEvent("beforeload", this, node, callback) !== false){
this.loading = true;
var nodesToAdd = this.treeNodeProvider.getNodes();
if(nodesToAdd)
this.processResponse(nodesToAdd, node, callback);
this.loading = false;
} else {
// if the load is cancelled, make sure we notify
// the node that we are done
if(typeof callback == "function"){
callback();
}
}
},

isLoading : function(){
return this.loading;
},

abort : function(){
},

processResponse : function(o, node, callback){
try {
node.beginUpdate();
for(var i = 0, len = o.length; i < len; i++){
var n = this.createNode(o[i]);
if(n){
node.appendChild(n);
}
}
node.endUpdate();
if(typeof callback == "function"){
callback(this, node);
}
}catch(e){
this.fireEvent("loadexception", this, node, e);
if(typeof callback == "function"){
callback(this, node);
}
}
},

updateTreeNodeProvider: function(obj){
if(this.treeNodeProvider)
this.treeNodeProvider.setData(obj);
},

getTreeNodeProvider: function(){
return this.treeNodeProvider;
},

setTreeNodeProvider: function(newTreeNodeProvider) {
if(newTreeNodeProvider == null || (typeof newTreeNodeProvider =='undefined'))
throw 'setTreeNodeProvider, newTreeNodeProvider == null || (typeof newTreeNodeProvider == undefined)';
this.treeNodeProvider = newTreeNodeProvider;
}
});

var flag = true;//To toggle the refresh

Ext.onReady(function(){
//Data to load in the tree
var treeData1 = [
{
name: "Folder1",
files:['File1', 'File2', 'File3']
},{
name: "Folder2",
files:['Picture1', 'Picture2']
}
];
//Data to load in the tree
var treeData2 = [
{
name: "Pictures",
files:['Doc1.txt', 'Doc2.txt']
},{
name: "Documents",
files:['xxx.txt', 'yyy.txt']
}
];
//Probably you will have an answer like this one from your serverside, after the ajax request

var serverResponseString = '[{"text":"Ray Abad","id":"contacts\/11","cls":"file","leaf":"true","listeners": { "click": function(node, eventObject){ alert(node);}} },{"text":"Kibbles Bits","id":"contacts\/9","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Johnny Bravo","id":"contacts\/18","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Mike Bridge","id":"contacts\/13","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Jane Brown","id":"contacts\/2","cls":"file","leaf":"true","listeners":"userNodeInterceptor"},{"text":"Jim Brown","id":"contacts\/19","cls":"file","leaf":"true","listeners":"userNodeInterceptor"}]';

var treeNodeProvider = {
data: [],//Property in which are set the data to elaborate

getNodes: function() { //Here you process your data

var nodeArray = [];//The tree structure used to refresh the TreePanel
for(var i = 0; i < this.data.length; i++){
var folder = this.data[i];
var filesInFolder = folder.files;
//Create the parent node
var node = {
text: folder.name,
leaf: false,
children: []
}
//Create the children node
for(var j = 0; j < filesInFolder.length; j++) {
var fileName = filesInFolder[j];
var childNode = {
text: fileName,
leaf: true
}
//Set the children to the parent node
node.children.push(childNode);
}
//Add the parent node to the nodeArray
nodeArray.push(node);
}
//return the tree structure here
return nodeArray;
},
setData: function(data) {//Called internally by Ext.tree.MyTreeLoader by the method updateTreeNodeProvider
this.data = data;
},
scope: this//Could be useful to use when you elaborates data to switch the context...not used in this example and it's not required
};

var treeNodeProvider2 = {
data: [],//Property in which are set the data to elaborate

getNodes: function() { //Here you process your data
return Ext.decode(this.data);
},
setData: function(data) {//Called internally by Ext.tree.MyTreeLoader by the method updateTreeNodeProvider
this.data = data;
},
scope: this//Could be useful to use when you elaborates data to switch the context...not used in this example and it's not required
};

var myTreeLoader = new Ext.tree.MyTreeLoader({
treeNodeProvider: treeNodeProvider
});
myTreeLoader.updateTreeNodeProvider(treeData1);//if you want to "preload" the TreePanel with this data

var ajaxCallGetDataForTree = function(inputParameters){
treePanel.body.mask("Loading data...");
setTimeout(ajaxCallbackGetDataForTree, 5000);
}

var ajaxCallbackGetDataForTree = function(){
treePanel.body.unmask();
treePanel.body.highlight('#c3daf9', {block:true});
//Simulating that I have received the response that is treeData2 from the callback of the ajaxCall
var rootNode = treePanel.getRootNode();//get the rootnode
var loader = treePanel.getLoader();//Get the loader, note that is of type MyTreeLoader
loader.updateTreeNodeProvider(treeData2);
loader.load(rootNode);
}

var ajaxCallGetDataForTree2 = function(inputParameters){
treePanel.body.mask("Loading data...");
setTimeout(ajaxCallbackGetDataForTree2, 5000);
}

var ajaxCallbackGetDataForTree2 = function(){
treePanel.body.unmask();
treePanel.body.highlight('#c3daf9', {block:true});
//Simulating that I have received the response that is treeData2 from the callback of the ajaxCall
var rootNode = treePanel.getRootNode();//get the rootnode
var loader = treePanel.getLoader();//Get the loader, note that is of type MyTreeLoader
loader.updateTreeNodeProvider(serverResponseString);
loader.load(rootNode);
}

var flag = true;
var basePanelCfg = {
title: 'TreePanel',
preloadChildren: true,
lines: false,
clearOnLoad: true,
rootVisible: false,
containerScroll: true,
frame: false,
collapsible: false,
animate: true,
loader: myTreeLoader,
tbar:[{
text: "Click here to Refresh using treeNodeProvider",
handler: function(){
//Simulating change the treeNodeProvider of the loader
var rootNode = treePanel.getRootNode();//get the rootnode
var loader = treePanel.getLoader();//Get the loader, note that is of type MyTreeLoader
loader.setTreeNodeProvider(treeNodeProvider);
//Simulating an ajax call
ajaxCallGetDataForTree();
}
},{
text: "Click here to Refresh using treeNodeProvider2",
handler: function(){
//Simulating change the treeNodeProvider2 of the loader
var rootNode = treePanel.getRootNode();//get the rootnode
var loader = treePanel.getLoader();//Get the loader, note that is of type MyTreeLoader
loader.setTreeNodeProvider(treeNodeProvider2);
//Simulating a different ajax call
ajaxCallGetDataForTree2();
}
}]
};



var treePanel = new Ext.tree.TreePanel(basePanelCfg);

var root = new Ext.tree.AsyncTreeNode({
text: 'Root',
draggable: false
});

treePanel.setRootNode(root);
treePanel.render('tree-ct');
});
</script>
</html>

EmilPalm
19 Mar 2008, 1:12 AM
thx for this great extension the only thing iv still got a kunomdrom about is how to load the server reponse into the function on the ordinary TreeLoader you just specify the url to load.

//Emil Palm

wm003
20 Mar 2008, 2:15 AM
just a hint:

you dont need this code, because Ext already does this for you automatically:


document.execCommand("BackgroundImageCache", false, true);

mabello
20 Mar 2008, 4:45 PM
Ops, thanks you are right of course...I've missed out to remove that line form my example, sorry about that!
Thanks again!

matt.hall
25 Mar 2008, 5:25 AM
mabello...

Great extension! Works well.

- Matt

mykes
25 Mar 2008, 4:35 PM
Question about something I see in the code:



while(node.firstChild){
node.removeChild(node.firstChild);
}


Looks like a memory leak to me, right?

Why? You are removing the child, but not that child's children (recursively). Maybe I'm missing something, but I don't see how those children will ever get garbage collected.

mabello
27 Mar 2008, 3:21 AM
Dear Mykes,
Sorry for my late answer.

I will get a depth look at it, I will verify asap and fix the problem if the problem exist.

Now, if you get a look at the source code of the AsyncTreeNode and you check the reload function, you will see that in reload the code is actually the same you can find in my code and I trusted that, but I need to get a closer look at the source code.



reload : function(callback){
...
while(this.firstChild){
this.removeChild(this.firstChild);
}
...


If you keep checking the source code, for TreeNode and TreeNodeUI you can see how removeChild actually works.

Have you made some test to verify if the memory leak exist? I think that if the memory leak exist, the real problem will be the
removeChild function of the TreeNode class because remove children recursivly is probably quite simple and I agree with you about that.

Thank you very much for your help, I really appreciate it.

mykes
27 Mar 2008, 1:56 PM
Dear Mykes,
Sorry for my late answer.

I will get a depth look at it, I will verify asap and fix the problem if the problem exist.

Now, if you get a look at the source code of the AsyncTreeNode and you check the reload function, you will see that in reload the code is actually the same you can find in my code and I trusted that, but I need to get a closer look at the source code.



reload : function(callback){
...
while(this.firstChild){
this.removeChild(this.firstChild);
}
...
If you keep checking the source code, for TreeNode and TreeNodeUI you can see how removeChild actually works.

Have you made some test to verify if the memory leak exist? I think that if the memory leak exist, the real problem will be the
removeChild function of the TreeNode class because remove children recursivly is probably quite simple and I agree with you about that.

Thank you very much for your help, I really appreciate it.


See this thread:
http://extjs.com/forum/showthread.php?t=14993

mabello
27 Mar 2008, 2:40 PM
Thanks mickes,
you've been really kind to point out the problem.

I'm going to update the code right away. But I think that this problem has to be fixed in TreeNode class in the removeChild function as I said in my previous post, in which the function removeChild will remove recursively every child and call the destroy() for each node in this function, because there is no point not doing this in removeChild function in my opinion, something like that (pseudoCode)



removeChild: function(theChildNode){
foreach(child of theChildNode) {
theChildNode.removeChild(child);//Clear the object, removeChild recursively called for each child node
}
theChildNode.destroy();//Clear the dom, so destory() won't be recursive in this case, because we are sure we call the destroy for each child node
}


Thanks again!

tford
2 Apr 2008, 11:39 AM
I noticed one thing though. Shouldn't the node's load event be fired at the end of the loader's load method?


load : function(node, callback){
if(this.clearOnLoad){
while(node.firstChild){
node.removeChild(node.firstChild);
}
}
if(this.doPreload(node)){ // preloaded json children
if(typeof callback == "function"){
callback();
}
}else if(this.treeNodeProvider){
this.requestData(node, callback);
}
node.fireEvent('load', node);
},

mabello
2 Apr 2008, 2:28 PM
Dear tford,
thanks for your help, you are right I'm not calling the load event, sorry about that :">.

Now, what you have suggested has the following problem (check the code):



load : function(node, callback){
if(this.clearOnLoad){
while(node.firstChild){
node.removeChild(node.firstChild);
}
}
if(this.doPreload(node)){ //preloaded json children, if you go here, the standard TreeLoader do not fire the load event
if(typeof callback == "function"){
callback();
}
}else if(this.treeNodeProvider){
this.requestData(node, callback);//----a) here is called processResponse and if you have an exception loadexception is fired
}
node.fireEvent('load', node);//---here you eventually fire the event load ALSO if in a) loadexception has been fired
},


if you fire the load event like in your submited code, could happen that if you have an exception in the processResponse function, the event loadexception is fired but you eventually fire also the load event, and this is not good.

Also if beforeload return false, it means that you do not want to fire the load event.

Besides following the standar TreeLoader, it does not fire the load event if you only preload the nodes (I mean in the load function, if this.doPreload(node) is used, in the TreeLoader load event is not fired).


So I had fixed that, changing a little bit of my code, so that now it fires the load event and if loadexception is fired, load event is not fired.

Besides I've added also a Ext.tree.TreeNodeProvider class, so that you can use also something defined like the following:



var treeNodeProvider = new Ext.tree.TreeNodeProvider({

getNodes: function(dataToElaborate) { //Here you process your data
var nodeArray = [];//The tree structure used to refresh the TreePanel
for(var i = 0; i < dataToElaborate.length; i++){
var folder = dataToElaborate[i];
var filesInFolder = folder.files;
//Create the parent node
var node = {
text: folder.name,
leaf: false,
children: []
}
//Create the children node
for(var j = 0; j < filesInFolder.length; j++) {
var fileName = filesInFolder[j];
var childNode = {
text: fileName,
leaf: true
}
//Set the children to the parent node
node.children.push(childNode);
}
//Add the parent node to the nodeArray
nodeArray.push(node);
}
throw 'exception';
//return the tree structure here
return nodeArray;
}
});


As usual, I post the new Version in the first post.

Thanks again for your help, I really appreciate your help and sorry again for my mistake.

allampraveen
4 Apr 2008, 1:26 AM
Hi mabello,

Can i have code for Tree with XML, where in when i click root node then it should get data and display childnodes.. and then up on clicking childnode it should expand if it has children

as
23 Apr 2008, 3:59 AM
Hello.

I've been trying to use custom TreeNodeUI classes together with this TreeLoader extension, but there is something strange.

This is how I'm trying to use it:

In the TreeLoader instantiation, I am including the uiProviders:


var myTreeLoader = Ext.tree.MyTreeLoader({
treeNodeProvider: treeNodeProvider,
uiProviders:{
'contact': Ext.tree.ContactNodeUI,
'group': Ext.tree.GroupNodeUI
});


Both UI Providers classes are very simple, just like this:


Ext.tree.ContactNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
focus: Ext.emptyFn, // prevent odd scrolling behavior

renderElements : function(n, a, targetNode, bulkRender){
this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';

var buf = [
'<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf ', a.cls,'">',
'<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
'<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow">',
'<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on">',
'<a hidefocus="on" class="x-tree-node-anchor" href="',a.href ? a.href : "#",'" tabIndex="1" ',
a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '>',
'<span unselectable="on">', n.text, "</span></a>"];

buf.push(
'<div class="x-clear"></div></div>',
'<ul class="x-tree-node-ct" style="display:none;"></ul>',
"</li>");

if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){
this.wrap = Ext.DomHelper.insertHtml("beforeBegin",
n.nextSibling.ui.getEl(), buf.join(""));
}else{
this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(""));
}

this.elNode = this.wrap.childNodes[0];
this.ctNode = this.wrap.childNodes[1];
var cs = this.elNode.firstChild.childNodes;
this.indentNode = cs[0];
this.ecNode = cs[1];
this.iconNode = cs[2];
this.anchor = cs[3];
this.textNode = cs[3].firstChild;
}
});


The node provider will place a node property
uiProvider: 'contact' for contacts, and
uiProvider: 'group' for groups, so that appropriate UI providers are set by the tree loader.

The problem is that when I use this configuration, the nodes are not being rendered.

I have tried to debug the code, both using the custom ui providers and not using the ui providers in order to detect when the ui providers are invoked, but I could not detect it. Breakpoints in the UI provider render or renderElements won't be triggered.

Has anyone used custom UI providers together with MyTreeLoader extension? Any hint on how to apply that?

mabello
23 Apr 2008, 4:18 AM
Dear as,
I think you are right, and I will try to fix asap, I've never tried that but I already know where the problem is: in breaf, during the creation of the nodes, I don't pick up the uiProviders attribute and I'm not actually using it, but it has to be an easy "fixing".

I have made also some internal refactoring to my code and also the first post start to be to "messy" so I need to have a look at it and post the new code also fixing the use of uiProviders config property.

Thanks for your help we will fix it together :)

mabello
23 Apr 2008, 2:40 PM
Dear as,
ignore my previous post, because everything works just fine with uiProviders used with MyTreeLoader extension.

The class you provided in your previous post which extends Ext.tree.TreeNodeUI does not work, but I haven't fixed it, but firefox gives me some errors...

Anyway, if you download the attachment in the first post of this thread, you can find the example of Ext.tree.ColumnNodeUI provided by ExtJS using MyTreeLoader extension.
The file name is MyTreeLoader-ColumnUI.html.

I've also made a refactoring of MyTreeLoader, so that you can easly understand the public interface of a TreeLoader.

Thanks

pokerking400
29 Apr 2008, 3:05 PM
Can you show me how it supports XML,Json,Static (typed) Data?.

I see the default treeloader supports json...

what is different with these extension?. Can you give me example ?...i could n't understand from the package...

i think if you write some kind of instruction on how to use it and why?. What i gain from using your extension..

i have used default treeloader (json) , now i need to use xml as the source of data.

thanks
alexk

pokerking400
29 Apr 2008, 8:25 PM
Can you also follow the format like this for all extension?

ControlClass ( file) - XTreePanel.js
ControlDataProvider (file) - XTreeXMLDataProvider.js , XTreeJSONDataProvider.js ,
ControlListeners (file) - XTreeListeners (events) (load , render, click , beforePost ,..)

if you have these 3 files for every extension, it is easier to debug and extend. i just need a standard format for all extensions. It helps...

Thanks.
alex

mabello
30 Apr 2008, 12:41 AM
Dear Alex,

you make confusion between extension and examples in my folders :)

In the download folder, the extension classes (so the extension files) are only 2 files:

Ext.tree.TreeNodeProvider class (file TreeNodeProvider.js)

Ext.tree.MyTreeLoader class (file MyTreeLoader.js)

The other files are only simple examples, and are not part of the extension, their only aim is showing up how you can work with the extension classes.

So in you application, you can define in different files the classes you need to build up your project in order to create a tidy application.

So you can create a listener class to manage the events of the TreeLoader, create your XmlTreeDataProvider, JSonDataProvider etc in different files, but it is only up to you.

The classs TreeNodeProvider, as you can understand if you take a look to its implementation, is more a template class used as an "interface checker" that help the developer to understand the contract a TreeNodeProvider instance need to "honour" in order to be used inside MyTreeLoader class.

And about changing the names of the classes, it's not a great idea, because it means that all the people using the extension will have to change their code in order to keep using the extension...as long as there is no problem with "namespace collision", nothing will change.

Regards

pokerking400
30 Apr 2008, 9:22 AM
Thats ok. Also it can have a sub directory that can have helper classes. For example grid window will have many sub control classes that can move to helper directory.
Anyway thanks for the reply.

sz_146
5 Nov 2008, 9:39 AM
I am using the TreeNodeProvider extention to render my tree. I have successfully written it in the way that it will only load the top level nodes in the first render. Now I want to be able to render the children of the nodes when someone clicks a node, but I can't get it to work. Will highly appreciate if someone could point me to the right direction or demonstrate how to achieve on-demand rendering. If you know a different way of achieving the same please do let me know. thanks. Here is my code :



/*
* Trees.js
*/
/// <reference path="ext-adapter/ext/ext-base.js" />
/// <reference path="ext-all-debug.js" />
Ext.onReady(function() {

Ext.BLANK_IMAGE_URL = '../images/s.gif';

start();

//**********************************************************************************
//
//* Ajax call to get top level entities from the server
//* Using the JsonRpc class
//

//**********************************************************************************/

function start() {

// Top level nodes for the tree
var topLevelNodes = [];

rpc = new JsonRpc({
url: GlobalConstants.serverUrl
});

rpc.call({
method: 'GetTopLevelOrgEntities',
//parameters: {'a': 'Hello', 'b' : 'World'},
callback: handleResponse,
scope: this
}); // eo call

}


function handleResponse(success, data, response) {
var topLevelNodes = [];
// If success==true and data exists
if (success && data instanceof Object) {
// total number of Answer objects in the store
var totalObjects = data.Objs.length;
// If any Answer exists/NOT EMPTY
if (totalObjects != null && totalObjects > 0) {
// store objects in datastore
ds.store(data.Objs);

var ObjInDs = [];
// Loop through the data
for (var i = 0; i < totalObjects; i++) {
ObjInDs.push(ds[data.Answer[i]]);
} // eo for

// loadTree
loadTree(ObjInDs);
} // eo if
} else {
Ext.Msg.alert('SORRY !!!', 'No data returned from server. Please try again by refreshing the page');
} // eo condition if-else (success ...)
} // eo startApp


/** ========================================================================================================== */



function loadTree(treeData1) {

// Tree node provider configuration
var treeNodeProvider = {
data: [],

getNodes: function() { //process data

var nodeArray = []; //The tree structure used to refresh the TreePanel

Ext.each(this.data, function(obj, index, allObjs) {
if (obj.hasChildren()) {
var node = {
id: obj.getID(),
text: obj.getName(),
icon: obj.getIcon(),
leaf: false,
children: []
}
} else {
var node = {
id: obj.getID(),
text: obj.getName(),
icon: obj.getIcon(),
leaf: true
}
}
nodeArray.push(node);
});

//return the tree structure here
return nodeArray;
},
setData: function(data) {//Called internally by Ext.tree.MyTreeLoader by the method updateTreeNodeProvider
this.data = data;
},
scope: this
}; // eo TreeNodeProvider

var myTreeLoader = new Ext.tree.MyTreeLoader({
treeNodeProvider: treeNodeProvider
});

// Preload tree panel with top level
myTreeLoader.updateTreeNodeProvider(treeData1);

var flag = true;

var basePanelCfg = {
title: 'TreePanel',
preloadChildren: true,
lines: false,
clearOnLoad: true,
rootVisible: false,
containerScroll: true,
frame: false,
collapsible: false,
animate: true,
loader: myTreeLoader
};

var treePanel = new Ext.tree.TreePanel(basePanelCfg);

var root = new Ext.tree.AsyncTreeNode({
text: 'Root',
draggable: false
});

treePanel.setRootNode(root);
treePanel.render('tree-ct');


//********************************************************************************
//* *
//***************************** TREE EVENT LISTENERS *****************************
//* *
//********************************************************************************/

treePanel.on('click', function(n) {
// Ds operations
if (!n.isExpanded()) {
if (n.childNodes.length == 0) {
// Get children IDs
var childrenIDs = ds[n.id].getChildrenIDs();
ds.getObjsByIds(childrenIDs, updateTree, this);
} else {
n.expand();
} //eo if childNodes
} //eo if isExpanded

function updateTree(objs) {
var rootNode = treePanel.getRootNode(); //get the rootnode
var loader = treePanel.getLoader(); //Get the loader, note that is of type MyTreeLoader
loader.updateTreeNodeProvider(objs);
loader.load(rootNode);
} //eo updateTree
}); // eo onClick
} // eo loadTree
});

// EOF

durlabh
17 Nov 2008, 2:26 AM
To replace the node with custom loaded data, here is the code I'm using:


Ext.override(Ext.tree.TreeLoader, {
setData: function(node, o) {
if (this.clearOnLoad) {
while (node.firstChild) {
node.removeChild(node.firstChild);
}
}

node.beginUpdate();
for (var i = 0, len = o.length; i < len; i++) {
var n = this.createNode(o[i]);
if (n) {
node.appendChild(n);
}
}
node.endUpdate();
}
});


So, I can call it with something like:



tree.loader.setData(tree.getRootNode(), [text:'Test']);


I hope this helps!

Zef
16 Mar 2009, 7:31 AM
Hello,

I'm using at the moment a AsyncTreeNode to get data from a url (js array): all is working fine.
No "children" attribute given, only "url" one is used.


var tree = new Ext.tree.TreePanel({
root: new Ext.tree.AsyncTreeNode({
loader: new Ext.tree.TreeLoader({
url: 'tree.html'
})
})
});Now, I want to populate the rootNode statically (with an embeded array), but only for first data filling.
Then normal URL update will be used.

I use "children" to populate my rootNode, but it seems incompatible with "url" attribute.


var tree = new Ext.tree.TreePanel({
root: new Ext.tree.AsyncTreeNode({
children: INIT,
loader: new Ext.tree.TreeLoader({
// url: 'tree.html'
})
})
});Here, the commented line has not effect, commented or not.
I spent hours on the forum, found some tips, but nothing that applies to my needs.

I tried to use "loader: new Ext.tree.TreeLoader" directly at Panel level, and :


tree.getLoader().dataUrl = 'tree.html';
tree.getRootNode().reload();But this only works when the loader had previously a "url" attribute.


Could your extension help me?

I did a TreeNodeProvider from embeded array, it's working.
Now, I'm trying to update the provider with setTreeNodeProvider to use data from a URL.

In examples I saw "dummy ajax calls" :s but no real one.


tree.getLoader().urlData = 'tree.html';
tree.getRootNode().reload();Nothing happens...
Do you see some way to fix my problem? I'm not able to determine if your extension can help me or not

Thanks for your help,
Z.

mabello
16 Mar 2009, 7:45 AM
Hi there.
You should just change the "dummy" ajax call with a real Ext.Ajax call and use the callback of that ajax call to populate the tree the same way I did in my example (the "fake" callback in my example is called using setTimeout to simulate the server elaboration to get back the response).
Does it answer your question/help a little?
Let me know
Cheers

Zef
16 Mar 2009, 8:25 AM
Hello,

Yes it confirms what I supposed: I have to do the ajax call by myself.
But I'm a little surprised, the Plugin extends Ext objects (TreeLoader...) that normally do that automatically ; that's why I supposed they was some simple way to do that just giving the url.

I will try asap your solution ; thanks a lot.
Z.

Zef
18 Mar 2009, 12:41 AM
hello, My pb was Ajax call needs a success callback and can't be just a function returning server response (Asynchronous call). I need to re-design some of my functions to do that nicer, but ... thanks a lot, it works! Very useful extension Z. P.S. One thing, I confused updateTreeNodeProvider / setTreeNodeProvider (tried first one to update treeNodeProvider until it's updating TreeNode content and not TreeNodeProvider). Maybe consider renaming method?

mabello
18 Mar 2009, 2:29 AM
Happy it worked, good job Zef.
I could change the name if it is a source of confusion though...
Thanks for your feedback