Ext JS 4: Filtering a TreeStore

21,455

Solution 1

Thanks for catching that other one, I fixed up the answer to include the more dynamic treestore filter override that I included below to answer your Q.

It is working fine in 4.1b2, I know there were some changes to the treestore between 4.07 and 4.1 but I think 4.07 still had the tree objects I am using here.

Here's the override:

Ext.override(Ext.data.TreeStore, {

    hasFilter: false,

    filter: function(filters, value) {

        if (Ext.isString(filters)) {
            filters = {
                property: filters,
                value: value
            };
        }

        var me = this,
            decoded = me.decodeFilters(filters),
            i = 0,
            length = decoded.length;

        for (; i < length; i++) {
            me.filters.replace(decoded[i]);
        }

        Ext.Array.each(me.filters.items, function(filter) {
            Ext.Object.each(me.tree.nodeHash, function(key, node) {
                if (filter.filterFn) {
                    if (!filter.filterFn(node)) node.remove();
                } else {
                    if (node.data[filter.property] != filter.value) node.remove();
                }
            });
        });
        me.hasFilter = true;

        console.log(me);
    },

    clearFilter: function() {
        var me = this;
        me.filters.clear();
        me.hasFilter = false;
        me.load();
    },

    isFiltered: function() {
        return this.hasFilter;
    }

});

It uses the store.tree.nodeHash object to iterate through all nodes against the filters rather than just the first child. It will accept a filter as a function or property/value pair. I suppose the clearFilter method could be worked over though to prevent another ajax call.

Solution 2

This is the answer that I came up with... it's not ideal, so I'm hoping someone can provide a better, more generic approach. Why? Well, if my tree had a parent that had a child that had a child, I'd like to filter on those, but my solution only goes one child deep.

Thanks to this thread, I figured some things out. The only problem with this thread is that it 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):

TreeStore

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()) {
    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;
},
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 also clear the filter by doing Ext.getCmp('myTree').store.clearFilter().

Now, I realize I just answered my own question, but like I posted above, I'd really like critiquing/advice on what I can make more efficient and generic. If anyone has any tips, I'd love to hear them! Also, if you need help getting this code up and running, let me know.

Sha, I also tried filters, but no luck. Have a look at this thread.

Share:
21,455
incutonez
Author by

incutonez

Web developer that enjoys building web applications full of pizzazz. I'm a reformed Ext JS developer that's currently loving Vue.js.

Updated on July 10, 2022

Comments

  • incutonez
    incutonez almost 2 years

    I originally posted this on the Sencha forums here but didn't get any responses (other than my own answer, which I will post soon), so I am going to repost it here and see if I get anymore help.

    I've been racking my brain on how to filter a TreeStore in 4.0.7. I've tried the following:

    The model

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

    The store

    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
    incutonez about 12 years
    So I've been struggling with trying to get this to work. I'm assuming it just doesn't work in 4.0.7... when I filter the tree, I get nothing returned, and if I try to clear the filter, nothing is returned, but that might be because I'm using memory type as the proxy. Also, in your clearFilter function, is filters.clear() defined for an AbstractStore, or does that not matter? Would you by any chance have a working solution for 4.0.7?
  • incutonez
    incutonez about 12 years
    I take some of that back... the filtering works if I use your "leaf only filter as a function" code. The clearFilter() still doesn't work. I'll try messing around with it more.
  • incutonez
    incutonez about 12 years
    Ok, I think I've got it working... if I add me.snapshot = me.snapshot || me.getRootNode().copy(null, true); to the filter code right after you create your variables, and then change clearFilter to if (me.isFiltered()) { me.getRootNode().removeAll(); me.snapshot.eachChild(function(child) { me.getRootNode().appendChild(child.copy(null, true)); }); delete me.snapshot; } me.hasFilter = false; everything seems to work. Only problem is, looping over the snapshot's child elements is kind of expensive, so maybe there's a better way?
  • egerardus
    egerardus about 12 years
    Sorry, I was out for a few days. It seems like you have it now in 4.07, I never tried with anything other than 4.1b2.
  • incutonez
    incutonez about 12 years
    Yep, definitely works. Thanks! Only thing is, reloading the store with .load wasn't really what I was looking for, so that's why I changed clearFilter.
  • incutonez
    incutonez about 10 years
    Thankfully, you won't be having to rely on this override much longer... in the Ext JS 5 beta release, they have native tree filtering.
  • Roy Tinker
    Roy Tinker over 9 years
    Warning: this causes major problems if you're using autoSync.
  • Roy Tinker
    Roy Tinker over 9 years
    Be very careful if you're using autoSync on your TreeStore. This could cause major problems.
  • incutonez
    incutonez over 9 years
    Yeah, at this point, I'd just recommend upgrading to Ext JS 5 and using their tree filtering.
  • Roy Tinker
    Roy Tinker over 9 years
    Or, move your filtering to the server.
  • Meer
    Meer almost 7 years
    This works fine for me on the desktop version of the sencha app but when i create a phonegap build for ios/android this override does not work. Any thoughts?
  • Meer
    Meer almost 7 years
    works fine on the desktop version of sencha touch app, but when creating the phone gap build for ios/android this solution breaks. Any ideas what might be the issue? were you able to successfully run it on iOS?