@hhangus
Thank you for your code. Unfortunately, I was not able to get your solution to work. I tried implementing a similar approach to yours based on toggling node visibility but I was not pleased with the choppy effect when expanding/collapsing branches. Additionally, considering that Ext has stubbed out filter methods in the tree store, I felt that this approach would be more in line with their implementation. Using this stackoverflow post as a starting post, I wrote the following code. I have tested it against a pre-loaded tree (all children nodes are loaded immediately) and an async remote-loaded tree. I do not support a maxDepth like you did because most of my trees are several levels and filtering should apply to all nodes.
Code:
Ext.define('Ext.data.TreeStoreOverride',{
override: 'Ext.data.TreeStore',
/**
* @private
* @param {Object[]} filters The filters array
*/
applyFilters: function(filters){
var me = this,
decoded = me.decodeFilters(filters),
i = 0,
length = decoded.length,
node,
visibleNodes = [],
resultNodes = [],
root = me.getRootNode(),
flattened = me.tree.flatten(),
items,
item,
fn;
/**
* @property {Ext.util.MixedCollection} snapshot
* A pristine (unfiltered) collection of the records in this store. This is used to reinstate
* records when a filter is removed or changed
*/
me.snapshot = me.snapshot || me.getRootNode().copy(null, true);
for (i = 0; i < length; i++) {
me.filters.replace(decoded[i]);
}
//collect all the nodes that match the filter
items = me.filters.items;
length = items.length;
for (i = 0; i < length; i++){
item = items[i];
fn = item.filterFn || function(item){ return item.get(item.property) == item.value; };
visibleNodes = Ext.Array.merge(visibleNodes, Ext.Array.filter(flattened, fn));
}
//collect the parents of the visible nodes so the tree has the corresponding branches
length = visibleNodes.length;
for (i = 0; i < length; i++){
node = visibleNodes[i];
node.bubble(function(n){
if (n.parentNode){
resultNodes.push(n.parentNode);
} else {
return false;
}
});
}
visibleNodes = Ext.Array.merge(visibleNodes, resultNodes);
//identify all the other nodes that should be removed (either they are not visible or are not a parent of a visible node)
resultNodes = [];
root.cascadeBy(function(n){
if (!Ext.Array.contains(visibleNodes,n)){
resultNodes.push(n);
}
});
//we can't remove them during the cascade - pulling rug out ...
length = resultNodes.length;
for (i = 0; i < length; i++){
resultNodes[i].remove();
}
//necessary for async-loaded trees
root.getOwnerTree().getView().refresh();
}, //eof applyFilters
//@inheritdoc
filter: function(filters, value) {
var nodes, nodeLength, i, filterFn;
if (Ext.isString(filters)) {
filters = {
property: filters,
value: value
};
}
//find branch nodes that have not been loaded yet - this approach is in contrast to expanding all nodes recursively, which is unnecessary if some nodes are already loaded.
filterFn = function(item){ return !item.isLeaf() && !(item.isLoading() || item.isLoaded()); };
nodes = Ext.Array.filter(this.tree.flatten(), filterFn);
nodeLength = nodes.length;
if (nodeLength === 0){
this.applyFilters(filters);
} else {
for (i = 0; i < nodeLength; i++){
this.load({
node: nodes[i],
callback: function(){
nodeLength--;
if (nodeLength === 0){
//start again & re-test for newly loaded nodes in case more branches exist
this.filter(filters,value);
}
},
scope: this
});
}
}
},
clearFilter: function(suppressEvent) {
var me = this;
me.filters.clear();
if (me.isFiltered()){
me.setRootNode(me.snapshot);
delete me.snapshot;
}
},
isFiltered: function() {
var snapshot = this.snapshot;
return !! snapshot && snapshot !== this.getRootNode();
}
});
Example pseudo-usage:
Code:
var store = Ext.ComponentQuery.query('treepanel')[0].getStore();
var regex = new RegExp(Ext.String.escapeRegex("some search term"),'i');
store.clearFilter(true); //by default, filters will be appended to any existing so we clear them first
store.filter(new Ext.util.Filter({
filterFn: function(item){
return regex.test(item.get('text'));
}
}));