Grails - 简单的 hasMany 问题 - 在 create.gsp 中使用 CheckBoxes 而不是 HTML Select
Posted
技术标签:
【中文标题】Grails - 简单的 hasMany 问题 - 在 create.gsp 中使用 CheckBoxes 而不是 HTML Select【英文标题】:Grails - Simple hasMany Problem - Using CheckBoxes rather than HTML Select in create.gsp 【发布时间】:2010-05-10 20:11:46 【问题描述】:我的问题是:我想创建一个 grails 域实例,定义它拥有的另一个域的“许多”实例。我在Google Code Project 中有实际来源,但以下内容应该可以说明问题。
class Person
String name
static hasMany[skills:Skill]
static constraints =
id (visible:false)
skills (nullable:false, blank:false)
class Skill
String name
String description
static constraints =
id (visible:false)
name (nullable:false, blank:false)
description (nullable:false, blank:false)
如果您将此模型和def scaffold
用于两个控制器,那么您最终会得到一个不起作用的表单;
我自己尝试让这个工作将技能枚举为复选框,看起来像这样;
但是当我保存志愿者时,技能无效!
这是我保存方法的代码;
def save =
log.info "Saving: " + params.toString()
def skills = params.skills
log.info "Skills: " + skills
def volunteerInstance = new Volunteer(params)
log.info volunteerInstance
if (volunteerInstance.save(flush: true))
flash.message = "$message(code: 'default.created.message', args: [message(code: 'volunteer.label', default: 'Volunteer'), volunteerInstance.id])"
redirect(action: "show", id: volunteerInstance.id)
log.info volunteerInstance
else
render(view: "create", model: [volunteerInstance: volunteerInstance])
这是我的日志输出(我有自定义 toString() 方法);
2010-05-10 21:06:41,494 [http-8080-3] INFO bumbumtrain.VolunteerController - Saving: ["skills":["1", "2"], "name":"Ian", "_skills":["", ""], "create":"Create", "action":"save", "controller":"volunteer"]
2010-05-10 21:06:41,495 [http-8080-3] INFO bumbumtrain.VolunteerController - Skills: [1, 2]
2010-05-10 21:06:41,508 [http-8080-3] INFO bumbumtrain.VolunteerController - Volunteer[ id: null | Name: Ian | Skills [Skill[ id: 1 | Name: Carpenter ] , Skill[ id: 2 | Name: Sound Engineer ] ]]
请注意,在最后的日志行中,正确的技能已被拾取并且是对象实例的一部分。当志愿者被保存时,“技能”被忽略并且没有提交到数据库中,尽管创建的内存版本清楚地确实有这些项目。施工时不能通过技能吗?一定有办法解决这个问题?我需要一个表格来允许一个人注册,但我想规范化数据,以便以后添加更多技能。
如果您认为这应该“正常工作”,那么一个工作示例的链接会很棒。
如果我使用 HTML Select 则可以正常工作!如下所示制作创建页面;
<tr class="prop">
<td valign="top" class="name">
<label for="skills"><g:message code="volunteer.skills.label" default="Skills" /></label>
</td>
<td valign="top" class="value $hasErrors(bean: volunteerInstance, field: 'skills', 'errors')">
<g:select name="skills" from="$uk.co.bumbumtrain.Skill.list()" multiple="yes" optionKey="id" size="5" value="$volunteerInstance?.skills" />
</td>
</tr>
但我需要它来处理这样的复选框;
<tr class="prop">
<td valign="top" class="name">
<label for="skills"><g:message code="volunteer.skills.label" default="Skills" /></label>
</td>
<td valign="top" class="value $hasErrors(bean: volunteerInstance, field: 'skills', 'errors')">
<g:each in="$skillInstanceList" status="i" var="skillInstance">
<label for="$skillInstance?.name"><g:message code="$skillInstance?.name.label" default="$skillInstance?.name" /></label>
<g:checkBox name="skills" value="$skillInstance?.id.toString()"/>
</g:each>
</td>
</tr>
日志输出完全相同! 使用这两种样式的表单,创建志愿者实例时使用在“技能”变量中正确引用的技能。保存时,后者失败并出现空引用异常,如本问题顶部所示。
希望这是有道理的,在此先感谢!
Gav
【问题讨论】:
【参考方案1】:将您的 create.gsp <g:checkbox...>
代码替换为:
<g:checkBox name="skill_$skillInstance.id"/>
然后在控制器的save
操作中,将def volunteerInstance = new Volunteer(params)
替换为:
def volunteerInstance = new Volunteer(name: params.name)
params.each
if (it.key.startsWith("skill_"))
volunteerInstance.skills << Skill.get((it.key - "skill_") as Integer)
应该可以。 (代码未测试)
【讨论】:
救命稻草!这真是令人头疼 小优化——it.key.startsWith 比 it.key.contains 好【参考方案2】:我会阅读器发送您有许多元素的 id 列表,因为这可以在 Grails 中默认轻松分配。 您的 .gsp 应如下所示:
<g:each in="$skills" var="skill">
<input type="checkbox"
name="skills"
value="$skill?.id"
</g:each>
在您的控制器中,您可以像这样简单地存储值:
person.properties = params
person.validate()
person.save()
这很容易,不是吗? :-)
【讨论】:
如果您的表单包含一些错误,您的解决方案将不起作用。您必须检查您的命令 - 人员 - 是否包含参考集合的每个元素 - 技能 - 如果是,请将您的复选框标记为选中。此外,在域类上调用 save 方法将首先调用 validate,因此您也可以使用 save 来验证您的域类。【参考方案3】:当您使用复选框并且想要绑定 ToMany 关联时,Grails 不提供数据绑定支持。至少,直到版本 2.2.0
解决方法?
1º 选项 - 编写 gsp 代码,其行为类似于选择组件
<g:each var="skillInstance" in="$skillInstanceList">
<div class="fieldcontain">
<g:set var="checked" value=""/>
<g:if test="$volunteerInstance?.skills?.contains(skillInstance)">
<input type="hidden" name="_skills" value="$skillInstance?.id"/>
<g:set var="checked" value="checked"/>
</g:if>
<label for="$skillInstance?.name">
<g:message code="$skillInstance?.name.label"
default="$skillInstance?.name" />
</label>
<input type="checkbox" name="skills" value="$skillInstance?.id"
$checked />
</div>
</g:each>
2º创建自己的标签库
/**
* Custom TagLib must end up with the TagLib suffix
*
* It should be placed in the grails-app/taglib directory
*/
class BindingAwareCheckboxTagLib
def bindingAwareCheckbox = attrs, body ->
out << render(
template: "/<TEMPLATE_DIR>/bindingAwareCheckboxTemplate.gsp",
model: [referenceColletion: attrs.referenceColletion,
value:attrs.value])
/grails-app/views
目录。此外,模板应以_
为前缀。
现在您可以按如下方式使用自定义 TagLib
<g:bindingAwareCheckbox
referenceCollection="$skillInstanceList"
value="$volunteerInstance?.skills"/>
完成后,绑定过程将自动进行。不需要额外的代码。
【讨论】:
这是一个很好的解决方法。请注意,只有在使用hasMany
设置关系时,绑定才会自动起作用——如果是简单的List<Skill> skills
,则不会为您完成绑定。
@elias 是的,你可以。当我可以创建自己的解决方法时,我不会等待 Grails。使用 BindEventListener 和 Google Guava 的 Function 对象将请求转换为您的 List。见***.com/a/13538222 和docs.guava-libraries.googlecode.com/git-history/release/javadoc/…
对不起,我的意思是它不是自动为你完成的,并不是说它不能完成。但是很高兴看到可以进一步自定义绑定的东西,没有意识到这一点——感谢您的指点。 =)【参考方案4】:
普惠制
<g:checkBox name="skills" value="$skillInstance.id" checked="$skillInstance in volunteerInstance?.skills"/>
时髦
def volunteerInstance = new Volunteer(params).save()
def skills = Skill.getAll(params.list('skills'))
skills.each volunteerInstance.addToSkills(it).save()
【讨论】:
以上是关于Grails - 简单的 hasMany 问题 - 在 create.gsp 中使用 CheckBoxes 而不是 HTML Select的主要内容,如果未能解决你的问题,请参考以下文章
使用 hasmany 字符串查询 Grails / GORM 条件
grails:脚手架为具有 hasMany 关系的域创建视图