进一步丰富和简化表单管理的组件:form.js
上文《简洁易用的表单数据设置和收集管理组件》介绍了我自己的表单管理的核心内容,本文在上文的基础上继续介绍自己关于表单初始值获取和设置以及表单数据提交等内容方面的做法,上文的组件粒度很小,都是跟单个表单元素相关的某种特定类型的组件,所以内容很多;本文要介绍的内容集中于整个表单组件本身,有点像上文介绍的formMap.js组件,但不同的是在我自己的项目中form.js用的更多,formMap几乎不用,因为在form的内部就有用到formMap组件的实例来管理表单的数据,之所以这么做,也是为了让各个组件的功能更加单一,方便今后的维护和重用。form.js的代码不多,只有200多行,该组件以及我提供的demo页面的js内都有比较详细的注释,方便有兴趣的朋友阅读参考。
form.js的代码地址:
https://github.com/liuyunzhuge/blog/blob/master/form/src/js/mod/form.js
demo地址:
新增模式:
http://liuyunzhuge.github.io/blog/form/dist/html/demo2.html?mode=1
编辑模式:
http://liuyunzhuge.github.io/blog/form/dist/html/demo2.html?mode=2&id=1
form.js解决的问题
在我自己以前开发项目的经验中,在开发一个表单的时候会遇到下列的一些问题:
1)表单各个字段的初始值如何设置?
虽然上文的内容已经解决了如何区分新增模式和编辑模式的初始值,但是还存在的问题是如果某个字段的初始值需要从后台返回该如何处理?传统项目中我们可以用后台模板来解决,但是假如是一个纯前后端分离的项目呢,那就没有后台模板可以利用了;还有,从后台返回的话,如果是获取编辑模式的初始值,意味着要后台从数据库查询相应的数据,这个时候如何规范传递给后台查询初始值的接口参数?
2)假如通过接口来获取初始值,编辑模式一定需要ajax,但是新增模式就不一定需要,意味着同样的一个功能,有的时候可能是异步的,有的时候可能是同步的,这种情况该如何统一?
3)如果新增模式和编辑模式要使用不同的接口来保存该如何处理?
4)一般表单在保存之后都会根据后台返回的响应添加一些交互逻辑,但是每个保存功能的交互逻辑都是不固定的,更别说项目之间的区别了,如何才能让表单的保存功能更加单一,不受其它功能的影响?
5)提交到后台的数据一般都是按照querystring的格式提交,但有时候为了方便,在php里面会将querystring的参数名都封装成数组索引的形式,比如有一个参数id=1,就会变成某个model名称加参数名称的形式如 User[id]=1,这么做是为了配合后台的数据解析功能;而在java里面,更喜欢直接把整个表单的所有参数合并成一个参数,把所有数据通过json字符串来传递,java后台也有好用的工具将数据直接解析成model,要考虑兼容这样的问题,表单组件在提交数据的时候该如何管理?
这些问题,我考虑的解决方法是:
1)使用以下几个option来管理通过接口初始值的获取和设置的功能:
queryUrl: '',//编辑模式时查询初始值的url
key: '',//编辑模式时使用它作为主键的值,跟在queryUrl后面传递到后台查询数据
keyName: '',//编辑模式时使用它作为主键的名称,跟在queryUrl后面传递到后台查询数据
defaultData: {},//新增模式时的默认值,可以是一个object,也可以是一个字符串,是字符串的时候表示一个后台查询的接口地址
看这部分的代码就能明白它们的实际作用了:
//获取表单初始化数据
getInitData: function () {
var opts = this.options,
mode = this.mode; //这个函数返回的格式,包含三个参数,各个参数的含义如下:
//valid: true表示有效,false表示无效
//ajax: true表示data返回的是jquery创建的ajax对象,false表示不是
//data: 当ajax为true的时候返回jquery创建的ajax对象,否则直接返回一个object实例表示初始化数据 //新增模式,通过defaultData来获取初始值
if (mode == 1) {
var defaultData = opts.defaultData;
//如果defaultData是一个字符串,表示它需要从后台加载
if (typeof(defaultData) == 'string') {
return {
valid: true,
ajax: true,
data: Ajax.get(defaultData)
};
} else {
return {
valid: true,
ajax: false,
data: defaultData
};
}
} else {
//非新增模式,通过queryUrl来获取初始值
//此模式下必须跟后台传递keyName跟key值
//否则后台无法查询到要编辑的数据给前端返回
var url = $.trim(opts.queryUrl),
keyName = $.trim(opts.keyName),
key = $.trim(opts.key); if (!url || !key || !keyName) {
//url key keyName 为falsy值时均返回valid:false,表示初始值无效
return {
valid: false
};
} else {
var params = {};
params[keyName] = key;
return {
valid: true,
ajax: true,
data: Ajax.get(url, params)
}
}
}
}
2) 利用$.Deffered将同步的功能变成异步的功能,但是由于异步的功能里面调用异步回调传递的参数是$.ajax返回的对象,所以调用resolve方法时也必须传递同样的格式,才能保证功能的健壮:
//设置初始值
//方法名起的不好...
//因为这是一个带返回值的设置型函数
//而且为了将初始化数据的设置统一成异步任务
//用到了$.Deferred()
setInitData: function () {
var $defer = $.Deferred(),
initData = this.getInitData(),
opts = this.options; if (!initData.valid) {
setTimeout(function () {
$defer.resolve({});
}, 0);
} else if (initData.ajax) {
initData.data.done(function (res) {
var data = opts.parseInitResponse(res);
$defer.resolve(data);
}).fail(function () {
$defer.resolve({});
});
} else {
setTimeout(function () {
$defer.resolve(initData.data);
}, 0);
} return $.when($defer);
}
3)将表单保存的接口分成两个,一个postUrl用于新增模式新增,一个putUrl用于编辑模式新增,结合mode参数,通过下面的接口来返回实际发起ajax请求时的地址:
//获取表单保存时调用的接口地址
getSaveUrl: function () {
var url = '',
opts = this.options,
mode = this.mode; //mode=1时用postUrl
if (opts.postUrl && mode == 1) {
url = opts.postUrl;
} //mode!=1时用putUrl
if (opts.putUrl && mode != 1) {
url = opts.putUrl;
} return url;
}
4)保存的方法返回的时候直接返回$.ajax创建的对象,不考虑对外部提供任何的保存后的回调,目的就是为了让保存方法更加简单和单一:
//表单保存逻辑
save: function () {
if (this.mode > 2) return false; var opts = this.options,
formData = this.getData(),
event; //触发beforeSave事件
this.trigger((event = $.Event('beforeSave')), formData); //方便外部对formData进行一些额外的处理
formData = opts.parseSubmitData(formData); //如果beforeSave事件默认行为被阻止,则直接返回
if (event.isDefaultPrevented()) {
return false;
} var url = this.getSaveUrl(); //发ajax请求保存,同时把Ajax组件创建的实例返回
//方便外部根据实际情况添加自己的回调
return Ajax[opts.ajaxMethod](url, formData);
}
5)通过parseSubmitData回调来解决以何种结构传递数据到后台的问题,在上面的save方法的代码中,发起ajax请求前有一个调用parseSubmitData的代码,这个回调需返回一个有效的对象作为实际要传递的数据。formData在调用这个回调前是一个object实例,假如后台是php,我们可以把parseSubmitData定义成:
function (data) {
var hasOwn = Object.prototype.hasOwnProperty;
var ret = {};
for (var i in data) {
if (hasOwn.call(data, i)) {
ret['User[' + i + ']'] = data[i];
}
}
return ret;
}
假如formData默认传递时是这样的结构:

调用parseSubmitData后将会是这样的结构:

从我自身的经验来说,一个表单管理的整体组件能够把以上问题解决,基本上功能就够了,因为表单管理的功能本身是比较单一的,当我想要往这个组件添加功能的时候,我总是想两个问题:
1. 是否违背单一原则,加东西会不会让这个组件今后的改动更不稳定
2. 是否能够添加新的组件来解决要添加的功能。
比如说,我原来想把表单校验的功能集成到form.js里面,后面我就发现这是个明显地违背单一原则的决定,最后将表单校验的功能单独出来形成了一个新的组件,这样做最大的好处就是两边的功能没有任何关联影响;而且分开之后,从代码印象上都感觉代码质量跟原来明显不同。
form.js的整体功能
首先它定义的option如下:
var DEFAULTS = {
mode: 1, //同FormFieldBase的mod
postUrl: '',//编辑时保存的url
putUrl: '',//新增时保存的url
queryUrl: '',//编辑模式时查询初始值的url
key: '',//编辑模式时使用它作为主键的值,跟在queryUrl后面传递到后台查询数据
keyName: '',//编辑模式时使用它作为主键的名称,跟在queryUrl后面传递到后台查询数据
defaultData: {},//新增模式时的默认值,可以是一个object,也可以是一个字符串,是字符串的时候表示一个后台查询的接口地址
ajaxMethod: 'post',//发ajax请求的时候用的方法
fieldOptions: {},//各个字段的选项
parseData: $.noop,//获取初始化数据时,通过这个回调来解析初始化数据
parseSubmitData: function (data) {
//保存提交数据到后台之前,可以通过这个回调对要提交的数据做些额外的处理
return data;
},
parseInitResponse: function (res) {
//使用这个回调来解析获取初始化数据时ajax返回的响应
if (res.code == 200) {
return res.data;
} else {
return {};
}
},
onInit: $.noop,//表单初始化完成后的事件回调
onBeforeSave: $.noop//表单保存接口调用前触发的回调
};
要说明的是form.js内部使用了formMap组件来管理表单元素的实例,所以以上option中的mode跟fieldOptions的用法跟上文中的一模一样。
form.js提供了以下api方法在实际工作中可以经常用到:
getMode(): 返回表单的模式:1 2 3
getData(): 获取表单数据
reset(): 表单重置
save(): 保存。
在源码中有一部分可能还需要解释一下:
//设置初始值
this.setInitData().always(function (data) {
opts.parseData(data); var fields = {};
for (var i in data) {
if (hasOwn.call(data, i)) {
fields[i] = '';
}
} for (var i in opts.fieldOptions) {
if (hasOwn.call(opts.fieldOptions, i)) {
fields[i] = '';
}
} //解析字段的初始值
var fieldOptions = {};
for (var i in fields) {
if (hasOwn.call(fields, i)) {
fieldOptions[i] = (i in opts.fieldOptions) && opts.fieldOptions[i] || {};
(i in data ) && (fieldOptions[i][that.mode == 1 ? 'defaultValue' : 'value'] = data[i]);
}
} //初始值可能是异步获取的,所以必须在初始数据获取完毕之后再初始化formMap组件
that.formMap = new FormMap($element, {
mode: that.mode,
fieldOptions: fieldOptions
}); //告诉外部初始化完成
that.trigger('formInit');
});
1)注意always这个方法使用,跟前面介绍的setInitData()的返回值有关系;
2)以上代码中的三个循环,前面2个是为了找出fieldOptions和initData中所有的字段,第三个是为了将字段的option跟initData中的值合并起来,以便最后实例化formMap的时候,直接把fieldOptions传递进去,这样里面的每个表单元素组件在实例化的时候就能得到外部表单组件获取的初始值。
3)还有一种做法:不在表单获取完initData后再去初始化formMap,而是之前就初始化好,然后当initData获取完以后再通过formMap的setData方法来设置初始值,这样有两个问题:
a. formMap提前初始化,各个表单元素组件的初始值都是空的,当form调用reset的时候,不会重置成form获取的值,而是reset成空值;
b. setData方法如果管理不好,会导致在初始化调用的时候触发各个表单元素实例的change事件,这对于初始化过程来说,是不应该的,因为那个时候的change事件不符合语义。
form.js的注意事项
form.js的使用方式可参考demo中的demo2.js。
http://liuyunzhuge.github.io/blog/form/dist/js/app/demo2.js
由于formMap的初始化以及form的init事件触发都是异步的,所以如果外部有些逻辑依赖formMap的话,要考虑把那些逻辑放到form的onInit事件回调里面去做,否则即使不报undefined错误,也达不到想要的功能。
本文小结
本文提供了一个代码跟功能都很简单的表单组件,它跟上文的那些组件一起,构成了我自己在工作中做表单开发的全部内容,由于它们跟我自身的开发经验有很大的关系,所以我也不敢保证这些东西对每个人来说都一定是好用的,但是至少启发作用还是有的,我写这些东西就是受曾经公司开发平台的启发以及后来的项目实际情况的影响,也许有人看到了这些,会写出更符合自己使用习惯的另一套组件出来,那样的话,对自己或者对工作,都会有很大的价值。
下一篇介绍如何自定义jquery.validation,来实现好看的带tooltip的表单校验,敬请关注:)
进一步丰富和简化表单管理的组件:form.js的更多相关文章
- ajax表单提交插件jquery.form.js的运用
该插件提交的数据包含上传的图片. 1.先导入jquery.form.js 2.form表单的元素: <form id="form2_form" method="po ...
- input ,button, textarea 1)使用disabled , 2) 显示值, 3) 表单提交. 4) jquery.form.js ajaxSubmit() 无刷新ajax提交表单.
1.使用disabled input , button textarea 可以 被 禁用, 禁用的效果 : 1) 上面的点击事件无法使用 --- button : 下面的 onclick ...
- jquery表单验证插件 jquery.form.js ------转载
Form插件,支持Ajax,支持Ajax文件上传,功能强大,基本满足日常应用. 1.JQuery框架软件包下载 文件: jquery.rar 大小: 29KB 下载: 下载 2.Form插件下载 文件 ...
- 基于Struts2框架实现登录案例 之 使用Struts2标签库简化表单+继承ActionSupport完成输入交验
一,使用Struts2标签库简化表单 在文章[基于Struts2框架实现登录案例]的基础上,通过使用Struts标签库可以简化登录页面login2.jsp <%@ page language=& ...
- RookeyFrame Bug 表单管理 -> 查看表单 ->编辑字段页面 JS报错
表单管理 -> 查看表单 ->编辑字段页面 小bug onchange里面直接就是方法,修改:去掉外面的function(){},直接把方法体写在onchange里面就可以了. 后台方法: ...
- C#中缓存的使用 ajax请求基于restFul的WebApi(post、get、delete、put) 让 .NET 更方便的导入导出 Excel .net core api +swagger(一个简单的入门demo 使用codefirst+mysql) C# 位运算详解 c# 交错数组 c# 数组协变 C# 添加Excel表单控件(Form Controls) C#串口通信程序
C#中缓存的使用 缓存的概念及优缺点在这里就不多做介绍,主要介绍一下使用的方法. 1.在ASP.NET中页面缓存的使用方法简单,只需要在aspx页的顶部加上一句声明即可: <%@ Outp ...
- 第四节:Vue表单标签和组件的基本用法,父子组件间的通信
vue表单标签和组件的基本用法,父子组件间的通信,直接看例子吧. <!DOCTYPE html> <html> <head> <meta charset=&q ...
- Ajax表单提交插件jquery form
jQuery Form插件是一个优秀的Ajax表单插件,我们可以非常容易的使用它处理表单控件的值,清空和复位表单控件,附件上传,以及完成Ajax表单提交. jQuery Form有两个核心方法ajax ...
- 028、HTML 标签3表单标签插入组件
内容:表单标签插入组件(经常使用)############################################################## form表单标签和input组件 < ...
随机推荐
- Android课程---关于Service的学习(后台运行)
MainActivity.java package com.hanqi.test2; import android.content.ComponentName; import android.cont ...
- 正在运行的android程序,按home键之后退回到桌面,在次点击程序图标避免再次重新启动程序解决办法
正在运行的android程序,按home键之后退回到桌面,在次点击程序图标避免再次重新启动程序解决办法 例如:一个android程序包含两个Activity,分别为MainActivity和Other ...
- 详解Java GC的工作原理+Minor GC、FullGC
详解Java GC的工作原理+Minor GC.FullGC 引用地址:http://www.blogjava.net/ldwblog/archive/2013/07/24/401919.html J ...
- JavaScript多文件下载
对于文件的下载,可以说是一个十分常见的话题,前端的很多项目中都会有这样的需求,比如 highChart 统计图的导出,在线图片编辑中的图片保存,在线代码编辑的代码导出等等.而很多时候,我们只给了一个链 ...
- CoreData教程
网上关于CoreData的教程能搜到不少,但很多都是点到即止,真正实用的部分都没有讲到,而基本不需要的地方又讲了太多,所以我打算根据我的使用情况写这么一篇实用教程.内容将包括:创建entity.创建r ...
- 《CLR.via.C#第三版》第二部分第6,7章节读书笔记(三)
第6章讲的是类型和成员基础 重要认知:虚方法 虚方法的设计原则:设计一个类型时,应尽量减少所定义的虚方法的数量. 首先,调用虚方法的速度比调用非虚方法慢. 其次,JIT编译器不能内嵌虚方法,这进一步影 ...
- C#设计模式之职责链
Iron之职责链 需求: "Iron"的建造一直没有停止,现在单个部件是有的,但是在部件从工厂里出来的时候,在组装到一起之前,我们还是非常有必要对部件进行质量检测,或者是其它个方面 ...
- 利用C#自带组件强壮程序日志
在项目正式上线后,如果出现错误,异常,崩溃等情况 我们往往第一想到的事就是查看日志 所以日志对于一个系统的维护是非常重要的 声明 正文中的代码只是一个栗子,一个非常简单的栗子,只是说明这个框架是怎么工 ...
- spring事务管理器设计思想(二)
上文见<spring事务管理器设计思想(一)> 对于第二个问题,涉及到事务的传播级别,定义如下: PROPAGATION_REQUIRED-- 如果当前没有事务,就新建一个事务.这是最常见 ...
- Java中迭代器
任何容器类,都必须有某种方法可以插入元素并将它们再次取回,毕竟,持有事物是容器最基本的工作,对于List,add()是出入元素的方法之一,而get()是取出元素的方法之一. 如果从更高层的角度思考,会 ...