easyloader源码
1 /** 2 * easyloader - jQuery EasyUI 3 * 4 * Licensed under the GPL: 5 * http://www.gnu.org/licenses/gpl.txt 6 * 7 * Copyright 2010 stworthy [ stworthy@gmail.com ] 8 * 9 */ 10 (function(){ 11 //将所有的插件,和插件资源和依赖文件放进modules对象中。 12 var modules = { 13 14 draggable:{ 15 js:'jquery.draggable.js' 16 }, 17 droppable:{ 18 js:'jquery.droppable.js' 19 }, 20 resizable:{ 21 js:'jquery.resizable.js' 22 }, 23 linkbutton:{ 24 js:'jquery.linkbutton.js', 25 css:'linkbutton.css' 26 }, 27 pagination:{ 28 js:'jquery.pagination.js', 29 css:'pagination.css', 30 dependencies:['linkbutton'] 31 }, 32 datagrid:{ 33 js:'jquery.datagrid.js', 34 css:'datagrid.css', 35 dependencies:['panel','resizable','linkbutton','pagination'] 36 }, 37 treegrid:{ 38 js:'jquery.treegrid.js', 39 css:'tree.css', 40 dependencies:['datagrid'] 41 }, 42 panel: { 43 js:'jquery.panel.js', 44 css:'panel.css' 45 }, 46 window:{ 47 js:'jquery.window.js', 48 css:'window.css', 49 dependencies:['resizable','draggable','panel'] 50 }, 51 dialog:{ 52 js:'jquery.dialog.js', 53 css:'dialog.css', 54 dependencies:['linkbutton','window'] 55 }, 56 messager:{ 57 js:'jquery.messager.js', 58 css:'messager.css', 59 dependencies:['linkbutton','window'] 60 }, 61 layout:{ 62 js:'jquery.layout.js', 63 css:'layout.css', 64 dependencies:['resizable','panel'] 65 }, 66 form:{ 67 js:'jquery.form.js' 68 }, 69 menu:{ 70 js:'jquery.menu.js', 71 css:'menu.css' 72 }, 73 tabs:{ 74 js:'jquery.tabs.js', 75 css:'tabs.css', 76 dependencies:['panel','linkbutton'] 77 }, 78 splitbutton:{ 79 js:'jquery.splitbutton.js', 80 css:'splitbutton.css', 81 dependencies:['linkbutton','menu'] 82 }, 83 menubutton:{ 84 js:'jquery.menubutton.js', 85 css:'menubutton.css', 86 dependencies:['linkbutton','menu'] 87 }, 88 accordion:{ 89 js:'jquery.accordion.js', 90 css:'accordion.css', 91 dependencies:['panel'] 92 }, 93 calendar:{ 94 js:'jquery.calendar.js', 95 css:'calendar.css' 96 }, 97 combo:{ 98 js:'jquery.combo.js', 99 css:'combo.css', 100 dependencies:['panel','validatebox'] 101 }, 102 combobox:{ 103 js:'jquery.combobox.js', 104 css:'combobox.css', 105 dependencies:['combo'] 106 }, 107 combotree:{ 108 js:'jquery.combotree.js', 109 dependencies:['combo','tree'] 110 }, 111 combogrid:{ 112 js:'jquery.combogrid.js', 113 dependencies:['combo','datagrid'] 114 }, 115 validatebox:{ 116 js:'jquery.validatebox.js', 117 css:'validatebox.css' 118 }, 119 numberbox:{ 120 js:'jquery.numberbox.js', 121 dependencies:['validatebox'] 122 }, 123 spinner:{ 124 js:'jquery.spinner.js', 125 css:'spinner.css', 126 dependencies:['validatebox'] 127 }, 128 numberspinner:{ 129 js:'jquery.numberspinner.js', 130 dependencies:['spinner','numberbox'] 131 }, 132 timespinner:{ 133 js:'jquery.timespinner.js', 134 dependencies:['spinner'] 135 }, 136 tree:{ 137 js:'jquery.tree.js', 138 css:'tree.css', 139 dependencies:['draggable','droppable'] 140 }, 141 datebox:{ 142 js:'jquery.datebox.js', 143 css:'datebox.css', 144 dependencies:['calendar','validatebox'] 145 }, 146 parser:{ 147 js:'jquery.parser.js' 148 } 149 }; 150 //将国际化文件放入一个locales对象中 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 //加载js方法 169 function loadJs(url, callback){ 170 //标志变量,js是否加载并执行 171 var done = false; 172 var script = document.createElement('script');//创建script dom 173 script.type = 'text/javascript'; 174 script.language = 'javascript'; 175 script.src = url; 176 script.onload = script.onreadystatechange = function(){ //onload是firefox 浏览器事件,onreadystatechange,是ie的,为了兼容,两个都写上,这样写会导致内存泄露 177 //script.readyState只是ie下有这个属性,如果这个值为undefined,说明是在firefox,就直接可以执行下面的代码了。反之为ie,需要对script.readyState 178 //状态具体值进行判别,loaded和complete状态表示,脚本加载了并执行了。 179 if (!done && (!script.readyState || script.readyState == 'loaded' || script.readyState == 'complete')){ 180 done = true; 181 182 script.onload = script.onreadystatechange = null;//释放内存,还会泄露。 183 if (callback){//加载后执行回调 184 callback.call(script); 185 } 186 } 187 } 188 //具体加载动作,上面的onload是注册事件, 189 document.getElementsByTagName("head")[0].appendChild(script); 190 } 191 //运行js ,看代码逻辑可知,运行js,只是在js执行后,将这个script删除而已,主要用来加载国际化文件 192 function runJs(url, callback){ 193 loadJs(url, function(){ 194 document.getElementsByTagName("head")[0].removeChild(this); 195 if (callback){ 196 callback(); 197 } 198 }); 199 } 200 201 //加载css没什么好说的 202 function loadCss(url, callback){ 203 var link = document.createElement('link'); 204 link.rel = 'stylesheet'; 205 link.type = 'text/css'; 206 link.media = 'screen'; 207 link.href = url; 208 document.getElementsByTagName('head')[0].appendChild(link); 209 if (callback){ 210 callback.call(link); 211 } 212 } 213 //加载单一一个plugin,仔细研究module ,可以发现,pingin之间通过dependence,构造成了一颗依赖树, 214 //这个方法,就是加载具体树中的一个节点 215 function loadSingle(name, callback){ 216 //把整个plugin的状态设置为loading 217 queues[name] = 'loading'; 218 219 var module = modules[name]; 220 //把js状态设置为loading 221 var jsStatus = 'loading'; 222 //如果允许css,并且plugin有css,则加载css,否则设置加载过了,其实是不加载 223 var cssStatus = (easyloader.css && module['css']) ? 'loading' : 'loaded'; 224 //加载css,plugin 的css,如果是全称,就用全称,否则把简写换成全称,所以简写的css文件要放入到themes/type./文件下 225 if (easyloader.css && module['css']){ 226 if (/^http/i.test(module['css'])){ 227 var url = module['css']; 228 } else { 229 var url = easyloader.base + 'themes/' + easyloader.theme + '/' + module['css']; 230 } 231 loadCss(url, function(){ 232 cssStatus = 'loaded'; 233 //js, css加载完,才调用回调 234 if (jsStatus == 'loaded' && cssStatus == 'loaded'){ 235 finish(); 236 } 237 }); 238 } 239 //加载js,全称用全称,简写补全。 240 if (/^http/i.test(module['js'])){ 241 var url = module['js']; 242 } else { 243 var url = easyloader.base + 'plugins/' + module['js']; 244 } 245 loadJs(url, function(){ 246 jsStatus = 'loaded'; 247 if (jsStatus == 'loaded' && cssStatus == 'loaded'){ 248 finish(); 249 } 250 }); 251 //加载完调用的方法,改plugin状态 252 function finish(){ 253 queues[name] = 'loaded'; 254 //调用正在加载的方法,其实已经加载完了, 255 easyloader.onProgress(name); 256 if (callback){ 257 callback(); 258 } 259 } 260 } 261 //加载主模块入口, 262 function loadModule(name, callback){ 263 //定义数组,最后是形成的是依赖插件列表,最独立的插件放在首位,name是末尾 264 var mm = []; 265 var doLoad = false; 266 //name有两种,一种是string ,一种是string array,这样一次可以加载多个plugin,都是调用add方法进行添加 267 if (typeof name == 'string'){ 268 add(name); 269 } else { 270 for(var i=0; i<name.length; i++){ 271 add(name[i]); 272 } 273 } 274 275 function add(name){ 276 //如果modules中没有这个plugin那退出 277 if (!modules[name]) return; 278 //如果有,查看它是否依赖其他plugin 279 var d = modules[name]['dependencies']; 280 //如果依赖,就加载依赖的plugin.同时在加载依赖的plugin的依赖。注意循环中调用了add,是递归 281 if (d){ 282 for(var i=0; i<d.length; i++){ 283 add(d[i]); 284 } 285 } 286 mm.push(name); 287 } 288 289 function finish(){ 290 if (callback){ 291 callback(); 292 } 293 //调用onLoad,传递name 为参数 294 easyloader.onLoad(name); 295 } 296 //形成依赖树,不行还没有做实质性工作呢,那就是加载。打起精神来,最核心的代码就是以下的了 297 //超时用 298 var time = 0; 299 //定义一个加载方法,定义后直接调用 300 function loadMm(){ 301 //如果mm有长度,长度!=0,加载plugin,为0,即加载完毕,开始加载国际化文件。 302 if (mm.length){ 303 var m = mm[0]; // the first module 304 if (!queues[m]){//状态序列中没有这个plugin的信息,说明没有加载这个plug,调用laodSingle进行加载 305 doLoad = true; 306 loadSingle(m, function(){ 307 mm.shift();//加载完成后,将这个元素从数组去除,在继续加载,直到数组 308 loadMm(); 309 }); 310 } else if (queues[m] == 'loaded'){//如果这个plugin已经加载,就不用加载,以为mm中可能有重复项 311 mm.shift(); 312 loadMm(); 313 } else { 314 if (time < easyloader.timeout){//超时时候,10秒钟调用一次loadMn().注意arguments.callee代表函数本身 315 time += 10; 316 setTimeout(arguments.callee, 10); 317 } 318 } 319 } else { 320 if (easyloader.locale && doLoad == true && locales[easyloader.locale]){ 321 var url = easyloader.base + 'locale/' + locales[easyloader.locale]; 322 runJs(url, function(){ 323 finish(); 324 }); 325 } else { 326 finish(); 327 } 328 } 329 } 330 331 loadMm(); 332 } 333 // 定义一个加载器,注意,是全局变量,没有var, 334 easyloader = { 335 modules:modules, 336 locales:locales, 337 338 base:'.',//该属性是为了加载js,记录文件夹路径的 339 theme:'default', //默认主题 340 css:true, 341 locale:null, 342 timeout:2000,//加载超时事件 343 //easyloader.load(),该模块加载的调用方法,先加载css,然后加载js 344 load: function(name, callback){ 345 //如果加载是*.css文件,判断是不是以http开头,如果是,直接调用 346 if (/\.css$/i.test(name)){ 347 if (/^http/i.test(name)){ 348 loadCss(name, callback); 349 } else { 350 //不是http的,加上base.文件夹路径 351 loadCss(easyloader.base + name, callback); 352 } 353 } 354 //加载js文件 355 else if (/\.js$/i.test(name)){ 356 if (/^http/i.test(name)){ 357 loadJs(name, callback); 358 } else { 359 loadJs(easyloader.base + name, callback); 360 } 361 } else { 362 //如果直接传递一个插件名,就去modole数组中加载。改方法是重点,也是easyui自带的plugin加载方式 363 loadModule(name, callback); 364 } 365 }, 366 367 onProgress: function(name){}, 368 onLoad: function(name){} 369 }; 370 //以上一直在定义函数,和变量,此处为真正执行处 371 //获取页面的所有的script,主要是为了获取我们现在解释的easyloader.js文件路径,来设置base属性 372 var scripts = document.getElementsByTagName('script'); 373 for(var i=0; i<scripts.length; i++){ 374 var src = scripts[i].src; 375 if (!src) continue; 376 var m = src.match(/easyloader\.js(\W|$)/i);//判断文件是否含有easyloadr.js 377 if (m){ 378 //如果有,base为easyloadr.js 的相同前缀 379 easyloader.base = src.substring(0, m.index); 380 } 381 } 382 //定义一个简化调用接口 383 window.using = easyloader.load; 384 385 if (window.jQuery){ 386 jQuery(function(){ 387 //系统数据加载完后,加载parser.js插件,该插件是渲染界面的 388 easyloader.load('parser', function(){ 389 jQuery.parser.parse();//渲染方法 390 }); 391 }); 392 } 393 394 })();