引言
表单(Form)是 Web 应用中数据展现和收集常用的 HTML 元素。开发人员经常需要处理表单的数据填充、数据校验和格式化以及数据打包。另外,页面在加载完毕后往往需要执行一段初始化逻辑。本期将介绍 decj 对 HTML 表单的声明式增强和声明式页面 / 模块初始化这 2 个特性。
声明式表单功能增强
decj 以声明式编程的方式对表单数据展现和收集功能进行增强。在数据展现方面,decj 支持根据指定的数据自动将数据填充到表单中、对表单字段值进行自动格式化。在数据收集方面,decj 支持对表单字段值进行自动校验、对表单数据自动打包提交。同时,decj 支持对表单的 Reset 按钮的功能进行增强。
模块业务对象元数据
decj 的表单增强功能依赖于模块的业务对象元数据。从 MVC(Model-View-Control)设计模式的角度来看,表单本身属于 View,其所展现和收集的数据属于 Model。decj 称呼每个 Model 的实例为一个业务对象(Business Object,简称 BO)。一个页面中可以有多个表单,这些表单对应各自的业务对象。模块的业务对象元数据则用于描述一个模块所涉及的各个业务对象的信息,包括业务对象的名称、别名和类型。业务对象类型则描述了该类型的业务对象包含哪些字段以及每个字段的名称、别名、字段类型、数据校验规则以及格式化要求。
模块的业务对象元数据是一个普通 Javascript 对象,可以使用 JSON 进行声明。其格式化如下:
{"boMetaData":{ "BO 类型名称 ":{ // 字段声明 } }, "bo":{"BO 名称 ":{"type":"BO 类型名称 ","alias":"BO 别名 "}} }
上述 JSON 对象中,属性“bo”的值声明了模块使用了哪些 BO,并声明了各个 BO 的名称、别名以及 BO 类型名。BO 的别名默认为 BO 名称本身,它对应于表单的 name 属性值。每个 BO 类型的声明包含了若干个字段声明。每个字段声明的格式如下:
" 字段名称 ":{ "stereoType": " 字段类型名称 ", "alias":" 字段别名 ", "typeParam":{},// 声明字段类型参数 "validation": { " 数据校验规则 ": {}// 声明数据校验规则 } }
字段声明描述了每个字段的类型名称(stereoType)、字段别名(alias)、字段类型参数(typeParam)及数据校验规则(validation)。其中字段别名默认为字段名本身,它对应表单控件的 name 属性值。
假设某 IT 社交网站要实现一个会员信息编辑的功能,会员信息包括会员的姓名、性别、技能专长领域等,如图 1 所示:
图 1. 虚构的某 IT 社交网站的会员信息页面
针对这个例子,开发人员可能创建如清单 11 所示的业务对象元数据:
清单 11. 虚构的某 IT 社交网站的业务对象(会员信息)元数据
{"boMetaData":{ "decj.demo.MemberInfo":{// 声明业务对象类型,其名称为 decj.demo.MemberInfo "firstName": {// 声明业务对象类型中名为 firstName 的字段信息 "alias":"fn",// 字段别名为 fn "typeParam":{"min":4,"max":15} }, "lastName": { "alias":"lastname", "typeParam":{"min":4,"max":15} }, "passwd": { "stereoType": "String", "typeParam":{"min":6,"max":10} }, "gender": { "validation": { // 声明该字段的数据校验规则包括 NotNull,即该字段不允许为空 "NotNull": {} } }, "birthdate":{ "stereoType": "Date",// 声明该字段是个日期型数据 "typeParam":{"format":"yyyy-MM-dd"}, // 声明字段的类型参数,其中说明该字段格式为 yyyy-MM-dd "validation": { "NotNull": {} } }, "prefLang":{ "alias":"preferred-lang", "validation": { "NotNull": {} } }, "expertise":{ "stereoType": "String[]", "validation": { "NotNull": {} } }, "email":{ "typeParam":{"min":15,"max":40} }, "mobilephone":{ "typeParam":{"min":11,"max":11} }, "notification":{ "stereoType": "String[]", "typeParam":{"min":0,"max":10} }, "intro":{ "typeParam":{"min":0,"max":450} }, "mgmtExpr":{ "stereoType": "Decimal", "typeParam":{"locale":"cn","fraction":2} } } }, "bo":{"memberInfo":{"type":"decj.demo.MemberInfo"}} // 声明模块包含一个名为 memberInfo 的业务对象,其类型为 decj.demo.MemberInfo }
模块业务对象元数据可以在 Javascript 代码中直接声明,也可以采用服务端代码生成。若采用服务代码生成,需要在模块定义中声明一个名为 metaDataURL 的属性,其值提供业务对象元数据的 URL。如清单 12 所示:
清单 12. 在模块定义中声明业务对象元数据提供者的 URL
metaDataURL:'handler.jsp?src=metaData.json'
MVC 是被普通采用的设计模式。许多 Web 应用的服务端代码也是采用了 MVC,因此我们可以利用服务端代码自动根据服务端代码中的业务对象生成模块的业务对象元数据。这样既可以减少代码编写量,又可以使客户端和服务端在 Model 层保持同步。比如,在 Java 平台中,开发人员可以使用 Java 的反射(Reflection)API 及 JSON API 自动生成相关 BO 的元数据。
表单内容自动填充
Web 应用经常需要将服务端的数据填充到表单中,以便对数据进行查看、编辑。该功能多数是通过服务端代码(如 JSP)或者调用客户端封装好的代码来实现。decj 框架则支持根据指定的业务对象元数据自动对表单进行内容填充,而无需开发人员编写代码调用任何 API。
下面以上一节中提到的虚构的某 IT 社交网站的会员信息编辑功能为例,讲解 decj 的表单内容自动填充功能。在给定会员信息相应数据的情况下,要实现该表单的自动填充,开发人员只需要为 decj 提供好相关的业务对象元数据(如清单 11 所示),并编写好表单的 HTML 代码即可,如清单 13 所示。
清单 13. 表单的 HTML 代码
<form action="handler.jsp?src=test.jsp" name="memberInfo" title="form.title"> <label> <label class="res-replace">firstname</label> <input name="fn" type="text" value="firstname" title="firstname"/> </label><br /> <label> <label class="res-replace">lastname</label> <input name="lastname" type="text" value="lastname" title="lastname"/> </label><br /> <!-- ... --> <label> <label class="res-replace">expertise-area</label><br /> <select multiple="true" name="expertise"> <option value="java">Java</option> <option value="cpp">C++</option> <option value="Javascript">Javascript</option> <option value="design-pattern">Design Pattern</option> <option value="oracedb">Oracle Database</option> <option value="css">CSS</option> <option value="html">html</option> </select> </label><br /> <!-- ... --> <input name="reset" type="reset" value="Reset" /> <input name="btnSubmit" type="submit" value="update"/> </form>
编写表单的 HTML 代码的时候,要注意表单元素的 name 属性值以及表单中各个控件的 name 属性值与表单所在模块的业务对象元数据中声明的业务对象别名、字段别名要分别相同。decj 正是根据这种对应关系将业务数据显示到相应表单的对应控件上。
填充表单所需的业务对象数据,可以由服务端代码生成或者直接在 Javascript 代码中指定。清单 14 的代码展示了在 decj 应用启动代码中声明初始模块所要加载的服务端数据。
清单 14. 声明初始模块所要加载的服务端数据
function decjApp(){ return { //... initialModule:['module/UpdateRegInfoStatic',{ httpRequest:{ url:'dataProvider.jsp?src=userInfoData.json' } } ] //... }; }
清单 15 展示了清单 14 中所示的 URL 提供的服务端数据。
清单 15. 虚构的某 IT 社交网站的某会员信息
{ "memberInfo":{ "lastName":"Framework", "firstName":"decj", "gender":"Male", "email":"decj@viscenthuang.info", "birthdate":318268800000, "prefLang":"English", "expertise":["html","oracedb","Javascript","java"], "notification":["SMS","email"], "mobilephone":"13612345678", "intro":"decj is a lightweight javascript framework. By taking advantages of JSON and AMD,it enables solving common issues elegantly in the client-side web development in a declarative and modular way.Declarative programming means we can achieve the same thing or even more with less code compared with imperative programming.All features supported by the framework do NOT require you write imperative code,you just declare what you need to do.", "mgmtExpr":1032.56 } }
表单自动填充的效果如图 2 所示:
图 2. decj 自动表单填充示例效果
表单数据校验、数据格式化
decj 框架可以根据业务对象元数据对自动填充的表单中的字段进行自动数据校验、格式化。开发人员无需编写任何代码(除非要进行扩展和定制)。
decj 的表单数据校验分两种。一种基于业务对象元数据中声明的数据类型(stereoType 属性),另一种基于业务对象元数据中声明的数据校验规则(validation 属性)。前者可以根据字段的数据类型对其值进行校验。比如 Number 型的字段,当输入值包含非数字字符时,decj 就会提示数据校验不通过。后者可以根据指定的规则名对应的校验规则对字段的输入值进行校验。比如,String 型字段默认包含了一个名为“Size”的校验规则,该规则会指示 decj 对相应字段的值的长度进行校验。若字段值长度不符合要求,则 decj 会提示数据校验不通过。
例如,上述例子中字段“Management expierence“的元数据声明如下:
"mgmtExpr":{ "stereoType": "Decimal", "typeParam":{"locale":"cn","fraction":2} }
说明名为“mgmtExpr”的字段其类型为数字型,其类型参数中声明了其数据格式化采用的 Locale 为“cn”(中国),小数点为 2 位。那么,当该字段的输入值包含无效的非数字字符时,decj 就会提示数据校验未通过。如图 3 所示:
图 3. decj 基于字段类型的数据校验——校验失败
若“Management expierence“的字段值为”1032.56”,则 decj 的数据校验会通过,并且该字段值会被自动按照中国的数字格式化规则进行格式化,如图 4 所示:
图 4. decj 自动数据格式化
上述例子中字段“Last name“的元数据声明如下:
"lastName": { "alias":"lastname", "typeParam":{"min":4,"max":15} }
这说明,名为“lastName”的字段的类型是默认类型(即 String 型)。由于 String 型的字段蕴含着一个名为“Size”的数据校验规则,且该字段的类型参数中设定 min 属性为 4、max 属性为 15,则 decj 会根据上述声明自动对该字段值的长度进行校验,若字段值的长度不在 4~15 个字符之间,则数据校验未能通过。如图 5 所示:
图 5. decj 基于规则名的数据校验——校验失败
开发人员也可对 decj 进行扩展和定制,建立自己的数据类型和数据校验规则、数据格式化。限于篇幅,本文不详述。
表单数据自动提交
越来越多 Web 应用开始支持 JSON 格式的数据,将表单的数据以 JSON 格式打包提交给服务端程序进行处理已经不稀奇。采用传统的编程方式,这需要编写代码来调用相关 API 实现。而采用 decj,开发人员只需要提供模块的业务对象元数据即可实现自动将表单的数据以 JSON 格式提交到服务端。
decj 默认会以 JSON 格式对表单数据进行打包。和传统的开发方式相同的是,要提交表单,开发人员需要设置表单的 action 属性(指定服务端接收程序的 URL)以及在表单中添加 submit 按钮。
所不同的是,传统方式下表单提交数据时只能采用 HTTP 的 POST 或 GET 方法。而 decj 则支持以任何 HTTP 方法提交数据,包括 PUT、POST、GET 等。这使得 decj 开发的客户端代码可以访问 REST(Representational State Transfer) Web 服务。
decj 默认采用 POST 方法提交表单数据。若要以其它 HTTP 方法提交表单数据,可以在模块定义中声明 decj 表单描述符(Form Descriptor)的 method 属性。表单描述符是 decj 模块的一个名为 forms 的属性。forms 属性的值是一个普通 Javascript 对象,该对象的各个属性的名字与模块页面中的各个表单的 name 属性一一对应。
例如,要更改一个名为 memberInfo 的表单的提交方法为 PUT,只需要在表单描述符中设置 method 属性为 PUT 即可,代码如清单 16 所示。
清单 16. 更改表单的提交方法
// 指定名为 memberInfo 的表单的提交方法为 PUT forms:{// 表单描述符 "memberInfo":{// 表单名称 //... method:'PUT'// 声明表单的提交方法为 PUT //... } }
表单提交时 decj 发送给服务端代码的 HTTP 请求如图 6 所示:
图 6. decj 以 JSON 格式提交表单效果示意(使用 Chrome 浏览器的 Developer Tools 查看)
decj 也支持以传统的 URI-Encode 格式(即以 name/value 对的方式编码表单各个字段)提交表单数据。若要以 URI-Encode 格式提交表单数据,开发人员需要将表单的描述符的 encodingType 属性值设置为“application/x-www-form-urlencoded”,或者给表单元素添加一个名为“_enctype”的自定义属性,设置其值为“application/x-www-form-urlencoded”。此时,表单提交时 decj 发送给服务端代码的 HTTP 请求如图 7 所示:
图 7. decj 以 URI-Encode 格式提交表单效果示意(使用 Chrome 浏览器的 Developer Tools 查看)
声明式页面 / 模块初始化
许多页面、模块往往需要在页面、模块刚被加载完毕后执行一些初始化的代码。采用传统的编程方式,这需要我们侦听 window 对象的 onload 事件,然后在相应的事件监听器中编写初始化代码。采用这种方式,即便使用 jQuery 库,应用也需要调用 jQuery 的相关 API,如清单 17 代码所示:
清单 17. 使用 jQuery 编写页面初始化代码
$(document).ready(function(){// 页面加载完毕后执行该函数 alert('通知:\n\r 。。。'); });
而采用 decj 框架,应用代码则无需调用任何与事件绑定有关的 API,而是直接声明所需要的初始化操作即可。代码如清单 18 所示:
清单 18. 使用 decj 的声明式页面、模块初始化
define(['jquery','decj'],function(jQuery,decj){ //… return { //… init:function(){// 直接在该函数中编写模块初始化代码 alert('通知:\n\r 。。。'); } //… }; });
可见,采用 decj 框架,应用代码只需在模块定义中声明 init 属性即可,而无需调用任何 API。init 属性的值是一个函数,该函数会在当前模块被加载完毕后被执行。因此,页面、模块的初始化代码可以写在该函数内。
另外,decj 中初始化代码被执行的时机与传统编程范式有些区别。传统的编程范式是通过侦听 window 对象的 onload 事件来执行初始化代码。而 window 的 onload 事件是在当前加载的页面及其引用的所有资源(包括 CSS 文件、Javascript 文件和图片文件等)都被加载完毕才会被触发。而 decj 框架会在其加载完当前模块所需的 Javascript 文件、HTML 文件(并将其内容附加到当前 HTML DOM 树上)以及国际化资源文件就执行初始化代码。并且,decj 是以并行的方式加载模块所需的 Javascript 文件、HTML 文件以及国际化资源文件的。因此,采用 decj 框架时,模块、页面的初始化代码可以更加早地被执行,从而提高了页面就绪的速度。
小结
本期介绍了 decj 的“声明式表单功能增强”及“声明式模块初始化”这几个特性如何解决 Web 前端开发中如下常规问题:
- 表单的数据填充、数据打包提交、数据校验和格式化
- 页面初始化逻辑
下一期将介绍如何以零编码的方式实现 Web 应用的国际化化支持。
感谢崔康对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。
评论