Sencha Touch 中，如何开发 nestedList 查询功能?
李小翔
2014-01-07 11:17

内容列表

Touch中，定义了一个通讯录的面板，面板中包含了一个 nestedList，同时还定义了一个 TreeStore，通过ajax的方式加载远程数据并显示，代码片段如下：
// 定义通讯录面板
Ext.define(’MyApp.view.AddressPanel’, { 
    extend: ’Ext.Panel’,
    config: { 
        itemId: ’addressPanel’,
        items: [
            { 
                xtype: ’nestedlist’,
                store: ’AddressJsonTreeStore’,
                title: ’通讯录’,
                ...
            } , ...
        ], ...
    } , ...
} ;
// 定义 TreeStore
Ext.define(’MyApp.store.AddressJsonTreeStore’, { 
    extend: ’Ext.data.TreeStore’,
    config: { 
        model: ’MyApp.model.AddressModel’,
        storeId: ’AddressJsonTreeStore’,
        proxy: { 
            type: ’ajax’,
            url: ’address’,
            reader: { 
                type: ’json’
            } 
        } , ...
    } , ...
} );
现在，需要在这个面板的title bar上，添加一个查询框，以实现根据名称查询的功能，由于数据库比较大，并且都是延迟加载，因此仍然采用ajax的加载方式。
首先，我们在通讯录面板的title bar上添加一个查询框searchfield：
...
title: ’通讯录’,
toolbar: { 
    xtype: ’titlebar’,
    items: [
        { 
            xtype: ’searchfield’,
            align: ’right’,
            itemId: ’seaerchAddressField’,
            maxWidth: ’130px’,
            label: ’’,
            placeHolder: ’ 姓名..’
        } ,...
    ]
} 
添加了查询框后，界面如下图所示：
然后，修改控制器，添加查询和重置事件：
Ext.define(’MyApp.controller.AddressController’, { 
    extend: ’Ext.app.Controller’,
    config: { 
        refs: { 
           // 定义一个属性，便于使用
            addressNestedList: ’addresspanel nestedlist[itemId=addressNestedList]’,
            ...
        } ,
        control: { 
           // 定义两个事件
            ”addresspanel [itemId=seaerchAddressField]”: { 
                action: ’onSeaerchAddressFieldAction’,    // 点击查询后的事件
                clearicontap: ’onSeaerchAddressFieldClearicontap’ // 点击重置图标后的事件
            } , ...
        } 
    } ,
   
   /**
     * 点击查询后的事件
     */
    onSeaerchAddressFieldAction: function(textfield, e, eOpts) { 
       this.searchAction( textfield.getValue().trim() );
    } ,
   /**
     * 点击重置图标后的事件
     */
    onSeaerchAddressFieldClearicontap: function(textfield, e, eOpts) { 
       this.searchAction();
    } ,
   /**
     * 执行查询或重置
     */
    searchAction: function(val) { 
        ......
    } 
} );
上面的控制器代码中，关键就是下面那个 searchAction 方法，这里将执行查询操作：
方案1：TreeStore 有一个 remoteFilter 属性，设置为 true 后，可以调用其 filter 或 find 方法执行ajax查询。
但是在实际测试中，这种方法的性能非常低下，基本不可用
方案2：每次查询时，重新创建一个新的 store 对象，并设置为 nestedList 的 store，执行ajax查询与重新渲染。
这中方案执行效率相当高，最终的代码示例如下。
searchAction: function(val) { 
   if ( val ) { 
        val = val.replace(/[’”\ /\ \ \ s]/g, ’’);
       if ( val.length < 2 ) { 
           return Ext.Msg.alert(”请输入至少两个字”);
        } 
    } 
   var list = this.getAddressNestedList();
   var storeId = ”AddressJsonTreeStore_” + val;
   if ( !val ) {  // 重置
        store = Ext.getStore(”AddressJsonTreeStore”);
    }  else {       // 搜索
        store = Ext.getStore(storeId);
       if ( !store ) { 
           // 不同的查询条件，后台返回的机构id必须不一样，否则可能显示后台数据中没有的机构节点
            store = Ext.create(”MyApp.store.AddressJsonTreeStore”, {  storeId: storeId } );
            store.getProxy().setExtraParam(’find’, val);  // 设置远程查询参数
            list.getStore().setAutoDestroy(true);
            list.setEmptyText(”没有找到您想要的数据！”);
        } 
    } 
    list.updateStore( store );    // 速度最快
    list.setStore( store );
} 
在上面的最终代码中，需要注意后台返回的查询结果 json 数据中，如果仍然为树形结构，那么每个节点（非叶子节点）的id建议保持不一样，例如：
不是查询时，有如下的结构：{  data:{ id:’foo’, name:’foobar’, data:{ ...} } , ... } 
则查询结果应为：{  data:{ id:’bar’, name:’foobar’, data:{ ...} } , ... } 
原因在于 nestedList 的渲染缓存，如：
点开节点foobar，可以看到子节点 foo 与 bar
查询后，如果返回结果中的节点foobar的id保持不变，且子节点只有foo了，但此时点开节点foobar，仍然会显示出节点 foo 与 bar，这显然是错误的
若查询的返回结果中的节点foobar的id设置为一个新的值，则点开节点foobar后，只会显示出一个foo子节点，这才是正确结果。