aform的诞生已经有些年头了,当时起源于团队有时会承载一些表单的开发,比如一些数据列表录入界面,一些开关的配置,另外还有一些场合需要动态表单,发现当时市面上表单引擎非常少,一些工作流系统自带表单引擎,但功能有限,相反,表单的皮肤库、控件库、校验库倒是很多,但在表单开发模式上并没有一套比较简便和完整的框架。
因此我打算自己做1个表单引擎,作为一个懒人,我首先想到的就是不用再写html了,只要有一份json数据,我自动生成表单,比如发现是字符串,我生成text field,数字生成number控件,对象生成fieldset,数组生成table,而且是需要支持嵌套;
另外一点就是表单的取数,当时大部分表单取数还是需要自己编写代码的,比如要取checkbox的选中态,你需要自己去遍历被勾选的checkbox,jquery尽管提供了一个serializeForm的方法,然而它不支持层级的嵌套以及组件的封装,所有的input会打平成一个kv对象,这仅适合非常简单的场景,我希望这个表单框架能实现用什么结构来渲染,那么也可以自动取到其表单数据,且结构也和渲染时数据保持一致。
因此,aform的最初版本就是这样1个东西,即根据json生成表单,再调用取数方法获取一致的json结构,如图所示:
可别小看这么一个小功能,单就表单嵌套和数组自动生成这一块已经超越大部分专业的工作流系统使用的表单引擎了,而且它没依赖任何库,且支持各种ie浏览器。
但是对于真正商业使用的表单来说,仅支持表单生成和取数这块的自动化是远远不够的,aform在后续的发展过程中,逐渐支持了根据schema生成表单、基于model的校验、自定义输入控件、自定义指令、数据适配、数据监听等各种机制,那么接下来,就对aform做一个完整的介绍。
aform对表单生命周期的涵盖
尽管表单通常是一次性的渲染,但也可以从成千上万的表单中抽出一些公共逻辑,这就是表单的生命周期,通常涵盖渲染前的一些数据获取、渲染中、用户交互(通常是数据录入,会有一些表单联动和校验的逻辑),最后是表单的提交(即数据的获取)
渲染一个aform及获取表单数据
正如前文所述,aform会根据数据生成符合语义化的标签,比如object生成fieldset、数组生成table,基本字段生成一个行容器及label+input组合。
aform有一套自己的schema描述表单结构,和jsonschema类似,但又有很多差异最大的差别是对type
的意义不同,aform认为type是html的input的type,而jsonschema认为type是数据类型,这是由于二者的出发点不一样,因为aform主要用于定义表单,因此会全面像html规范靠拢,而jsonschema更加适合定义数据,但由于aform采用了一个模型驱动的思路,因此也会有数据结构定义的含义在里面,当然后面你会发现,二者的schema是非常方便转换的(如果你需要的话)。
aform的schema主要配置:
配置名 | 释义 | 类型 | 默认值 | 范例 |
---|---|---|---|---|
label | 生成的label中的内容,不设置则使用字段名 | 字符串 | 空字符串 | label:"年龄" |
jtype | js数据类型,通常无需设置,仅在需要区分对象还是数组的情况下设置,Boolean、Array、Number等 | 字符串 | 空字符串 | jtype:"Boolean" |
defaultValue | 默认值 | any | 不设置 | defaultValue:"Y" |
tips | 输入控件右侧生成的tips内容,需配合tipsTpl使用 | 字符串 | 不设置 | tips:"你好" |
placeholder | 输入框为空时显示的提示文字,同html5的对应属性 | 字符串 | 不设置 | placeholder:"please input your address" |
type | 输入控件类型,和html的type保持一致 | 字符串 | text | type:"select" |
maxlength | 输入控件的输入值最大长度,仅针对文本框有效 | 数字 | 不设置 | maxlength:20 |
minlength | 输入控件的输入值最小长度 | 数字 | 不设置 | minlength:10 |
readonly | 输入控件是否只读,仅针对文本框和文本区域有效 | 布尔型 | 不设置 | readonly:true |
disabled | 输入控件是否禁用,效果同html元素的disabled属性 | 布尔型 | 不设置 | disabled:true |
required | 输入控件是否必填,用value是否为空字符串判断 | 布尔型 | 不设置 | required:true |
pattern | 输入值的校验正则表达式,格式同html5的表单元素的pattern属性 | 字符串 | 不设置 | pattern:"\\d+" |
patternErrorMsg | 正则校验失败后的错误提示 | 字符串 | 不设置 | patternErrorMsg:"宽度需要是一个整数" |
title | 鼠标移到输入控件上时显示的提示内容 | 字符串 | 不设置 | title:"请填写数字" |
datalist | 输入控件的可选项目列表。 | 数组 | 不设置 | datalist:[{value:0,text:"男",group:"分组名"}] |
可以看到,aform的schema几乎和html的input的属性完全保持一致,这是为了减少学习成本。
当指定以local模式渲染表单时,aform将根据你的schema定义生成表单,即使渲染的数据有超出schema定义的字段:
var jf = new AForm("target",{
schemaMode:"local",
fields:{
a:{label:"a",defaultValue:1},
b:{label:"b",defaultValue:2},
c:{
fields:{
c1:{defaultValue:3}
c2:{defaultValue:4}
}}
}
});
jf.render()
alert(jf.getJson());//输出{a:1,b:2,c:{c1:3,c2:4}}
表单的校验
由于aform模型驱动的思想,因此表单校验再也不用关心dom了,aform会在合适的时机(如input blur之后)自动校验字段,或者在取数时校验。
aform的校验支持两个维度,一个是字段级别,只能校验该字段的值,另一个是表单全局级别,此时可校验整个表单json。
每一条校验规则均可自定义校验逻辑,如下所:
var jf = new AForm("target",{
fields:{
a:{label:"a",required:true},//必填校验
b:{label:"b",pattern:"\\d+"}//正则校验
c:{label:"b",validators:{//自定义校验
rule : function(v , input){
return v.indexOf("http://") == 0;//以http开头
},
errorMsg:"字段b需以http开头"
}}
},
validators:{//全局校验器
rule : function(json){
return json.a > json.b;
},
errorMsg:"字段a的值应该大于b的值"
}
});
校验规则的函数可以提前注册,实现模块化管理,如:
//长度校验
AForm.registerValidator("100length", {
rule: function(v, input) {
this.errorMsg = "字符长度不能超过100,当前为" + v.length;
return v.length <= 100;
},
errorMsg: ""
});
//使用已注册的校验器
var jf = new AForm("target",{
fields:{
a:{label:"a",validators:["100length"]},//数组元素即验证器名
}
});
字段异常均会抛出事件,可以监听invalid事件从而实现输入提醒。
自定义输入控件
当html的默认输入控件不够用了或者你嫌弃它不好看,那么此时可以定义一些自定义控件,一个自定义控件的协议包含3个方面:
- render - 返回一个html字符串
- renderComplete - 处理render后mount到dom的逻辑,如事件交互
- getValue - 处理取数
实例:
//基类控件
AForm.registerControl("date", {
desc:"date",
render: function(k, v, conf, i, af) {
return "我是date";
},
getValue: function() {
return "my value";
}
});
//子控件
AForm.registerControl("datetime", "date", {
desc:"datetime",
render: function(k, v, conf, i, af) {
var html = this.__super.render.call(this ,k, v, conf, i, af);//this.__super 为父类对象
html += " -- datetime";
return html;
}
});
自定义属性
到后面,你会发现,aform自带的那些配置已经不够满足表单的需求和个性化的业务逻辑,因此需要实现一套机制,可以扩展aform的属性,这里参考了angular的思路,但实现机制有很大差别这里不赘述,aform的自定义属性,可以实现对表单的干预,比如想定义个minlength
的属性,它要求字段需要有最少输入字符,那么通过自定义属性可以这么实现:
AForm.registerProp("minlength", {
beforeRender: function(conf) {
if (!conf.minlength) {
return;
}
conf.ctrlAttr["minlength"] = conf.minlength;
conf.validators.push({
rule: function(v) {
return v.length >= conf.minlength;
},
errorMsg: "输入的字符长度需不少于" + conf.minlength
});
}
});
作为表单整体解决方案的aform
aform体系庞大,本文无意介绍各个方面,有兴趣的同学可以去这里看文档:http://apm.alibaba-inc.com/package/@alife/aform , 事实上,aform在基础平台支撑各种业务的过程中,已经发展成一整套解决方案,从表单设计器、表单数据web service以及组件生态圈,都有触及,并且在各种业务线上活跃。
aform designer致力于实现表单的在线构建,目前已在部分招商入驻应用使用,它能实现表单的分步骤构建,看下截图:
ui构建最终得到一个aform schema,前端通过web service读取到该schema并渲染表单,aform还提供了一个数据服务供表单业务保存数据,后端可通过hsf消费前台提交的数据,整个链路如下图所示:
在近2年的期间,aform形成了丰富的表单控件库,如图片上传、日期选择、类目选择、地址选择器等近30个组件和自定义指令,由于aform自定义控件主要是规定了协议,因此它们仍然是复用了更为基础的组件,并没有多做什么轮子。
在表单监控方面,借助everlog自定义上报系统,实现了更为细致的表单行为监控,如监控表单填写的耗时,表单频出错字段,以下是一个示例:
结语
在非框架体系下,aform是强有力的解决表单开发效率的利器,尽管react和mvvm库在解决表单开发效率上有一些进步,但并没有系统性和针对性的解决方案,由于aform没有依赖任何库,因此可极为简易地引入到现有的技术体系,当然它也是有缺陷的,比如暂不支持表单的联动(估计快了),aform目前已经应用到上百个表单,涵盖部分后台boss系统、1688招商报名、实力商家及源头好货的入驻、jdata的动态表单等业务,它是非常可靠的。
在未来,aform仍将作为一个非框架体系下的表单解决方案而存在,在中台dpl和react大背景下,aform会和国际站以及更多的表单领域融合,形成全新的react体系下的表单解决方案,共建集团表单大中台。