0

I want to apply filter on Ext.Data.TreeStore. The model which the tree store is using is having a property by the name 'ID' & based on this 'ID' I want to apply filter on the tree store.

I looked at the following links:

Filter 1

Filter 2

But these options are not working.

On the store 'load' event, I have added following code.

 'load': function (thisStore, records, successful, eOpts) {

            var v = 'Product';
            var count = 0;
            thisStore.filterBy(function (record) {
                count++;
                return record.data.ID == v;
            });
            alert(count);
        }

But the count is always coming as 0.

My tree looks like this:

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

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

Please suggest the changes I need to make to ensure I can filter a TreeStore.

Community
  • 1
  • 1
SharpCoder
  • 18,279
  • 43
  • 153
  • 249
  • after looking in extjs documentation. I didn't find any treefilter plugin. are you sure it exist? – peernohell Oct 03 '13 at 13:05
  • No surprise `count` comes as 0, look at [the code](http://docs.sencha.com/extjs/4.1.3/source/AbstractStore.html#Ext-data-AbstractStore-method-filterBy)! That's probably the reason why they've marked this method as private in tree store. – rixo Oct 03 '13 at 13:17
  • @peernohell: I have added the plugin code. – SharpCoder Oct 03 '13 at 13:43

1 Answers1

2

You have to implement something yourself... Here's a filterBy method that would work. Note that, contrarily to the regular store filter, this one doesn't keep a reference of the filtered out node (hence, no clearFilter method possible). If you need anything fancy, you'll have to adapt.

Fiddle here

var store = Ext.create('Ext.data.TreeStore', {
    root: {
        expanded: true,
        children: [
            { text: "detention", leaf: true },
            { text: "homework", expanded: true, children: [
                { text: "book report", leaf: true },
                { text: "algebra", leaf: true}
            ] },
            { text: "buy lottery tickets", leaf: true }
        ]
    }
});

var tree = Ext.create('Ext.tree.Panel', {
    title: 'Simple Tree',
    width: 200,
    height: 150,
    store: store,
    rootVisible: false,
    renderTo: Ext.getBody()

    /**
     * Filters the tree recursively with the given function.
     * 
     * @param {Function} fn
     * @param {Object} [scope]
     */
    ,filterBy: function(fn, scope) {
        scope = scope || this;

        function applyFilter(node) {
            var out = []; 
            Ext.each(node.childNodes, function(child) {
                if (fn.call(scope, child)) {
                    applyFilter(child);
                } else {
                    // we can't remove child right away, that would
                    // kill the loop
                    out.push(child);
                }
            });
            Ext.each(out, function(child) {
                // destroy, and suppressEvent
                node.removeChild(child, true, true);
            });
        }

        applyFilter(this.getRootNode());
    }
});

// example
tree.filterBy(function(record) {
    return record.get('text').indexOf('o') !== -1;
});
Stefano Sanfilippo
  • 32,265
  • 7
  • 79
  • 80
rixo
  • 23,815
  • 4
  • 63
  • 68
  • Thank you for quick reply. I have called the filterBy() on the store load event. I am using this code : 'load': function (thisStore, records, successful, eOpts) { var v = 'Product'; var count = 0; thisStore.filterBy(function (record) { alert('there'); count++; return record.data.ID == v; }); } But system never display the alert message which means this function is not getting called :(. Am I doing anything wrong ? – SharpCoder Oct 03 '13 at 14:18
  • With my code, the `fiterBy` method must be called from the tree component, not its store. Anyway, considering the code of the plugin you've posted... Are you positive that there is actually some data in your tree? Do you see some nodes? – rixo Oct 03 '13 at 14:23
  • I am still not able to pull this off. I have created a fiddle. It would be great if you can help me fix it. http://jsfiddle.net/BYqRN/22/ – SharpCoder Oct 04 '13 at 10:20
  • Fields configuration is missing in the store, so your custom fields ("Names", etc.) were ignored. See your [updated fiddle](http://jsfiddle.net/BYqRN/23/), I've just added some `fields` to the store and now it is working! – rixo Oct 04 '13 at 11:07
  • Thank you for the update. Is it possible to hide a node instead of removing it? So if I hide a node, I can make it visible that will work similar to clearFilter. – SharpCoder Oct 04 '13 at 11:26