    /**
     * @class Ext.tree.TreeFilter
     * Note this class is experimental and doesn't update the indent (lines) or expand collapse icons of the nodes
     * @param {TreePanel} tree
     * @param {Object} config (optional)
     */
    Ext.tree.TreeFilter = function(tree, config){
        this.tree = tree;
        this.filtered = {};
        Ext.apply(this, config);
    };

    Ext.tree.TreeFilter.prototype = {
        clearBlank:false,
        reverse:false,
        autoClear:false,
        remove:false,

         /**
         * 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.
         */
        filter : function(value, attr, startNode){
            attr = attr || "text";
            var f;
            if(typeof value == "string"){
                var vlen = value.length;
                // auto clear empty filter
                if(vlen == 0 && this.clearBlank){
                    this.clear();
                    return;
                }
                value = value.toLowerCase();
                f = function(n){
                    return n.attributes[attr].substr(0, vlen).toLowerCase() == value;
                };
            }else if(value.exec){ // regex?
                f = function(n){
                    return value.test(n.attributes[attr]);
                };
            }else{
                throw 'Illegal filter type, must be string or regex';
            }
            this.filterBy(f, null, startNode);
    	},

        /**
         * 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.
         * @param {Function} fn The filter function
         * @param {Object} scope (optional) The scope (this reference) in which the function is executed. 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 false;
                }
                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);
                       }
                   }
               }
            }
        },

        /**
         * Clears the current filter. Note: with the "remove" option
         * set a filter cannot be cleared.
         */
        clear : function(){
            var t = this.tree;
            var af = this.filtered;
            for(var id in af){
                if(typeof id != "function"){
                    var n = af[id];
                    if(n){
                        n.ui.show();
                    }
                }
            }
            this.filtered = {};
        }
    };