I updated the code in the first post to implement the config "hideEmptyFolders". It will hide any folders with no visible children.
The speed seems a little slower but it's hardly noticeable.
Printable View
I updated the code in the first post to implement the config "hideEmptyFolders". It will hide any folders with no visible children.
The speed seems a little slower but it's hardly noticeable.
I made one more update just now because the hideEmptyFolders feature did not un-hide empty folders after clearing filters, oops lol.
Any suggestion on how to actually filter the folders, though?
The current code that takes care of hiding empty folder looks at hasVisibleChild to see if it should hide the folder. the problem is, hasVisibleChild is always marked as true in the case of any folders as children, even if those folders leafs will ultimately be hidden...
>>the problem is, hasVisibleChild is always marked as true in the case of any folders as children, even if those folders leafs will ultimately be hidden...
This should not be the case. Any child folders that themselves have no visible children should be hidden, thus their parent, if it is similarly empty, should not be hidden. You may be having an issue with the maxExpandDepth though, because this will prevent filtering beyond the depth specified. Try setting the depth to 100 and see if it solves your problem.
Alternatively, it is often easier to modify the server code to include a parameter on nodes that can then be used to filter them appropriately on the client side. For example, you might include the node parameter "doNotFilter" true/false and check for that in your filterFn.
@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'));
}
}));
Thanks a lot zombeerose, it works and has the advantage to extends the treestore, not the treepanel or the treeview.
But be careful I had to change
byCode:override: 'Ext.data.TreeStore',
And I have a problem : the records ids are changed after filtering so getNodeById doesn't work after filtering...Code:extend: 'Ext.data.TreeStore',
FYI I have a small shortcut function:
Code:filterBy: function(funct)
{
this.filter(
new Ext.util.Filter(
{
filterFn: funct
})
);
},
@pierrocknroll
Can you explain why you "had to change" it to an extend vs override? Both options should work ... override affects everything while extends applies to a custom class used explicitly. Thx
Well I have a strange error "object is not a function" from nowhere :) It's maybe because I have a big application with several custom components.
By the way, I have an other problem : the records ids are changed after filtering so getNodeById doesn't work.
Hi zomberose
I originally implemented my filters on the store as you have done (in fact it's almost identical). The reason I moved away from doing it this way was because it turned out to be very slow on large datasets. The root.copy function is very time consuming and must be done each time the filtering changes.
I switched to filtering the View because it is an order of magnitude faster for me.
In any case, if you wish to discuss your extension and provide support I ask that you please create your own forum post for it rather than mucking up this one.
Thank you!
hhangus