Page 2 of 3 FirstFirst 123 LastLast
Results 11 to 20 of 21

Thread: TreeStore filtering.

  1. #11
    Sencha Premium Member
    Join Date
    Mar 2011
    Posts
    54
    Answers
    2

    Default Update

    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:
    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
    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);
            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):
    Code:
    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.

  2. #12
    Sencha Premium User westy's Avatar
    Join Date
    Feb 2009
    Location
    Bath, UK
    Posts
    1,035
    Answers
    3

    Default

    Good stuff... made one change though, since the filter method wasn't working:
    Code:
        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:
    Code:
            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)
            });
    Product Architect
    Altus Ltd.

  3. #13
    Ext Premium Member yyogev's Avatar
    Join Date
    Jun 2009
    Location
    Shoham, Israel
    Posts
    196
    Answers
    5

    Default

    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:

    Code:
    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
             });
    Yaron Yogev

    IT Software Developer

  4. #14
    Sencha Premium Member
    Join Date
    Mar 2011
    Posts
    54
    Answers
    2

    Default

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

  5. #15
    Ext Premium Member yyogev's Avatar
    Join Date
    Jun 2009
    Location
    Shoham, Israel
    Posts
    196
    Answers
    5

    Default Applying filtering to the store seems like the right approach

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

    IT Software Developer

  6. #16
    Ext JS Premium Member
    Join Date
    Mar 2008
    Location
    Phoenix, AZ
    Posts
    651
    Answers
    3

    Default

    I am using an override to the tree store to filter.

  7. #17
    Sencha User
    Join Date
    Feb 2012
    Posts
    221
    Answers
    10

    Default

    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.

  8. #18
    Sencha Premium Member
    Join Date
    Jul 2012
    Posts
    53
    Answers
    1

    Default Tree filter is really needed

    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.

  9. #19
    Sencha User slemmon's Avatar
    Join Date
    Mar 2009
    Location
    Boise, ID
    Posts
    6,165
    Answers
    505

    Default

    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

  10. #20
    Sencha User
    Join Date
    Jan 2012
    Posts
    15

    Default Problem with append

    All works fine but this code:
    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:
    Code:
    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

Page 2 of 3 FirstFirst 123 LastLast

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •