7

我的问题是:我想创建一个 grails 域实例,定义它拥有的另一个域的“许多”实例。我在Google 代码项目中有实际来源,但以下内容应说明问题。

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用于两个控制器,那么您最终会得到一个不起作用的表单;

脚手架

我自己尝试使其工作将技能枚举为复选框,如下所示;

自定义创建.gsp

但是当我保存志愿者时,技能无效!

保存技能失败

这是我保存方法的代码;

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> 

日志输出完全一样!使用这两种形式的形式,志愿者实例都是使用在“技能”变量中正确引用的技能创建的。保存时,后者因空引用异常而失败,如本问题顶部所示。

希望这是有道理的,在此先感谢!

加夫

4

4 回答 4

5

将您的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)
}

应该管用。(代码未测试)

于 2010-05-10T23:36:37.327 回答
3

我会阅读发送你的 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()

这很容易,不是吗?:-)

于 2011-11-29T16:51:45.813 回答
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>

创建自己的 TagLib

/**
  * 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])
    }

}

其中 <TEMPLATE_DIR> 应该是相对于/grails-app/views目录的。此外,模板应以_.

现在您可以按如下方式使用您的自定义 TagLib

<g:bindingAwareCheckbox
      referenceCollection="${skillInstanceList}"
      value="${volunteerInstance?.skills}"/>

完成后,绑定过程将自动进行。不需要额外的代码。

于 2013-01-12T22:47:01.530 回答
0

普惠制

 <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() }
于 2013-03-13T12:06:58.763 回答