Ext JS 4: Filtering a TreeStore
Solution 1
Thanks for catching that other one, I fixed up the answer to include the more dynamic treestore filter override that I included below to answer your Q.
It is working fine in 4.1b2, I know there were some changes to the treestore between 4.07 and 4.1 but I think 4.07 still had the tree objects I am using here.
Here's the override:
Ext.override(Ext.data.TreeStore, {
hasFilter: false,
filter: function(filters, value) {
if (Ext.isString(filters)) {
filters = {
property: filters,
value: value
};
}
var me = this,
decoded = me.decodeFilters(filters),
i = 0,
length = decoded.length;
for (; i < length; i++) {
me.filters.replace(decoded[i]);
}
Ext.Array.each(me.filters.items, function(filter) {
Ext.Object.each(me.tree.nodeHash, function(key, node) {
if (filter.filterFn) {
if (!filter.filterFn(node)) node.remove();
} else {
if (node.data[filter.property] != filter.value) node.remove();
}
});
});
me.hasFilter = true;
console.log(me);
},
clearFilter: function() {
var me = this;
me.filters.clear();
me.hasFilter = false;
me.load();
},
isFiltered: function() {
return this.hasFilter;
}
});
It uses the store.tree.nodeHash
object to iterate through all nodes against the filters rather than just the first child. It will accept a filter as a function or property/value pair. I suppose the clearFilter method could be worked over though to prevent another ajax call.
Solution 2
This is the answer that I came up with... it's not ideal, so I'm hoping someone can provide a better, more generic approach. Why? Well, if my tree had a parent that had a child that had a child, I'd like to filter on those, but my solution only goes one child deep.
Thanks to this thread, I figured some things out. The only problem with this thread is that it made filtering flat... so child nodes wouldn't appear under their parent nodes. I modified their implementation and came up with this (it only goes 1 child deep, so it wouldn't work if you have a parent that contains a child that has a child):
TreeStore
filterBy : function(fn, scope) {
var me = this,
root = me.getRootNode(),
tmp;
// the snapshot holds a copy of the current unfiltered tree
me.snapshot = me.snapshot || root.copy(null, true);
var hash = {};
tmp = root.copy(null, true);
tmp.cascadeBy(function(node) {
if (fn.call(me, node)) {
if (node.data.parentId == 'root') {
hash[node.data.id] = node.copy(null, true);
hash[node.data.id].childNodes = [];
}
else if (hash[node.data.parentId]) {
hash[node.data.parentId].appendChild(node.data);
}
}
/* original code from mentioned thread
if (fn.call(scope || me, node)) {
node.childNodes = []; // flat structure but with folder icon
nodes.push(node);
}*/
});
delete tmp;
root.removeAll();
var par = '';
for (par in hash) {
root.appendChild(hash[par]);
}
return me;
},
clearFilter: function() {
var me = this;
if (me.isFiltered()) {
var tmp = [];
var i;
for (i = 0; i < me.snapshot.childNodes.length; i++) {
tmp.push(me.snapshot.childNodes[i].copy(null, true));
}
me.getRootNode().removeAll();
me.getRootNode().appendChild(tmp);
delete me.snapshot;
}
return me;
},
isFiltered : function() {
return !!this.snapshot;
}
So this works when I do something like this (using my tree in the first post):
Ext.getCmp('myTree').store.filterBy(function(rec) {
return rec.data.id != 'child1';
});
This code will return every record that doesn't have a child1 id, so under leaf1, it will only have child2 as the node. I can also clear the filter by doing Ext.getCmp('myTree').store.clearFilter()
.
Now, I realize I just answered my own question, but like I posted above, I'd really like critiquing/advice on what I can make more efficient and generic. If anyone has any tips, I'd love to hear them! Also, if you need help getting this code up and running, let me know.
Sha, I also tried filters, but no luck. Have a look at this thread.
incutonez
Web developer that enjoys building web applications full of pizzazz. I'm a reformed Ext JS developer that's currently loving Vue.js.
Updated on July 10, 2022Comments
-
incutonez almost 2 years
I originally posted this on the Sencha forums here but didn't get any responses (other than my own answer, which I will post soon), so I am going to repost it here and see if I get anymore help.
I've been racking my brain on how to filter a TreeStore in 4.0.7. I've tried the following:
The model
Ext.define('model', { extend: 'Ext.data.Model', fields: [ {name: 'text', type: 'string'}, {name: 'leaf', type: 'bool'}, {name: 'expanded', type: 'bool'}, {name: 'id', type: 'string'} ], hasMany: {model: 'model', name: 'children'} });
The store
Ext.define('myStore', { extend: 'Ext.data.TreeStore', model: 'model', storeId: 'treestore', root: { text: 'root', children: [{ text: 'leaf1', id: 'leaf1', children: [{ text: 'child1', id: 'child1', leaf: true },{ text: 'child2', id: 'child2', leaf: true }] },{ text: 'leaf2', id: 'leaf2', leaf: true }] }, proxy: { type: 'memory', reader: { type: 'json' } } });
The tree
var myTree = Ext.create('Ext.tree.Panel', { id: 'myTree', selType: 'cellmodel', selModel: Ext.create('Ext.selection.CellModel', {mode: 'MULTI'}), rootVisible: false, store: Ext.create('myStore'), width: 300 });
The filter
var filter = Ext.create('Ext.util.Filter', { filterFn: function(item) { return item.data.text == 'leaf1'; } });
So I think my problem is... I don't know how to use this filter due to TreeStore not actually inheriting any type of filter functions like a normal store. I've tried:
myTree.store.filters.add(filter); myTree.store.filters.filter(filter); // This seems to work // I can get into the filterFn when debugging, but I think item is the "this" of my filter object.
Normally, if I have a grid and I create a filter like above, I can just do
myTree.store.filter(filter)
and it'll grab each row's item/filter on what I return... but I'm thinking because TreeStore doesn't inherit a filtering function, that's not being passed in.If someone could provide some clarity as to what I'm doing wrong or any insight on how to set up a filter function/my thinking process, please go ahead. I'd appreciate any help.
-
incutonez about 12 yearsSo I've been struggling with trying to get this to work. I'm assuming it just doesn't work in 4.0.7... when I filter the tree, I get nothing returned, and if I try to clear the filter, nothing is returned, but that might be because I'm using memory type as the proxy. Also, in your clearFilter function, is filters.clear() defined for an AbstractStore, or does that not matter? Would you by any chance have a working solution for 4.0.7?
-
incutonez about 12 yearsI take some of that back... the filtering works if I use your "leaf only filter as a function" code. The clearFilter() still doesn't work. I'll try messing around with it more.
-
incutonez about 12 yearsOk, I think I've got it working... if I add
me.snapshot = me.snapshot || me.getRootNode().copy(null, true);
to the filter code right after you create your variables, and then change clearFilter toif (me.isFiltered()) { me.getRootNode().removeAll(); me.snapshot.eachChild(function(child) { me.getRootNode().appendChild(child.copy(null, true)); }); delete me.snapshot; } me.hasFilter = false;
everything seems to work. Only problem is, looping over the snapshot's child elements is kind of expensive, so maybe there's a better way? -
egerardus about 12 yearsSorry, I was out for a few days. It seems like you have it now in 4.07, I never tried with anything other than 4.1b2.
-
incutonez about 12 yearsYep, definitely works. Thanks! Only thing is, reloading the store with
.load
wasn't really what I was looking for, so that's why I changed clearFilter. -
incutonez about 10 yearsThankfully, you won't be having to rely on this override much longer... in the Ext JS 5 beta release, they have native tree filtering.
-
Roy Tinker over 9 yearsWarning: this causes major problems if you're using autoSync.
-
Roy Tinker over 9 yearsBe very careful if you're using autoSync on your TreeStore. This could cause major problems.
-
incutonez over 9 yearsYeah, at this point, I'd just recommend upgrading to Ext JS 5 and using their tree filtering.
-
Roy Tinker over 9 yearsOr, move your filtering to the server.
-
Meer almost 7 yearsThis works fine for me on the desktop version of the sencha app but when i create a phonegap build for ios/android this override does not work. Any thoughts?
-
Meer almost 7 yearsworks fine on the desktop version of sencha touch app, but when creating the phone gap build for ios/android this solution breaks. Any ideas what might be the issue? were you able to successfully run it on iOS?