zoukankan      html  css  js  c++  java
  • 扩展 jquery miniui 组件实现自动查询数据

    主题

      之前写过一篇文章分享了公司basecode查找数据后台是怎么实现的(http://www.cnblogs.com/abcwt112/p/6085335.html).今天想分享一下公司前台是怎么扩展jquery miniui组件去配合后台实现自动查找数据的.

    一个使用例子

    项目中多多少少会遇到要下拉一些数据(不只是下拉,可能还有选择框等等).

    这些数据来自数据库里的某张代码表(当然也可以是复杂的SQL查询).这些数据写死在前台明显是不可能的,因为不同环境下配置的数据可能不同.但是为了每个下拉框去写一个Controller方法,Service方法.Dao方法...好像代价又太大了,而且他们具有一定的同性.这就是Basecode的存在的一个原因.

    前台实现

    后台的实现请参考我另外一篇文章,这篇文章主要分享下怎么扩展jquery miniui组件(miniui是类似于jquery easyui的一个前台JS框架)来实现前台自动发送请求加载数据实现basecode的下拉.

    jquery miniui不是开源的,所以自己写组件继承起来比较蛋疼...但是多多少少还是可以做一些简单的扩展的.

    一个最简单的代码表下拉的例子

    实现basecode自动下拉的主要原理是利用了beforeload事件.

    我们自己定义的组件继承了原本miniui的组件,重写了部分方法,在组件在初始化的时候额外加入了beforeload事件并触发,这样这个组件就会自动去后台查找数据了.

     1 mini.extend(framework.component.ComboList , mini.ComboBox , {
     2     
     3     codeType : "", //绑定代码类型
     4     
     5     remote : false, //是否从远端加载数据
     6     
     7     textField : "label",
     8     
     9     uiCls: "mini-ext_combolist",
    10     
    11     valueField : "value",
    12     
    13     queryparam : {},
    14     
    15     /**
    16      * 重定义加载数据的方法,根据codeType指定的加载数据
    17      */
    18     load : function(data){
    19         if(this.codeType && this.codeType.length > 0){
    20             if(basecode[this.codeType] && !this.remote){
    21                 this.setData(basecode[this.codeType]);
    22             }else{
    23                 this.setUrl(common.getBascodeUrl()+"/"+this.codeType);
    24             }
    25         }
    26     },
    27     
    28     set : function(kv){
    29         var me = this;
    30         framework.component.ComboList.superclass.set.call(this,kv);        
    31         me.on('beforeload',function(e){
    32             e.type = 'POST';
    33             e.dataType = 'json';
    34             e.contentType = 'application/json';
    35             e.data = mini.encode(this.queryparam);
    36         });
    37         this.load({});
    38 
    39     },
    40     
    41     setCodeType: function (codeType) {
    42         this.codeType = codeType;
    43     },
    44     
    45     getCodeType : function(){
    46         return this.codeType ; 
    47     },
    48     getAttrs: function (el) {
    49         
    50         var attrs = framework.component.ComboList.superclass.getAttrs.call(this, el);
    51         mini._ParseString(el, attrs , ['codeType']);
    52         mini._ParseString(el, attrs , ['param']);
    53         var param = attrs['param'];
    54         if(param){
    55             var cotrolids = param.match(common.PARAM_CMP_VALUE_REGEX);
    56             if(cotrolids && cotrolids.length > 0){
    57                 $.each(param.match(common.PARAM_CMP_VALUE_REGEX),function(i,item){
    58                     var id = '#'+item.replace(/#/gi,'');
    59                     var tempobj = mini.get(id);
    60                     if(tempobj && tempobj.value){
    61                         param = param.replace(item , tempobj.getValue());
    62                     }
    63                 });
    64             }
    65             this.queryparam =  mini.decode(param) != 'undefined' ? mini.decode(param) : "";
    66         }
    67         attrs['textField'] = attrs['textField'] || 'label';
    68         attrs['valueField'] = attrs['valueField'] || 'value';
    69         mini._ParseBool(el, attrs,['remote']);
    70         return attrs;
    71 
    72     }
    73     
    74 });
    75 
    76 mini.regClass(framework.component.ComboList , 'ext_combolist');

    完整的一个扩展的组件代码如上面.

    当组件被渲染的时候:

    第一个被调用的方法是L48的getAttrs方法,这个方法的用处就是把你页面上HTML标签比如input里面的各种属性解析成JS对象里面的属性.

    比如你<input codeType="ENUM_SEX">那codeType就会被解析成JS对象的一个属性,{codeType:"ENUM_SEX"}.当然.不同框架的解析方法肯定不同,所以这里并不需要仔细研究(而且也没有源码).

    最主要的属性是codeType,当然param这些也有用处(param里的key和value相当于是查询SQL where里面的条件).但是最简单的例子里不需要用到这些额外的参数,只要知道codeType就能知道basecode里去查哪张表了(详见另外一篇文章)

    第二个被调用的方法是L28 set方法:set的作用就是把之前getAttrs取出来的JS对象的各种属性设置到miniui组件的属性中去.所以getAttrs返回的值就是set方法的入参.

    在这里除了调用父组件的set方法完成基本的set之外,额外做了一个操作,就是最核心的beforeload事件.为自定的组件绑定了beforeload事件.

    beforeload事件里主要是定义了ajax请求的一些参数.比如dataType是json等等..还有一个比较重要的就是ajax传递的参数data是来自组件的queryparam属性(所以组件要传递的参数应该放到这个属性里).

    第三个调用的当然是L18 load方法.它是在set方法最后一行被调用的

    load方法里因为我们设置了codeType又设置了remote为true,所以会走else里面的代码.即从远程服务器端加载数据.

    当我们调用setUrl方法以后miniui组件去发起get请求查找basecode的Controller的数据.后续后台流程请参考另外一篇文章.

    至此前台请求发送完毕,把返回的数据根据textField和valueField显示到页面上即可.

    当组件查询的值需要依赖于其他组件的时候(多级联动)

    多级联动的情况在现实中还是比较常见的,公司本身扩展的组件也不是支持的很好,旁边小哥也自己扩展了一个,仍然有一些BUG.然后我又在他们的基础上修改了一下.(大部分时候开发还是自己去写valuechange事件,而不是自动多级联动)

    多级联动和一般的查代码表的区别在于,多级联动的时候你后面的下拉框的查询的值要依赖于前面的值.

    比如上面3个组件.大类A,种类B,小类C.

    b能下拉出什么值依赖于a,c依赖于b.

    所以我们在组件里要有个属性记录当前组件依赖的组件.

    比如组件b的html标签:

    <input baseValue="#a组件的id#" ........>
    getAttrs: function (el) {
        ..........
        this.baseValue = attrs['baseValue'];
        ...............
    }

    我们用的是baseValue.

    当然在getAttrs里直接set属性是不好的,应该在set那个方法里去设置.不过这样做也没啥问题(反正也能用).

    this.on('beforeload',function(e){
    ...............
    var tmpBaseValue = this.baseValue; ............各种加工tmpBasevalue this.queryParam = {}; this.queryParam.parent = (e.params && e.params['value']) || tmpBaseValue; if(this.queryParam.initParam != undefined){ this.queryParam.initParam = (e.params && this.queryParam.initParam) ? null : this.queryParam.initParam; } var param = {}; $.extend(true, param,this.queryParam );
    ................
    }

    然后在beforeload事件里把baseValue对应的id的miniui组件的getValue值合并到param里(中间处理逻辑被我省略了..因为不同框架处理逻辑肯定不同,这里也没啥通用性).最后会发送给后台.这样在查询组件b的basecode的值的时候还会附带上组件a的值作为查询条件之一.

    多级联动还有个小特点就是你选了a,最好要把b的值清空.

    这里是用blur事件来做的.

             var id = '#'+this.baseValue.replace(/#/gi,'');
                    var tempobj = mini.get(id);
                    if(tempobj){
                        tempobj.on("blur",function(){
                            if(!tempobj.oldValue || (tempobj.oldValue && tempobj.getValue() != tempobj.oldValue)){
                                tempobj.oldValue = tempobj.getValue();
                                me.setData([]);
                                me.setValue("");
                                me.setUrl(me.getUrl());
                            }
                        });
                    }

    根据baseValue找到依赖的组件,然后为组件绑定blur事件,当依赖组件的焦点丢失的时候(这个时候肯定是操作过组件,并且新选择的值和原本的值不同),触发blur事件,更新当前组件的值.

    不过这里只更新了当前组件,并没有修改依赖于当前组件的组件的值,算是有点瑕疵吧.

    树组件

    树组件是最复杂的一个组件了吧..不过我们这里用到的地方也不多,即使用到大部分时候也就CV一下别人写好的就OK了.不过学习的话还是有些地方值得研究研究的.

    树组件复杂是因为有懒加载的情况,第一次取数据和后续取数据的SQL会有很大不同,比如第一次查数据的时候你的where条件里需要条件1,2,3....第二次加载子节点的时候你的where条件可能变成了只需要条件1加上父节点的代码值.所以where条件变化比较大,和一般的下拉框不同.

    对于后台来说(详见另外一篇文章)就是filterSQL里依赖的参数Map不同.需要根据传过来的filterParam或者initParam来拼不同SQL.

    对于前台来说就是怎么根据不同的情况组织不同的数据(主要是initParam怎么区分是第一次加载还是后续加载,因为initParam在第一次加载和后续加载的时候会传不同的参数,而filterParam则每次都会传一样的参数).

            this.on('beforeload',function(e){
    .................
                this.queryParam.parent = (e.params && e.params['value']) || tmpBaseValue;   
                if(this.queryParam.initParam != undefined){
                    this.queryParam.initParam = (e.params && this.queryParam.initParam) ? null : this.queryParam.initParam;
                }
    ................
        }

    忽略其他的代码,从这4行代码中可以看出,this.queryParam.parent取什么值是根据e.param来的.e.param到底什么时候会有值呢? 实践中发现是当你点击父节点加载子节点的时候才会有值(然而miniui的api中啥都没提到.).

    那么这段代码就可以解释为:当第一次load树组件加载数据的时候,initParam是会有值的(自己写在html里),而parent是没值的,当后续懒加载点击父节点加载子树的时候initParam是null而parent是e.param也就是父节点的代码值.

    这样就可以保证不同情况下树组件可以向后台传递不同的值.

    一个小例子

    <input .... param="{'initParam':{'SCENARIO':'NONE','SWJG_DM_VALUE':['#swjg-zgsws#']} ,'filterParam':{'SCENARIO':'ASSIGNSWKS','GDSLX_DM_VALUE':${loginDTO.gdslxDm}}}"/>

    第一次加载树的时候请求的时候initParam有值

    第二次加载子节点的时候请求initParam没有值,附加了一个parent节点的值.

    小问题

    组件虽然好用,但是还是有一点小问题的:

    1.当没有使用filterParam,initParam的scenario的时候,html中的param是直接把key拼接到where里当做查询条件,把value设置到JPA的Query的Parameter里的.所以查询条件是有SQL注入的风险的.意思就是你如果构造了一个请求,传递的param是{1:"1"}那这样拼接成的SQL就是select.....from...where 1=1 and 1=1. 如果使用filterParam,initParam的scenario则没有问题,因为拼接的where条件是写在数据库里的.

    2.拼接条件都是and连接的.如果你要复杂的条件,或者来个or连接就跪了...

    小小结

    虽然有一些不足的地方,但是公司封装的这套basecode在大多数情况下还是非常好用的,而且让我学到了不少知识:尤其是给我提供了一种自动加载数据的思路.

  • 相关阅读:
    设计模式之工厂模式-抽象工厂(02)
    1036 跟奥巴马一起编程 (15 分)
    1034 有理数四则运算 (20 分)
    1033 旧键盘打字 (20 分)
    1031 查验身份证 (15 分)
    大学排名定向爬虫
    1030 完美数列 (25 分)二分
    1029 旧键盘 (20 分)
    1028 人口普查 (20 分)
    1026 程序运行时间 (15 分)四舍五入
  • 原文地址:https://www.cnblogs.com/abcwt112/p/6251075.html
Copyright © 2011-2022 走看看