Ext JS 4: фильтрация TreeStore - PullRequest
6 голосов
/ 09 марта 2012

Я первоначально разместил это на форумах Sencha здесь , но не получил никаких ответов (кроме моего собственного ответа, который я скоро опубликую), поэтому я собираюсь опубликовать его здесь и посмотреть,Мне больше помогают.

Я ломал голову над тем, как отфильтровать TreeStore в 4.0.7.Я пробовал следующее:

Модель

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 ]

6 голосов
/ 10 марта 2012

Спасибо, что поймали этот другой , я исправил ответ, включив в него более динамическое переопределение фильтра дерева хранилищ, которое я включил ниже, чтобы ответить на ваш вопрос.

Он работает нормально в 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 голос
/ 09 марта 2012

Это ответ, который я придумал ... он не идеален, поэтому я надеюсь, что кто-то может предложить лучший, более общий подход.Зачем?Ну, если бы у моего дерева был родитель, у которого был ребенок, у которого был ребенок, я бы хотел отфильтровать его, но мое решение подходит только для одного ребенка.

Благодаря этой теме Я кое-что понял.Единственная проблема с этим потоком состоит в том, что он сделал фильтрацию плоской ... чтобы дочерние узлы не появлялись под их родительскими узлами.Я изменил их реализацию и придумал это (он подходит только для 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, поэтому в листе будет только child2 в качестве узла.Я также могу очистить фильтр, выполнив Ext.getCmp('myTree').store.clearFilter().

Теперь я понимаю, что только что ответил на свой собственный вопрос, но, как я уже писал выше, мне бы очень хотелось критиковать / советовать, что я могу сделать более эффективными общий.Если у кого-нибудь есть какие-либо советы, я бы хотел их услышать!Также, если вам нужна помощь в настройке и запуске этого кода, дайте мне знать.

Ша, я тоже пробовал фильтры, но не повезло.Посмотрите на эту тему .

0 голосов
/ 02 января 2015

Мне удалось выполнить базовую фильтрацию, используя событие 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;
        }
    }
});
0 голосов
/ 10 апреля 2014

Вышеуказанное переопределение отлично, и оно решает некоторые из моих проблем, однако я обнаружил ошибку, которую трудно найти с помощью приведенного выше кода.Потратив полдня, я понял, что нам нужно использовать 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;
      }
    });
0 голосов
/ 06 марта 2014

Я искал способ фильтрации хранилища деревьев, чтобы, если функция 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;
        }
    });

Так что он работает так же, как и обычный фильтр storeBy.

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