Ext JS 4:过滤 TreeStore

Posted

技术标签:

【中文标题】Ext JS 4:过滤 TreeStore【英文标题】:Ext JS 4: Filtering a TreeStore 【发布时间】:2012-03-26 12:51:35 【问题描述】:

我最初在 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 没有'不继承过滤函数,它没有被传入。

如果有人可以澄清我做错了什么,或者对如何设置过滤功能/我的思考过程有任何见解,请继续。如有任何帮助,我将不胜感激。

【问题讨论】:

有趣...在他们的文档中说 Treestore 具有过滤器属性,但没有提及任何过滤方法。您是否尝试在加载之前指定 filters[] 并设置 filterOnLoad: true? 【参考方案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() 清除过滤器。

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

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

【讨论】:

如果您在 TreeStore 上使用自动同步,请务必小心。这可能会导致重大问题。 是的,在这一点上,我只建议升级到 Ext JS 5 并使用他们的树过滤。 或者,将您的过滤移至服务器。 这在 sencha 应用程序的桌面版本上对我来说很好,但是当我为 ios/android 创建 phonegap 构建时,这个覆盖不起作用。有什么想法吗?【参考方案2】:

感谢您了解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 调用。

【讨论】:

所以我一直在努力让它发挥作用。我假设它在 4.0.7 中不起作用......当我过滤树时,我没有返回任何内容,如果我尝试清除过滤器,则没有返回任何内容,但这可能是因为我正在使用内存类型作为代理。此外,在您的 clearFilter 函数中,filters.clear() 是为 AbstractStore 定义的,还是无关紧要?您是否有任何适用于 4.0.7 的有效解决方案? 我收回其中的一部分...如果我使用您的“仅叶子过滤器作为函数”代码,过滤将起作用。 clearFilter() 仍然不起作用。我会尝试更多地弄乱它。 好的,我想我已经成功了...如果我在创建变量后立即将 me.snapshot = me.snapshot || me.getRootNode().copy(null, true); 添加到过滤器代码中,然后将 clearFilter 更改为 if (me.isFiltered()) me.getRootNode().removeAll(); me.snapshot.eachChild(function(child) me.getRootNode().appendChild(child.copy(null, true)); ); delete me.snapshot; me.hasFilter = false; 一切似乎都有效.唯一的问题是,循环遍历快照的子元素有点昂贵,所以也许有更好的方法? 对不起,我出去几天了。看来您现在在 4.07 中拥有它,我从未尝试过除 4.1b2 以外的任何东西。 是的,绝对有效。谢谢!唯一的问题是,使用 .load 重新加载商店并不是我真正想要的,所以这就是我更改 clearFilter 的原因。【参考方案3】:

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

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

【讨论】:

在桌面版 sencha touch 应用程序上运行良好,但在为 ios/android 创建手机间隙构建时,此解决方案会中断。任何想法可能是什么问题?你能在 iOS 上成功运行它吗?【参考方案4】:

上面的覆盖很棒,它解决了我的一些问题,但是,我发现了一个用上面的代码很难找到的错误。花了半天,我发现我们需要使用 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;
      
    );

【讨论】:

谢天谢地,您不必再依赖这个覆盖了...在 Ext JS 5 beta 版本中,它们具有原生 tree filtering。 警告:如果您使用自动同步,这会导致严重问题。【参考方案5】:

我能够使用 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;
        
    
);

【讨论】:

以上是关于Ext JS 4:过滤 TreeStore的主要内容,如果未能解决你的问题,请参考以下文章

Ext JS 6学习文档-第4章-数据包

如何在 Ext JS Grid 3.2 的列标题中添加过滤器选择器

在 Ext JS 中搜索模式

如何在 extjs 4 (mvc) 中过滤静态 treeStore

ext.js 网格内部手风琴:如何将网格标题设置为手风琴的

Ext JS 4 存储加载回调