最近在学习OperamasksUI(版本v2.1)的时候,有了一个想法,想把UI的组件生成通过标签属性扩展的方式来实现。比如对于按钮,通过标签扩展,增加left-icon,right-icon,label,width等属性来实现属性的定义,而通过扩展一个fn属性来达到对事件的定义。如:
<a class="ui-button" label="按钮3" id="btn3" fn="{onClick:function(){alert('按钮3的事件通过扩展属性fn实现');}}" left-icon="${ctx }/static/icons/down.png" width="100" right-icon="${ctx }/static/icons/help.png"></a>
这样的标签扩展带来的好处有:
(1) 页面中不再需要大量的JSON和JS代码,减少了维护的工作量
(2) 降低了开发人员学习om-ui的难度
(3) 能够方便地通过属性的是否输出或EL表达式等方式实现权限的控制。
(4) 标签扩展的代码样式可以方便地通过代码生成器来达到快速生成代码的目的。
由于我自己是做java开发的,所以对于这里的(3)和(4),是很容易来实现的。其实我的终极目的就是要通过代码生成器来达到快速开发的目的。现在的java应用中,一般都会通过自定义TAG的方式来实现快速开发,自定义tag可以做更多的事情,将很复杂的功能封装在一个简单的tag里,但是考虑到自定义tag的可扩展性和可维护性,以及跨语言平台的原因,我还是更愿意尝试通过标签扩展的方式来实现这个目标(当然,我也很想与同行交流一下,自定义tag与标签扩展方式的优劣)。
在对方法的调用上,Operamaks-ui基于和jquery-ui同样的思想,比如对于Dialog的open方法,调用代码如下:
$("#dialog").omDialog("open");
这与我们通常所说的面向对象编程理念不太一致,面向对象的编程思想中,对于一个Dialog的open方法,应该是这样的调用代码:
dialog.open()
就是说,在组件生成之后,应该把句柄暴露给开发者,开发者直接通过该句柄即可完成对方法的调用。
基于这样的思路,我先是对最基本的两个组件:按钮和对话框进行了封装和改造,具体的代码如下(operamasks-ui-plugin.js):
$.omPlugin={}; $.extend.apply($.omPlugin,{ "omButtonbar":{}, "omButton":{}, }); $.omPlugin["omButtonbar"]={ attrs:["width"] }; $.omPlugin["omButton"]={ attrs:["label","width","disabled"], methods:["changeIcons","changeLabel","click","disable","enable"] }; $.omPlugin["omDialog"]={ attrs:["autoOpen|b","closeOnEscape|b","modal|b","resizable|b","title","width","zIndex","dialogClass","draggable|b","height","maxHeight","maxWidth","minWidth"], methods:["close","isOpen","open"] }; $.omPlugin["omDialog-Button"]={ attrs:["text"] }; /** * 对页面中的控件以定义的class进行初始化 * @param $config 预定义的配置信息 */ function initUI($config) { var $$config = $config || {}; // 清理浏览器内存,只对IE起效 $(document).ajaxStart(function() { // }).ajaxStop(function() { // }); function $generateId($uiType){ $uiType = $uiType || "Control"; var idCounter = $(window).data("_ID_COUNTER") || 10000; var $ret = $uiType + "_" + idCounter; $(window).data("_ID_COUNTER",++idCounter); return $ret; } /** * 从扩展标签中提取属性的定义 */ function $buildConfig($obj,$uiType){ // 这里的id一定需要 var $config = {id:$obj.attr("id")}; if(!$config.id){ $config.id=$generateId(); } else { if($.isPlainObject($$config[$config.id])){ $.each($$config[$config.id],function(k,v){ $config[k]=v; }); } } $obj.attr("id",$config.id); var $arrAttr = null; var $arrFn=null; //加载扩展属性 if($uiType && $.omPlugin[$uiType]){ $arrAttr = $.omPlugin[$uiType].attrs; $arrFn = $.omPlugin[$uiType].fn; } $.isArray($arrAttr) && $.each($arrAttr,function(i,attr){ var $attrN=attr; var $attrT=null; if(attr && attr.length >2 && attr.charAt(attr.length-2)=="|"){ $attrN=attr.substring(0,attr.length-2); $attrT=attr.charAt(attr.length-1); } var $v = $obj.attr($attrN); if($v && $attrT==="b"){ $v = $v==="true" ? true : $v==="false"?false : undefined; } if(typeof($v) != "undefined"){ ($config[$attrN]=$v); } }); //从扩展属性fn中取出事件的配置 if($obj.attr("fn")){ var fn = eval("("+$obj.attr("fn")+")"); $.extend($config,fn); } return $config; } /** * 创建UI对象,并将对象ID作为句柄附加给window对象,以快速调用其方法 */ function $buildUI($obj,$uiType,$config){ var $ui = $obj[$uiType]($config); var $dom = $obj[0]; $dom._ui=$dom.$=$ui; var $instance = $ui.data($uiType); var $arrMethod = null; if($uiType && $.omPlugin[$uiType]){ $arrMethod = $.omPlugin[$uiType].methods; } $.isArray($arrMethod) && $.each($arrMethod,function(i,method){ if($instance && $.isFunction( $instance[method] )){ $dom[method]=function(){ //暴露公共函数 return $instance[method].apply($instance,arguments); }; } }); } // buttonbar工具栏及button if($.fn.omButtonbar && $.fn.omButton){ /** * 生成按钮的配置信息 */ function $buildButtonConfig($obj) { var $config = $buildConfig($obj,"omButton"); return $.extend($config,{ label : $config.label || $obj.attr("label") || null, icons : { left : $obj.attr("left-icon") || null, right : $obj.attr("right-icon") || null } }); } $(".ui-buttonbar").each(function() { var $this = $(this); var $config = $buildConfig($this,"omButtonbar"); var $buttons = []; $(".ui-button", $this).each(function() { $buttons.push($buildButtonConfig($(this))); }); $config.btns=$buttons; $buildUI($this,"omButtonbar",$config); }); $(".ui-button").each(function() { var $this = $(this); if(!$this.parent(".ui-buttonbar").length){ $buildUI($this,"omButton",$buildButtonConfig($this)); } }); } // dialog if($.fn.omDialog){ $(".ui-dialog").each(function() { var $this = $(this); var $config = $buildConfig($this,"omDialog"); var $buttons = []; $(".ui-dialog-button", $this).each(function() { var $btnConfig = $buildConfig($(this),"omDialog-Button"); $buttons.push($btnConfig); }); $config.buttons=$buttons; $buildUI($this,"omDialog",$config); }); } }
这里完成了对Buttonbar Button Dialog三个组件的封装。对于支持的属性扩展,通过$.omPlugin[组件类型].attrs进行预定义;而对于需要暴露的方法,通过$.omPlugin[组件类型].methods进行预定义。
先来看一下对于Buttonbar和Button的测试代码(这里是用jsp技术来做的,但没有夹杂太多的java代码,其他语言的开发者应该可以很方便地把它改造成HTML代码或asp代码,如果有不明白的地方,欢迎进行交流。)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@include file="/WEB-INF/include/taglibs.jsp"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>插件使用示例</title> <d:script id="jquery,om-ui" /> <d:script path="/static/om-ui/operamasks-ui-plugin.js" /> <d:css id="om-ui" /> <script type="text/javascript"> function click(){ alert("通过全局的事件及fn扩展属性实现点击事件."); } function changeLabel() { btn3.changeIcons({ right : "${ctx}/static/icons/down.png" }) } function disable() { btn3.disable(); } function enable() { btn3.enable(); } $(document).ready(function() { initUI({ "btn4":{ label:"按钮4(标签通过预定义的配置实现)", width:320, onClick:function(e){alert("通过initUI的初始化配置实现点击事件.");} } }); }); </script> <style type="text/css"> html,body { width: 100%; height: 100%; margin: 0; padding: 0; } </style> </head> <body> <div class="ui-buttonbar"> <a class="ui-button" label="按钮1" left-icon="${ctx }/static/icons/down.png" right-icon="${ctx }/static/icons/help.png" fn="{onClick:click}"></a> <a class="ui-button" label="按钮2" left-icon="${ctx }/static/icons/down.png" right-icon="${ctx }/static/icons/help.png" fn="{onClick:click}"></a> </div> <a class="ui-button" label="按钮3" id="btn3" fn="{onClick:function(){alert('按钮3的事件通过扩展属性fn实现');}}" left-icon="${ctx }/static/icons/down.png" width="100" right-icon="${ctx }/static/icons/help.png"></a> <input type="button" value="更改btn3的标签" onclick="changeLabel();"> <input type="button" value="禁用" onclick="disable();"> <input type="button" value="启用" onclick="enable();"> <!-- --> <br/> <button class="ui-button" id="btn4" left-icon="${ctx }/static/icons/down.png" right-icon="${ctx }/static/icons/help.png"></button> </body> </html>
可以看到这里的页面中并没有太多的JS代码,都是一些简单的JS函数。对于没有jquery基础,或者json应用不熟练的开发人员,也能很容易看懂。而且对按钮3的启用和禁用使用的代码为:
btn3.enable(); btn3.disable();
这里的btn3就是标签中的ID属性。
生成的页面效果如下图,对于事件和方法和调用都通过了测试:
再来看一下对于dialog的测试:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@include file="/WEB-INF/include/taglibs.jsp"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>插件使用示例</title> <d:script id="jquery,om-ui" /> <d:script path="/static/om-ui/operamasks-ui-plugin.js" /> <d:css id="om-ui" /> <script type="text/javascript"> $(document).ready(function() { initUI(); }); </script> <style type="text/css"> html,body { width: 100%; height: 100%; margin: 0; padding: 0; } </style> </head> <body> <a class="ui-button" label="打开第二个对话框" fn="{onClick:function(){dialog2.open()}}"></a> <div id='dialog1' class="ui-dialog" draggable="false" modal="false" title="第一个对话框" resizable="false"> <span class="ui-dialog-button" text=" 是否打开 " fn='{click:function(){alert("第一个是否打开:" + dialog1.isOpen());}}'></span> <span class="ui-dialog-button" text=" 关闭 " fn='{click:function(){dialog1.close();}}'></span> 第一个对话框的内容 </div> <div id='dialog2' class="ui-dialog" autoOpen=false draggable="false" modal="true" title="第二个对话框" resizable="false"> <span class="ui-dialog-button" text=" 关闭 " fn='{click:function(){dialog2.close();}}'></span> 第二个对话框的内容(默认是不可见的) <a class="ui-button" label="检查是否打开" fn="{onClick:function(){alert('第二个对话框是否打开:' +dialog2.isOpen())}}"></a> </div> </body> </html>
页面打开之后的效果如下:
点击左上角的【打开第二个对话框】按钮,将会弹出第二个对话框,点击对话框中的【检查是否打开】按钮,会弹出对应的提示信息,如图:
目前已经实现的只有这两个组件,后续将会不断增加对其他常用组件的实现,希望能与感兴趣的同学一起交流这样的实现方式,也十分期待能有志同道合的朋友能一起进行后续的开发。目前在组件的事件实现方面,是通过扩展了一个fn属性,并在fn属性中传递json格式的事件定义实现的,自己感觉这种方式并不是很好,但是还没更好的方法;也希望各位朋友能提一些宝贵的意见,对事件的处理方面进行一些优化。