標籤:
http://www.cnblogs.com/jasonoiu/p/easyloader_source_code_analysis.html
Jquery easyui是一個javascript UI 組件庫,使用它可以快速開發企業級的業務系統。如果你正準備開發系統後台,可以選擇jquery easyui,也可以選擇Ext JS。我個人的看法是,如果Team Dev就兩三個人,開發工期很短,就一兩個月。那麼選擇jquery easyui就對了,jquery easyui原始碼量不多,便於閱讀和自行修改。而Ext JSy原始碼太多,短時間很難看完,學習曲線也比較陡峭。如果人手充足,時間也富裕,可以考慮使用Ext JS來開發,畢竟Ext JS更強大,控制項更多。
Jquery easyui是基於Jquery外掛程式機制擴充的,所以如果你很熟悉Jquery,那麼使用它開發會很簡單。
Jquery easyui的原始碼svn地址:http://jquery-easyui.googlecode.com/svn
easyloader模組是用來載入jquery easyui的js和css檔案的,而且它可以分析模組的依賴關係,先載入依賴項。模組載入好了會調用parse模組來解析頁面。把class是easyui開頭的標籤都轉化成easyui的控制項。
先看Demo1例子,再分析原始碼。
<!DOCTYPE HTML><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Demo1</title><script type="text/javascript" src="jquery-easyui-1.2.3/jquery-1.4.4.min.js"></script><script type="text/javascript" src="jquery-easyui-1.2.3/easyloader.js"></script></head><body> <div class="easyui-panel" title="Demo1" fit="true" collapsible="true" minimizable="true" maximizable="true" closable="true" style="width: 100%; height: 200px;" > easyloader會自動解析這個div,因為class屬性中帶有easyui開頭的類! </div></body></html>
作者開發此模組,說明應該盡量使用easyuiloader來載入。因為他定製性更強,可以自訂佈景主題和國際化等。而壓縮過的檔案的優勢在於一次性把所有模組都載入進來,速度上更快。
1 /** 2 * easyloader - jQuery EasyUI 3 * 4 * Licensed under the GPL: 5 * http://www.gnu.org/licenses/gpl.txt 6 * 7 * Copyright 2010 stworthy [ [email protected] ] 8 * 9 */ 10 (function(){ 11 // 模組檔案定義 12 var modules = { 13 draggable:{ 14 js:‘jquery.draggable.js‘ 15 }, 16 droppable:{ 17 js:‘jquery.droppable.js‘ 18 }, 19 resizable:{ 20 js:‘jquery.resizable.js‘ 21 }, 22 linkbutton:{ 23 js:‘jquery.linkbutton.js‘, 24 css:‘linkbutton.css‘ 25 }, 26 pagination:{ 27 js:‘jquery.pagination.js‘, 28 css:‘pagination.css‘, 29 dependencies:[‘linkbutton‘] 30 }, 31 datagrid:{ 32 js:‘jquery.datagrid.js‘, 33 css:‘datagrid.css‘, 34 dependencies:[‘panel‘,‘resizable‘,‘linkbutton‘,‘pagination‘] 35 }, 36 treegrid:{ 37 js:‘jquery.treegrid.js‘, 38 css:‘tree.css‘, 39 dependencies:[‘datagrid‘] 40 }, 41 panel: { 42 js:‘jquery.panel.js‘, 43 css:‘panel.css‘ 44 }, 45 window:{ 46 js:‘jquery.window.js‘, 47 css:‘window.css‘, 48 dependencies:[‘resizable‘,‘draggable‘,‘panel‘] 49 }, 50 dialog:{ 51 js:‘jquery.dialog.js‘, 52 css:‘dialog.css‘, 53 dependencies:[‘linkbutton‘,‘window‘] 54 }, 55 messager:{ 56 js:‘jquery.messager.js‘, 57 css:‘messager.css‘, 58 dependencies:[‘linkbutton‘,‘window‘] 59 }, 60 layout:{ 61 js:‘jquery.layout.js‘, 62 css:‘layout.css‘, 63 dependencies:[‘resizable‘,‘panel‘] 64 }, 65 form:{ 66 js:‘jquery.form.js‘ 67 }, 68 menu:{ 69 js:‘jquery.menu.js‘, 70 css:‘menu.css‘ 71 }, 72 tabs:{ 73 js:‘jquery.tabs.js‘, 74 css:‘tabs.css‘, 75 dependencies:[‘panel‘,‘linkbutton‘] 76 }, 77 splitbutton:{ 78 js:‘jquery.splitbutton.js‘, 79 css:‘splitbutton.css‘, 80 dependencies:[‘linkbutton‘,‘menu‘] 81 }, 82 menubutton:{ 83 js:‘jquery.menubutton.js‘, 84 css:‘menubutton.css‘, 85 dependencies:[‘linkbutton‘,‘menu‘] 86 }, 87 accordion:{ 88 js:‘jquery.accordion.js‘, 89 css:‘accordion.css‘, 90 dependencies:[‘panel‘] 91 }, 92 calendar:{ 93 js:‘jquery.calendar.js‘, 94 css:‘calendar.css‘ 95 }, 96 combo:{ 97 js:‘jquery.combo.js‘, 98 css:‘combo.css‘, 99 dependencies:[‘panel‘,‘validatebox‘]100 },101 combobox:{102 js:‘jquery.combobox.js‘,103 css:‘combobox.css‘,104 dependencies:[‘combo‘]105 },106 combotree:{107 js:‘jquery.combotree.js‘,108 dependencies:[‘combo‘,‘tree‘]109 },110 combogrid:{111 js:‘jquery.combogrid.js‘,112 dependencies:[‘combo‘,‘datagrid‘]113 },114 validatebox:{115 js:‘jquery.validatebox.js‘,116 css:‘validatebox.css‘117 },118 numberbox:{119 js:‘jquery.numberbox.js‘,120 dependencies:[‘validatebox‘]121 },122 spinner:{123 js:‘jquery.spinner.js‘,124 css:‘spinner.css‘,125 dependencies:[‘validatebox‘]126 },127 numberspinner:{128 js:‘jquery.numberspinner.js‘,129 dependencies:[‘spinner‘,‘numberbox‘]130 },131 timespinner:{132 js:‘jquery.timespinner.js‘,133 dependencies:[‘spinner‘]134 },135 tree:{136 js:‘jquery.tree.js‘,137 css:‘tree.css‘,138 dependencies:[‘draggable‘,‘droppable‘]139 },140 datebox:{141 js:‘jquery.datebox.js‘,142 css:‘datebox.css‘,143 dependencies:[‘calendar‘,‘validatebox‘]144 },145 parser:{146 js:‘jquery.parser.js‘147 }148 };149 150 // 國際化資源檔151 var locales = {152 ‘af‘:‘easyui-lang-af.js‘,153 ‘bg‘:‘easyui-lang-bg.js‘,154 ‘ca‘:‘easyui-lang-ca.js‘,155 ‘cs‘:‘easyui-lang-cs.js‘,156 ‘da‘:‘easyui-lang-da.js‘,157 ‘de‘:‘easyui-lang-de.js‘,158 ‘en‘:‘easyui-lang-en.js‘,159 ‘fr‘:‘easyui-lang-fr.js‘,160 ‘nl‘:‘easyui-lang-nl.js‘,161 ‘zh_CN‘:‘easyui-lang-zh_CN.js‘,162 ‘zh_TW‘:‘easyui-lang-zh_TW.js‘163 };164 165 // 載入隊列166 var queues = {};167 168 /**169 * 載入js檔案函數,過程就是動態建立一個script標籤,然後添加到head標籤中去。170 * 有一個問題是監聽了script標籤的兩個事件函數,一個是onload,另一個是onreadystatechange,這個數要是針對IE和非IE瀏覽器準備的171 * 萬惡的IE瀏覽器!!!172 */173 function loadJs(url, callback){174 var done = false;175 var script = document.createElement(‘script‘);176 script.type = ‘text/javascript‘;177 script.language = ‘javascript‘;178 script.src = url;179 script.onload = script.onreadystatechange = function(){180 if (!done && (!script.readyState || script.readyState == ‘loaded‘ || script.readyState == ‘complete‘)){181 done = true;182 script.onload = script.onreadystatechange = null;183 if (callback){184 callback.call(script);185 }186 }187 }188 document.getElementsByTagName("head")[0].appendChild(script);189 }190 191 /**192 * 執行js檔案。就是把js檔案載入進來,再remove掉193 * @param url js的url194 * @callback 回呼函數,執行完js時會調用這個函數195 */196 function runJs(url, callback){197 loadJs(url, function(){198 document.getElementsByTagName("head")[0].removeChild(this);199 if (callback){200 callback();201 }202 });203 }204 205 /**206 * 載入css檔案。和載入js檔案一樣,動態建立一個link標籤,然後追加到head標籤中去207 * @param url css的url208 * @param callback 回呼函數,載入完成後調用此函數209 */210 function loadCss(url, callback){211 var link = document.createElement(‘link‘);212 link.rel = ‘stylesheet‘;213 link.type = ‘text/css‘;214 link.media = ‘screen‘;215 link.href = url;216 document.getElementsByTagName(‘head‘)[0].appendChild(link);217 if (callback){218 callback.call(link);219 }220 }221 222 /**223 * 載入單獨的一個模組224 */225 function loadSingle(name, callback){226 227 // 載入隊列存入該模組名,並表示狀態為loading。228 queues[name] = ‘loading‘;229 230 // 根據模組名,取出該模組定義231 var module = modules[name];232 233 // js載入狀態234 var jsStatus = ‘loading‘;235 236 // css載入狀態,從這裡你就可以看出easyloader.css就是一個開關變數,控制是否載入模組相應的css檔案237 var cssStatus = (easyloader.css && module[‘css‘]) ? ‘loading‘ : ‘loaded‘;238 239 // 是css檔案,就使用loadCss來載入之240 if (easyloader.css && module[‘css‘]){241 if (/^http/i.test(module[‘css‘])){242 var url = module[‘css‘];243 } else {244 var url = easyloader.base + ‘themes/‘ + easyloader.theme + ‘/‘ + module[‘css‘];245 }246 loadCss(url, function(){247 cssStatus = ‘loaded‘;248 if (jsStatus == ‘loaded‘ && cssStatus == ‘loaded‘){249 finish();250 }251 });252 }253 254 // 是js檔案,就是用LoadJs來載入之255 if (/^http/i.test(module[‘js‘])){256 var url = module[‘js‘];257 } else {258 var url = easyloader.base + ‘plugins/‘ + module[‘js‘];259 }260 loadJs(url, function(){261 jsStatus = ‘loaded‘;262 if (jsStatus == ‘loaded‘ && cssStatus == ‘loaded‘){263 finish();264 }265 });266 267 // 最終調用finish函數,來結束載入。並觸發onProgress函數,每載入成功一個模組,就調用一次onProgress268 function finish(){269 queues[name] = ‘loaded‘;270 easyloader.onProgress(name);271 if (callback){272 callback();273 }274 }275 }276 277 /**278 * easyui模組載入函數279 * @param name 模組名,可以是string,也可以是數組280 * @param callback 回呼函數,當載入結束後會調用此函數281 */282 function loadModule(name, callback){283 284 // 模組名,根據依賴關係,從前到後,依次排開285 var mm = [];286 287 // 載入標識,當其值為true時,表示需要載入的模組已經載入好了288 var doLoad = false;289 290 // 模組名支援兩中,一種是string,一種是數組。這樣就可以一次載入多個模組了291 if (typeof name == ‘string‘){292 // 是string的時候,調用add方法把模組名push到mm數組中去293 add(name);294 } else {295 for(var i=0; i<name.length; i++){296 // 是數組的時候,迴圈調用add方法把模組名統統push到mm數組中去297 add(name[i]);298 }299 }300 301 /**302 * loadModule函數中內嵌的一個函數,用來載入模組名到變數mm數組中去303 * @param name 模組名,只能是string304 */305 function add(name){306 // 保護措施,如果該模組名不存在,我們就不要載入了307 if (!modules[name]) return;308 309 // 否則,就是該模組存在。然後,我們在看看其有沒有相依模組310 var d = modules[name][‘dependencies‘];311 if (d){312 // 如果有相依模組,我們要先把相依模組的名字push到mm中去313 // 這裡用了遞迴調用314 for(var i=0; i<d.length; i++){315 add(d[i]);316 }317 }318 319 // 把模組名放到mm中320 mm.push(name);321 }322 323 /**324 * 當一個模組及其相依模組載入完成時,執行回呼函數,並且觸發onLoad函數325 */326 function finish(){327 if (callback){328 callback();329 }330 easyloader.onLoad(name);331 }332 333 // 載入用時334 var time = 0;335 336 /**337 * 載入所需要的模組,需要的模組,我們已經統計好了,並按依賴關係,先後push到mm中去了338 */339 function loadMm(){340 // 判斷mm是不是空的341 if (mm.length){342 343 // 第一個模組344 var m = mm[0];345 346 // 判斷載入隊列是否包含此模組347 if (!queues[m]){348 349 // 載入隊列不包含此模組,開始載入該模組350 // 把doLoad置成true,表示開始載入351 doLoad = true;352 353 // 調用loadSingle方法來載入模組,載入成功後會把此模組從mm中shift掉,然後繼續調用loadMM方法,就形成了遞迴調用354 loadSingle(m, function(){355 mm.shift();356 loadMm();357 });358 } else if (queues[m] == ‘loaded‘){359 // 載入隊列已經載入過此模組了,不需要在載入了,直接從mm中shift掉即可360 mm.shift();361 loadMm();362 } else {363 // 表示正在載入該模組,累計所用時間如果沒有超過timeout364 // 則過10毫秒再調用一次loadMm函數365 if (time < easyloader.timeout){366 time += 10;367 setTimeout(arguments.callee, 10);368 }369 }370 } else {371 // 走到這裡,表示該載入的模組都已經載入好了372 if (easyloader.locale && doLoad == true && locales[easyloader.locale]){373 // 如果設定了國際化,並且已經載入好了,而且該國際化資源還存在,那麼載入該資源js374 var url = easyloader.base + ‘locale/‘ + locales[easyloader.locale];375 376 // 執行js完事後,調用finish方法377 runJs(url, function(){378 finish();379 });380 } else {381 // 沒定義國際化檔案,那麼直接調用finish方法吧382 finish();383 }384 }385 }386 387 loadMm();388 }389 390 /**391 * easyloader定義為全域變數392 */393 easyloader = {394 395 // 各個模組檔案的定義,包括js、css和相依模組396 modules:modules,397 398 // 國際化資源檔399 locales:locales,400 401 // jquery-easyui的根目錄,在載入easyloader時,會自動根據你放置的位置而改變402 base:‘.‘,403 404 // 控制項的主題,一共就有兩個,在theme目錄中。還有一個gray主題,淺灰色的,很難看。405 theme:‘default‘,406 407 // 這是一個開關變數,控制easyloader載入模組時,要不要載入相應的css檔案,預設是需要載入的408 css:true,409 410 // 國際化語言,可以根據window.navigator.language或者window.navigator.userLanguage來擷取當前瀏覽器的語言。411 // 有兩個屬性,主要因為IE瀏覽器只認識userLanguage和sysLanguage,萬惡的IE瀏覽器啊!412 locale:null,413 414 // 載入一個模組的最長時間,超過這個時間,就開始載入下一個模組了。415 timeout:2000,416 417 // easyloader就公開了這麼一個方法,用來載入模組。418 // name是模組名,callback是載入成功後執行的函數419 load: function(name, callback){420 if (//.css$/i.test(name)){421 // 如果模組名是以.css結尾422 423 if (/^http/i.test(name)){424 // 如果模組名是以http開頭,那麼css是一個檔案的url425 loadCss(name, callback);426 } else {427 // 否則,說明模組名相對於jquery easyui根目錄來說的428 loadCss(easyloader.base + name, callback);429 }430 } else if (//.js$/i.test(name)){431 // 如果模組名是以.js結尾432 433 if (/^http/i.test(name)){434 // 如果模組名是以http開頭,那麼js是一個檔案的url435 loadJs(name, callback);436 } else {437 // 否則,說明模組名相對於jquery easyui根目錄來說的438 loadJs(easyloader.base + name, callback);439 }440 } else {441 // 以上兩種都不是,說明是easyui自己的模組,直接使用loadModule來載入,就可以了442 loadModule(name, callback);443 }444 },445 446 // 當一個模組載入完會觸發此函數447 onProgress: function(name){},448 449 // 當一個模組和其依賴都載入完會觸發此函數450 onLoad: function(name){}451 };452 /**453 * 這一小段代碼就是尋找jquery-easyui的根目錄,並賦值給easyloader的base屬性上。這樣easyloader再載入css檔案和js檔案就很方便定位了。454 */455 var scripts = document.getElementsByTagName(‘script‘);456 for(var i=0; i<scripts.length; i++){457 var src = scripts[i].src;458 if (!src) continue;459 var m = src.match(/easyloader/.js(/W|$)/i);460 if (m){461 easyloader.base = src.substring(0, m.index);462 }463 }464 /**465 * 這個就起一個別名的作用,比如頁面中可以想如下這麼下:466 * using(‘window‘);467 * 這樣window模組就載入進來了!468 */469 window.using = easyloader.load;470 /**471 * easyloader.js載入的第一模組是parse模組,parser模組調用parse方法,可以解析頁面上的easyui控制項472 */473 if (window.jQuery){474 jQuery(function(){475 easyloader.load(‘parser‘, function(){476 jQuery.parser.parse();477 });478 });479 }480 481 })();
easyloader.js原始碼分析