1. #1
    Sencha User
    Join Date
    Mar 2011
    Posts
    146
    Answers
    5
    Vote Rating
    4
    incutonez is on a distinguished road

      0  

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

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

  2. #2
    Sencha User
    Join Date
    Mar 2011
    Posts
    146
    Answers
    5
    Vote Rating
    4
    incutonez is on a distinguished road

      0  

    Default


    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?

  3. #3
    Sencha User
    Join Date
    Mar 2011
    Posts
    146
    Answers
    5
    Vote Rating
    4
    incutonez is on a distinguished road

      0  

    Default


    Bump.

  4. #4
    Sencha User
    Join Date
    Mar 2011
    Posts
    146
    Answers
    5
    Vote Rating
    4
    incutonez is on a distinguished road

      0  

    Default


    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.
    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;
    }
    So this works when I do something like this (using my tree in the first post):
    Code:
    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.

  5. #5
    Sencha User
    Join Date
    Mar 2011
    Posts
    146
    Answers
    5
    Vote Rating
    4
    incutonez is on a distinguished road

      0  

    Default


    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. #6
    Sencha User
    Join Date
    Mar 2011
    Posts
    146
    Answers
    5
    Vote Rating
    4
    incutonez is on a distinguished road

      0  

    Default


    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:

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

  7. #7
    Sencha - Support Team slemmon's Avatar
    Join Date
    Mar 2009
    Location
    Boise, ID
    Posts
    4,800
    Answers
    359
    Vote Rating
    167
    slemmon is a splendid one to behold slemmon is a splendid one to behold slemmon is a splendid one to behold slemmon is a splendid one to behold slemmon is a splendid one to behold slemmon is a splendid one to behold slemmon is a splendid one to behold

      1  

    Default


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

  8. #8
    Sencha User
    Join Date
    Nov 2010
    Posts
    27
    Vote Rating
    0
    Diavololt is on a distinguished road

      0  

    Default Change

    Change


    Any changes to 4.1.1 RC1?

  9. #9
    Sencha User
    Join Date
    Nov 2010
    Posts
    27
    Vote Rating
    0
    Diavololt is on a distinguished road

      0  

    Default


    Any news on TreeStore filtering?

  10. #10
    Sencha Premium Member
    Join Date
    Apr 2010
    Posts
    3
    Vote Rating
    0
    yoshiki tanaka is on a distinguished road

      0  

    Default


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