extjs - Ext JS 4: Filtrar una TreeStore
extjs4 (5)
La anulación anterior es excelente y resuelve algunos de mis problemas, sin embargo, encontré un error que es difícil de encontrar con el código anterior. Después de pasar medio día, me di cuenta de que necesitamos usar slice () para copiar la matriz, de lo contrario, algunos nodos se eliminan.
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;
}
});
Originalmente publiqué esto en los foros de Sencha pero no recibí ninguna respuesta (aparte de mi propia respuesta, que publicaré pronto), así que voy a volver a publicarla aquí y ver si recibo más ayuda.
He estado analizando mi cerebro sobre cómo filtrar un TreeStore en 4.0.7. He intentado lo siguiente:
El modelo
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''}
});
La tienda
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''
}
}
});
El árbol
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
});
El filtro
var filter = Ext.create(''Ext.util.Filter'', {
filterFn: function(item) {
return item.data.text == ''leaf1'';
}
});
Así que creo que mi problema es ... No sé cómo usar este filtro debido a que TreeStore no hereda ningún tipo de funciones de filtro, como una tienda normal. He intentado:
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.
Normalmente, si tengo una grilla y creo un filtro como el anterior, puedo hacer myTree.store.filter(filter)
y myTree.store.filter(filter)
el ítem / filtro de cada fila en lo que devuelva ... pero estoy pensando porque TreeStore no hereda una función de filtrado, que no se está transfiriendo.
Si alguien puede darme alguna aclaración sobre lo que estoy haciendo mal o alguna idea sobre cómo configurar una función de filtro / mi proceso de pensamiento, por favor siga adelante. Agradecería cualquier ayuda.
Estaba buscando una forma de filtrar un TreeStore para que si una función filterBy devolviera verdadero para cualquier nodo, quería mostrar la jerarquía de nodos completa de ese nodo, incluidos todos los nodos principales, nodo principal, etc. y nodos secundarios, grand child nodo, etc. Lo modifiqué de las otras soluciones proporcionadas en esta pregunta. Esta solución funciona de manera recursiva, por lo que la arboleda puede ser de cualquier tamaño.
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;
}
});
Por lo tanto, funciona con un filtro de tienda normal. Por llamada.
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;
});
Pude hacer algunos filtros básicos usando el evento onbeforeappend . Si bien no está tan bien estructurado como las soluciones anteriores, esto proporciona una manera fácil y directa de aplicar el filtrado básico sin la necesidad de anular los métodos de la clase base o usar complementos externos.
Implementé mi filtrado en la tienda en sí. En escenarios más avanzados, esto también se puede hacer en el controlador.
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;
}
}
});
Esta es la respuesta que se me ocurrió ... no es ideal, así que espero que alguien pueda proporcionar un enfoque mejor y más genérico. ¿Por qué? Bueno, si mi árbol tiene un padre que tuvo un hijo que tuvo un hijo, me gustaría filtrar sobre ellos, pero mi solución solo tiene un hijo profundo.
Gracias a este hilo , calculé algunas cosas. El único problema con este hilo es que hizo el filtrado plano ... por lo que los nodos secundarios no aparecerían debajo de sus nodos principales. Modifiqué su implementación y se me ocurrió esto (solo va 1 hijo profundo, por lo que no funcionaría si tienes un padre que contiene un hijo que tiene un hijo):
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;
}
Entonces esto funciona cuando hago algo como esto (usando mi árbol en la primera publicación):
Ext.getCmp(''myTree'').store.filterBy(function(rec) {
return rec.data.id != ''child1'';
});
Este código devolverá cada registro que no tenga una identificación child1, por lo que bajo leaf1, solo tendrá child2 como nodo. También puedo borrar el filtro haciendo Ext.getCmp(''myTree'').store.clearFilter()
.
Ahora, me doy cuenta de que acabo de responder mi propia pregunta, pero como publiqué anteriormente, realmente me gustaría criticar / asesorar sobre lo que puedo hacer más eficiente y genérico. Si alguien tiene algún consejo, ¡me encantaría escucharlos! Además, si necesita ayuda para poner en funcionamiento este código, hágamelo saber.
Sha, también probé filtros, pero no tuve suerte. Eche un vistazo a este hilo .
Gracias por atrapar a ese otro , arreglé la respuesta para incluir la anulación del filtro TreeStore más dinámica que incluí a continuación para responder a su Q.
Está funcionando bien en 4.1b2, sé que hubo algunos cambios en la arborescencia entre 4.07 y 4.1, pero creo que 4.07 todavía tenía los objetos de árbol que estoy usando aquí.
Aquí está la anulación:
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;
}
});
Utiliza el objeto store.tree.nodeHash
para iterar a través de todos los nodos frente a los filtros en lugar de solo el primer elemento secundario. Aceptará un filtro como una función o par de propiedad / valor. Supongo que el método clearFilter podría ser resuelto para evitar otra llamada ajax.