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 &lt;g:checkbox...&gt; 代码替换为:

<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>

创建自己的标签库

/**
  * 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&lt;Skill&gt; 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的主要内容,如果未能解决你的问题,请参考以下文章

Grails:如何在 hasMany 关系中搜索孩子?

grails中hasMany关系的选择性深度渲染

使用 hasmany 字符串查询 Grails / GORM 条件

grails:脚手架为具有 hasMany 关系的域创建视图

为啥grails在第一次访问hasMany关系时抛出空指针异常?

Grails 获取子域对象