有时候我们需要实现双文本的多条记录录入,传给后台保存成List的结构。
界面交互设计如图:
可动态添加行数,每行固定两个文本录入。
我们在之前的文章中已经在SpringMVC基础框架的基础上应用了BootStrap的后台框架,在此基础上记录 多条文本录入用法。
应用bootstrap模板
基础项目源码下载地址为:
SpringMVC+Shiro+MongoDB+BootStrap基础框架
我们在基础项目中已经做好了首页index的访问。
现在就在index.jsp页面和index的路由Controller上做修改,实现 双文本的多条记录录入,传给后台保存成List用法。
jsp页面
html代码
<div class="col-md-10 col-sm-10 col-xs-12 " style="margin-top: 30px">
<form class="form-horizontal col-sm-6" id="channel">
<!-- 其他联系人 -->
<div class="form-group">
<label for="otherContacts" class="col-sm-2 control-label">其他联系人</label>
<div class="col-sm-8 input-group">
<table id="otherContants" class="table table-striped table-bordered" cellspacing="0" style="margin-bottom: 0;">
<thead>
<tr>
<th width="25%" class="text-center">姓名</th>
<th width="*" class="text-center">电话</th>
<th width="20%"></th>
</tr>
</thead>
<tbody id="contactBody">
</tbody>
</table>
<script type="text/template" id="tpl_contact">
<tr>
<td><input type="text" class="form-control" value="{name}" name="name"></td>
<td><input type="text" class="form-control" value="{tel}" name="tel" maxlength="11"></td>
<td class="text-center">
<button type="button" class="btn btn-default btn-sm row-add" title="添加行"><i class="fa fa-plus fa-fw"></i></button>
<button type="button" class="btn btn-default btn-sm row-del" title="删除行"><i class="fa fa-minus fa-fw"></i></button>
</td>
</tr>
</script>
</div>
</div>
<div class="form-group">
<div class="col-sm-6 text-right">
<button type="button" class="btn btn-default cancel" data-dismiss="modal">放弃</button>
<button type="button" class="btn btn-primary save" data-loading-text="Saving...">确认</button>
</div>
</div>
</form>
</div>
js代码获取数据并传输
<script type="text/javascript">
//基础函数开始
/**
* 基础封装函数
*/
var _fnObjectGetPropertyChainValue = function(obj, propertyChain) {
/* 获取属性链的值 */
if (!obj) return;
if (!propertyChain) return obj;
var property,
chains = propertyChain.split('.'),
i = 0,
len = chains.length;
for (;
(property = chains[i]) && i < len - 1; i++) {
if (!obj[property]) return;
obj = obj[property];
}
return obj[property];
},
_fnObjectSetPropertyChainValue = function(obj, propertyChain, value, allowMulti) {
/* 设置属性链的值 */
if (!obj || !propertyChain) return;
var property,
chainObj = obj,
chains = propertyChain.split('.'),
i = 0,
len = chains.length;
for (;
(property = chains[i]) && i < len - 1; i++) {
if (!chainObj[property]) {
chainObj[property] = {};
}
chainObj = chainObj[property];
}
// 改进版:checkbox的多选可以组合为数组
if (!allowMulti || chainObj[property] === undefined) {
chainObj[property] = value;
} else {
var pv = chainObj[property];
if ($.isArray(pv)) {
pv.push(value);
} else {
chainObj[property] = [pv, value];
}
}
return obj;
};
/**
* 格式化字符串 第一个参数为格式化模板 format('this is a {0} template!', 'format');
* format('this is a {0.message} template!', { message: 'format'}); 等同于
* format('this is a {message} template!', { message: 'format' });
*/
$.format = function() {
var template = arguments[0],
templateArgs = arguments,
stringify = function(obj) {
if (obj == null) {
return '';
} else if (typeof obj === 'function') {
return obj();
} else if (typeof obj !== 'string') {
return JSON.stringify ? JSON.stringify(obj) : obj;
}
return obj;
};
return template.replace(/\{\w+(\.\w+)*\}/g, function(match) {
var propChains = match.slice(1, -1).split('.');
var index = isNaN(propChains[0]) ? 0 : +propChains.shift();
var arg, prop;
if (index + 1 < templateArgs.length) {
arg = templateArgs[index + 1];
while (prop = propChains.shift()) {
arg = arg[prop] == null ? '' : arg[prop];
}
return stringify(arg);
}
return match;
});
};
/**
* jQuery form 扩展获取数据
*/
$.fn.formGet = function(opts) {
opts = $.extend({}, opts);
var data = {},
els = opts.formGroup ? this.find('[form-group="' + opts.formGroup + '"]') : this.find('[name]');
if (!els || !els.length) {
return data;
}
var fnSetValue = (function(emptyToNull) {
return emptyToNull ? function(obj, propertyChain, value, allowMulti) {
value !== '' && _fnObjectSetPropertyChainValue(obj, propertyChain, value, allowMulti)
} : _fnObjectSetPropertyChainValue
})(opts.emptyToNull);
els.each(function() {
var $this = $(this),
type = $this.attr('type'),
name = $this.attr('name'), // 可能为属性链
tag = this.tagName.toLowerCase();
if (tag == 'input') {
if (type == 'checkbox') {
var v = $(this).val();
if (v == 'on' || !v) {
fnSetValue(data, name, $(this).prop('checked'));
} else {
$(this).prop('checked') && fnSetValue(data, name, v, true);
}
} else if (type == 'radio') {
this.checked && fnSetValue(data, name, $this.val());
} else {
fnSetValue(data, name, $.trim($this.val()));
}
} else if ('|select|textarea|'.indexOf('|' + tag + '|') > -1) {
fnSetValue(data, name, $this.val());
} else {
fnSetValue(data, name, $.trim($this.text()));
}
});
return data;
};
/**
* jQuery form 扩展绑定数据
*
*/
$.fn.formSet = function(data, formGroup) {
var els = formGroup ? this.find('[form-group="' + formGroup + '"]') : this.find('[name]');
if (!els || !els.length) {
return this;
}
els.each(function() {
var $this = $(this),
type = $this.attr('type'),
name = $this.attr('name'),
tag = this.tagName.toLowerCase();
var value = _fnObjectGetPropertyChainValue(data, name);
if (tag == 'input') {
if (type == 'checkbox') {
var v = $(this).val();
if (v == 'on' || !v) {
this.checked = value ? 'checked' : '';
} else {
this.checked = $.isArray(value) && value.indexOf(v) > -1 ? 'checked' : ''
}
} else if (type == 'radio') {
this.checked = $this.val() == String(value) ? 'checked' : '';
} else {
$this.val(value);
}
} else if ('|select|textarea|'.indexOf('|' + tag + '|') > -1) {
$this.val(value);
} else {
$this.html(value);
}
});
return this;
};
//基础函数结束
//业务代码开始
var $contactsBody = $('#contactBody'),
contactsTpl = $('#tpl_contact').html();
// 初始化其他联系人列表
(function (contacts) {
if (!contacts) {
contacts = [{}];
}
var buffer = [];
for (var i = 0, contact; contact = contacts[i]; i++) {
$contactsBody.append($($.format(contactsTpl)).formSet(contact));
}
})(${cf:toJSON(channel.otherContacts)});
function _getContacts() {
var contacts = [];
$contactsBody.find('tr').each(function (index, ele) {
var row = $(ele).formGet();
if(row.name || row.tel){
if(!/^(1[0-9])\d{9}$/.test(row.tel)){
alert("请输入正确的手机号.");
return false;
}
contacts.push({"name":row.name,"tel":row.tel });
}
});
return contacts;
}
$contactsBody.on('click', '.row-del', function () {
var $tr = $(this).closest('tr');
if ($tr.parent().children().length == 1) return;
$tr.remove();
}).on('click', '.row-add', function () {
var $tr = $(this).closest('tr').after($.format(contactsTpl, {}));
});
$('button.save').on('click',function(){
debugger;
var data = $('#channel').formGet();
if(_getContacts().length)
data.otherContacts = _getContacts();
console.log(data);
$.ajax({
type: "POST",
url: "/channel/save",
contentType: "application/json",
data: JSON.stringify(data),
success: function (result) {
console.log(result);
if (!result.code) {
$('#channel').formSet(data);
location.href = '/'
}else{
alert(result.msg);
}
},
error: function (result) {
alert("出错了,请稍后重试");
}
});
});
</script>
cf是jsp自定义标签
不了解的同学可以参考
jsp自定义标签—–EL表达式中连接两个字符串
新建tags.tld文件如下:
<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<description>23mofang custom JSTL 1.1 tag library</description>
<display-name>JSTL functions</display-name>
<tlib-version>1.1</tlib-version>
<short-name>cf</short-name>
<uri>com.data.web.view.function</uri>
<function>
<description>对象序列化为json字符串</description>
<name>toJSON</name>
<function-class>com.test.util.JSONUtils</function-class>
<function-signature>java.lang.String toJSONString(java.lang.Object)</function-signature>
</function>
</taglib>
对应的JSONUtils文件如下:
package com.test.util;
import com.alibaba.fastjson.JSON;
/**
* JSONUtils
*
*/
public final class JSONUtils {
/**
*
* @param object
* @return
*/
public static String toJSONString(Object object) {
return JSON.toJSONString(object);
}
/**
*
* @param json
* @param clazz
* @return
*/
public static <T> T parseJSON(String json, Class<T> clazz) {
return JSON.parseObject(json, clazz);
}
}
作用是把List转换为Json。
在index.jsp页面引入JSP自定义标签cf使用代码:
<%@ taglib prefix="cf" uri="com.data.web.view.function" %>
这里的prefix值与tags.tld文件中short-name的值对应,uri值与tags.tld文件中uri的值对应。
完整jsp代码如下:
<%@ include file="./include/header.jsp"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="cf" uri="com.data.web.view.function" %>
<div id="page-wrapper">
<div id="page-inner">
<div class="row">
<div class="col-md-12">
<h1 class="page-header">
多条记录多文本录入 <small>示例</small>
</h1>
</div>
</div>
<!-- /. ROW -->
<div class="col-md-10 col-sm-10 col-xs-12 " style="margin-top: 30px">
<form class="form-horizontal col-sm-6" id="channel">
<!-- 其他联系人 -->
<div class="form-group">
<label for="otherContacts" class="col-sm-2 control-label">其他联系人</label>
<div class="col-sm-8 input-group">
<table id="otherContants" class="table table-striped table-bordered" cellspacing="0" style="margin-bottom: 0;">
<thead>
<tr>
<th width="25%" class="text-center">姓名</th>
<th width="*" class="text-center">电话</th>
<th width="20%"></th>
</tr>
</thead>
<tbody id="contactBody">
</tbody>
</table>
<script type="text/template" id="tpl_contact">
<tr>
<td><input type="text" class="form-control" value="{name}" name="name"></td>
<td><input type="text" class="form-control" value="{tel}" name="tel" maxlength="11"></td>
<td class="text-center">
<button type="button" class="btn btn-default btn-sm row-add" title="添加行"><i class="fa fa-plus fa-fw"></i></button>
<button type="button" class="btn btn-default btn-sm row-del" title="删除行"><i class="fa fa-minus fa-fw"></i></button>
</td>
</tr>
</script>
</div>
</div>
<div class="form-group">
<div class="col-sm-6 text-right">
<button type="button" class="btn btn-default cancel" data-dismiss="modal">放弃</button>
<button type="button" class="btn btn-primary save" data-loading-text="Saving...">确认</button>
</div>
</div>
</form>
</div>
<!-- /. ROW -->
</div>
<!-- /. PAGE INNER -->
</div>
<!-- /. PAGE WRAPPER -->
<%@ include file="./include/footer.jsp"%>
<script type="text/javascript">
//基础函数开始
/**
* 基础封装函数
*/
var _fnObjectGetPropertyChainValue = function(obj, propertyChain) {
/* 获取属性链的值 */
if (!obj) return;
if (!propertyChain) return obj;
var property,
chains = propertyChain.split('.'),
i = 0,
len = chains.length;
for (;
(property = chains[i]) && i < len - 1; i++) {
if (!obj[property]) return;
obj = obj[property];
}
return obj[property];
},
_fnObjectSetPropertyChainValue = function(obj, propertyChain, value, allowMulti) {
/* 设置属性链的值 */
if (!obj || !propertyChain) return;
var property,
chainObj = obj,
chains = propertyChain.split('.'),
i = 0,
len = chains.length;
for (;
(property = chains[i]) && i < len - 1; i++) {
if (!chainObj[property]) {
chainObj[property] = {};
}
chainObj = chainObj[property];
}
// 改进版:checkbox的多选可以组合为数组
if (!allowMulti || chainObj[property] === undefined) {
chainObj[property] = value;
} else {
var pv = chainObj[property];
if ($.isArray(pv)) {
pv.push(value);
} else {
chainObj[property] = [pv, value];
}
}
return obj;
};
/**
* 格式化字符串 第一个参数为格式化模板 format('this is a {0} template!', 'format');
* format('this is a {0.message} template!', { message: 'format'}); 等同于
* format('this is a {message} template!', { message: 'format' });
*/
$.format = function() {
var template = arguments[0],
templateArgs = arguments,
stringify = function(obj) {
if (obj == null) {
return '';
} else if (typeof obj === 'function') {
return obj();
} else if (typeof obj !== 'string') {
return JSON.stringify ? JSON.stringify(obj) : obj;
}
return obj;
};
return template.replace(/\{\w+(\.\w+)*\}/g, function(match) {
var propChains = match.slice(1, -1).split('.');
var index = isNaN(propChains[0]) ? 0 : +propChains.shift();
var arg, prop;
if (index + 1 < templateArgs.length) {
arg = templateArgs[index + 1];
while (prop = propChains.shift()) {
arg = arg[prop] == null ? '' : arg[prop];
}
return stringify(arg);
}
return match;
});
};
/**
* jQuery form 扩展获取数据
*/
$.fn.formGet = function(opts) {
opts = $.extend({}, opts);
var data = {},
els = opts.formGroup ? this.find('[form-group="' + opts.formGroup + '"]') : this.find('[name]');
if (!els || !els.length) {
return data;
}
var fnSetValue = (function(emptyToNull) {
return emptyToNull ? function(obj, propertyChain, value, allowMulti) {
value !== '' && _fnObjectSetPropertyChainValue(obj, propertyChain, value, allowMulti)
} : _fnObjectSetPropertyChainValue
})(opts.emptyToNull);
els.each(function() {
var $this = $(this),
type = $this.attr('type'),
name = $this.attr('name'), // 可能为属性链
tag = this.tagName.toLowerCase();
if (tag == 'input') {
if (type == 'checkbox') {
var v = $(this).val();
if (v == 'on' || !v) {
fnSetValue(data, name, $(this).prop('checked'));
} else {
$(this).prop('checked') && fnSetValue(data, name, v, true);
}
} else if (type == 'radio') {
this.checked && fnSetValue(data, name, $this.val());
} else {
fnSetValue(data, name, $.trim($this.val()));
}
} else if ('|select|textarea|'.indexOf('|' + tag + '|') > -1) {
fnSetValue(data, name, $this.val());
} else {
fnSetValue(data, name, $.trim($this.text()));
}
});
return data;
};
/**
* jQuery form 扩展绑定数据
*
*/
$.fn.formSet = function(data, formGroup) {
var els = formGroup ? this.find('[form-group="' + formGroup + '"]') : this.find('[name]');
if (!els || !els.length) {
return this;
}
els.each(function() {
var $this = $(this),
type = $this.attr('type'),
name = $this.attr('name'),
tag = this.tagName.toLowerCase();
var value = _fnObjectGetPropertyChainValue(data, name);
if (tag == 'input') {
if (type == 'checkbox') {
var v = $(this).val();
if (v == 'on' || !v) {
this.checked = value ? 'checked' : '';
} else {
this.checked = $.isArray(value) && value.indexOf(v) > -1 ? 'checked' : ''
}
} else if (type == 'radio') {
this.checked = $this.val() == String(value) ? 'checked' : '';
} else {
$this.val(value);
}
} else if ('|select|textarea|'.indexOf('|' + tag + '|') > -1) {
$this.val(value);
} else {
$this.html(value);
}
});
return this;
};
//基础函数结束
//业务代码开始
var $contactsBody = $('#contactBody'),
contactsTpl = $('#tpl_contact').html();
// 初始化其他联系人列表
(function (contacts) {
if (!contacts) {
contacts = [{}];
}
var buffer = [];
for (var i = 0, contact; contact = contacts[i]; i++) {
$contactsBody.append($($.format(contactsTpl)).formSet(contact));
}
})(${cf:toJSON(channel.otherContacts)});
function _getContacts() {
var contacts = [];
$contactsBody.find('tr').each(function (index, ele) {
var row = $(ele).formGet();
if(row.name || row.tel){
if(!/^(1[0-9])\d{9}$/.test(row.tel)){
alert("请输入正确的手机号.");
return false;
}
contacts.push({"name":row.name,"tel":row.tel });
}
});
return contacts;
}
$contactsBody.on('click', '.row-del', function () {
var $tr = $(this).closest('tr');
if ($tr.parent().children().length == 1) return;
$tr.remove();
}).on('click', '.row-add', function () {
var $tr = $(this).closest('tr').after($.format(contactsTpl, {}));
});
$('button.save').on('click',function(){
debugger;
var data = $('#channel').formGet();
if(_getContacts().length)
data.otherContacts = _getContacts();
console.log(data);
$.ajax({
type: "POST",
url: "/channel/save",
contentType: "application/json",
data: JSON.stringify(data),
success: function (result) {
console.log(result);
if (!result.code) {
$('#channel').formSet(data);
location.href = '/'
}else{
alert(result.msg);
}
},
error: function (result) {
alert("出错了,请稍后重试");
}
});
});
</script>
</body>
</html>
java代码
在jsp页面代码中,初始化赋值时使用了channel.otherContacts。Channel实体如下:
package com.test.domain.entity;
import java.util.Date;
import java.util.List;
public class Channel {
private String id;
private String name;// 名字
private int category;// 类别
private String affiliation;// 归属
private String address;// 地址
private List<Contacts> otherContacts;// 其他联系人
private String remarks;// 备注
private int status;// 状态
private Date createTime;// 新增时间
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCategory() {
return category;
}
public void setCategory(int category) {
this.category = category;
}
public String getAffiliation() {
return affiliation;
}
public void setAffiliation(String affiliation) {
this.affiliation = affiliation;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public List<Contacts> getOtherContacts() {
return otherContacts;
}
public void setOtherContacts(List<Contacts> otherContacts) {
this.otherContacts = otherContacts;
}
public String getRemarks() {
return remarks;
}
public void setRemarks(String remarks) {
this.remarks = remarks;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
}
Contacts实体如下:
package com.test.domain.entity;
public class Contacts {
private String name;// 姓名
private String tel;// 电话
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
}
Controller代码接收实体代码如下:
package com.test.web.controller;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.test.domain.entity.Channel;
import com.test.web.message.response.JSONResult;
/**
* IndexController
*
*
*/
@Controller
public class IndexController {
@Autowired
MongoTemplate mongoTemplate;
DateFormat fmt =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@RequestMapping("/")
public String index(Model model) throws IOException {
return "/index";
}
@RequestMapping("/channel/save")
@ResponseBody
public JSONResult saveChannel(@RequestBody Channel channel) {
int result=upsertChannel(channel);
return result>0?JSONResult.success(channel): JSONResult.error("手机号已存在,保存失败");
}
private int upsertChannel(Channel channel) {
System.out.println(channel.getOtherContacts().size());
return 1;
}
}