1. #11
    Ext JS Premium Member
    Join Date
    Oct 2009
    Posts
    60
    Vote Rating
    1
    hhangus is on a distinguished road

      0  

    Default


    I updated the code in the first post to implement the config "hideEmptyFolders". It will hide any folders with no visible children.

    The speed seems a little slower but it's hardly noticeable.

  2. #12
    Ext JS Premium Member
    Join Date
    Oct 2009
    Posts
    60
    Vote Rating
    1
    hhangus is on a distinguished road

      0  

    Default


    I made one more update just now because the hideEmptyFolders feature did not un-hide empty folders after clearing filters, oops lol.

  3. #13
    Sencha User
    Join Date
    Dec 2012
    Posts
    6
    Vote Rating
    0
    Bcg24 is on a distinguished road

      0  

    Default


    Any suggestion on how to actually filter the folders, though?

  4. #14
    Sencha User
    Join Date
    Dec 2012
    Posts
    6
    Vote Rating
    0
    Bcg24 is on a distinguished road

      0  

    Default


    The current code that takes care of hiding empty folder looks at hasVisibleChild to see if it should hide the folder. the problem is, hasVisibleChild is always marked as true in the case of any folders as children, even if those folders leafs will ultimately be hidden...

  5. #15
    Ext JS Premium Member
    Join Date
    Oct 2009
    Posts
    60
    Vote Rating
    1
    hhangus is on a distinguished road

      0  

    Default


    >>the problem is, hasVisibleChild is always marked as true in the case of any folders as children, even if those folders leafs will ultimately be hidden...

    This should not be the case. Any child folders that themselves have no visible children should be hidden, thus their parent, if it is similarly empty, should not be hidden. You may be having an issue with the maxExpandDepth though, because this will prevent filtering beyond the depth specified. Try setting the depth to 100 and see if it solves your problem.

    Alternatively, it is often easier to modify the server code to include a parameter on nodes that can then be used to filter them appropriately on the client side. For example, you might include the node parameter "doNotFilter" true/false and check for that in your filterFn.

  6. #16
    Ext JS Premium Member
    Join Date
    Mar 2008
    Location
    Phoenix, AZ
    Posts
    627
    Vote Rating
    10
    zombeerose will become famous soon enough zombeerose will become famous soon enough

      0  

    Default


    @hhangus

    Thank you for your code. Unfortunately, I was not able to get your solution to work. I tried implementing a similar approach to yours based on toggling node visibility but I was not pleased with the choppy effect when expanding/collapsing branches. Additionally, considering that Ext has stubbed out filter methods in the tree store, I felt that this approach would be more in line with their implementation. Using this stackoverflow post as a starting post, I wrote the following code. I have tested it against a pre-loaded tree (all children nodes are loaded immediately) and an async remote-loaded tree. I do not support a maxDepth like you did because most of my trees are several levels and filtering should apply to all nodes.

    Code:
    Ext.define('Ext.data.TreeStoreOverride',{
        override: 'Ext.data.TreeStore',
        
        /**
         * @private
         * @param {Object[]} filters The filters array
         */
        applyFilters: function(filters){
            var me = this,
                decoded = me.decodeFilters(filters),
                i = 0,
                length = decoded.length,
                node,
                visibleNodes = [],
                resultNodes = [],
                removedRecords = Ext.Array.clone(me.getRemovedRecords()), //must clone since we don't want a ref
                root = me.getRootNode(),
                flattened = me.tree.flatten(),
                items,
                item,
                fn;
    
    
            /**
             * @property {Ext.util.MixedCollection} snapshot
             * A pristine (unfiltered) collection of the records in this store. This is used to reinstate
             * records when a filter is removed or changed
             */
            me.snapshot = me.snapshot || me.getRootNode().copy(null, true);
            
            for (i = 0; i < length; i++) {
                me.filters.replace(decoded[i]);
            }
    
    
            //collect all the nodes that match the filter
            items = me.filters.items;
            length = items.length;
            for (i = 0; i < length; i++){
                item = items[i];
                fn = item.filterFn || function(item){ return item.get(item.property) == item.value; };
                visibleNodes = Ext.Array.merge(visibleNodes, Ext.Array.filter(flattened, fn));
            }
            
            //collect the parents of the visible nodes so the tree has the corresponding branches
            length = visibleNodes.length;
            for (i = 0; i < length; i++){
                node = visibleNodes[i];
                node.bubble(function(n){
                    if (n.parentNode){
                        resultNodes.push(n.parentNode);
                    } else {
                        return false;
                    }
                });
            }
            visibleNodes = Ext.Array.merge(visibleNodes, resultNodes);
            
            //identify all the other nodes that should be removed (either they are not visible or are not a parent of a visible node)
            resultNodes = [];
            root.cascadeBy(function(n){
                if (!Ext.Array.contains(visibleNodes,n)){
                    resultNodes.push(n);
                }
            });
            //we can't remove them during the cascade - pulling rug out ...
            length = resultNodes.length;
            for (i = 0; i < length; i++){
                resultNodes[i].remove();
            }
            //filtering should NOT add rows to the queue of records to remove ... we reverse this from happening
            me.removed = removedRecords;
            
            //necessary for async-loaded trees
            root.getOwnerTree().getView().refresh();
            //try to expand everything
            root.expand(true);
        }, //eof applyFilters
        
        //@inheritdoc
        filter: function(filters, value) {
            var nodes, nodeLength, i, filterFn;
            
            if (Ext.isString(filters)) {
                filters = {
                    property: filters,
                    value: value
                };
            }
            
            //find branch nodes that have not been loaded yet - this approach is in contrast to expanding all nodes recursively, which is unnecessary if some nodes are already loaded.
            filterFn = function(item){ return !item.isLeaf() && !(item.isLoading() || item.isLoaded()); };
            nodes = Ext.Array.filter(this.tree.flatten(), filterFn);
            nodeLength = nodes.length;
            
            if (nodeLength === 0){
                this.applyFilters(filters);
            } else {
                for (i = 0; i < nodeLength; i++){
                    this.load({ 
                        node: nodes[i], 
                        callback: function(){
                            nodeLength--;
                            if (nodeLength === 0){
                                //start again & re-test for newly loaded nodes in case more branches exist
                                this.filter(filters,value);
                            }
                        },
                        scope: this
                    });
                }
            }
        },
    
    
        clearFilter: function(suppressEvent) {
            var me = this;
            
            me.filters.clear();
            
            if (me.isFiltered()){
                me.setRootNode(me.snapshot);
                delete me.snapshot;
            }
        },
    
    
        isFiltered: function() {
            var snapshot = this.snapshot;
            return !! snapshot && snapshot !== this.getRootNode();
        }
    });


    Example pseudo-usage:
    Code:
    var store = Ext.ComponentQuery.query('treepanel')[0].getStore();
    var regex = new RegExp(Ext.String.escapeRegex("some search term"),'i');
    
    store.clearFilter(true); //by default, filters will be appended to any existing so we clear them first
    store.filter(new Ext.util.Filter({
        filterFn: function(item){
            return regex.test(item.get('text'));
        }
    }));
    Last edited by zombeerose; 8 Aug 2013 at 10:47 AM. Reason: Fix issue with filtered records being removed

  7. #17
    Sencha User pierrocknroll's Avatar
    Join Date
    Aug 2011
    Posts
    36
    Vote Rating
    -3
    pierrocknroll can only hope to improve

      0  

    Default


    Thanks a lot zombeerose, it works and has the advantage to extends the treestore, not the treepanel or the treeview.

    But be careful I had to change
    Code:
    override: 'Ext.data.TreeStore',
    by
    Code:
    extend: 'Ext.data.TreeStore',
    And I have a problem : the records ids are changed after filtering so getNodeById doesn't work after filtering...


    FYI I have a small shortcut function:
    Code:
    filterBy: function(funct)
      {
        this.filter(
          new Ext.util.Filter(
          {
            filterFn: funct
          })
        );
      },

  8. #18
    Ext JS Premium Member
    Join Date
    Mar 2008
    Location
    Phoenix, AZ
    Posts
    627
    Vote Rating
    10
    zombeerose will become famous soon enough zombeerose will become famous soon enough

      0  

    Default


    @pierrocknroll

    Can you explain why you "had to change" it to an extend vs override? Both options should work ... override affects everything while extends applies to a custom class used explicitly. Thx

  9. #19
    Sencha User pierrocknroll's Avatar
    Join Date
    Aug 2011
    Posts
    36
    Vote Rating
    -3
    pierrocknroll can only hope to improve

      0  

    Default


    Well I have a strange error "object is not a function" from nowhere It's maybe because I have a big application with several custom components.

    By the way, I have an other problem : the records ids are changed after filtering so getNodeById doesn't work.

  10. #20
    Ext JS Premium Member
    Join Date
    Oct 2009
    Posts
    60
    Vote Rating
    1
    hhangus is on a distinguished road

      -1  

    Default


    Hi zomberose

    I originally implemented my filters on the store as you have done (in fact it's almost identical). The reason I moved away from doing it this way was because it turned out to be very slow on large datasets. The root.copy function is very time consuming and must be done each time the filtering changes.

    I switched to filtering the View because it is an order of magnitude faster for me.

    In any case, if you wish to discuss your extension and provide support I ask that you please create your own forum post for it rather than mucking up this one.

    Thank you!

    hhangus