`
cloudtech
  • 浏览: 4604251 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

Grails完美解决一对多(One to many)Model动态表单的自动组装

 
阅读更多

这里的试验是在Grails下做的,但是在Spring MVC中应该也能适用,即便不行,通过简单的扩展也能达到效果。对于其他框架像Struct之类应该也都没问题(不了解,不确定)。

能实现的自动封装包含:

1.一层简单模型

class AddressCommand {
	String city
	String street
	int hourseNumber
}

2.One to one,多层模型

class PersonCommand {
	String name
	int age
	AddressCommand address
}

3.One to many, 多层模型

class ChildCommand {
	String name
	int age
}

class PersonCommand {
	String name
	int age
	AddressCommand address
	List children
}

这个模型模型就是我们最终要使用的模型。这里我们不讨论Many to One 和 Many to Many模型,不常见(我还没遇到过),而且也可以通过转换转成以上几种类型;例子中的模型只有两层,多层的依葫芦画瓢,原理不变,增加了一些复杂性。

我们先创建一个Controller,之后我们都会把数据Post到save Action,如果数据对的话,Grails就会帮助自动组装好模型:

class PersonController {	
	def save={PersonCommand person->		
		
	}
}

一.数据传输格式

数据传输格式是最重要的问题,直接关系到后续过程中前,后台的工作量。

对于一层简单模型和One to one多层模型,数据格式是很成熟的。现有的框架都处理的很好,对于上面的model,他们的数据格式如下:

Name Value
name yanical
age 26
address.city Zhuhai
address.street Jida
address.hourseNumber 344

其实我们要解决的主要是一对多的问题。一对多模型里面有个list(array)对象,在form里的数据是扁平的,我们要在扁平的数据里带上list顺序特性。

对于Grails,我尝试了几种数据格式,比如:

Name Value
children.0.name little
children.0.age 2
都没有成功,最后在网上别人建议了如下的格式:

Name Value
children[0].name little
children[0].age 2
children[1].name large
children[1].age 4

这里List有两个元素。这个时候还是没有成功,但是出了如下的exception:

Stacktrace follows:
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.RangeCheck(ArrayList.java:547)
	at java.util.ArrayList.get(ArrayList.java:322)
	at java.lang.Thread.run(Thread.java:619)

这个exception说明,grails的form->model模型组装模块已经识别了上传的数据,只是因为我们的list是空的,在组装模型的时候Grails试图从List中找到对应的element来塞数据,但是没有成功。修改Person类后,模型就可以成功组装了。

class PersonCommand {
	String name
	int age
	AddressCommand address
	List children = []
	
	Person() {
		children[0] = new ChildCommand()
		children[1] = new ChildCommand()
	}
}

二.子对象的初始化

上面我们发现对于根对象Person,和One to one的对象,Grails都会帮忙创建对象实例,但是对于One to many,Grails并没有做相关工作。我们要想个方案能够在组装模型前,能把对应的对象实例都创建好。

有两种思路:1.是在代码里就先hardcode创建好一定数量的实例(像前面做的那样),比如10个,在能确定实例数量会很小的时候,这种方案也还可行。2.是考虑可以不可以通过插入一些代码,实现在组装模型的时候动态创建实例。

这里的实现采用第二种思路。

看回上面的Exception,这个exception是去list里取数据的时候发现数据不存在的时候抛出来的。说明数据组装模块会去List里拿实例,再把数据塞到实例里面去,我们就在数据组装模块去List拿数据的时候插入一些代码。

我们使用一个工具来达到这个目的,这个工具就是org.apache.commons.collections.list.LazyList,LazyList在我们这里的作用就是确保数据组装模块去List取数据的时候始终能够把实例取出来。在List的里面有这个对象实例的时候,就把对象实例返回,否则就创建一个新的对象实例。修改Person类如下:

class PersonCommand {
	String name
	int age
	AddressCommand address
	List children = LazyList.decorate([],
              FactoryUtils.instantiateFactory(ChildCommand.class))
}

这个时候模型就已经可以成功创建了,即便List里面会有很多的元素,也能很好的处理。

三.Form表单定义

接下来,前台的任务就是要组装出前面要求的数据格式,对于One to one,我们还是一笔带过,form表单如下:


<input type="text" name="name" id="name" value="">
<input type="text" name="age" id="age" value="">
<input type="text" name="address.city" id="age" value="">
<input type="text" name="address.street" id="age" value="">
<input type="text" name="address.hourseNumber" id="age" value="">


对于One to many,页面中肯定会有Add和Delete的地方来增加和删除某一个元素。由于增加或者删除元素的过程中会调整UI,所以希望能够提取出一段公用的JS来实现这个功能。代码大致如下,包含两个方法onOneToManyAddClick和onOneToManyDeleteClick,分别用来处理Add和Delete点击事件(基于JQuery,现在不需要理会这段代码的细节):

function onOneToManyAddClick(triggerElement, oneToManyProperty) {
	var table = $("table[scaffoldFor='"+oneToManyProperty+"']");
	var count = table.attr("count");
	if(count == ""){
		count = 0;
	}
	
	var tr = $("tr[scaffoldFor='"+oneToManyProperty+"']").filter(function() {
		return $(this).is(":hidden");
	}).clone();
		
	tr.attr("index",count);
	var inputs = $(tr).find(":input");//has include input, textarea, select and button
	$.each(inputs, function(i, n) {
		var input = $(n);
		var name = input.attr("name");
		name = name.replace("*","["+count+"]");
		input.attr("name",name);
		input.attr("id",name);
	});
		
	count++;
	table.attr("count", count);
	tr.appendTo(table)
	tr.show();
}
function onOneToManyDeleteClick(triggerElement, oneToManyProperty) {
	var current = $(triggerElement);
	var currentTr;
	while(true) {
		if(current.is("tr") && current.attr("scaffoldFor") && oneToManyProperty.indexOf(current.attr("scaffoldFor"))>=0) {
			currentTr = current;
			break;
		} else {
			current = current.parent();
		}
	}
		
	var currentIndex = currentTr.attr("index");
	var currentFor = currentTr.attr('scaffoldFor');
		
	var table = $("table[scaffoldFor='"+currentFor+"']");
	var count = table.attr("count");
		
	//remove element -- start
	var nextTrs = currentTr.nextAll("tr").filter(function() {
		return $(this).attr("scaffoldFor") == currentFor;
	});
	$.each(nextTrs, function(i, n) {
		var tr = $(n);
		var index = tr.attr("index");
		var replaceFrom = currentFor+"["+index+"]";
		var replaceTo = currentFor+"["+(index-1)+"]";
		tr.attr("index", (index-1));
		$.each($(tr).find(":input"), function(i, n) {
			var input = $(n);
			var name = input.attr("name");
			name = name.replace(replaceFrom, replaceTo);
			input.attr("name",name);
			input.attr("id",name);
		});
	});
		
	if(count == ""){
		table.attr("count", 0);
	}
	count--;
	table.attr("count", count);
	
	currentTr.remove();
	//remove element -- end
}

而,我们的One to many form表单就是这样的了:

<table scaffoldfor="children" count="1">
	<tbody>
		<tr scaffoldfor="children" style="display: none">
			<td>
				<input type="text" name="children*.name" id="children*.name" value=""> 
				<input type="text" name="children*.age" id="children*.age" value="">
				<span> 
					<a href="#" onclick="onOneToManyDeleteClick(this,'children');return false;">Add</a>
					<a href="#" onclick="onOneToManyAddClick(this,'children');return false;">Delete</a>
				</span>
			</td>
		</tr>
		<tr>
		</tr>
		<tr scaffoldfor="children" index="0">
			<td>
				<input type="text" name="children[0].name" id="children[0].name" value=""> 
				<input type="text" name="children[0].age" id="children[0].age" value="">
				<span> 
					<a href="#" onclick="onOneToManyDeleteClick(this,'children');return false;">Add</a>
					<a href="#" onclick="onOneToManyAddClick(this,'children');return false;">Delete</a>
				</span>
			</td>
		</tr>
	</tbody>
</table>


初始化的时候包含两个TR:第二个是可见的,就是用户打开页面是可以看到的输入框;第一个是隐藏的,用户点击Add的时候,我们会克隆这个TR并append到table最后显示给用户,隐藏的TR的数据不需要提交给后台,我们设置特殊的input name属性(中间包含*,如children*.name),这样后台Grails就会忽略这些input。

用户在点击Add的时候,我们要修正克隆的input的name熟悉;用户点击Delete的时候,可能要调整现有input的name属性。这些都在JS里实现,详情请参考JS代码,有兴趣的话,现在可以去读那段代码了。

关键字: Grails, Spring MVC,Java web, 一对多, one to may, 动态表单, 自动组装

版权所有,转发请注明来源


分享到:
评论

相关推荐

    Grails Grails Grails

    Grails Grails Grails Grails Grails

    the definitive guide to grails 2

    详细介绍grails框架的奥秘,英文版你值得拥有

    The definitive guide to grails 2 英文版 书 代码

    The definitive guide to grails 2 英文版 书和随书代码, 学习grails必备

    Grails权威指南 Grails权威指南

    Grails权威指南Grails权威指南Grails权威指南Grails权威指南Grails权威指南Grails权威指南

    Eclipse下搭建Grails项目

    Grails项目的应用越来越多,而对于初学者来说,在Eclipse下搭建Grails项目是一个难题,这个文档将教会你如何搭建Grails项目,希望对你有所帮助。

    Grails 教程

    像Rails,Django和TurboGears这样的动态框架在Web开发领域开辟了一条新的道路,Grails基于这些概念之上,采用动态方法减小了Java平台上进行Web开发的复杂度,不过与那些框架不同的是,Grails是构建在Spring和...

    Getting Started with Grails Second Edition

    First-time developers are amazed at how quickly you can get a page-centric MVC web site up and running thanks to the scaffolding and convention over configuration that Grails provides. Advanced web ...

    Grails权威指南

    第1章 寻找grails之旅  1.1 java的困惑  1.2 webc2.0时代  1.3 java的力量  1.4 什么是grails  1.4.1 与java集成  1.4.2 简单而强大  1.4.3 吸取的经验教训  1.5 使用grails的原因 ...

    Grails1.1中文文档

    Grails是个一栈式开发框架,它尝试通过核心技术和插件技术来解决许多Web开发难题。Grails包含了如下内容: 由 Hibernate 构成的易于使用的 Object Relational Mapping (ORM)层 称为 Groovy Server Pages (GSP) 的...

    grails 1.0.4

    Grails尽量为更多现有的Java项目创建一个全面的框架(不仅局限于视图处理),这和当前一些Java框架提供给用户的一种AnemicAPI形成了明显的对比。Grails的出现并不是偶然的,而是随着Web应用的日趋复杂及Web2.0和Ajax...

    grails

    grails-2.1.zip.001

    grails+Xfire webservice

    grails+Xfire webservice

    grails入门经典

    grails grails入门经典 grails入门 grails例子 grails资料 通过自学一点点积累起来的,相信对你有帮助的。

    grails框架

    grails的插件系统也是其亮点之一。首先,和rails,django等web框架类似,基于微内核的思想,插件(可重用模块)是框架的一等公民。grails除了核心模块以外的功能几乎都是通过插件方式实现的。实际上,一个grails插件...

    Grails入门指南 -- 针对grails1.0.4更新

    Grails入门指南中文pdf -- 针对grails1.0.4更新,附加idea8 开发grails的流程

    Groovy轻松入门——Grails实战基础篇

    在学习任何东西之前,最重要的是培养兴趣,Groovy世界最耀眼的技术之一--Grails相信大家早已耳闻,我将通过Grails实战系列文章 向您展现Grails的迷人风采,使您感受到Grails的魅力,以至疯狂地爱上Grails,并坠入...

    Grails中文参考手册

    Grails 中文 参考手册

    grails3.2.8-01

    grails3.2.8 part1

    grails项目配置

    grails演示项目,配置了一对多,多对对模型配置,配置了Mysql,Sqlserver数据库

Global site tag (gtag.js) - Google Analytics