PDA

View Full Version : Ext.ux.tree.TreeFilterX



jsakalos
17 Dec 2008, 9:47 AM
Inspired by http://extjs.com/forum/showthread.php?p=252709 I've just rolled out a preview version of the eXtended tree filter. The main problem with default tree filter that comes with Ext is that if a child matches filter expression but its parent doesn't, the parent is hidden hiding also the matching child.

What I needed was to find all children in already loaded tree and show its parents regardless if they themselves match the filter expression or not. The code is below and the live demo at http://remotetree.extjs.eu

Let me please know what do you think and report bugs and/or some ideas for improvement.

Thanks.



// vim: ts=4:sw=4:nu:fdc=4:nospell
/*global Ext */
/**
* @class Ext.ux.tree.TreeFilterX
* @extends Ext.tree.TreeFilter
*
* <p>
* Shows also parents of matching nodes as opposed to default TreeFilter. In other words
* this filter works "deep way".
* </p>
*
* @author Ing. Jozef Sakáloš
* @version 1.0
* @date 17. December 2008
* @revision $Id: Ext.ux.tree.TreeFilterX.js 752 2009-10-26 08:25:46Z jozo $
* @see <a href="http://extjs.com/forum/showthread.php?p=252709">http://extjs.com/forum/showthread.php?p=252709</a>
*
* @license Ext.ux.tree.CheckTreePanel is licensed under the terms of
* the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
* that the code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
*
* <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
* target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
*
* @forum 55489
* @demo http://remotetree.extjs.eu
*
* @donate
* <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
* <input type="hidden" name="cmd" value="_s-xclick">
* <input type="hidden" name="hosted_button_id" value="3430419">
* <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif"
* border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
* <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
* </form>
*/

Ext.ns('Ext.ux.tree');

/**
* Creates new TreeFilterX
* @constructor
* @param {Ext.tree.TreePanel} tree The tree panel to attach this filter to
* @param {Object} config A config object of this filter
*/
Ext.ux.tree.TreeFilterX = Ext.extend(Ext.tree.TreeFilter, {

// {{{
/**
* Filter the data by a specific attribute.
*
* @param {String/RegExp} value Either string that the attribute value
* should start with or a RegExp to test against the attribute
* @param {String} attr (optional) The attribute passed in your node's attributes collection. Defaults to "text".
*/
filter:function(value, attr, startNode) {

var animate = this.tree.animate;
this.tree.animate = false;
this.tree.expandAll();
this.tree.animate = animate;
Ext.ux.tree.TreeFilterX.superclass.filter.apply(this, arguments);

} // eo function filter
// }}}
// {{{
/**
* Filter by a function. The passed function will be called with each
* node in the tree (or from the startNode). If the function returns true, the node is kept
* otherwise it is filtered. If a node is filtered, its children are also filtered.
* Shows parents of matching nodes.
*
* @param {Function} fn The filter function
* @param {Object} scope (optional) The scope of the function (defaults to the current node)
*/
,filterBy:function(fn, scope, startNode) {
startNode = startNode || this.tree.root;
if(this.autoClear) {
this.clear();
}
var af = this.filtered, rv = this.reverse;

var f = function(n) {
if(n === startNode) {
return true;
}
if(af[n.id]) {
return false;
}
var m = fn.call(scope || n, n);
if(!m || rv) {
af[n.id] = n;
n.ui.hide();
return true;
}
else {
n.ui.show();
var p = n.parentNode;
while(p && p !== this.root) {
p.ui.show();
p = p.parentNode;
}
return true;
}
return true;
};
startNode.cascade(f);

if(this.remove){
for(var id in af) {
if(typeof id != "function") {
var n = af[id];
if(n && n.parentNode) {
n.parentNode.removeChild(n);
}
}
}
}
} // eo function filterBy
// }}}

}); // eo extend

// eof

galdaka
17 Dec 2008, 1:06 PM
Hi,

Awesome!! ;)

I

jsakalos
17 Dec 2008, 1:35 PM
Re 2 trees: Same id somewhere?
Re textfield: It is not a part of tree filter, I can make example with clearable textfield though.

galdaka
17 Dec 2008, 1:39 PM
Now works fine! ;)

jsakalos
17 Dec 2008, 1:53 PM
I've changed textfield to triggerfield. Also Esc clears it.

galdaka
18 Dec 2008, 12:26 AM
The plugin works fine!! Thanks again.

Would be great, a specific textfield for search with this features:

1) Auto-adjust or auto-resize to panel tbar.
2) Two state of button: Initial state with "Search" icon and when you write in it change to "Clear" icon.

P.D: In your example, context menu is wrong (see image)

Greetings,

jsakalos
18 Dec 2008, 2:36 AM
Re Search field: This is not part of TreeFilterX so every user can implement its own field. The behavior you describe can be achieved quite easily.

Re Context menu: Try to refresh cache - I cannot reproduce it.

galdaka
18 Dec 2008, 2:48 AM
Re Context menu: Try to refresh cache - I cannot reproduce it.

I use IE7. I refresh the cache. Not work. :-?

Thanks,

jsakalos
18 Dec 2008, 3:04 AM
Oh yes, IE again... I don't have time to fix it now as I force my users to Firefox. Perhaps css needs some tweaking.

galdaka
2 Jan 2009, 4:50 AM
Hi again,

I quit textfield and I replace by 'Searchfield'. You can view the results in http://www.jadacosta.es/

But I have a problem, in the second panel searhfield not appears! If I change he xtype by "textfield" works fine.

Any suggestions?

Thanks in advance,

jsakalos
2 Jan 2009, 9:18 AM
Are you trying to use same instance of searchfield? Or, you may need to clone some objects instead of plainly reusing them. Just guessing...

kazon
4 Jan 2009, 7:33 PM
Hi Saki,

I was wondering if you've had any reports of the filter disappearing if it's put on a form which can be closed.

Here is my scenario:

The first time I open my form, the filter appears fine, however, it disappears if I close, then re-open the form.

My code is as follows:

Ext.ux.tree.TreeFilterX and Ext.ux.tree.RemoteTreePanel remains unchanged while remotetree.js has been adapted to look like the following:



// vim: sw=4:ts=4:nu:nospell:fdc=4
/**
* Ext.ux.tree.RemoteTreePanel Extension Example Application
*
* @author Ing. Jozef Sakáloš
* @copyright (c) 2008, by Ing. Jozef Sakáloš
* @date 5. April 2008
* @version $Id: remotetree.js 99 2008-12-17 19:24:43Z jozo $
*
* @license remotetree.js is licensed under the terms of the Open Source
* LGPL 3.0 license. Commercial use is permitted to the extent that the
* code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
*
* License details: http://www.gnu.org/licenses/lgpl.html
*/

/*global Ext, Example, WebPage */

Ext.ns('Ext.ux.tree');
Ext.BLANK_IMAGE_URL = '../../resources/images/default/s.gif';
Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
//Example.version = 'beta-1';

Ext.ux.tree.treeView = new Ext.ux.tree.RemoteTreePanel({
id: 'remotetree'
,title: 'Online Users'
,autoScroll: true
,rootVisible: false
,root:{
nodeType: 'async'
,id: 'root'
,text: 'Root'
,expanded: true
,uiProvider: false
}
,loader: {
url: 'process-request.php'
,preloadChildren: true
,baseParams:{
cmd: 'getTree'
,treeTable: 'tree'
,treeID: 1
}
}
,tbar:['Filter:', {
xtype: 'trigger'
,triggerClass: 'x-form-clear-trigger'
,onTriggerClick: function() {
this.setValue('');
Ext.ux.tree.treeView.filter.clear();
}
,id: 'filter'
,width: 200
,enableKeyEvents: true
,listeners:{
keyup:{buffer: 150, fn:function(field, e) {
if(Ext.EventObject.ESC == e.getKey()) {
field.onTriggerClick();
}
else {
var val = this.getRawValue();
var re = new RegExp('.*' + val + '.*', 'i');
Ext.ux.tree.treeView.filter.clear();
Ext.ux.tree.treeView.filter.filter(re, 'text');
}
}}
}
}]
,tools:[{
id: 'refresh'
,handler: function() {
Ext.ux.tree.treeView.actions.reloadTree.execute();
}
}]
});
Ext.ux.tree.treeView.filter = new Ext.ux.tree.TreeFilterX(Ext.ux.tree.treeView);


And is used as follows:



Ext.ns('Ext.ux.tree');
MyDesktop.ChatWindow = Ext.extend(Ext.app.Module, {
appType: 'accordion',
id: 'chat-win',
init: function(){
this.launcher = {
text: 'Member Chat',
iconCls: 'accordion',
handler: this.createWindow,
scope: this
}
},
createWindow: function(){

var desktop = this.app.getDesktop();
var win = desktop.getWindow('chat-win');
if(!win){
win = desktop.createWindow({
id: 'chat-win',
title: 'Buddy List',
width: 250,
height: 400,
iconCls: 'icon-expand'
,x: 400
,y: 220
,plain: true
,layout: 'fit'
,closable: true
,border: false,
//,maximizable: true,
tbar: [{
tooltip: 'Add a new user',
iconCls: 'user-add'
},' ',{
tooltip: 'Remove the selected user',
iconCls: 'user-delete'
}],
items: Ext.ux.tree.treeView
});
}
win.show();
}
});



Maybe something that I did wrong, but can't see it. Please see the attached images

Regards,
kazon

jsakalos
5 Jan 2009, 2:18 AM
TreeFilterX has no UI. It is only filtering engine that is used by a tree. That what you see on the screen at the example page is just ordinary TextField with some listeners attached. Thus, the problem is not TreeFilterX disappears, but TextField disappears.

Next, I do not see any FormPanel in the code you posted.

And last, the recommended way of working with closable Ext windows is to set closeAction:'hide' and re-using them, not creating new ones over and over.

kazon
6 Jan 2009, 12:15 PM
Saki,

Thanks for the explanations. Everything is working as I expect it to now, and it was just by chance that I decided to use closeAction:'hide' until I heard from you. You guys keep up the good work that you're doing on this framework and I hope to have something developed to make you guys proud ;)

Thanks again and I'm sure this won't be the last time you hear from me.

-kazon-

kazon
8 Jan 2009, 11:04 PM
Hi Again Saki,

Have you or anyone else used your Ext.ux.tree.RemoteTreePanel class to have it work with multiple contextMenus? I'm trying to define one menu to use when the user clicks on a folder node (this is working as it should) and another menu to use when the user clicks on a leaf, which in my case will be user names. I can't figure out a way to make this work using your class. I've tried to define another contextMenu which I call leafContextMenu and in the onContextMenu function, I check node.isLeaf() to determine whether or not to use the standard contextMenu or my new leafContextMenu. Nothing seems to work.

Regards,
-kazon-

jsakalos
9 Jan 2009, 6:22 AM
Just override onContextMenu method of RemoteTreePanel. That is the method responsible for showing the context menu.

kazon
9 Jan 2009, 10:11 AM
Hi Saki,

All is fine now. I guess I was working and looking at the problem for too long and way into the morning. As developer's we never want to quit coding even though we know we should and especially if we're stuck.

I figured that the onContextMenu method was where the magic happened, and was overriding it just as you mentioned, but I just could'nt get the code I was writing to work. After reading your reply, I was able to get my application working within 4 lines of code.

Thanks again!
-kazon-

jsakalos
9 Jan 2009, 10:31 AM
Congrats! I'm glad your making successes. :) :) :)

zhegwood
16 Jan 2009, 12:26 PM
Does this filter/extension have to apply to a RemoteTreePanel object or can I use it on a regular TreePanel? I'm trying to get this filter to work on a TreePanel that is populated with straight json and not through a url... Reference this thread:

http://www.extjs.com/forum/showthread.php?t=4595&highlight=TreePanel

Thanks.
-Z

Here's what I have so far and I'm able to display and filter... However, this is not working in an Ext.form.FormPanel & that's the ultimate goal. Any ideas?

"this.body is null" is the error I get in firebug when I extend Ext.form.FormPanel



Ext.namespace("Zach");

Zach.TestPanel = Ext.extend(Ext.Panel,{

initComponent: function() {

var treepanel = new AV.RemoteTreePanel({
id: 'remotetree',
autoScroll: true,
rootVisible: false,
/*
root: {
nodeType: 'async',
id: 'root',
text: 'Root',
expanded: true,
uiProvider: false
},
*/
loader: new Ext.tree.TreeLoader(),
tbar: [
'Filter:',
{
xtype: 'trigger',
triggerClass: 'x-form-clear-trigger',
onTriggerClick: function() {
this.setValue('');
treepanel.filter.clear();
},
id: 'filter',
enableKeyEvents: true,
listeners: {
keyup: {
buffer: 150,
fn: function(field,e) {
if(Ext.EventObject.ESC==e.getKey()) {
field.onTriggerClick();
} else {
var val=this.getRawValue();
var re=new RegExp('.*'+val+'.*','i');
treepanel.filter.clear();
treepanel.filter.filter(re,'text');
}
}
}
}
}
]
});
treepanel.filter = new AV.TreeFilterX(treepanel);

// json data describing the tree
var json = [
{
id: 1,
org_id: 0,
parent_issue_type_id: 0,
text: "Architectural Request",
issue_type_desc: "Architectural Request",
prodedures: "1) Step 1",
create_date: "01/14/2009",
is_active: 1,
is_global: 1,
children:
[
{
id: 4,
org_id: 0,
parent_issue_type_id: 1,
text: "Architectural Request - Landscaping",
issue_type_desc : "Landscaping",
prodedures: "",
create_date: "01/15/2009",
is_active: 1,
is_global: 1,
expanded:true,
children:[]
}
]
},
{
id: 2,
org_id: 0,
parent_issue_type_id: 0,
text: "Maintenance Request",
issue_type_desc: "Maintenance Request",
prodedures: "blah blah blah",
create_date: "01/14/2009",
is_active: 1,
is_global: 1,
expanded :true,
children:[]
},
{
id: 3,
org_id: 0,
parent_issue_type_id: 0,
text: "Violation",
issue_type_desc: "Violation",
prodedures : "blah blah blah",
create_date: "01/14/2009",
is_active: 1,
is_global: 1,
expanded:true,
children:[]
}
];

// set the root node
var root=new Ext.tree.AsyncTreeNode({
text: 'Testing',
draggable: false,
id: 'source',
children: json
});
treepanel.setRootNode(root);

Ext.apply(this,{
items:
[
treepanel
]
});

Zach.TestPanel.superclass.initComponent.apply(this,arguments);
},

onRender: function() {

Zach.TestPanel.superclass.onRender.apply(this,arguments);

}

});
Ext.reg("formpanelzach",Zach.TestPanel);

jsakalos
16 Jan 2009, 3:45 PM
Filter should work anywhere; it has no UI so I don't see any reason for not-working in FormPanel.

If still in troubles, post please your simplified but complete showcase that I can copy to my server and run and troubleshoot.

jsakalos
19 Jan 2009, 9:01 AM
Once again, FileTreeX has no UI so if you have difficulties with displaying anything it is most likely not related to the extension. Also, I wouldn't discuss off-topic matters here to not confuse other readers.

Post please your question/problem in a separate thread if it is not a FileTreeX specific question/problem.

zhegwood
19 Jan 2009, 10:05 AM
OK, removed it...

I am kind of stuck right now. I must admit that I don't have a lot of experience working with tree panels, but I'm trying to use this extension to choose a form value from a tree. The reason I'm using a tree and not a combo is some child nodes will have the same values, but fall under different parent nodes.

Anyway, I want to select the first node after the filter has been applied and then have the ability to arrow up and arrow down through the filtered nodes and press "Enter" to select the highlighted node.

I figure the selection would happen where my comment is, and I figure I can add key listeners to the tree panel to handle the arrow up, arrow down and enter, but I can't find how to get access to the nodes through the tree object. I've used this.tree.nodeHash and also this.tree.getSelectionModel(), but I don't see where the nodes are listed:


filter: function(value,attr,startNode) {

// expand start node
if(false!==this.expandOnFilter) {
startNode=startNode||this.tree.root;
var animate=this.tree.animate;
this.tree.animate=false;
startNode.expand(true,false,function() {

// call parent after expand
AV.TreeFilterX.superclass.filter.call(this,value,attr,startNode);

}.createDelegate(this));
this.tree.animate=animate;

console.log(this.tree,this.tree.getSelectionModel()); //don't see a getNodes() or similar function or a list of nodes w/in the properties
for (var node in this.tree.nodeHash) {
console.log(node + " " + this.tree.nodeHash[node])
//only returns text
}

} else {
// call parent
AV.TreeFilterX.superclass.filter.apply(this,arguments);
}

},

jsakalos
19 Jan 2009, 11:08 AM
Have you seen http://checktree.extjs.eu ? It can be combined with TreeFilterX.

zhegwood
19 Jan 2009, 1:00 PM
Have you seen http://checktree.extjs.eu ? It can be combined with TreeFilterX.

Nope, but will use that in the future for sure. Thanks!

Here's what I did to automatically select the first non-hidden node & also create a reference to the node list from the tree. If there's an easier/better way to do this, let me know but this works as expected:



filter: function(value,attr,startNode) {

// expand start node
if(false!==this.expandOnFilter) {
startNode=startNode||this.tree.root;
var animate=this.tree.animate;
this.tree.animate=false;
startNode.expand(true,false,function() {

// call parent after expand
AV.TreeFilterX.superclass.filter.call(this,value,attr,startNode);

}.createDelegate(this));
this.tree.animate=animate;

/* Creates an array of all nodes */
var tempArray = [];
function addToArray (node) {
node.eachChild(function(child){
tempArray.push(child);
if (child.hasChildNodes()) {
addToArray(child);
}
});
}
addToArray(startNode);
this.tree.nodeArray = tempArray;

//highlights the first non-hidden node
for (var a = 0; a < this.tree.nodeArray.length; a++) {
if (!this.tree.nodeArray[a].hidden) {
this.tree.nodeArray[a].select();
break;
}
}
} else {...}}

jsakalos
19 Jan 2009, 4:41 PM
There is always other way, however, if your way reliably works it is the way for you.

jbird526
2 Feb 2009, 1:52 PM
I am having a bit of trouble figuring out where to make changes to make the nodes in CheckTreePanel expanded on render. I would like to have all the nodes and leaves expanded when the widget renders. I know Saki is the one to ask, but other comments are appreciated.

jsakalos
2 Feb 2009, 2:06 PM
Wrong forum - this is about TreeFilterX. Anyway: root.expand(true, false)

jbird526
3 Feb 2009, 12:29 PM
Wrong forum - this is about TreeFilterX. Anyway: root.expand(true, false)

Sorry I was jumping around different forums and entered my question in the wrong area.

honlin
6 Feb 2009, 1:35 AM
the TreeFilterX works well with ext 2.2, but not in ext 2.02, have you guys the same problem?

jsakalos
6 Feb 2009, 1:49 AM
Be please specific so I know where to look.

demongloom
19 Feb 2009, 7:08 AM
Hello, I have a problem with filtering after tree data reload.
When I doing filtering through user input, or firebug console it filter ok,
but when i tried to apply filtering after store reload, it's filter, but not correct for nodes with childs. In these nodes I getting all subnodes instead just filtered.

I have use code from remote tree example.

My code is:


/**
* Trigger field for filter input, based on remote tree example
**/
o.navigation.filter_input = new Ext.form.TriggerField({

triggerClass: 'x-form-clear-trigger',
width : 300,

onTriggerClick : function() {

this.setValue('');
o.navigation.filter.clear();

},

enableKeyEvents : true,
listeners: {

keyup : {

buffer: 150,
fn: function(field, e) {

if(Ext.EventObject.ESC == e.getKey()) {

field.onTriggerClick();

} else {

var val = this.getRawValue();

// I've put try catch to avoid error on non-correct regexp entered by user
try {

var re = new RegExp('.*' + val + '.*', 'i');
o.navigation.filter.clear();
o.navigation.filter.filter(re, 'text');

} catch(e) {}

}

}

}

}
});

/**
* My navigation tree
* It have stucture
* +- Overrides
* | +- Nodes
* +- Global
* +- Nodes
* +- Sub nodes
**/
o.navigation.tree = new Ext.tree.TreePanel({

title : "Navigation:",
region : "west",
collapsible : false,
split : false,
width : 350,
margins : "3 0 3 3",
border : true,
autoScroll : true,
animate : false,
editable : false,
enableDD : false,
rootVisible : false,
useArrows : false,
lines : true,

columns:[
{
header : 'Title',
width : 250,
dataIndex : 'title'
}
],

root: {
nodeType : 'async',
id : 'root',
text : 'Root',
expanded : true,
uiProvider : false
},

loader: new Ext.tree.TreeLoader({

url : '?_p=payments__order&_pa=get_navigation&_rt=text/json',
preloadChildren : true,
clearOnLoad : true,

listeners: {

beforeload: function() {

o.navigation.button_show.disable();
o.navigation.button_show.setText("Please choose item to display");
o.navigation.tree.getEl().mask("Loading...");

},

load: function() {

o.navigation.tree.getEl().unmask();

}

}

}),

tbar : [
"Search:&nbsp;",
o.navigation.filter_input
],

tools : [
{
id : "minus",
qtip : "Collapse all",
handler : function() {

var n = o.navigation.tree.getNodeById("G_1");
if(n) { n.collapseChildNodes() };

}
},
{
id : "plus",
qtip : "Expand all groups",
handler : function() {

var n = o.navigation.tree.getNodeById("G_1");
if(n) { n.expandChildNodes() };

}
},
{
id : "refresh",
qtip : "Refresh data",
handler : function() {

o.navigation.reload();

}
}
],

listeners: {

render: function() {

this.tbar2 = new Ext.Toolbar({

renderTo: this.tbar,
items: [
o.navigation.button_show
]

});

},

click: function(node) {

if( node.id == "OVERRIDES" ) {
return; }

o.navigation.button_show.enable();
o.navigation.button_show.setText("Show '" + node.attributes.text + "'");
o.navigation.node = node;

},

collapseNode: function(node) {

switch( node.attributes.etype ) {

case "P":

node.getUI().getIconEl()
.addClass("icon_e_profile")
.removeClass("icon_e_profile_");

break;

}

},

expandNode: function(node) {

var render = function(node) {

if( node.attributes.is_provider ) {
node.getUI().getTextEl().addClass("c_blue"); }

if( !node.getUI().getIconEl() ) {
return; }

switch( node.attributes.etype ) {

case "OVERRIDES":
node.getUI().getIconEl().addClass("icon_e_override");
break;

case "G":
node.getUI().getIconEl().addClass("icon_e_global");
break;

case "P":

if( !node.isExpanded() ) {

node.getUI().getIconEl()
.addClass("icon_e_profile");

} else {

node.getUI().getIconEl()
.removeClass("icon_e_profile")
.addClass("icon_e_profile_");

} // if

break;

case "W":
node.getUI().getIconEl().addClass("icon_e_website");
break;

}

if( !node.isLeaf() && node.isLoaded() ) {
node.eachChild(render); }

};

render(node);

}

}

});

/**
* Filtering object
**/
o.navigation.filter = new Ext.ux.tree.TreeFilterX(o.navigation.tree);

/**
* My interface to reload whole tree and apply filter after reloading.
* Same code as in filter_input onKeyUp.
* Also i've tried to put this code into loader's listener->onload, but got same effect.
**/
o.navigation.reload = function() {

o.navigation.tree.getLoader().load( o.navigation.tree.getRootNode(), function() {

var flt = o.navigation.filter_input.getRawValue();

if( flt ) {

try {

var re = new RegExp('.*' + flt + '.*', 'i');
o.navigation.filter.clear();
o.navigation.filter.filter(re, 'text');

} catch(e) { }

} // if

} );

};

jsakalos
19 Feb 2009, 12:49 PM
TreeFilterX is good for fully loaded trees. If the filter at http://remotetree.extjs.eu/ has the functionality you would want but your code application doesn't work as expected then there must be a difference.

demongloom
19 Feb 2009, 1:36 PM
Ok, but why when tree is loaded filtering work correctly (mean user input) ? When i should execute my code, on which tree or loader event i should bind?

jsakalos
19 Feb 2009, 2:13 PM
You see, the tree filtering is just selective hiding of tree node UIs based on the attribute you want to search in and text or RegExp to search for. If the tree is not loaded there are no nodes, no nodes UIs to hide.

For more details see http://extjs.eu/docs/?class=Ext.ux.tree.TreeFilterX&member=filter and you can also read the code - it is a simple one.

badgerd
22 Mar 2009, 7:03 PM
Saki,

I am using your TreeFilterX plugin with http://extjs.com/forum/showthread.php?t=37704&page=3, and was wondering how I can make the "loading" mask work on it.

Apologies for the double post. This is really doing my head in and have been trying to get a solution working all day.

jsakalos
22 Mar 2009, 7:21 PM
TreeFilterX was made for fully loaded trees and it has no own UI; it's just filtering routine. Therefore, I'm afraid, you're on the wrong track. Or am I missing something?

badgerd
22 Mar 2009, 7:38 PM
Saki,

I have the tree loading asynchronously as each node is expanded.

I am using 3 Extensions to build my tree:

TreeCheckNodeUI - http://extjs.com/forum/showthread.php?t=27267 - So I can use checkboxes
TreePanelFilter - http://extjs.com/forum/showthread.php?t=37704

and your filter so that I can get parent nodes appearing while it is filtered.

While the filter is running I wish to have masking to let the user know that it is currently running and does not appear to be "frozen".

I could be asking the wrong person, and should probably be asking the author of the TreePanelFilter plugin?

jsakalos
23 Mar 2009, 1:41 AM
Now I understand; I thought you want to load while filtering and display load mask while loading.

Take a look at source of http://remotetree.extjs.eu, there you see:


,listeners:{
keyup:{buffer:150, fn:function(field, e) {
if(Ext.EventObject.ESC == e.getKey()) {
// show mask
field.onTriggerClick();
// hide mask
}
else {
var val = this.getRawValue();
var re = new RegExp('.*' + val + '.*', 'i');
// show mask
Example.tree.filter.clear();
Example.tree.filter.filter(re, 'text');
// hide mask
}
}}



You show and hide load mask at the bold points in your code.

zhegwood
27 Mar 2009, 9:54 AM
I have this up and running and working, but it seems there's a problem with the selectNext() function when the tree nodes are filtered.

It doesn't look like the selectNext() function accounts for hidden nodes in a tree and when you arrow down to a hidden node, the key listeners break and nothing is selected.

I've tried overriding the selectNext() function, but have had no luck so far. Just reporting this in case anyone else wants to take a shot at it.

jsakalos
27 Mar 2009, 10:19 AM
TreeFilter(X) does nothing else in addition to hiding non-matching nodes. selectNext could check if the next node is hidden or not and decide on the result.

zhegwood
27 Mar 2009, 11:49 AM
This seems to work for a selectNext and selectPrevious overrides:



Ext.override(Ext.tree.DefaultSelectionModel,{

selectNext : function(node){
if (!node) {
node = this.selNode || this.lastSelNode;
if(!node){
node = this.tree.getRootNode();
}
}
this.tree.getTreeEl().focus();
if (node.firstChild) {
if (!node.firstChild.hidden) {
return this.select(node.firstChild);
} else {
this.selectNext(node.firstChild);
}
} else if (node.nextSibling) {
if (!node.nextSibling.hidden) {
return this.select(node.nextSibling);
} else {
this.selectNext(node.nextSibling);
}
} else {
var newS = null;
node.parentNode.bubble(function(){
if(this.nextSibling){
newS = this.nextSibling;
} else {
return false;
}
});
if (!newS.hidden) {
return this.select(newS);
} else {
this.selectNext(newS);
}
}
},

selectPrevious : function(node){
if (!node) {
node = this.selNode || this.lastSelNode;
}
if(node === this.tree.getRootNode()){
return null;
}
this.tree.getTreeEl().focus();

if (node.previousSibling) {
if (node.previousSibling.childNodes.length > 0){
if (!node.previousSibling.lastChild.hidden) {
return this.select(node.previousSibling.lastChild);
} else {
this.selectPrevious(node.previousSibling.lastChild);
}
} else {
if (!node.previousSibling.hidden) {
return this.select(node.previousSibling);
} else {
this.selectPrevious(node.previousSibling);
}
}
} else if (node.parentNode) {
if (!node.parentNode.hidden) {
return this.select(node.parentNode);
} else {
this.selectPrevious(node.parentNode);
}
}
}
});

nayato
17 Apr 2009, 8:41 AM
Here's modded version which works a bit better for me:

Ext.override(Ext.tree.DefaultSelectionModel,{
selectNext : function(node){
if (!node) {
node = this.selNode || this.lastSelNode;
if(!node){
node = this.tree.getRootNode();
}
}
this.tree.getTreeEl().focus();
if (node.isExpanded() && node.firstChild) {
if (!node.firstChild.hidden) {
return this.select(node.firstChild);
}
return this.selectNext(node.firstChild);
}
var newS = null;
node.bubble(function(){
if(this.nextSibling){
newS = this.nextSibling;
return false;
}
});
if (newS !== null) {
if (!newS.hidden) {
return this.select(newS);
}
return this.selectNext(newS);
}
return null;
},

selectPrevious : function(node){
if (!node) {
node = this.selNode || this.lastSelNode;
}
if(node === this.tree.getRootNode()){
return null;
}
this.tree.getTreeEl().focus();

var prevNode = node.previousSibling;
if (prevNode) {
if (prevNode.hidden === true) {
return this.selectPrevious(prevNode);
}
while (prevNode.hasChildNodes() && prevNode.isExpanded()) {
var subNode = prevNode.lastChild;
while (subNode !== null && subNode.hidden === true) {
subNode = subNode.previousSibling;
}
if (subNode !== null) {
prevNode = subNode;
}
}
return this.select(prevNode);
}
var nodeParent = node.parentNode;
if (nodeParent) {
if (nodeParent.parentNode === null && !nodeParent.ownerTree.rootVisible) {
return null;
}
return this.select(nodeParent);
}
}
});

jsakalos
18 Apr 2009, 2:57 PM
Just to make sure, you're talking about key navigation on filtered tree, right?

If so, could you please make a demo I could test so that I could integrate your patches to TreeFilterX?

msairam.28@gmail.com
16 Jun 2009, 8:56 AM
Hello everyone,

I am using this TreeFilterX plugin to filter my tree panel and it works perfectly fine and thanks to Saki for this wonderful plugin however there is small problem with it. I am using another plugin called Ext.ux.tree.State to maintain the state of my Tree Panel but when I (clear out the filter textfield or press the "X" button that is adjacent to the filter textfield in the TreeFilterX plugin) all the nodes in my tree panel are expanded and the state of my tree is lost. Can anyone tell me how do I maintain the state of my tree after I have cleared out the fields that I want to search and I do not want to expand all the nodes once I press the "X" button

I have provided the code below.



var navTree = new Ext.tree.TreePanel({
id: 'navTree',
animate: true,
region: 'west',
collapsible: true,
title: 'Navigation',
xtype: 'treepanel',
width: 200,
autoScroll: true,
split: true,
stateful: true,
root: treeNodeNav,
rootVisible: false,
plugins: [new Ext.ux.tree.State()],
tbar:['Filter:', {
xtype:'trigger'
,triggerClass:'x-form-clear-trigger'
,onTriggerClick:function() {
this.setValue('');
// Some action has to be performed here but I am not sure about it becoz dis is the function that is triggered when I clear out the search field in the TreeFilterX plugin and all
navTree.filter.clear() function causes all my nodes to expand thereby losing my state.
navTree.filter.clear();
navTree.collapseAll();
}
,id:'filter'
,enableKeyEvents:true
,listeners:
{
keyup:{buffer:150, fn:function(field, e) {
if(Ext.EventObject.ESC == e.getKey()) {
field.onTriggerClick();
}
else {
var val = this.getRawValue();
var re = new RegExp('.*' + val + '.*', 'i');
navTree.filter.clear();
navTree.filter.filter(re, 'text');
}
}}
}
}]
});

navTree.filter = new Ext.ux.tree.TreeFilterX(navTree);

I am not able to figure out this so can anyone guide me regarding this.
Thanks in advance for any help.

Thanks and Regards
Sairam

jsakalos
16 Jun 2009, 9:30 AM
Hmmm, tree filter + tree state somehow doesn't fit together, at least for me. If I have a large tree I want to quickly find what's there, then I want to find something else, and else, etc. I don't care about state.

msairam.28@gmail.com
16 Jun 2009, 9:36 AM
Thanks for the reply Saki but can you suggest me a way so that all the nodes do not expand once I click on the "X button and can you also guide me so that I can maintain my state on this tree panel. Any help is greatly appreciated.

Thanks in advance
Sairam

jsakalos
16 Jun 2009, 1:49 PM
Try to set expandOnFilter:false for TreeFilterX, however, I'm not sure if it helps. The last, but best, resort is to study code(s).

msairam.28@gmail.com
16 Jun 2009, 1:55 PM
I tried that option and when I put expandOnFilter:false it maintains the state properly but it only searches among the parents. I does not search among the children of all nodes. The state is maintained but search functionality is lost. I will look through the code of the plugin and try to figure it out.

Thanks
Sairam

jsakalos
16 Jun 2009, 1:59 PM
That is because nodes are not loaded/rendered. If you can ensure that you preload the tree w/o expanding it (see http://checktree.extjs.eu to find out how to do it) then you could perhaps use that option.

msairam.28@gmail.com
16 Jun 2009, 2:08 PM
The checktree doesnt have the filter plugin embedded to it ?? I am not sure that I followed you on that one. If you dont mind can you elaborate on this one.

Thanks in advance
Sairam

jsakalos
16 Jun 2009, 2:10 PM
Please read. The link contains a method of preloading the whole tree.

zoro2003
23 Jul 2009, 6:00 AM
Congratulation ... , nice tree filter =D> can we apply filter onto tabs control and stacked panels ? :-? , i hope if there is client-side example of tree filtering

thanks

marcora
4 Aug 2009, 1:35 AM
I am trying to use the filterBy method of TreeFilterX but it is not behaving as it should in my hands (I am a total noob on Ext). This is the filter function:


tree.filter.filterBy(function(node) {
if (node.isLeaf()) {
return re.test(node.text);
} else {
return false;
}
});

I just want to match on leaf nodes, but by filtering out branch nodes it seems that the children are filtered as well. Is this the desired behavior for a filter that is supposed to show children even if parent is filtered out?

Thanx in advance for your help and consideration,

Dado

jsakalos
4 Aug 2009, 2:13 AM
it is not behaving as it should

What does it mean? A Firebug error?

Are you sure that you need TreeFilterX? It looks like you want your own filtering logic.

Sujan
23 Oct 2009, 7:16 AM
Hey Saki,

small bugreport on TreeFilterX. In some special cases is doesn't filter correctly.

1. Go to http://remotetree.extjs.eu/
2. Click on the filter text field, hit "g"
3. First leaf is called "Newasfd" - no 'g' in it => wrong.

I debugged this locally and come to the following conclusion:
Ext.ux.tree.TreeFilterX.filter calls its parent's filter in the callback function for startNode. startNode is a AsyncTreeNode that is a TreeNode, and in TreeNode's documentation you can find the following note for the callback function:

callback : Function(optional) A callback to be called when expanding this node completes (does not wait for deep expand to complete). Called with 1 parameter, this node.So filter is called before all the childnodes had the time to expand.

The problem dissappears after all nodes had the time to expand, e.g. entering a second character a few miliseconds later and so on.

Possible solutions:


If you only use the filter on the tree's rootnode you can rootnode.expandAll() before
tree.filter.filter(re, 'text'); in your button's code and get rid of the startNode.expand()-Call in Ext.ux.tree.TreeFilterX.filter(). Just call the superclass.filter directly.
Perhaps there is some way to determine when all childnodes were successfully expanded with an event or something and use this to trigger the filter. I couldn't find one :(

It's quite an obscure bug, but it's there and perhaps we can find a solution.

- Jan

jsakalos
25 Oct 2009, 12:16 AM
I could not reproduce it. The demo tree is editable so anybody can change the tree while you're trying to filter it. It would call for a "static" showcase.

Sujan
25 Oct 2009, 7:12 AM
Okay, I hacked a static example here:
http://janpiotrowski.de/ext/remotetree.html

Go there, directly type "a" into the filter box and see "child b" appear, too.
(Only works if you didn't touch the tree before entering a into the box)

- Jan

jsakalos
26 Oct 2009, 12:28 AM
Yes, you're right. I think we can sacrifice possibility to filter a branch (I've never used it anyway) for the filter accuracy. Anybody used startNode different from tree root?

The code would then be:


// vim: ts=4:sw=4:nu:fdc=4:nospell
/*global Ext */
/**
* @class Ext.ux.tree.TreeFilterX
* @extends Ext.tree.TreeFilter
*
* <p>
* Shows also parents of matching nodes as opposed to default TreeFilter. In other words
* this filter works "deep way".
* </p>
*
* @author Ing. Jozef Sakáloš
* @version 1.0
* @date 17. December 2008
* @revision $Id: Ext.ux.tree.TreeFilterX.js 752 2009-10-26 08:25:46Z jozo $
* @see <a href="http://extjs.com/forum/showthread.php?p=252709">http://extjs.com/forum/showthread.php?p=252709</a>
*
* @license Ext.ux.tree.CheckTreePanel is licensed under the terms of
* the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
* that the code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
*
* <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
* target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
*
* @forum 55489
* @demo http://remotetree.extjs.eu
*
* @donate
* <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
* <input type="hidden" name="cmd" value="_s-xclick">
* <input type="hidden" name="hosted_button_id" value="3430419">
* <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif"
* border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
* <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
* </form>
*/

Ext.ns('Ext.ux.tree');

/**
* Creates new TreeFilterX
* @constructor
* @param {Ext.tree.TreePanel} tree The tree panel to attach this filter to
* @param {Object} config A config object of this filter
*/
Ext.ux.tree.TreeFilterX = Ext.extend(Ext.tree.TreeFilter, {

// {{{
/**
* Filter the data by a specific attribute.
*
* @param {String/RegExp} value Either string that the attribute value
* should start with or a RegExp to test against the attribute
* @param {String} attr (optional) The attribute passed in your node's attributes collection. Defaults to "text".
*/
filter:function(value, attr, startNode) {

var animate = this.tree.animate;
this.tree.animate = false;
this.tree.expandAll();
this.tree.animate = animate;
Ext.ux.tree.TreeFilterX.superclass.filter.apply(this, arguments);

} // eo function filter
// }}}
// {{{
/**
* Filter by a function. The passed function will be called with each
* node in the tree (or from the startNode). If the function returns true, the node is kept
* otherwise it is filtered. If a node is filtered, its children are also filtered.
* Shows parents of matching nodes.
*
* @param {Function} fn The filter function
* @param {Object} scope (optional) The scope of the function (defaults to the current node)
*/
,filterBy:function(fn, scope, startNode) {
startNode = startNode || this.tree.root;
if(this.autoClear) {
this.clear();
}
var af = this.filtered, rv = this.reverse;

var f = function(n) {
if(n === startNode) {
return true;
}
if(af[n.id]) {
return false;
}
var m = fn.call(scope || n, n);
if(!m || rv) {
af[n.id] = n;
n.ui.hide();
return true;
}
else {
n.ui.show();
var p = n.parentNode;
while(p && p !== this.root) {
p.ui.show();
p = p.parentNode;
}
return true;
}
return true;
};
startNode.cascade(f);

if(this.remove){
for(var id in af) {
if(typeof id != "function") {
var n = af[id];
if(n && n.parentNode) {
n.parentNode.removeChild(n);
}
}
}
}
} // eo function filterBy
// }}}

}); // eo extend

// eof


If there are no negative responses from users then I'll make the above code "official" and I'll move it to the first post.

Sujan
26 Oct 2009, 4:45 AM
I think you could keep the old code for the case that startNode is not the root node. Then it still works semi-good (like before) but is perfect for filtering on the root node.

lepelerin
19 Mar 2010, 4:21 AM
Hi guys, so here is my case, i want to use the TreeFilterX on 4 trees showd on the following screenshot:

http://www.pasteall.org/pic/show.php?id=2002

My change will occur to use the filter on the lefest tree ( staffList ).
I want to filter only the first level of nodes but keep there children visible if the parent has been filtered.
On the other trees it works perfectly smoothly :D thanks to saki :).

I was going to change a bit the code of the extention but may be that is not the best way of doing it so I'm asking here.

cheers,

Tom.

jsakalos
19 Mar 2010, 10:39 AM
There could be an option, let's say "deep" (default true), that would switch off recursion and would filter only one level.

lepelerin
19 Mar 2010, 10:47 AM
That sounds good and could probably be helpfull to more than one :)

jsakalos
19 Mar 2010, 10:59 AM
Yeah, it also seems not to be too difficult to implement.

lepelerin
19 Mar 2010, 11:27 AM
Should i give it a shot then and post my results here ?

jsakalos
19 Mar 2010, 12:24 PM
Definitely. I have no time right now.

lepelerin
19 Mar 2010, 2:31 PM
may be a level of depth should be more smart instead of just stopping at level one each time.
I will try to set this as a parameter to the filter method and see how it goes.

tom.

lepelerin
20 Mar 2010, 7:47 AM
So here is my version of your filter :
I just added a global parameter depth.
It can be specified by the constructor or as a parameter of the filter method.



// vim: ts=4:sw=4:nu:fdc=4:nospell
/*global Ext */
/**
* @class Ext.ux.tree.TreeFilterX
* @extends Ext.tree.TreeFilter
*
* <p>
* Shows also parents of matching nodes as opposed to default TreeFilter. In other words
* this filter works "deep way".
* </p>
*
* @author Ing. Jozef Sakáloš
* @version 1.0
* @date 17. December 2008
* @revision $Id: Ext.ux.tree.TreeFilterX.js 589 2009-02-21 23:30:18Z jozo $
* @see <a href="http://extjs.com/forum/showthread.php?p=252709">http://extjs.com/forum/showthread.php?p=252709</a>
*
* @license Ext.ux.tree.CheckTreePanel is licensed under the terms of
* the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
* that the code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
*
* <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
* target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
*
* @forum 55489
* @demo http://remotetree.extjs.eu
*
* @donate
* <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
* <input type="hidden" name="cmd" value="_s-xclick">
* <input type="hidden" name="hosted_button_id" value="3430419">
* <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif"
* border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
* <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
* </form>
*/

Ext.ns('Ext.ux.tree');

/**
* Creates new TreeFilterX
* @constructor
* @param {Ext.tree.TreePanel} tree The tree panel to attach this filter to
* @param {Object} config A config object of this filter
*/
Ext.ux.tree.TreeFilterX = Ext.extend(Ext.tree.TreeFilter, {
/**
* @cfg {Boolean} expandOnFilter Deeply expands startNode before filtering (defaults to true)
*/
expandOnFilter:true,
/**
* @cfg {int} depth automatically stop the filter at a specified depth level of the tree (defaults to true)
*/
depth:true
// {{{
/**
* Filter the data by a specific attribute.
*
* @param {String/RegExp} value Either string that the attribute value
* should start with or a RegExp to test against the attribute
* @param {String} attr (optional) The attribute passed in your node's attributes collection. Defaults to "text".
* @param {TreeNode} startNode (optional) The node to start the filter at.
* @param {int} depth {optional} the maxDepth parameter to stop the filter at.
*/
,filter:function(value, attr, startNode, depth) {

// expand start node
if(false !== this.expandOnFilter) {
startNode = startNode || this.tree.root;
this.depth = depth || true;
var animate = this.tree.animate;
this.tree.animate = false;

startNode.expand(true, false, function() {

// call parent after expand
Ext.ux.tree.TreeFilterX.superclass.filter.call(this, value, attr, startNode);

}.createDelegate(this));
this.tree.animate = animate;

}
else {
// call parent
Ext.ux.tree.TreeFilterX.superclass.filter.apply(this, arguments);
}

} // eo function filter
// }}}
// {{{
/**
* Filter by a function. The passed function will be called with each
* node in the tree (or from the startNode). If the function returns true, the node is kept
* otherwise it is filtered. If a node is filtered, its children are also filtered.
* Shows parents of matching nodes.
*
* @param {Function} fn The filter function
* @param {Object} scope (optional) The scope of the function (defaults to the current node)
*/
,filterBy:function(fn, scope, startNode) {
startNode = startNode || this.tree.root;
if(this.autoClear) {
this.clear();
}
var af = this.filtered, rv = this.reverse;

// set the maxDepth
var maxDepth = this.depth;

var f = function(n) {
// if the depth of the checked node is strictly bigger than
// the max depth, always return true.

if(n.getDepth()>maxDepth){
return true;
}
if(n === startNode) {
return true;
}
if(af[n.id]) {
return false;
}
var m = fn.call(scope || n, n);
if(!m || rv) {
af[n.id] = n;
n.ui.hide();
return true;
}
else {
n.ui.show();
var p = n.parentNode;
while(p && p !== this.root) {
p.ui.show();
p = p.parentNode;
}
return true;
}
return true;
};
startNode.cascade(f);

if(this.remove){
for(var id in af) {
if(typeof id != "function") {
var n = af[id];
if(n && n.parentNode) {
n.parentNode.removeChild(n);
}
}
}
}
} // eo function filterBy
// }}}

}); // eo extend

// eof

so i have a trriger field configured as following :

{
width:200,
xtype:'trigger',
triggerClass:'x-form-clear-trigger',
onTriggerClick:function() {
this.setValue('');
ths.filter.clear();
},
id:'staffFilter',
enableKeyEvents:true,
listeners:{
keyup:{buffer:150, fn:function(field, e) {
if(Ext.EventObject.ESC == e.getKey()) {
field.onTriggerClick();
}
else {
var val = this.getRawValue();
var re = new RegExp('.*' + val + '.*', 'i');
ths.filter.clear();

// overwrite the deptg to 1
ths.filter.filter(re, 'text',null,1);

}
}}
}
}and i finally obtain what i wanted in my tree with the filtered staff and all its children nodes :
http://www.pasteall.org/pic/show.php?id=2016

i join the js file here for you to see and a .patch file.

Thanks for the fast reaction to my question,
Tom.

jsakalos
20 Mar 2010, 1:34 PM
I haven't run it (yet) just skimmed over the source; what is the difference between depth and maxDepth?

lepelerin
20 Mar 2010, 2:50 PM
no difference i just didnt remembered how to get the global :( and i forgot to change it after.
So yeah i could directly use depth.

jsakalos
20 Mar 2010, 5:58 PM
OK, I'll test it but soonest in a week or two, now I'm terribly busy. Nudge me if I forget.

pawelb1973
6 Apr 2010, 3:40 AM
Recently I worked on a remote filter in the tree view.
Local filter (eg such as TreeFilterX) for asynchronous tree freezes the browser and take over all the CPU resources available.
My idea for the remote filtering is the following:
1. search all the items in the SQL table 'tree' pattern matching
2. search path for them
3. of these paths build a tree and give it as a result of filtering

Trick that still need to do, is change the parameters of the tree TreeLoader to add pattern to the query of filtered tree . The rest deals with server-side script (written in PHP for example) to handle the tree view.

If anyone of you is interested in the PHP implementation can be made available (currently I am in preparation for the demo of this solution.)

jsakalos
6 Apr 2010, 3:42 AM
Would be interesting. Post it when you're done.

pawelb1973
7 Apr 2010, 6:44 AM
And Here is a demo of remote filtering on Tree. Just put 'remotetree.zip' in extjs examples directory and unzip. In SQL(MySQL) make table tree by this:
CREATE TABLE IF NOT EXISTS `tree` (
`id` int(11) NOT NULL auto_increment,
`pid` int(11) NOT NULL,
`text` varchar(255) collate utf8_bin default NULL,
`href` varchar(255) collate utf8_bin default NULL,
`iconCls` varchar(255) collate utf8_bin default NULL,
`icon` varchar(255) collate utf8_bin default NULL,
`disabled` tinyint(4) NOT NULL default '0',
`leaf` tinyint(4) NOT NULL default '0',
`expanded` tinyint(4) NOT NULL default '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

edit config.php file and browse to eg. http://127.0.0.1/ext-3.1.1/examples/remotetree/remotetree.html
Should work.
Don't forget to fullfill tree table some data to test wich method is the best - local or remote filtering.

Of course, in this implementation, there are many shortcomings because it was written as prototype but not for production environment.
By the way, where should I look for a class 'ctree'? It was used in 'proces-request.php' but this class not included in archive 'remotetree.zip' . Why?

jsakalos
7 Apr 2010, 8:21 AM
Because ctree.php is proprietary software so I cannot publish it.

pawelb1973
7 Apr 2010, 11:02 PM
Thank You for reply. My first Idea was to build a tree from array that contains paths.
But this idea seems to be difficult to do by client side ( browser).
Server is much more quicker than client so imho this remote filter is a good for async tree with many nodes ( eg. over 2000) and levels.

Did You already tested demo? Any tips?

Off Topic question : Ctree class is a class for using with nested sets tree or other? Who is a author of this class and where ( website) can I found?

jsakalos
8 Apr 2010, 3:44 AM
Sorry, I didn't have time to test it. I'm the author of ctree.

pawelb1973
8 Apr 2010, 10:32 PM
Thank you for your reply. I hope that a solution to the remote filtering trees will be useful.

jsakalos
8 Apr 2010, 10:45 PM
I personally don't have such big trees at present but who knows...

MrSparks
5 Sep 2010, 3:21 AM
@Saki

Have there been any updates since V1.0? http://remotetree.extjs.eu

I've been trying to produce my own Filtered TreePanel for the last week (with partial success) however today came across your RemoteTree plug-in

Are there any known performance issues on the amount of data displayed? i.e. if I were to list 5000 items under 1 node, how would the plug-in perform?

Will definitely be making a donation once I've got it up and running.

Great work and thanks for saving me a huge amount of time and effort!

pawelb1973
5 Sep 2010, 6:19 AM
Hi
How about Paging Tree. There is on the forum snippet and user extention of paging Tree.
My solution for remote filter is for site those had a lot nodes ( parents and leafes). Recurection by client side depend on memory and CPU speed. In many cases, broser ( client side) will be hang 'cause computer resources is not enough to this task.
Have a nice day,
Pawel

jsakalos
5 Sep 2010, 2:43 PM
I don't think that a "paging" tree is a good idea. Tree is hierarchical structure, not a linear sequence of items.

pawelb1973
5 Sep 2010, 3:24 PM
Maybe, but if you have 5000 nodes under selected parent node and You had to wait about 180 sec what you choose?
Wait or paging until you find what you need? I'll be chose paging.

varunach
31 Mar 2011, 7:55 AM
Hi saki,

First of all i'd like to say thanks for making this tool.. it's really help to a LOT of developers.

I'm having a little problem in implementing your tool. Here's the code i'm using inside a TreePanel config:



tbar : ['Filter:', {
xtype : 'trigger',
anchor : '-20',
triggerClass : 'x-form-clear-trigger',
onTriggerClick : function() {
this.setValue('');
treePanel.filter.clear();
},
id : 'filter',
enableKeyEvents : true,
listeners : {
keyup : {
buffer : 150,
fn : function(field, e) {
if (Ext.EventObject.ESC == e.getKey()) {
field.onTriggerClick();
} else {
var val = this.getRawValue();
var re = new RegExp('.*' + val + '.*', 'i');
treePanel.filter.clear();
treePanel.filter.filter(re, 'text');
}
}
}
}
}]


The problem here is that when i haven't expanded a node and searching for any of its child nodes, the trigger isn't able to find it. But if i manually expand the node once, the trigger is working perfectly. What could be the problem? I'm using ExtJS 3.3.1

Thanks

jsakalos
31 Mar 2011, 11:12 AM
This is only for fully loaded trees. It cannot know anything about nodes that are sill on the server.

varunach
31 Mar 2011, 11:18 AM
tree was already fully loaded. I mean the data has already been passed from server to the browser. I got it running by doing this..



treePanel.filter.filter(re, 'text', treePanel.getNodeById('1'));



It wasn't considering the root node configured along with the TreePanel root config option.

mjhaston
5 Jun 2012, 8:56 AM
I'm sorry to add to this years old thread, but I am wondering if anyone has the same issue as me. I got this extension working pretty much out of the box. Plugged it into an existing tree. Finds all instances of my search term perfectly ... but it continues to call my loader over and over until I kill or refresh my browser.

I think this is all of my tree code:




var treeLoaderExt = new Ext.tree.TreeLoader({
dataUrl : '/cgi-bin/cgijson003.pgm',
baseParams : {
library : '',
file : ''
},
listeners : {
load : function(loader, node, response) {
loadAddColumnsBtn();
if (treePanelExt.getNodeById('application-user')) {
Ext.getCmp('login').update('Logged in as: ' + treePanelExt.getNodeById('application-user').text);
}
}
}
});

var treeRoot = new Ext.tree.AsyncTreeNode();

var treePanelExt = new Ext.tree.TreePanel({
id : 'navTree',
region : 'center',
// title : 'Cube Dimensions',
split : true,
height : 400,
minSize : 150,
autoScroll : true,
rootVisible : false,
margins : '0 0 4 0',
useArrows : true,
animate : true,
enableDD : false,
containerScroll : true,
loader : treeLoaderExt,
root : treeRoot,
bbar : {
xtype : 'toolbar',
items : {
xtype : 'button',
id : 'clear',
text : 'Clear All',
iconCls : 'icon-bullet_cross',
handler : function() {
treePanelExt.getRootNode().cascade(function(n) {
var ui = n.getUI();
ui.toggleCheck(false);
});
}
}
},
tbar : ['Filter:', {
xtype : 'trigger',
triggerClass : 'x-form-clear-trigger',
onTriggerClick : function() {
this.setValue('');
treePanelExt.filter.clear();
},
id : 'filter',
enableKeyEvents : true,
listeners : {
keyup : {
buffer : 150,
fn : function(field, e) {
if (Ext.EventObject.ESC == e.getKey()) {
field.onTriggerClick();
} else {
var val = this.getRawValue();
var re = new RegExp('.*' + val + '.*', 'i');
treePanelExt.filter.clear();
treePanelExt.filter.filter(re, 'text');
}
}
}
}
}],

listeners : {
click : function(node, event) {
if (node.id.substring(0, 10) == 'visitation') {
userid = node.id.substring(11);
userName = node.text.substring(9);
filtersForm.getForm().findField('hiddenUser').setValue(userid);

columnsStore.load({
params : {
userid : userid,
view : filtersForm.getForm().findField('hiddenView').getValue(),
hiddenUser : quickSearchForm.getForm().findField('library').getValue()
}
});

quickSearchForm.form.reset();
columnsGrid.getSelectionModel().clearSelections();
// conditionsGrid.store.load();
resultsGrid.store.load();

} else if (node.id == 'user-maintenance') {
userMaintenanceWindow.show();
}
},
checkchange : function(node, checked) {
if (checked) {
node.getUI().addClass('selected');
} else {
node.getUI().removeClass('selected');
}
}
}
});

treePanelExt.filter = new Ext.ux.tree.TreeFilterX(treePanelExt);