Ext JS 4:过滤TreeStore

时间:2012-03-09 01:40:36

标签: extjs tree extjs4 store

我最初是在Sencha论坛here上发布的,但没有得到任何回复(除了我自己的答案,我将很快发布),所以我将在这里重新发布,看看我是否得到再帮助了。

我一直在讨论如何在4.0.7中过滤TreeStore。我尝试了以下内容:

模型

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

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

过滤器

var filter = Ext.create('Ext.util.Filter', {
  filterFn: function(item) {
    return item.data.text == 'leaf1';
  }
});

所以我认为我的问题是......我不知道如何使用这个过滤器,因为TreeStore实际上没有继承任何类型的过滤器函数,如普通商店。我试过了:

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.

通常情况下,如果我有一个网格并且我创建了一个像上面那样的过滤器,我可以做myTree.store.filter(filter)并且它会抓住我返回的每一行的项目/过滤器...但我在想,因为TreeStore不会继承一个未被传入的过滤函数。

如果有人能够清楚地说明我做错了什么,或者对如何设置过滤功能/思考过程有任何见解,请继续。我很感激任何帮助。

5 个答案:

答案 0 :(得分:6)

感谢您抓住other one,我修复了答案,以包含下面包含的更具动态的树存储过滤器覆盖,以回答您的问题。

它在4.1b2中工作正常,我知道4.07和4.1之间的树库有一些变化,但我认为4.07仍然有我在这里使用的树对象。

这里是覆盖:

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

});

它使用store.tree.nodeHash对象来迭代所有节点而不是第一个子节点。它将接受过滤器作为函数或属性/值对。我认为clearFilter方法可以解决,以防止另一个ajax调用。

答案 1 :(得分:1)

这是我想出的答案......它并不理想,所以我希望有人可以提供更好,更通用的方法。为什么?好吧,如果我的树上有一个孩子有一个孩子的父母,我想过滤那些,但我的解决方案只有一个孩子深。

感谢this thread,我想出了一些事情。这个线程的唯一问题是它使得过滤平面...所以子节点不会出现在它们的父节点下。我修改了他们的实现并想出了这个(它只有1个孩子深,所以如果你的父母包含一个有孩子的孩子,那就不行了):

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

所以当我做这样的事情时(在第一篇文章中使用我的树),这是有效的:

Ext.getCmp('myTree').store.filterBy(function(rec) {
  return rec.data.id != 'child1';
});

此代码将返回每个没有child1 id的记录,因此在leaf1下,它只会将child2作为节点。我也可以通过Ext.getCmp('myTree').store.clearFilter()清除过滤器。

现在,我意识到我刚刚回答了我自己的问题,但就像我上面发布的那样,我真的很喜欢批评/建议我可以提高效率和通用性。如果有人有任何提示,我很乐意听到他们!另外,如果您需要帮助来获取并运行此代码,请告诉我们。

沙,我也试过过滤器,但没有运气。看看this thread

答案 2 :(得分:0)

我一直在寻找一种过滤树库的方法,这样如果filterBy函数为任何节点返回true,我想显示该节点的完整节点层次结构,包括所有父节点,祖父节点等和子节点,大子节点等我从这个问题中提供的其他解决方案中修改了它。此解决方案以递归方式工作,因此treestore可以是任何大小。

Ext.override(Ext.data.TreeStore, {

        hasFilter: false,

        /**
        * Filters the current tree by a function fn
        * if the function returns true the node will be in the filtered tree
        * a filtered tree has also a flat structure without folders
        */
        filterBy : function(fn, scope) {
            var me    = this,
            nodes = [],
            root  = me.getRootNode(),
            tmp;


            // the snapshot holds a copy of the current unfiltered tree
            me.snapshot = me.snapshot || root.copy(null, true);


            tmp = me.snapshot.copy(null, true);
            var childNodes = tmp.childNodes;
            root.removeAll();
            for( var i=0; i < childNodes.length; i++ ) {

                //Recursively tranverse through the root and adds the childNodes[i] if fn returns true
                if( this.traverseNode( childNodes[i], root, fn ) == true ) {
                                 i--;
                            }

            }

            return me;
        },

        /**
        * Recursively tranverse through the root and adds the childNodes[i] if fn returns true
        */
        traverseNode: function( node, parentNode, fn ) {

            var me = this;

            if( fn.call( me, node ) ) {
                parentNode.appendChild( node );
                return true;
            }

            if( node.hasChildNodes() ) {

                var childNodes = node.childNodes;
                var found = false;

                for( var i=0; i < childNodes.length; i++ ) {
                    if( this.traverseNode( childNodes[i], node, fn ) == true ) {
                        found = true;
                    }
                }

                if( found == true ) {
                    parentNode.appendChild( node );
                    return true;
                }
            }

            return false;
        },


        /**
        * Clears all filters a shows the unfiltered tree
        */
        clearFilter : function() {
            var me = this;

            if (me.isFiltered()) {
                me.setRootNode(me.snapshot);
                delete me.snapshot;
            }

            return me;
        },

        /**
        * Returns true if the tree is filtered
        */
        isFiltered : function() {
            return !!this.snapshot;
        }
    });

所以它就像普通商店filterBy调用一样。

searchText = "searchText";
store.filterBy( function(item) {

            var keys = item.fields.keys;

            for( var i=0; i < keys.length; i++ ) {
                var value = item.get( keys[i] );
                if( value != null ) {
                    if( value.toString().toLowerCase().indexOf( searchText ) !== -1 ) {
                        return true;
                    }
                }
            }

            return false;
        });

答案 3 :(得分:0)

上面的覆盖是很好的,它解决了我的一些问题,但是,我发现了一个很难用上面的代码找到的bug。花了半天后,我发现我们需要使用slice()来复制数组,否则会删除一些节点。

    Ext.override(Ext.data.TreeStore, {

      hasFilter: false,

      /**
      * Filters the current tree by a function fn
      * if the function returns true the node will be in the filtered tree
      * a filtered tree has also a flat structure without folders
      */
      filterBy: function (fn, scope) {
        var me = this,
                nodes = [],
                root = me.getRootNode(),
                tmp;


        // the snapshot holds a copy of the current unfiltered tree
        me.snapshot = me.snapshot || root.copy(null, true);


        tmp = me.snapshot.copy(null, true);
        var childNodes = tmp.childNodes.slice();
        root.removeAll();
        for (var i = 0; i < childNodes.length; i++) {

          //Recursively tranverse through the root and adds the childNodes[i] if fn returns true
          this.traverseNode(childNodes[i], root, fn);
        }

        return me;
      },

      /**
      * Recursively tranverse through the root and adds the childNodes[i] if fn returns true
      */
      traverseNode: function (node, parentNode, fn) {

        var me = this;
        if (fn.call(me, node)) {
          parentNode.appendChild(node);
          return true;
        }


        if (node.hasChildNodes()) {

          var t_childNodes = node.childNodes.slice();
          var found = false;

          for (var i = 0; i < t_childNodes.length; i++) {
            if (this.traverseNode(t_childNodes[i], node, fn) == true) {
              found = true;
            }
          }

          if (found == true) {
            parentNode.appendChild(node);
            return true;
          }
        }

        return false;
      },


      /**
      * Clears all filters a shows the unfiltered tree
      */
      clearFilter: function () {
        var me = this;

        if (me.isFiltered()) {
          me.setRootNode(me.snapshot);
          delete me.snapshot;
        }

        return me;
      },

      /**
      * Returns true if the tree is filtered
      */
      isFiltered: function () {
        return !!this.snapshot;
      }
    });

答案 4 :(得分:0)

我可以使用 onbeforeappend 事件进行一些基本过滤。 虽然结构不如上述解决方案,但它提供了一种简单直接的方法来应用基本过滤,而无需覆盖基类方法或使用外部插件。

我在商店本身实现了过滤。 在更高级的场景中,这也可以在控制器中完成。

Ext.define('MyApp.store.FilteredTreeStore', {
extend: 'Ext.data.TreeStore',
....

....
listeners: {
        beforeappend: function (thisStore, node, eOpts) {
            var allowAppend = false;
            allowAppend = --your filtering logic here
            --returning false will cancel append of the entire sub tree

            return allowAppend;
        }
    }
});