View Full Version : Saving tree state example
gnaegi
10 Jul 2007, 7:57 AM
Hi
I didn't find a standard way how to store the tree state, so I wrote some code myself. By tree state I mean which nodes are expanded and which are collapsed.
I'm pretty new to Extjs, so maybe there is another way how this is normally done. I'm also a Java developer and not a Javascript developer, so there might also be much better way to programm the actual thing.
I did not extend the Ext.tree.TreePanel but created my own class to not get confused about what is part of extjs and what is my own code.
The library itself:
function TreePanelState(mytree) {
this.mytree = mytree;
}
TreePanelState.prototype.init = function() {
this.cp = new Ext.state.CookieProvider();
this.state = this.cp.get('TreePanelState_' + this.mytree.id, new Array() );
}
TreePanelState.prototype.saveState = function(newState) {
this.state = newState;
this.cp.set('TreePanelState_' + this.mytree.id, this.state);
}
TreePanelState.prototype.onExpand = function(node) {
var currentPath = node.getPath();
var newState = new Array();
for (var i = 0; i < this.state.length; ++i) {
var path = this.state[i];
if (currentPath.indexOf(path) == -1) {
// this path does not already exist
newState.push(path);
}
}
// now ad the new path
newState.push(currentPath);
this.saveState(newState);
}
TreePanelState.prototype.onCollapse = function(node){
var closedPath = closedPath = node.getPath();
var newState = new Array();
for (var i = 0; i < this.state.length; ++i) {
var path = this.state[i];
if (path.indexOf(closedPath) == -1) {
// this path is not a subpath of the closed path
newState.push(path);
}
}
if (newState.length == 0) {
var parentNode = node.parentNode;
newState.push((parentNode == null ? this.mytree.pathSeparator : parentNode.getPath()));
}
this.saveState(newState);
}
TreePanelState.prototype.restoreState = function(defaultPath) {
if (this.state.length == 0) {
var newState = new Array(defaultPath);
this.saveState(newState);
this.mytree.expandPath(defaultPath);
return;
}
for (var i = 0; i < this.state.length; ++i) {
// activate all path strings from the state
try {
var path = this.state[i];
this.mytree.expandPath(path);
} catch(e) {
// ignore invalid path, seems to be remove in the datamodel
// TODO fix state at this point
}
}
}
To initialize the state:
var tree = Ext.tree.TreePanel(....);
[...]
tree.render();
// add cookie base tree state
var treeState = new TreePanelState(tree);
treeState.init();
// initialize event handlers
tree.on('expand', treeState.onExpand, treeState);
tree.on('collapse', treeState.onCollapse, treeState);
// restore last state from tree or to the root node as default
treeState.restoreState(tree.root.getPath());
If you find it usefull, takt it and improve it ;-)
Cheers,
Florian
skyey
10 Jul 2007, 8:17 AM
:) i tested. work fine...thank you!
galdaka
10 Jul 2007, 11:20 AM
Hi,
Thanks for your code. I
gnaegi
11 Jul 2007, 12:54 AM
Hi Galdaka
This is strage, I just tested it with rootVisible : false and I had no problem whatsoever.
Try deleting the cookie and start over again.
If it still does not work, add some debugging code to the restoreState function, specially in the catch part and see if something is wrong with the second path.
I attached a new version of the script, but the only difference should be that it contains a bit more documentation ;-)
Cheers
Florian
galdaka
11 Jul 2007, 10:49 AM
Sorry not work for me,
I use Ext 1.0 and IE6. I put use your TreePanelState.js with DominoOutline.js : http://extjs.com/forum/showthread.php?p=12727#post12727
I put your code after tree.render():
// add cookie base tree state
var treeState = new TreePanelState(tree);
treeState.init();
// initialize event handlers
tree.on('expand', treeState.onExpand, treeState);
tree.on('collapse', treeState.onCollapse, treeState);
// restore last state from tree or to the root node as default
treeState.restoreState(tree.root.getPath());
Thanks in advance and good work,
gnaegi
11 Jul 2007, 1:46 PM
Hi Galdaka
I'm sorry, I did not mention that I wrote this for extjs 1.1. I don't know if it does also work with 1.0! I'm sorry, I cant' help you.
Can you upgrade to 1.1 or do you need to use 1.0?
Cheers
Florian
galdaka
12 Jul 2007, 6:34 AM
Sorry,
I upgrade my demo app to Ext 1.1 beta 2. And with IE6 and FF2 not work properly.
Thanks,
gnaegi
12 Jul 2007, 9:45 PM
I'm sorry, I can't help you with your general statement that it would not work. It works very fine for me and also for others, so I can't debug anything because it just works in my setup.
Did you add debugging code to the places I suggested? Do you get any error output from your JS console? Do you have an example page of your code where it does not work that I can access?
I can't help you without your help!
Cheers,
Florian
chuckledog
15 Nov 2007, 9:55 PM
Thanks gnaegi, this works very nicely. ext2 rc1, ie6, firefox linux. B)
Sunech
14 Feb 2008, 5:57 AM
Anyone using this successfully in Ext 2.0.1? Doesn't seem to work here, it just expands the root node.
inflames
5 Mar 2008, 4:29 AM
Anyone using this successfully in Ext 2.0.1? Doesn't seem to work here, it just expands the root node.
The post is a little old, but just in case anyone else needs it, I'm using it with Ext JS 2.0.2 and I only had to change these 2 lines of code:
Original:
tree.on('expand', treeState.onExpand, treeState);
tree.on('collapse', treeState.onCollapse, treeState);
Modified:
tree.on('expandnode', treeState.onExpand, treeState);
tree.on('collapsenode', treeState.onCollapse, treeState);
Hope this helps.
mkppk
13 Mar 2008, 8:48 AM
This code was a lifesaver, thank you gnaegi!
I noticed that some of my nodes were not expanding on restore and I think I know why. The loop that expands all the nodes saved in the state calls expandPath(), which not only expands the path (good), but will trigger the TreePanelState.prototype.onExpand to run also (bad?).
So, you end up modifying the state as you loop over it - never a good thing to do. I fixed the problem by just saving the "restore state" before looping over it so whatever TreePanelState.prototype.onExpand does that mucks with the state doesn't alter the which nodes you are trying to restore. Maybe a better solution would be to somehow not trigger TreePanelState.prototype.onExpand when restoring, but this seems to wok fine, and my tree is restoring correctly, consistently.
(FIXED) TreePanelState.prototype.restoreState
TreePanelState.prototype.restoreState = function(defaultPath) {
if (this.state.length == 0) {
var newState = new Array(defaultPath);
this.saveState(newState);
this.mytree.expandPath(defaultPath);
return;
}
var stateToRestore=this.state;
for (var i = 0; i < stateToRestore.length; ++i) {
// activate all path strings from the state
try {
var path = stateToRestore;
this.mytree.expandPath(path);
} catch(e) {
// ignore invalid path, seems to be remove in the datamodel
// TODO fix state at this point
}
}
}
[I](ORIGINAL) TreePanelState.prototype.restoreState
TreePanelState.prototype.restoreState = function(defaultPath) {
if (this.state.length == 0) {
var newState = new Array(defaultPath);
this.saveState(newState);
this.mytree.expandPath(defaultPath);
return;
}
for (var i = 0; i < this.state.length; ++i) {
// activate all path strings from the state
try {
var path = stateToRestore[i];
this.mytree.expandPath(path);
} catch(e) {
// ignore invalid path, seems to be remove in the datamodel
// TODO fix state at this point
}
}
}
TreePanelState.prototype.onExpand
TreePanelState.prototype.onExpand = function(node) {
var currentPath = node.getPath();
var newState = new Array();
for (var i = 0; i < this.state.length; ++i) {
var path = this.state[i];
if (currentPath.indexOf(path) == -1) {
// this path does not already exist
newState.push(path);
}
}
// now ad the new path
newState.push(currentPath);
this.saveState(newState);
}
symfony
31 Mar 2008, 8:37 AM
Excellent, it works like a treat =D>
gnaegi
1 Apr 2008, 9:59 PM
Hi mkppk
Thanks for fixing it for EXT 2 and the looping issue. I haven't used the code for some time now, the project I wrote it for was with the old EXT version. Hope I can soon upgrade...
Cheers
Florian
DhakouaniM
2 Jun 2008, 8:27 PM
In case you're not using the default 'id' attribute, this new version is even more useful... Thx to the original authors for the baseline, though ! It saved me a lot of time !
TreePanelState = function(mytree, attributeName) {
this.mytree = mytree;
this.myattr = attributeName || 'id';
}
TreePanelState.prototype.init = function() {
this.cp = new Ext.state.CookieProvider();
this.cp.set('TreePanelState_' + this.mytree.id, new Array());
this.state = this.cp.get('TreePanelState_' + this.mytree.id, new Array());
}
TreePanelState.prototype.saveState = function(newState) {
this.state = newState;
this.cp.set('TreePanelState_' + this.mytree.id, this.state);
}
TreePanelState.prototype.onExpand = function(node) {
var currentPath = node.getPath(this.myattr);
var newState = new Array();
for (var i = 0; i < this.state.length; ++i) {
var path = this.state[i];
if (currentPath.indexOf(path) == -1) {
// this path does not already exist
newState.push(path);
}
}
// now ad the new path
newState.push(currentPath);
this.saveState(newState);
}
TreePanelState.prototype.onCollapse = function(node){
var closedPath = closedPath = node.getPath(this.myattr);
var newState = new Array();
for (var i = 0; i < this.state.length; ++i) {
var path = this.state[i];
if (path.indexOf(closedPath) == -1) {
// this path is not a subpath of the closed path
newState.push(path);
}
}
if (newState.length == 0) {
var parentNode = node.parentNode;
newState.push((parentNode == null ? this.mytree.pathSeparator : parentNode.getPath(this.myattr)));
}
this.saveState(newState);
}
TreePanelState.prototype.restoreState = function(defaultPath) {
if (this.state.length == 0) {
var newState = new Array(defaultPath);
this.saveState(newState);
this.mytree.expandPath(defaultPath, this.myattr);
return;
}
var stateToRestore=this.state;
for (var i = 0; i < stateToRestore.length; ++i) {
// activate all path strings from the state
try {
var path = this.state[i];
this.mytree.expandPath(path, this.myattr);
} catch(e) {
// ignore invalid path, seems to be remove in the datamodel
// TODO fix state at this point
}
}
}
And you call it this way :
this.groupsTree = new TreePanel(............);
var gtState = new TreePanelState(this.groupsTree, 'cmisId');
gtState.init();
this.groupsTree.on('collapsenode', gtState.onCollapse, gtState);
this.groupsTree.on('expandnode', gtState.onExpand, gtState);
and in case you want to do 'reload', don't forget that the 'reload' method collpases all the node first, it is thus needed to remove the listeners first, as follow:
this.groupsTree.un('collapsenode', gtState.onCollapse, gtState);
this.groupsTree.un('expandnode', gtState.onExpand, gtState);
this.groupsTree.getRootNode().reload(function(node) {
gtState.restoreState(node.getOwnerTree().getRootNode().getPath('cmisId'))
node.getOwnerTree().on('collapsenode', gtState.onCollapse, gtState);
node.getOwnerTree().on('expandnode', gtState.onExpand, gtState);
if (selectedPath)
node.getOwnerTree().selectPath(selectedPath, 'cmisId');
});
VR
Mehdi
redstone6513
28 Jul 2008, 4:19 PM
it's really works very well with Ext version2.0,thanks a lot .
galdaka
14 Aug 2008, 6:27 AM
Hi all,
Any example with definitive code?
I want to use in www.jadacosta.es
Thanks in advance,
robincasey
30 Nov 2008, 7:49 AM
Great plugin, though I experienced a whole branch was not opened after a drop (in the middle), as there was no event monitoring this.
The fix for me was adding:
this.tree.on('nodedrop', function(tree) {
this.state.onExpand(tree.dropNode);
}, this.tree);
This ensures a update of the dropTarget upon drop, and does expand the modified branch.
(FIXED) TreePanelState.prototype.restoreState
TreePanelState.prototype.restoreState = function(defaultPath) {
....
.....
var stateToRestore=this.state;
...............
......
...
}
Actually this is not good because you will have just a reference to the 'state'. So when the 'state' is changed you'll have the same behaviour.
My suggestion is to write e.g.
var stateToRestore=[].concat(this.state)
Now you have a copy of the 'state' array.
Thanks.
nzlyoung
14 Dec 2008, 8:30 PM
This is so cool. It works on 2.2 too. I can confirm. Thanks!
ShaneB
16 Jan 2009, 2:16 PM
Thank you so much for this code. It is just what I needed
In my page, when the user clicks on a node to highlight it, an AJAX call loads some detail data about that node on the side of the page. I'd like this detail data to automatically load as well when the user navigates back to this page and the state of the tree is refreshed. I'd like to add this to my own code, not disrupting the TreePanelState function, but I'm not sure how to access (a) whether the tree actually expanded or if it just defaulted to the root and (b) which node it ended up on when it refreshed the tree.
I guess this might be easy if I put this functionality into the restoreState portion of the TreePanelState function, but, even doing that, I'm not sure how to access the last node and then call the AJAX. The AJAX call is just calling a javascript function when the user clicks on the node.
Any thoughts? If it's not obvious, I'm a novice at this.
ShaneB.
joeri
24 Feb 2009, 5:36 AM
I needed this to be a simple tree plugin, and also to remember collapsed state as well as expanded state, so I adapted this code (heavily) into a plugin.
The following code is the plugin:
Ext.namespace('Ext.ux.tree');
Ext.ux.tree.State = function(config) {
Ext.apply(this, config);
};
Ext.extend(Ext.ux.tree.State, Ext.util.Observable, {
init: function(tree) {
this.tree = tree;
this.stateName = 'TreePanelState_' + this.tree.id;
this.idField = this.idField || 'id';
this.provider = Ext.state.Manager.getProvider() || new Ext.state.CookieProvider();
this.state = this.provider.get(this.stateName, {});
this.tree.on({
scope: this,
collapsenode: this.onCollapse,
expandnode: this.onExpand,
append: this.onNodeAdded
});
},
saveState : function(newState) {
this.state = newState || this.state;
this.provider.set(this.stateName, this.state);
},
onNodeAdded: function(tree, parentNode, node, index) {
switch (this.state[node.id]) {
case 'C':
node.expanded = false;
break;
case 'E':
node.expanded = true;
break;
};
},
onExpand: function(node) {
this.state[node.id] = 'E';
this.saveState();
},
onCollapse: function(node) {
this.state[node.id] = 'C';
this.saveState();
}
});
To use:
// tree object
{ xtype: 'treepanel',
id: 'mytree', // id required for consistent state saving
plugins: [new Ext.ux.tree.State()],
...
}
ShaneB
24 Feb 2009, 6:42 AM
Joeri...
Thanks much for your plug-in and sample code. Looks like it's just what I need.
Shane.
mystix
24 Feb 2009, 8:10 AM
@joeri, some really minor code-size savings may be achieved in the init() function using this invocation of observable.on():
this.tree.on({
scope : this,
collapsenode : this.onCollapse,
expandnode : this.onExpand,
append : this.onNodeAdded
});
in favour of
this.tree.on('collapsenode', this.onCollapse, this);
this.tree.on('expandnode', this.onExpand, this);
this.tree.on('append', this.onNodeAdded, this);
HTH.
joeri
25 Feb 2009, 12:55 AM
@mystix: Thanks for the suggestion. I've seen that notation before, but I had forgotten its exact syntax and was too lazy to look it up. :)
@ShaneB: You could try hooking into the tree's selModel's selectionchange event, storing the path to the selected node in the state object, and then in some tree initialization event (render perhaps?) calling the selectPath method for that stored path. I guess that still wouldn't trigger a click event though, so you might have to add code to the onNodeAdded handler to fire a fake click event if that node is the selected node.
maski
23 Mar 2009, 1:24 AM
This plug-in is very great and simple, but if I need to select last node after page reload... How can I do this?
Hi,
is there a version of this Plugin for ext js 4.x
gnaegi
7 Sep 2011, 6:42 AM
Hi,
is there a version of this Plugin for ext js 4.x
Sorry, I haven't had time to test on ExtJS4, maybe someone else who uses my plugin can help you out. Did you test the most recent version in the post with ExtJS4?
Regards
Florian
It is not working...
I try this http://www.sencha.com/forum/showthread.php?141924-Creating-a-statefull-tree-panel but there are timing Problems.
Powered by vBulletin® Version 4.1.5 Copyright © 2012 vBulletin Solutions, Inc. All rights reserved.