擴展 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在大多數情況下還是非常好用的,而且讓我學到了不少知識:尤其是給我提供了一種自動加載數據的思路.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM