BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

使用decj简化Web前端开发(二):声明式表单增强和页面初始化

| 作者 黄文海 关注 6 他的粉丝 发布于 2013年10月25日. 估计阅读时间: 27 分钟 | QCon上海2018 关注大数据平台技术选型、搭建、系统迁移和优化的经验。

引言

表单(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)关注我们,并与我们的编辑和其他读者朋友交流。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT