2012-03-09 7 views
6

Я изначально размещал это на форумах Sencha here, но не получал ответов (кроме моего собственного ответа, который я опубликую в ближайшее время), поэтому я собираюсь отправить его здесь и посмотреть если я получу больше помощи.Ext JS 4: Фильтрация TreeStore

У меня возникла проблема с тем, как фильтровать 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 не наследует функцию фильтрации, которая не передается.

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

+0

Интересно ... В своей документации сказано, что у Treestore есть свойство filter, но нет упоминаний о каких-либо методах фильтрации. Вы пытались указать фильтры [] перед загрузкой, а также установить filterOnLoad: true? – sha

ответ

6

Благодаря отлов, что other one, я устроился ответ, включив в него более динамичный TreeStore фильтр переопределение, что я включал ниже, чтобы ответить на ваш Q.

Он работает отлично в 4.1b2, я знаю, что некоторые изменения к treestore между 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 объект для перебора всех узлов против фильтров, а не только первого ребенка. Он будет принимать фильтр как функцию или пару property/value. Я полагаю, что метод clearFilter может быть обработан, хотя для предотвращения другого вызова ajax.

+0

Удивительный! Я попробую в понедельник. Если бы я мог продвинуться, я бы, ха-ха ... – incutonez

+0

Так что я боролся с попыткой заставить это работать. Я предполагаю, что это просто не работает в 4.0.7 ... когда я фильтрую дерево, я ничего не получаю, и если я попытаюсь очистить фильтр, ничего не будет возвращено, но это может быть из-за того, что я использую тип памяти в качестве прокси. Кроме того, в вашей функции clearFilter установлен filter.clear() для AbstractStore, или это не имеет значения? Не могли бы вы получить рабочее решение для 4.0.7? – incutonez

+0

Я беру часть этого назад ... фильтрация работает, если я использую ваш код «только фильтр только как функция». ClearFilter() все еще не работает. Я попробую с ним пообщаться. – incutonez

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

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

Sha, я также пробовал фильтры, но не повезло. Посмотрите на this thread.

+0

Будьте очень осторожны, если вы используете autoSync в своем TreeStore. Это может вызвать серьезные проблемы. –

+0

Да, сейчас я просто рекомендую перейти на Ext JS 5 и использовать их фильтрацию деревьев. – incutonez

+0

Или переместите фильтрацию на сервер. –

0

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

Таким образом, он работает так же, как и обычный фильтр хранилища.

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

отлично работает на настольной версии приложения sencha touch, но при создании блокировки телефона для ios/android это решение ломается. Какие идеи могут быть проблемой? вы могли успешно запустить его на iOS? – Meer

0

выше переопределение велико, и он решает некоторые из моих проблем, однако, я нашел ошибку, что трудно найти с указанным кодом. Проведя полдня, я понял, что нам нужно использовать 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

К счастью, вам не придется полагаться на это переопределение намного дольше ... в бета-версии Ext JS 5, у них есть собственная [фильтрация дерева] (http://docs-origin.sencha.com/extjs/5.0 0,0/apidocs/#!/апи/Ext.data.TreeStore-метод-filterBy). – incutonez

+1

Внимание: это вызывает серьезные проблемы, если вы используете autoSync. –

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; 
     } 
    } 
}); 
Смежные вопросы