-
28 Feb 2012 9:41 AM #1
Unanswered: TreeStore filtering.
Unanswered: TreeStore filtering.
So I've been racking my brain on how to filter a TreeStore in 4.0.7. I've tried the following:
The model and store
The treeCode: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'} }); 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 filterCode: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 });
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:Code:var filter = Ext.create('Ext.util.Filter', { filterFn: function(item) { return item.data.text == 'leaf1'; } });
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.Code: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.
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.
-
28 Feb 2012 10:08 AM #2
I was looking at this thread (http://www.sencha.com/forum/showthre...eestore+filter), and I think this will do what I need to do, but I'm not really sure where I apply the treefilter and how the searchTree and setNodeVisible functions are being called. Are they listeners, and if so, how are they established as listeners?
-
29 Feb 2012 7:35 AM #3
-
5 Mar 2012 4:21 PM #4
So I figured some things out, thanks to this thread http://www.sencha.com/forum/showthre...213&viewfull=1. However, this thread 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):
In treestore definition.
So this works when I do something like this (using my tree in the first post):Code: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()) { me.setRootNode(me.snapshot); delete me.snapshot; } return me; }, isFiltered : function() { return !!this.snapshot; }
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 clear the filter by doing 'Ext.getCmp('myTree').store.clearFilter()', but if I try to access a record in the store using "Ext.getCmp('myTree').store.getNodeById('leaf1')", I get an 'undefined' value (unless I try accessing the root node), but the tree clearly shows all of my nodes (including the child node that I hid). However, if I do my filterBy function, it will filter out the tree, and I can access the records again.Code:Ext.getCmp('myTree').store.filterBy(function(rec) { return rec.data.id != 'child1'; });
So for some reason, the records aren't being registered as components when I clear the treestore, but I can access them after I run a filter on the tree, so something's getting lost in between. I'm definitely thinking it has something to do with the clearFilter function, but I've been tinkering around with it for the past couple days and haven't come up with a reasonable solution.
Anybody have any tips as to what I'm doing wrong? Also, if there's a better way of accomplishing what I want to accomplish, feel free to bash my code, haha.
-
6 Mar 2012 6:13 AM #5
I have a feeling that it has something to do with the setRootNode function. For example, if I have my tree and store like above, and I do Ext.getCmp('myTree').store.setRootNode(Ext.getCmp('myTree').store.getRootNode()), I can only access the root node when I use getNodeById... the same goes for if I had another store (with different child/parent id's), I still can only access the root node. Have there been any reported problems with setRootNode?
-
6 Mar 2012 11:14 AM #6
OK! I think I've come to a working solution... it's pretty inefficient, but it works. I changed the clearFilter function to not use setRootNode like so:
So, the reason why I'm looping through the childNodes is because simply doing me.getRootNode().appendChild(me.snapshot.childNodes) wouldn't work... I would get a "record is undefined," which to me meant that I needed to hard copy my child nodes, hence the loop.Code: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; }
I really hope someone goes over this code and offers improvements to it because I'm sure it's really inefficient.
-
19 Apr 2012 9:01 AM #7
Here's what I have for a plugin for tree filter now. Tested with 4.1 RC3. (*example code looks cluttered in the forum window due to lots of commenting. Copy and past into your favorite editor to read it a little easier.)
Code:Ext.define('TreeFilter', { extend: 'Ext.AbstractPlugin' , alias: 'plugin.jsltreefilter' , collapseOnClear: true // collapse all nodes when clearing/resetting the filter , allowParentFolders: false // allow nodes not designated as 'leaf' (and their child items) to be matched by the filter , init: function (tree) { var me = this; me.tree = tree; tree.filter = Ext.Function.bind(me.filter, me); tree.clearFilter = Ext.Function.bind(me.clearFilter, me); } , filter: function (value, property, re) { var me = this , tree = me.tree , matches = [] // array of nodes matching the search criteria , root = tree.getRootNode() // root node of the tree , property = property || 'text' // property is optional - will be set to the 'text' propert of the treeStore record by default , re = re || new RegExp(value, "ig") // the regExp could be modified to allow for case-sensitive, starts with, etc. , visibleNodes = [] // array of nodes matching the search criteria + each parent non-leaf node up to root , viewNode; if (Ext.isEmpty(value)) { // if the search field is empty me.clearFilter(); return; } tree.expandAll(); // expand all nodes for the the following iterative routines // iterate over all nodes in the tree in order to evalute them against the search criteria root.cascadeBy(function (node) { if (node.get(property).match(re)) { // if the node matches the search criteria and is a leaf (could be modified to searh non-leaf nodes) matches.push(node) // add the node to the matches array } }); if (me.allowParentFolders === false) { // if me.allowParentFolders is false (default) then remove any non-leaf nodes from the regex match Ext.each(matches, function (match) { if (!match.isLeaf()) { Ext.Array.remove(matches, match); } }); } Ext.each(matches, function (item, i, arr) { // loop through all matching leaf nodes root.cascadeBy(function (node) { // find each parent node containing the node from the matches array if (node.contains(item) == true) { visibleNodes.push(node) // if it's an ancestor of the evaluated node add it to the visibleNodes array } }); if (me.allowParentFolders === true && !item.isLeaf()) { // if me.allowParentFolders is true and the item is a non-leaf item item.cascadeBy(function (node) { // iterate over its children and set them as visible visibleNodes.push(node) }); } visibleNodes.push(item) // also add the evaluated node itself to the visibleNodes array }); root.cascadeBy(function (node) { // finally loop to hide/show each node viewNode = Ext.fly(tree.getView().getNode(node)); // get the dom element assocaited with each node if (viewNode) { // the first one is undefined ? escape it with a conditional viewNode.setVisibilityMode(Ext.Element.DISPLAY); // set the visibility mode of the dom node to display (vs offsets) viewNode.setVisible(Ext.Array.contains(visibleNodes, node)); } }); } , clearFilter: function () { var me = this , tree = this.tree , root = tree.getRootNode(); if (me.collapseOnClear) { tree.collapseAll(); } // collapse the tree nodes root.cascadeBy(function (node) { // final loop to hide/show each node viewNode = Ext.fly(tree.getView().getNode(node)); // get the dom element assocaited with each node if (viewNode) { // the first one is undefined ? escape it with a conditional and show all nodes viewNode.show(); } }); } });
-
31 May 2012 4:05 AM #8
-
8 Aug 2012 2:09 AM #9
-
15 Aug 2012 5:08 AM #10
i fixed your TreeFilter. it working on 4.1.1.
Code:Ext.define('Ext.ux.TreeFilter', { extend: 'Ext.AbstractPlugin', alias: 'plugin.treefilter', collapseOnClear: false, // collapse all nodes when clearing/resetting the filter allowParentFolders: false, // allow nodes not designated as 'leaf' (and their child items) to be matched by the filter init: function (tree) { var me = this; me.tree = tree; tree.filter = Ext.Function.bind(me.filter, me); tree.clearFilter = Ext.Function.bind(me.clearFilter, me); }, filter: function (value, property, re) { var me = this, tree = me.tree, matches = [], // array of nodes matching the search criteria root = tree.getRootNode(), // root node of the tree property = property || 'text', // property is optional - will be set to the 'text' propert of the treeStore record by default re = re || new RegExp(value, "ig"), // the regExp could be modified to allow for case-sensitive, starts with, etc. visibleNodes = [], // array of nodes matching the search criteria + each parent non-leaf node up to root viewNode; if (Ext.isEmpty(value)) { // if the search field is empty me.clearFilter(); return; } tree.expandAll(); // expand all nodes for the the following iterative routines // iterate over all nodes in the tree in order to evalute them against the search criteria root.cascadeBy(function (node) { if (node.get(property).match(re)) { // if the node matches the search criteria and is a leaf (could be modified to searh non-leaf nodes) matches.push(node); // add the node to the matches array } }); if (me.allowParentFolders === false) { // if me.allowParentFolders is false (default) then remove any non-leaf nodes from the regex match Ext.each(matches, function (match) { if (match !== undefined) { if (!match.isLeaf()) { Ext.Array.remove(matches, match); } } }); } Ext.each(matches, function (item, i, arr) { // loop through all matching leaf nodes root.cascadeBy(function (node) { // find each parent node containing the node from the matches array if (node.contains(item) === true) { visibleNodes.push(node); // if it's an ancestor of the evaluated node add it to the visibleNodes array } }); if (me.allowParentFolders === true && !item.isLeaf()) { // if me.allowParentFolders is true and the item is a non-leaf item item.cascadeBy(function (node) { // iterate over its children and set them as visible visibleNodes.push(node); }); } visibleNodes.push(item); // also add the evaluated node itself to the visibleNodes array }); root.cascadeBy(function (node) { // finally loop to hide/show each node viewNode = Ext.fly(tree.getView().getNode(node)); // get the dom element assocaited with each node if (viewNode) { // the first one is undefined ? escape it with a conditional viewNode.setVisibilityMode(Ext.Element.DISPLAY); // set the visibility mode of the dom node to display (vs offsets) viewNode.setVisible(Ext.Array.contains(visibleNodes, node)); } }); }, clearFilter: function () { var me = this, tree = this.tree, root = tree.getRootNode(), viewNode; if (me.collapseOnClear) { tree.collapseAll(); } // collapse the tree nodes root.cascadeBy(function (node) { // final loop to hide/show each node viewNode = Ext.fly(tree.getView().getNode(node)); // get the dom element assocaited with each node if (viewNode) { // the first one is undefined ? escape it with a conditional and show all nodes viewNode.show(); } }); } });


Reply With Quote