前端框架决择

对于BS管理系统,我很长一段时间都工作在Asp.Net Web Form上,Web Form的主要优势是可以使用服务器端控件,以类似CS的开发模式进行工作,通过拖拽控件和定义事件处理函数,极大的简化了BS的开发。服务器端控件会在渲染阶段把自身输出为Html标签,对我们完全透明,当需要设置相关属性时,只需要在属性面板上操作即可。

Web Form在诞生之时,Ajax还未流行,所以页面的提交是完全刷新方式。为了记住控件的状态,Web Form引入了ViewState和回发机制。对于一些复杂的界面,可能创建大量的服务端控件,查看ViewState序列化生成的hidden,有时候达到数百K。回发机制也让人郁闷,手工编写的Js与回发机制不能很好的配合,经常发现代码的执行时机不对。Web Form服务端控件生成的Html也相当杂乱,基本是面向机器的。

当然这些问题很多时候是自己的使用方式不对,不能全赖到Web Form上,但大家希望.Net能够提供更好的解决方案。

Ajax逐渐流行,对BS的要求大幅提升,比如局部刷新,干净整洁的Html等。一些 RIA框架发展起来,比如Ext、EasyUi、Dwz等。

微软也推出了轻量级的Mvc框架,我是在Mvc 3 发布时才开始使用的,主要是被Razor视图引擎吸引过来。为了提升用户体验,还选择了一款RIA框架,当时选择了Dwz,主要是它开源免费,而其它框架要么闭源,要么收费,或者太复杂了,学习成本高。

在使用了几年Dwz之后,最近决定更换一个前端框架,下面谈谈使用Dwz的一些情况。

Dwz作为一款国产免费的开源框架是值得支持的,官网http://www.j-ui.com。通过Dwz,我学习到大量Js和Css的相关知识,在此表示感谢。

Dwz基于JQuery,设计以Html扩展为主,对SPA (single page applications, 单页应用程序)有很好的支持,API命名规范,代码质量较高。

使用中碰到的问题也不少,首先是缺乏专业的文档,官方只提供了一个很简单的使用手册,每当碰到问题,我总是进入dwz源码断点调试,不过这样更能深入学习内部机制,似乎也不算严重的缺点。

从我开始使用Dwz,好像就没见更新过。

Dwz有些组件不够完善,比如弹出模态窗口,只支持弹出一层,如果在弹出的模态窗口上继续弹出模态窗就不支持了,而这属于常用功能,对于这些小功能,我自己修改,咬咬牙也还是可以搞出来。

对于一些重量级的组件,比如表格、树等,Dwz提供的比较弱,我一般都引入第三方插件,比如ZTree。

还有一个头痛的问题是布局,Dwz没有提供border或fit这样易用的布局方式,除了面板和Tabs等常规布局组件外,只有一个神秘的layoutH属性,它的值是一个数字,表示工具栏的高度,对于一个复杂点的布局,我经常东调西试以找出这个正确的数字。当然这也只能怪我水平有限,掌握不够好,所以引入一个更强大的前端框架就迫在眉睫。

对于大名鼎鼎的Ext,我已经关注它很长时间,不过一直没敢使用它,有几个原因。使用纯Js开发界面,有点违反一般程序员的习惯,大部分程序员还是喜欢用Html布局。另一个原因是面向对象的Js要求比较高,API丰富而庞大,学习成本高。当然最重要的一个原因是,对于管理后台这样的系统,大量手写Js开发效率低,且Js是弱类型语言,代码提示很弱,且没有编译时检查,容易出错,健壮性差。所以哪怕要使用Ext,我也会用C#来包装一次,这个工作已经有人做了,这就是Ext.Net。十分遗憾的是,Ext.Net不是开源的,而且还收费,它甚至把js等资源内嵌到dll中,另外还不支持DataAnnotations验证,这让我打消了使用它的念头。

Ext也是要收费的,但有一个版本2.0.2可以免费使用,我的想法是,如果做小项目,就用高版本,偷偷的用估计也没人知道,如果需要公开使用,就切换到2.0.2这个版本。通过C#创建一个抽象机制,不仅可以简化开发,而且可以方便切换版本。我在尝试了一段时间后,发现封装的工作量很大,所以暂停了,待以后确实需要的时候再继续。

目光转到EasyUi,EasyUi传说也是国人开发的,不过其官网却是纯英文。对于EasyUi,我几年前也曾了解过,当时认为没有源码,万一出现bug不是束手就擒吗。虽然如此,我却发现它越来越流行了,博客园搞框架的十有八九都是用的EasyUi,周边也有很多公司在使用,甚至在招聘要求上,我也看到过要求有EasyUi的经验。这说明EasyUi的稳定性还是有保证的,大家用都没问题,难道我的运气就那么背。

除了跟风以外,我选择EasyUi还有几个重要原因,首先是功能比较强大,重要的复杂组件和布局组件都提供了,虽然没有Ext那么完善,但也基本够用。其次是学习成本低,EasyUi也是基于jQuery,而且支持Html扩展。最后是官方文档比较齐全。

可以看到,EasyUi的功能、文档、易用性等介于Dwz与Ext之间。

还有一些朋友给我强力推荐Bootstrap,不过我感觉它有点轻量,管理系统需要更重口味的框架,开发类似会员后台的时候再考虑采用Bootstrap。

我学习EasyUi还不到一个月,很多东西仍处于摸索中,EasyUi虽然比较强大,但还是发现不少问题。

首先是它的方法调用方式让人很郁闷。比如我现在要关闭一个窗口,需要这样调用$('#xxx').dialog('close'),为什么不能这样调用$('#xxx'). close ()。还好我主要使用Html扩展方式,手工编写Js仅用来处理回调,这个问题可以忍忍。

其次发现不少小bug,比如多行文本框无法回车换行,时间控件在某些时候点击时报对象为null的错误等等,我使用的是IE 11,估计作者还没有对IE 11进行全面测试,希望能及时更新。

EasyUi虽然是一个比较完善的前端框架,但并不意味着不需要花任何力气,你就可以开发出健壮的应用。下面讨论两个重要的设计决策。

SPA还是IFrame

SPA,全称Single Page Applications, 即单页应用程序。它的设计理念是仅在主框架界面使用一个完整的Html页面,其它所有内容页面都是Html片断,没有html、head、body这些标签,主框架界面通过ajax的方式加载内容页面。

SPA是正宗的Ajax应用模式,并且逐步成为Ajax应用的趋势。它的优点显而易见,所有东西都在同一个页面,查找任何元素,直接用jQuery选择器就行了。

任何事物都有两面性,SPA也有很多缺点,最严重是命名冲突和兼容性。

对于同一个Html页面,如果两个元素的id出现重复,当你用css选择器进行格式化,或用jQuery选择器对其操作时,就会发生意想不到的情况。你会发现,操作某个内容页面时,居然影响到另外一个不相干的内容页。

jQuery解决这种命名冲突,是通过传入一个额外的上下文对象,比如$(“#xx”,context)。Context代表某个内容页,这样就可以仅查找该内容页的id,从而消除了命名冲突。

对于SPA,Dwz提供了天然的支持,它封装了CRUD相关的所有操作,并提供了一个当前上下文context来保存当前操作的内容页或弹出窗口。

虽然Dwz提供了SPA支持,但我在使用中,依然发现偶尔出现各内容页互相影响的情况,为了防止命名冲突,我将id命名得很复杂,以减少冲突。

再看EasyUi,对于CRUD操作,只在官网找到一个很简陋的Demo。仔细研究了Tabs等组件后,感觉EasyUi默认支持的是SPA模式,因为这些组件都没有对IFrame进行支持。EasyUi既然支持的是SPA模式,但却没有做进一步的封装,可以断定,以SPA模式使用EasyUi,命名冲突是比较严重的。

还有一个头痛的问题是兼容性,由于所有内容页面在同一个Html中,如果某些页面需要引用一个第三方插件,而这个插件不是基于jQuery的,或者jQuery版本不同,引入这些插件可能失败。

基于以往的经验,我决定不在管理系统这种复杂的应用中使用SPA,而是使用IFrame的方式加载内容页。

由于EasyUi没有对IFrame提供支持,当我向主界面的Tabs引入IFrame后,引发了一大堆连锁问题,我花了大把时间终于把这些问题解决了,后面将用专门的文章来介绍碰到的障碍。

封装有无必要

大部分使用Mvc的朋友,都是从Web Form转过来的。所谓一遭被蛇咬,十年怕井绳,由于在Web Form上吃过过度封装的苦头,来到轻量级的Mvc世界,他们害怕服务器端的任何东西,只敢使用原生的html和js了。

Mvc提供了一套Html表单控件的封装,比如文本框,@Html.TextBox( "id" ),它等价于<input type="text" name="id"/>。从这个简单的例子,好像使用服务器端语法优势并不明显。但值得注意的是,服务器端语法能够将DataAnnotations验证自动转化为jQuery验证,这一点还是比较强大的。

对于简单的html标签,你是否使用mvc的服务端语法不是特别重要,但对于像Dwz或EasyUi这种Html扩展为主的前端框架就非常必要了。

EasyUi将每个组件的属性都扩展到了Html标签上,对于Html标签,你还能指望代码提示吗?没有代码提示,就意味着你得随时打开EasyUi的官网,以复制相关的属性。当然你很多时候会自己手工输入,你必须把这些API记得非常精确,如果多一个或少一个字符,你就会得到错误的结果。如果你没有遵循敏捷开发的小步前进,而是把整个页面输入完成才开始运行,在密密麻麻的Html标签中找出输入错误的属性也不是一件容易的事。

如果把EasyUi的属性用C#封装起来,由C#来输出Html标签,你就可以完全不再操心API的问题,所有的API只需在代码提示中上下移动即可。可以看到,Web Form的很多思想其实是非常好的,比如用服务端代码输出客户端代码,你应该去其糟粕,取其精华。

对前端框架的封装,真正强大的地方来自Lambda表达式。

从Lambda表达式中,你可以获取到一些元数据信息,比如name、value、验证信息,一个简单的操作@Html.EasyUi().TextBox( t => t.Name ),可能设置了10几个属性,并且所有的属性均来自服务器端,这样维护也更加方便了,你不用来回修改客户端代码。

Lambda表达式还可以获取到属性的类型,这有什么用呢?当你写上一句@Html.EasyUi().TextBox( t => t.XXX ),如果XXX是整型,文本框就自动转成EasyUi的数字文本框,只能输入数字,如果它是一个日期,文本框就显示成一个日期控件。再比如@Html.EasyUi().Combox( t => t.XXX ),当它是布尔类型,就显示一个是、否的下拉列表,如果它是一个枚举就自动绑定一个枚举值的下拉列表,这是不是比你手工输入要强些呢。

结语

本文简要介绍了我对几个前端框架的认识,也说明了封装的必要性。

由于我也是EasyUi初学者,我提供的Demo并不是一个可以直接使用的框架,不过作为学习范例,你可以把它作为你的应用程序框架的起点。

我后续文章会逐步介绍Demo中各构造块的封装和使用要点,所有代码以我最新发放的Demo为准。

为了不冲淡本系列主题,我会专门为EasyUi框架的封装创建一个系列。

对于EasyUi的封装,我并没打算封装它的全部内容,毕竟我不是靠这个吃饭的,我仅在开发时碰到需求才会进行扩展,我的Demo会随着我的开发逐步增强,我会定期发放最新源码。

应用程序框架实战