`

Spring绑定动态列表成员

阅读更多
最近遇到一个需求,使用Spring MVC 3,需要绑定动态列表成员。Google了好多文章,没有找到完美的解决方案,绑定到不成问题,主要是动态添加、删除导致绑定的列表下标,在提交之前需要进行normalize,使用js进行normalize可行,但是费劲而且如果有对表单新的修改导致已有的js出现错误,本文提供了一个简单可行的易于维护的方法:

在添加的时候,我们根据当前条目的数量,计算新添加条目的下标。
在删除的时候,使用js将删除的条目隐藏,并将删除列表的下标记录到隐藏字段中。
这样在后台,我们只需要将删除的条目,按照记录在隐藏字段的下标删除,然后再保存即可。

关于绑定动态列表,在Spring 2.5中,不能使用普通的List,必须使用AutoPopulatingList进行绑定,否则会导致OutOfBoundException,Spring 3提供了Auto Grown的功能,但还仅限于List和Array,map和set还不能Auto Grown。
下面以一个简单的订单修改的例子说明如何进行动态列表绑定:
一个订单,有很多订单条目,用户可以修改订单,动态删除或者添加订单条目。
先看看model:
订单类,一个订单有一个订单列表:
package com.qunar.advertisement.advertiser.model;

import java.util.List;
/**
 * Author: fuliang
 * http://fuliang.iteye.com
 */
public class Order {
	private Long id;
	private String name;
	private List<OrderItem> orderItems;

	public void setId(Long id) {
		this.id = id;
	}

	public Long getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setOrderItems(List<OrderItem> orderItems) {
		this.orderItems = orderItems;
	}

	public List<OrderItem> getOrderItems() {
		return orderItems;
	}
}

订单条目类:
package com.qunar.advertisement.advertiser.model;
/**
 * Author: fuliang
 * http://fuliang.iteye.com
 */
public class OrderItem {
	private Long id;
	private String product;
	private Integer price;

	public void setId(Long id) {
		this.id = id;
	}
	
	public Long getId() {
		return id;
	}
	
	public Integer getPrice() {
		return price;
	}
	
	public void setPrice(Integer price) {
		this.price = price;
	}

	public void setProduct(String product) {
		this.product = product;
	}

	public String getProduct() {
		return product;
	}
}

我们的控制器使用@ModelAttribute注解和表单绑定:
package com.qunar.advertisement.advertiser.controller;

import java.util.Collections;
import java.util.List;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import com.qunar.advertisement.advertiser.model.Order;
import com.qunar.advertisement.advertiser.service.OrderManger;
/**
 * Author: fuliang
 * http://fuliang.iteye.com
 */
@Controller
@RequestMapping("/orders")
public class OrderController {
	private OrderManger orderManager;
	
	@Autowired
	public void setOrderManger(OrderManger orderManager){
		this.orderManager = orderManager;
	}
	
	
	@RequestMapping("/edit/{id}")
	public ModelAndView edit(@PathVariable("id") Long id){
		return new ModelAndView("/orders/edit_order",Collections.singletonMap("order",  orderManager.getOrderById(id)));
	}
	
	@RequestMapping("/update")
	public ModelAndView update(@ModelAttribute("order") @Valid Order order,@RequestParam(value="deletedIndexes",required=false) List<Integer> deletedIndexes){
		orderManager.update(order,deletedIndexes);
		return new ModelAndView("/orders/home");
	}
}


edit_order.jsp的表单和js添加、删除:
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jstl/fn" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<jsp:include page="../base.jsp"></jsp:include>
<head>
	<script src="js/jquery-1.3.1.js" type="text/javascript"></script>
	<title>Order Home</title>
	
	<script type="text/javascript">
		function deleteItem(itemIndex){
			$('#item_' + itemIndex).hide();
			$('#deletedIndexes').append("<input type='hidden' name='deletedIndexes' value='" + itemIndex + "'");
		}
		
		function addItem(){
			var itemCnt = parseInt($('#itemCnt').val());

			var newItem = 
			'<tr id="item_' + itemCnt + '">' + 
				'<th>订单条目' + (itemCnt + 1)+ '</th>' +
				'<td><input type="hidden" name="orderItems[' + itemCnt + '].id"/>' +
					 '<input type="text" name="orderItems[' + itemCnt + '].product"/>' + 
			    '</td>' + 
				'<td><input type="text" name="orderItems[' + itemCnt + '].price"/></td>' +
				'<td><a href="javascript:void(0)" onclick="deleteItem('+ itemCnt + ')">删除条目</a></td>' + 
			'</tr>';
			
			$('#item_' + (itemCnt-1)).after(newItem);
			$('#itemCnt').val(itemCnt + 1);
		}
	</script>
</head>
<body>
	<div id="form">
		<form:form commandName="order" id="orderForm" action="orders/update" method="post">
			<form:hidden path="id"/>
			<div id="deletedIndexes" style="display:none;">
				
			</div>
			<input type="hidden" value="${fn:length(order.orderItems)}" id="itemCnt"/>
			<table>
				<tr>
					<th>订单名</th>
					<td colspan="3"><form:input path="name"/></td>
				</tr>
				<c:forEach varStatus="vs" items="${order.orderItems}">
					<tr id="item_${vs.index}">
						<th>订单条目${vs.index + 1}</th>
						<td><form:hidden path="orderItems[${vs.index}].id"/>
							<form:input path="orderItems[${vs.index}].product"/>
						</td>
						<td><form:input path="orderItems[${vs.index}].price"/></td>
						<td><a href="javascript:void(0)" onclick="deleteItem('${vs.index}')">删除条目</a></td>
					</tr>
				</c:forEach>
				<tr>
					<td colspan="4" style="text-align:center">
						<input type="submit" value="保存"/>
						<input type="button" value="添加订单条目" onclick="addItem()"/>
					</td>
				</tr>
			</table>
		</form:form>
	</div>
</body>
</html>

在Serice类中我们只是简单的准备数据和打印出结果查看效果:
package com.qunar.advertisement.advertiser.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Service;

import com.qunar.advertisement.advertiser.model.Order;
import com.qunar.advertisement.advertiser.model.OrderItem;
/**
 * Author: fuliang
 * http://fuliang.iteye.com
 */
@Service
public class OrderManger {
	//Simple data for test
	public Order getOrderById(Long id){
		Order order = new Order();
		order.setId(id);
		order.setName("手机订单");
		List<OrderItem> orderItems = new ArrayList<OrderItem>();
		order.setOrderItems(orderItems);
		
		OrderItem orderItem1 = new OrderItem();
		orderItem1.setId(1L);
		orderItem1.setProduct("iPhone 4");
		orderItem1.setPrice(5000);
		orderItems.add(orderItem1);
		
		OrderItem orderItem2 = new OrderItem();
		orderItem2.setId(2L);
		orderItem2.setProduct("Nokia N97");
		orderItem2.setPrice(4000);
		orderItems.add(orderItem2);
		
		return order;
	}
	/**
	 * 
	 * @param order should be updated
	 * @param deletedItemIndexes orderItem indexes should be deleted
	 * 简单的在控制台打印出结果,如果存入到数据库,可以根据orderItem id
         * 是否为null,进行更新或者保存。
         */
	public void update(Order order, List<Integer> deletedItemIndexes) {
		if(deletedItemIndexes != null){
			for(int i = deletedItemIndexes.size() - 1; i >= 0; i--){
				order.getOrderItems().remove(deletedItemIndexes.get(i).intValue());
			}
		}
		StringBuilder sb = new StringBuilder();
		sb.append("Order Id: ").append(order.getId()).append("\tName: ").append(order.getName()).append("\n");
		List<OrderItem> items = order.getOrderItems();
		for (OrderItem orderItem : items) {
			sb.append("Item Id: ").append(orderItem.getId()).append("\tName: ").append(orderItem.getProduct()).append("\tprice: ").append(orderItem.getPrice()).append("\n");
		}
		System.out.println(sb.toString());
	}
}


BTW:Java的自动拆装箱要注意,特别是List#remove有两个重载的方法,
remove(Object) remove(int),这个必须手工拆箱。
分享到:
评论
3 楼 fuliang 2011-02-27  
junnyfan 写道
我想同问,如果成员属性是Set如何绑定呢?

好像不可以,绑定的需要能够按照下标访问的,而Set本身不能确保有序,所以不能下标访问。最好在VO中使用List绑定,然后和PO的Set转换一下。
2 楼 junnyfan 2011-02-27  
我想同问,如果成员属性是Set如何绑定呢?
1 楼 蓝之月 2011-01-20  
如果对象成员为SET,如何绑定呢?

相关推荐

    spring.net中文手册在线版

    4.3.5.引用其他对象或类型的成员 4.3.5.1.使用对象或类的属性值进行注入 4.3.5.2.使用字段值进行注入 4.3.5.3.使用方法的返回值进行注入 4.3.6.IFactoryObject接口的其它实现 4.3.6.1.Log4Net 4.3.7.使用depends-on ...

    weixin108校园二手交易平台的设计与实现+ssm(源码+部署说明+演示视频+源码介绍+lw).rar

    "weixin108校园二手交易平台的设计与实现+ssm(源码+部署说明+演示视频+源码介绍+lw).rar" 这个资源包是一个针对校园内二手物品交易的完整项目文件,它使用Spring、SpringMVC和MyBatis(简称SSM)框架搭建。...

    java-ee电子商城系统课程设计.doc

    ________ 专 业 软件工程 青 岛 工 学 院 目 录 1综述 1 1.1选题依据 1 1.2技术介绍 1 1.2.1开发工具介绍 2 1.2.2系统技术介绍 2 1.3组成员分工 2 2系统需求分析 3 2.1功能需求 3 2.1.1前台页面功能 3 2.1.2后台管理...

    Seshat Naming and Balancing Service:扩展 COS 命名和平衡服务-开源

    Seshat 扩展命名和平衡服务提供了一个完全符合 COS 命名服务的扩展,允许您在... 该服务使用 Spring Security Framework 进行访问控制,并且可以配置为使用 Spring Security Framework 支持的任何身份验证授权系统。

    java web 开发详解

     优点: 不需要书写XML配置文件 良好的学习文档 社区成员很热心 缺点: 社区比较小 不如其他的项目活跃 ActionBean里面的URL是硬编码的 Struts 2  优点: 架构简单——易于扩展 标记库很容易利用FreeMarker或者...

    SpringMVC接受前台传值

    控制器接受前台的值的方式 路径传值 @RequestMapping("/login/{id}")...这里定义实体类需要注意的是,对于基本数据类型的成员变量尽量声明成其对应的包装类型… public class User{ private String name; private

    RapidWebDev框架源码

    常用组件是一些常用的组件和可复用API的集合,它包含了公共方法、异常处理,缓存、验证、全球化\本地化、动态类生成器、Data Context工厂、日志处理、事物处理、Spring.NET整合等等 扩展模型 扩展模型可以让你在...

    JetLinks开源物联网平台-其他

    15、增加租户成员绑定(TenantMemberBindEvent),解绑(TenantMemberUnBindEvent)事件.可通过spring-event订阅处理此事件.(Pro) 16、优化子设备状态检查,当检查子设备状态时,将会尝试发送ChildDeviceMessage&lt;...

    乐优商城.xmind

    它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 select select * from category c where c.pid = #{pid} CollectionUtils.isnotblank 判断集合是否为空 测试 可以利用url直接查询数据库...

    asp.net知识库

    C#静态成员和方法的学习小结 C#中结构与类的区别 C#中 const 和 readonly 的区别 利用自定义属性,定义枚举值的详细文本 Web标准和ASP.NET - 第一部分 XHTML介绍 在ASP.NET页面中推荐使用覆写(Override)而不是事件...

    java开源包1

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包11

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包2

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包3

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包6

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包5

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包10

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包4

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包8

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包7

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

Global site tag (gtag.js) - Google Analytics