最近需要做一个动态表单管理,因为以前没什么经验,所以自己做了一个小demo研究了一下,现将自己的整体思路再从头顺一下,也顺便记录一下这次的学习经验,呵呵
1、 首先,要想实现动态表单的管理,自己首先想到的是不就是执行sql语句对数据库表的字段进行增删改嘛,后来想想并不这么简单,因为要关乎到以后用户添加数据时表单的动态生成,以及还要生成动态的js表单验证,所以我首先新建了一张表记录了某张表所有字段信息,如下:
CREATE TABLE `sys_user_fields_info` (
`field_id` varchar(255) NOT NULL DEFAULT '' COMMENT '字段编号',
`table_name` varchar(255) NOT NULL DEFAULT '' COMMENT '表名称',
`is_key` int(11) NOT NULL DEFAULT '0' COMMENT '0:非主键 1:主键',
`field_name` varchar(20) NOT NULL DEFAULT '' COMMENT '字段名',
`field_name_cn` varchar(20) NOT NULL DEFAULT '' COMMENT '字段中文名',
`field_type` varchar(10) NOT NULL DEFAULT '' COMMENT '字段类型',
`min_length` int(11) DEFAULT NULL COMMENT '最小字段长度',
`max_length` int(11) DEFAULT NULL COMMENT '最大字段长度',
`decimal_point` int(11) DEFAULT NULL COMMENT '小数点',
`is_null` int(11) NOT NULL DEFAULT '0' COMMENT '0:可空 1:不可空',
`default_val` varchar(50) DEFAULT '' COMMENT '默认值',
`context` varchar(255) DEFAULT '' COMMENT '描述',
`data_format` varchar(255) DEFAULT 'text' COMMENT '数据要求格式',
`display` varchar(50) NOT NULL DEFAULT 'text' COMMENT '页面显示方式',
`display_context` varchar(255) DEFAULT NULL COMMENT '页面显示方式对应内容',
`on_form` int(11) NOT NULL DEFAULT '0' COMMENT '是否在界面显示标志:0显示 1不显示',
`state` int(11) NOT NULL DEFAULT '0' COMMENT '0、已启用 1、已禁用 2、已删除',
`create_date` datetime DEFAULT NULL COMMENT '创建日期'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='表字段信息';
2、 有了这些字段,缺点是增加了一张表,对数据库的操作变多了,但是对数据表及后续表单的生成及验证是有好处的,若只有一个原始表并且直接操作生不成表单或者只能生成最简单的表单,并不能满足用户的需求,好了,有了上面这张表跟需要动态生成的表就可以完成基本的表元数据的管理了,就是执行一些sql语句而已,字段因为要求做成可以做成一次添加多个字段,所以界面做成了动态生成记录行让用户输入字段信息,最后一次全部保存,保存的时候我的做法是删除了字段信息表中该表所有字段信息重新添加,而需要维护的表也是删除了字段重新建的(这点不好,因为如果表中有数据的话那数据已经丢失了,可以考虑将drop column改成update,但是update的时候也有一个问题就是,如果字段原来的类型是varchar,现在改成了int那数据也会出问题的,还可以考虑先将数据导出,再导进来,这样也可以会出现数据格式不兼容的问题,总之这个要看具体场合采取对应的解决方案了,因为时间紧,现在做成了删除)。
完成对数据表的元数据的维护后,接下来就到了重点的地方了,那就是表单的生成了,表单的生成主要是借助于上面这张表,表中记录了字段的类型、长度、是否可空、界面显示方式、时候生成表单等一系列信息,借助这个就可以生成表单了,也不是很难吧,呵呵,有句话说得好那就是“难事必做于易”,再困难的事到最后也是由一些简单的问题组合而成的,因为现在用的是struts2做的,所以在界面上就需要进行一系列判断了,代码如下:
<form id="addUserForm" class="form-horizontal">
<table cellspacing=10 cellpadding=0 border=0 width=100% height=100%>
<tbody id="tby">
<s:iterator id="field" status="st" value="fieldList">
<s:if test="#field.onForm==0">
<s:if test='#field.fieldName!="user_id"'>
<tr>
<td align="right" id="<s:property value="#field.fieldName"/>_nameCn"><s:property value="#field.fieldNameCn"/>:</td>
<td>
<!-- 显示方式为text -->
<s:if test='#field.display=="text"'>
<input type="text" name="<s:property value="#field.fieldName"/>" size="20" class="input"/>
</s:if>
<!-- 显示方式为radio-->
<s:elseif test='#field.display=="radio"'>
<input type="radio" name="<s:property value="#field.fieldName"/>" value='<s:property value="#field.displayContext.substring(0,1)"/>' class="input" checked="checked"/><s:property value="#field.displayContext.substring(2,3)"/>
<input type="radio" name="<s:property value="#field.fieldName"/>" value='<s:property value="#field.displayContext.substring(4,5)"/>'/><s:property value="#field.displayContext.substring(6,7)"/>
</s:elseif>
<!-- 显示方式为date-->
<s:elseif test='#field.display=="date"'>
<input type="text" name="<s:property value="#field.fieldName"/>" size="20" class="Wdate input" onfocus="WdatePicker()" readonly="readonly"/>
</s:elseif>
<!-- 显示方式为textarea-->
<s:elseif test='#field.display=="textarea"'>
<textarea rows="3" cols="19" name="<s:property value="#field.fieldName"/>" class="input"></textarea>
</s:elseif>
<!-- 显示方式为select-->
<s:elseif test='#field.display=="select"'>
<select name="<s:property value="#field.fieldName"/>" class="input" type="select">
<option value="" selected="selected">---请选择---</option>
<s:iterator id="add" status="st" value="#field.displayContext.split(';')">
<option value="<s:property value="add"/>"><s:property value="add"/></option>
</s:iterator>
</select>
</s:elseif>
<!-- 显示方式为checkbox,取值跟显示问题-->
<s:elseif test='#field.display=="checkbox"'>
<s:iterator id="pre" status="st" value="#field.displayContext.split(';')">
<input type="checkbox" <s:if test="#st.index==0">class="input"</s:if> name='<s:property value="#field.fieldName"/>' value='<s:property value="pre"/>'/><s:property value="pre"/>
</s:iterator>
</s:elseif>
<!-- 如果非空,添加*号标注 -->
<s:if test="#field.isNull==1">
<font color="red">*</font>
</s:if>
</td>
</tr></s:if></s:if></s:iterator></tbody></table> </form>
可以生成不同种类的表单,如下:
3、现在生成表单了,但是用户在添加数据时还要拦截非法数据,这就要生成动态验证代码了,这还得需要借助于上面的表,我的做法是在生成表单的时候在每个表单域的后面都有几个隐藏域用来记录该表单域的验证规则,使用id标记,我这里是根据字段的name值再加上相应的标志动态生成的标记,如下:
<!-- 验证规则 :begin -->
<!-- 1:是否为空 --><input type="hidden" id="<s:property value="#field.fieldName"/>_isNull" value="<s:property value="#field.isNull"/>" />
<!-- 2:类型 -->
<input type="hidden" id="<s:property value="#field.fieldName"/>_type" value="<s:property value="#field.fieldType"/>" />
<!-- 3:最小长度 --><input type="hidden" id="<s:property value="#field.fieldName"/>_minLength" value="<s:property value="#field.minLength"/>" />
<!-- 4:最大长度 -->
<input type="hidden" id="<s:property value="#field.fieldName"/>_maxLength" value="<s:property value="#field.maxLength"/>" />
<!-- 5:数据要求格式 -->
<input type="hidden" id="<s:property value="#field.fieldName"/>_dataFormat" value="<s:property value="#field.dataFormat"/>" />
<!-- 验证规则 :end -->
4、现在既然有验证规则了,那保存的时候只需要取出隐藏表单域的值然后再跟实际值比较就可以了,如下://表单验证
function validateAddUserForm(){
var isTrue = true;
var info = "";
$(".input").each(function(){
//表单域左边的中文提示内容
var nameCn = $("#" + $(this).attr("name") + "_nameCn").html();
var display = nameCn.substring(0,nameCn.length-1);
var inputName = $(this).attr("name");//表单域name
var inputType = $(this).attr("type");
var inputValue = $.trim($(".input[name='"+inputName+"']").val());//表单域value
if(inputType=="radio"||inputType=="checkbox"){
inputValue = $(":"+inputType+"[name='" + inputName + "'][checked]").val();
}else if(inputType=="select"){
inputValue = $.trim($(inputType + "[name='" + inputName + "'] option[selected]").val());
}
//验证规则
var isNull = $("#" + inputName + "_isNull").val();//是否为空
var type = $("#" + inputName + "_type").val();//数据要求格式
var dataFormat = $("#" + inputName + "_dataFormat").val(); var minLength = $("#" + inputName + "_minLength").val();//最小长度
var maxLength = $("#" + inputName + "_maxLength").val(); //验证是否为空
if(isNull==1){//1不允许空
if((inputValue=="")||(inputValue==undefined)){
isTrue = false;
info += display + "不能为空!" + "\n";
}
}
if(isTrue){//验证整数(int)、小数(double)、email(email)、电话(phone)
if(inputValue!=""){
if(dataFormat=="int"){
if((!$.isNumeric(inputValue))||(!($.isNumeric(inputValue))&&(inputValue.indexOf(".")!=-1))){
isTrue = false;
info += display + "须为整数!\n";
}
}else if(dataFormat=="double"){
if(!$.isNumeric(inputValue)){
isTrue = false;
info += display + "须为小数!\n";
}
}else if(dataFormat=="email"){
if(!inputValue.match(/^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/)){
isTrue = false;
info += display + "格式不正确!\n";
}
}else if(dataFormat=="phone"){
if(!inputValue.match(/^1[3|4|5|8][0-9]\d{4,8}$/)){
isTrue = false;
info += display + "格式不正确!\n ";
}
}
}
}
if(isTrue){//以上验证全通过,再去验证长度
var inputLength = inputValue.length;
if(((inputLength<minLength)||(inputLength>maxLength))&&(type!="datetime")){
isTrue = false;
info += display + "长度须在(" + minLength + "," +maxLength+ ")之间!\n";
}
}
});
if(!isTrue){
alert(info);}
return isTrue;}
5、现在基本功能已经完成了,但是现在还有一个遗留的问题就是查询动态表数据的时候,因为字段都是动态生成的,查询的时候不能查询指定的某些字段,你也不知道表中都有哪些字段,只能查询所有字段,而且字段信息表中存有字段的中文信息,所以说查询出所有的字段是没问题,但是如果字段太多,页面样式肯定会出问题,这就需要限制只显示某几列字段,再添加一个查看详细功能。
6、还有一个问题是,动态生成表单时,因为后台不可能知道前台都有生成了哪些字段,只能是用户添加数据并通过验证后,在后台获取所有表单的name以及这些name所对应的值,我现在还不清楚使用struts2怎么获取,所以先用了request获取,然后将name跟value对应好后添加到数据表中。
Enumeration enumr = request.getParameterNames();
while (enumr.hasMoreElements()) {
String param = enumr.nextElement().toString();
String[] values = request.getParameterValues(param);
StringBuffer buf = new StringBuffer();
if (values.length == 1) {
buf.append(values[0].toString());
} else if (values.length > 1) {
for (String value : values) {
buf.append(value).append(";");
}
}
params.put(param, buf.toString());}