`
stationxp
  • 浏览: 17256 次
社区版块
存档分类
最新评论

Java程序员的JavaScript学习笔记(13—— jQuery UI)

 
阅读更多
计划按如下顺序完成这篇笔记:
  1. Java程序员的JavaScript学习笔记(1——理念)
  2. Java程序员的JavaScript学习笔记(2——属性复制和继承)
  3. Java程序员的JavaScript学习笔记(3——this/call/apply)
  4. Java程序员的JavaScript学习笔记(4——this/闭包/getter/setter)
  5. Java程序员的JavaScript学习笔记(5——prototype)
  6. Java程序员的JavaScript学习笔记(6——面向对象模拟)
  7. Java程序员的JavaScript学习笔记(7——jQuery基本机制)
  8. Java程序员的JavaScript学习笔记(8——jQuery选择器)
  9. Java程序员的JavaScript学习笔记(9——jQuery工具方法)
  10. Java程序员的JavaScript学习笔记(10——jQuery-在“类”层面扩展)
  11. Java程序员的JavaScript学习笔记(11——jQuery-在“对象”层面扩展)
  12. Java程序员的JavaScript学习笔记(12——jQuery-扩展选择器)
  13. Java程序员的JavaScript学习笔记(13——jQuery UI)
  14. Java程序员的JavaScript学习笔记(14——扩展jQuery UI)
这是笔记的第13篇,今天我们聊聊jQuery EasyUI。
EasyUI是jQuery的一个常用扩展,解决页面组件的问题,实现了可拖拽组件、功能更丰富的下拉框、日期组件、表格组件等。
今天我们尝试,通过分析Easyui的模块化设计、接口风格、实现细节,来探索我们自己的jQuery扩展之道 和 页面组件化之道。

作者博客:http://blog.csdn.net/stationxp 
作者微博:http://weibo.com/liuhailong2008  
转载请取得作者同意  

1 easyloader.js

easyloader是easyui的“加载器”,代码分为四大部分:
第1部分定义资源,代码节选如下所示:
var modules = {
      progressbar:{
           js:'jquery.progressbar.js',
          css:'progressbar.css'
      },
      numberbox:{
           js:'jquery.numberbox.js',
           dependencies:['textbox']
      },
      parser:{
           js:'jquery.parser.js'
      }
 };
 var locales = {
  'zh_CN':'easyui-lang-zh_CN.js'
 };

作为一个可扩展插件框架,同时考虑互联网页对加载时间优化的需求,easyui提供了灵活的定制方式,而且解决了组件间依赖的问题。

第2部分定义加载方法
这部分代码冗长,除非想实现自己的web资源动态加载机制,否则粗粗过一遍,了解功能即可。
定义的函数及调用关系如下所示。
 var queues = {};
 function loadJs(url, callback){ }
 function runJs(url, callback){ loadJs(url, function(){}); }
 function loadCss(url, callback){}
 // 解决加载状态的问题 loading/loaded
 function loadSingle(name, callback){ loadCss(url, function(){ }); loadJs(url, function(){ }); }
 //解决dependencies的问题
 function loadModule(name, callback){loadSingle(m, function(){});}
这部分只是做了定义,并未调用。

第3部分是变量easyloader的定义
代码如下所示:
easyloader = {

      modules:modules,
      locales:locales,
      base:'.',
      theme:'default',
      css:true,
      locale:null,
      timeout:2000,
      load: function(name, callback){

           loadCss(easyloader.base + name, callback);

           loadJs(name, callback);

           loadModule(name, callback);

      },
      onProgress: function(name){},
      onLoad: function(name){}

 };
 window.using = easyloader.load;

从代码层面,只是定义了easyloader变量,并把load方法绑定到全局的using上去。
如果用户调用了 using('progressbar') 即可加载相应的模块。
easyloader完整实现了作为loader的使命,它提供的只是一个代码隔离机制和动态加载框架。

既然easyloader只是加载框架,那么可easyui的具体组件之间,也就是若即若离的关系。
我们完全可以绕开easyloader直接引用要使用的组件。
是否真的可以这样呢?看完第4部分再说。

第4部分是easyloader份外之事:parse
代码如下:
if (window.jQuery){
      jQuery(function(){
           easyloader.load('parser', function(){ // 即using
                jQuery.parser.parse(); // 忽然意识到,easyloader本身是不以来jQuery的。
           });
      });
 }
为何这么迫不及待地调用parser组件的pasrse方法呢?
我们找到源码看一下:
 $.parser = {

      auto: true,
      onComplete: function(context){},
      plugins:['draggable','droppable','resizable','pagination','tooltip',
               'linkbutton' ], //这里仅包含页面组件

      parse: function(context){
       var aa = [];
       for(var i=0; i<$.parser.plugins.length; i++){ // 逐个处理plugin

            var name = $.parser.plugins[i]; 
            // 通过 样式类名约定,Err,这样好吗?
            // 侵入性太强,窃以为这是封闭性的做法

            var r = $('.easyui-' + name, context);
            if (r.length){ 
                 // 假如是  progressbar 组件,name的值是  progressbar 
                 if (r[name]){
                            // r 是jQuery对象,r['progressbar']有效,即已经加载过这个组件
                      r[name](); // 调用 r.progressbar() , 每个组件都体现为一个方法
                } else { // 如果组件还未加载,加载后再调用 
                     //  以下为逻辑模拟,并非源码
                         easyloader.load(name, function(){        r[name]();  });
                 }
            }
       }
}

好吧,清晰明了,easyui已经为我们揭开了面纱。

2 绕开easyloader

现在我们已经知道了easyloader的秘密,我们尝试绕开它,需要完成两部分工作:
一是easyloader做的,我们用下面的代码替换:
 <link rel="stylesheet" type="text/css" href="progressbar.css"> 
 <script type="text/javascript" src="jquery.progressbar.js"></script>
而是parser做的,我们用下面的代码替换:
$(function(){
       $('#p').progressbar();
});
progressbar代码中引用了parser,用于简化初始化参数配置,把parser也直接引入。
<script type="text/javascript" src="jquery.parser.js"></script>
页面代码重用esayui demo代码,完整代码如下:
<html>
<head>

     <meta charset="UTF-8">
     <title>Easy ProgressBar</title>
     <link rel="stylesheet" type="text/css" href="progressbar.css">
     <script type="text/javascript" src="jquery.min.js"></script>
     <script type="text/javascript" src="jquery.parser.js"></script>
     <script type="text/javascript" src="jquery.progressbar.js"></script>
     <script type="text/javascript">
      $(function(){

           $('#p').progressbar();

           $('#p').progressbar('setValue', 90);

      });
     </script>

</head>
<body>

     <h2>Easy ProgressBar</h2>
     <div id="p" style="width:400px;"></div>

</body>
</html>
easyloader 是个优秀的资源加载实现,我们绕开它只是为了加深对模块间关系的理解。
现在我们知道了,Dependencies:none并不是真的没有依赖,而是仅仅依赖了parser。
parser是可以绕开的,只要你不使用它提供的工具方法。

3 组件定义

我们尝试分析progressbar源码,了解easyui组件定义的规律。
(function($){
	// 定义属性和事件的默认值
	$.fn.progressbar.defaults = {
		width: 'auto',
		height: 22,
		value: 0,	// percentage value
		text: '{value}%',
		onChange:function(newValue,oldValue){}
	};
	// 初始化方法,在入口函数中调用
	// 这是很典型的一个页面组件,在初始化的时候增加样式,增加页面元素,绑定事件
	function init(target){
	  // 增加样式
		$(target).addClass('progressbar');
		// 增加页面元素以实现功能
		$(target).html('<div class="progressbar-text"></div><div class="progressbar-value"><div class="progressbar-text"></div></div>');		
		// 页面大小调整时触发?force传true可强制改变大小
		$(target).bind('_resize', function(e,force){
				if ($(this).hasClass('easyui-fluid') || force){
						setSize(target);
				}
				// 阻止事件冒泡?
				return false;
		});
		return $(target);
	}
	
	/* 入口函数 */
	$.fn.progressbar = function(options, param){
		  // 如果第一个参数是 string 类型,那就是在调用方法
		  // 形如: $('#x').progressbar('getValue');
			if (typeof options == 'string'){
					var method = $.fn.progressbar.methods[options];
					if (method){
							return method(this, param);
					}
			}
			// 配置项默认为空对象
			options = options || {};
			// 插件扩展的基本写法
			return this.each(function(){
								// 从缓存中取状态数据
								var state = $.data(this, 'progressbar');
								if (state){
									// 如果找到,把新传入的options合并
									$.extend(state.options, options);
								} else {
									// 新创建一个,并加入缓存
									state = $.data(this, 'progressbar', {
											//$.fn.progressbar.parseOptions(this)是自定义的,稍后看代码
											// 这里可以看到配置信息的优先级
											options: $.extend({}, $.fn.progressbar.defaults, $.fn.progressbar.parseOptions(this), options),
											bar: init(this) // init()返回的是 $(target)
									});
								}
								$(this).progressbar('setValue', state.options.value);
								setSize(this);
			});
	};
	
	// 闭包之内,对$.fn.progressbar函数内可见,对外不可见
	function setSize(target,width){
		// 从缓存中取得配置信息
		var opts = $.data(target, 'progressbar').options;
		// 获得bar对象,即 init()返回的 $(target)
		var bar = $.data(target, 'progressbar').bar;
		if (width) opts.width = width;
		// parser中定义的
		bar._size(opts);
		// 更改进度条显示
		bar.find('div.progressbar-text').css('width', bar.width());
		bar.find('div.progressbar-text,div.progressbar-value').css({
			height: bar.height()+'px',
			lineHeight: bar.height()+'px'
		});
	}
	
	// 提供给用户调用的方法
	$.fn.progressbar.methods = {
	
		// 调用方式: $('p').progressbar('options').value;
		// 第一个参数jq已经被progressbar传了this
		options: function(jq){
					return $.data(jq[0], 'progressbar').options;
		},
		
		
		// 调用方式: $('p').progressbar('resize',width);
		resize: function(jq, width){
				return jq.each(function(){
					setSize(this, width);
				});
		},
		
		// 调用方式: $('p').progressbar('getValue');
		getValue: function(jq){
			return $.data(jq[0], 'progressbar').options.value;
		},
		
		// 调用方式: $('p').progressbar('setValue',90);
		setValue: function(jq, value){
			if (value < 0) value = 0;
			if (value > 100) value = 100;
			return jq.each(function(){
						var opts = $.data(this, 'progressbar').options;
						var text = opts.text.replace(/{value}/, value);
						var oldValue = opts.value;
						opts.value = value;
						$(this).find('div.progressbar-value').width(value+'%');
						$(this).find('div.progressbar-text').html(text);
						if (oldValue != value){
								opts.onChange.call(this, value, oldValue);
						}
			});
		}
	};
	
	// 调用了 $.parser.parseOptions
	// 有两处依赖  parser
	$.fn.progressbar.parseOptions = function(target){
		return $.extend({}, $.parser.parseOptions(target, ['width','height','text',{value:'number'}]));
	};
	
})(jQuery);

有两处依赖parser,感兴趣的话,可以看看源码。

4、小结

easyloader是个优秀的资源加载框架。

parser是easyui的核心,实现了自动加载解析(虽然约定过于死板,不是我喜欢的风格)页面控件,同时提供优秀的页面操作基础函数(这个价值很大)。

progressbar代码清晰,结构合理,可以作为自己开发页面组件的模版。






分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics