PDA

View Full Version : TreeStore filtering.



incutonez
28 Feb 2012, 9:41 AM
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


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 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
28 Feb 2012, 10:08 AM
I was looking at this thread (http://www.sencha.com/forum/showthread.php?154560-treestore-filter-(ext-4.0.7-gpl)&highlight=treestore+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?

incutonez
29 Feb 2012, 7:35 AM
Bump.

incutonez
5 Mar 2012, 4:21 PM
So I figured some things out, thanks to this thread http://www.sencha.com/forum/showthread.php?146694-TreeGRID-panel-filter&p=647213&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.


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;
}


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 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.

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.

incutonez
6 Mar 2012, 6:13 AM
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?

incutonez
6 Mar 2012, 11:14 AM
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:



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;
}


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.

I really hope someone goes over this code and offers improvements to it because I'm sure it's really inefficient.

slemmon
19 Apr 2012, 9:01 AM
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.)



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();
}
});
}
});

Diavololt
31 May 2012, 4:05 AM
Any changes to 4.1.1 RC1?

Diavololt
8 Aug 2012, 2:09 AM
Any news on TreeStore filtering?

yoshiki tanaka
15 Aug 2012, 5:08 AM
i fixed your TreeFilter. it working on 4.1.1.



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();
}
});
}
});

maikhorma
17 Aug 2012, 1:53 PM
First of all thanks to both for the original code and the update. I was shocked when I found out that tree's didn't have a "filterBy" function, so this was (almost) exactly what I needed. I have updated to support a filterBy function in addition to the original property regex.

There is one change I made that some may or may not want. When using allowParentFolders: true, there was a loop that would make all the children visible, but that's not what I wanted. The children didn't pass the filter, so they shouldn't be shown. I suppose this might come into play if a decedent passes but not a node in between? Works for me for now, feel free to offer suggestions/improve.

Commented out code:


/* Commented out because this shows all children whether or not they pass the filter
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);
});
}
*/



Full plugin


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);
tree.filterBy = Ext.Function.bind(me.filterBy,me);
},


filter: function (value, property, re) {
var me = this;
if (Ext.isEmpty(value)) { // if the search field is empty
me.clearFilter();
return;
}

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.

// iterate over all nodes in the tree in order to evalute them against the search criteria
me.filterBy(function(node){
return node.get(property).match(re);// if the node matches the search criteria and is a leaf (could be modified to searh non-leaf nodes)
});

},

filterBy: function (fn,scope){


var me = this,
tree = me.tree,
matches = [], // array of nodes matching the search criteria
root = tree.getRootNode(), // root node of the tree
visibleNodes = [], // array of nodes matching the search criteria + each parent non-leaf node up to root
viewNode;


if (!fn) { // if no fn defined
me.clearFilter();
return;
}




tree.expandAll(); // expand all nodes for the the following iterative routines



//fn.call(scope || me, record)
root.cascadeBy(function (node){
if(fn.call(scope || me, node)){
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
}
});

/* Commented out because this shows all children whether or not they pass the filter
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();
}
});
}
});


And for those who aren't sure how to use it, you put this on your Ext.tree.Panel (exact code not tested but should give the idea):


Ext.create('Ext.tree.Panel', {
title: 'Simple Tree',
width: 200,
height: 150,
store: store,
rootVisible: false,
renderTo: Ext.getBody(),
plugins:[Ext.create('plugin.treefilter',{
pluginId: 'treefilter',
allowParentFolders: true
})]
});



then you should automatically get the filter and filterBy functions on your tree.

westy
21 Aug 2012, 2:06 AM
Good stuff... made one change though, since the filter method wasn't working:


filter: function (value, property, re) {
var me = this;
if (Ext.isEmpty(value)) { // if the search field is empty
me.clearFilter();
return;
}


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.


// iterate over all nodes in the tree in order to evalute them against the search criteria
me.filterBy(function(node){
return re.test(node.get(property));// if the node matches the search criteria and is a leaf (could be modified to searh non-leaf nodes)
});


},





Edit: Sorry, that should be:


me.filterBy(function(node){
return new String(node.get(property)).match(re); // if the node matches the search criteria and is a leaf (could be modified to searh non-leaf nodes)
});

yyogev
4 Oct 2012, 1:07 AM
Hi,

Thanks for this useful extension.

Here's a patch to allow applying filter in big trees (some of trees we have are very big) and when updating an existing tree:


Index: TreeFilter.js
===================================================================
RCS file: /arch/cvs/it/lib/extjs/ux/TreeFilter.js,v
retrieving revision 1.2
diff -u -r1.2 TreeFilter.js
--- TreeFilter.js 27 Sep 2012 14:06:37 -0000 1.2
+++ TreeFilter.js 4 Oct 2012 08:56:43 -0000
@@ -15,21 +15,21 @@
tree.filterBy = Ext.Function.bind(me.filterBy,me);
},

- filter: function (value, property, re) {
+ filter: function (re, property, scope) {
var me = this;
- if (Ext.isEmpty(value)) { // if the search field is empty
+ if (Ext.isEmpty(re)) { // if the search field is empty
me.clearFilter();
return;
}

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.
+ re = typeof re == "object" ? re :
+ new RegExp(re, "ig"); // the regExp could be modified to allow for case-sensitive, starts with, etc.

// iterate over all nodes in the tree in order to evalute them against the search criteria
me.filterBy(function(node){
- return node.get(property).match(re);// if the node matches the search criteria and is a leaf (could be modified to searh non-leaf nodes)
- });
-
+ return new String(node.get(property)).match(re); // if the node matches the search criteria and is a leaf (could be modified to searh non-leaf nodes)
+ }, scope);
},

filterBy: function (fn,scope){
@@ -37,7 +37,7 @@
var me = this,
tree = me.tree,
matches = [], // array of nodes matching the search criteria
- root = tree.getRootNode(), // root node of the tree
+ root = Ext.valueFrom(scope, tree.getRootNode()), // root node of the tree
visibleNodes = [], // array of nodes matching the search criteria + each parent non-leaf node up to root
viewNode;

@@ -47,13 +47,6 @@
return;
}

-
-
-
- tree.expandAll(); // expand all nodes for the the following iterative routines
-
-
-
//fn.call(scope || me, record)
root.cascadeBy(function (node){
if(fn.call(scope || me, node)){
@@ -78,13 +71,6 @@
}
});

-/* Commented out because this shows all children whether or not they pass the filter
- 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
});

maikhorma
4 Oct 2012, 5:12 PM
When I get a chance I'll add that patch to my latest post to make it easier for others, but I wanted to propose a question for discussion. This is to the best of my knowledge, so i may be wrong on some points so please correct me.

The core functionality of this plugin is accomplished by :


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));
}


Which is hiding dom elements from the screen.

My question is: Is this the right approach for the current version of the framework?

To me tree filtering isn't that much different then grid & store filtering. Here we are filtering the "view" by hiding dom elements, but grid's don't work that way. A grid's store is what you filter, and the grid just reflects what is in the store. So a more "modern" plugin would actually be filtering a treestore in a way that the tree gets drawn appropriately?

Of course i want to thank everyone who has worked on the plugin. It works well for me and I'm happy with it. Just wanted to open the discussion for future enhancements.

yyogev
5 Oct 2012, 1:19 AM
I agree that using the store filtering is probably the correct approach.
However, note that the 'expanded' attribute needs to be respected. This allows for faster response, but it also means that the plugin needs to handle 'expand' event.

zombeerose
4 Feb 2013, 2:09 PM
I am using an override to the tree store to filter (http://www.sencha.com/forum/showthread.php?245120-Tree-filtering&p=935641&viewfull=1#post935641).

webfriend13
4 Oct 2013, 3:37 AM
Hi All,

So many modification is been made to the original post. I just wonder which option is the best and what version of ExtJS is supported for that answer.

Drömbolaget
7 Oct 2013, 1:34 AM
I need filtering too, would prefer if this was built into ExtJS and not have to rely on third-party extensions in case future ExtJS udpates breaks the functionality.

slemmon
7 Oct 2013, 10:50 AM
There is a feature request logged internally for a tree store filter.

But, in the meantime feel free to make any use you can of the below Fiddle (not official - just something I was messing with a while back).

https://fiddle.sencha.com/#fiddle/ra

boooch
13 Aug 2014, 12:57 AM
All works fine but this code:


append: function(parentNode, appendedNode, index) { console.log(appendedNode);
var me = this,
snapshotParentNode =me.snapshot.findChild('id', parentNode.get('id'), true) || me.shapshot,
foundNode = me.snapshot.findChild('id', appendedNode.get('id'), true);
snapshotParentNode.insertChild(index, foundNode || appendedNode.copy(null, true));
},


Uncaught TypeError: Cannot read property 'get' of null

in snapshotParentNode =me.snapshot.findChild('id', parentNode.get('id'), true) || me.shapshot,

so this is node dump:


id: "Online-root"
internalId: "root"
lastChild: j
modified: Object
nextSibling: null
parentNode: null

When I type in filter 2 letters (for example 'oo') it filters good, but if i remove one letter 'o' filter dont work and i have to clear filter and type again

boooch
13 Aug 2014, 12:59 AM
All works fine but this code:


append: function(parentNode, appendedNode, index) { console.log(appendedNode);
var me = this,
snapshotParentNode =me.snapshot.findChild('id', parentNode.get('id'), true) || me.shapshot,
foundNode = me.snapshot.findChild('id', appendedNode.get('id'), true);
snapshotParentNode.insertChild(index, foundNode || appendedNode.copy(null, true));
},


Uncaught TypeError: Cannot read property 'get' of null

in snapshotParentNode =me.snapshot.findChild('id', parentNode.get('id'), true) || me.shapshot,

so this is node dump:


id: "Online-root"
internalId: "root"
lastChild: j
modified: Object
nextSibling: null
parentNode: null

When I type in filter 2 letters (for example 'oo') it filters good, but if i remove one letter 'o' filter dont work and i have to clear filter and type again