2010-05-10 43 views
7

我的問題是這樣的:我想創建一個grails域實例,定義它擁有的另一個域的'Many'實例。我有一個Google Code Project的實際來源,但下面應該說明問題。Grails - 簡單的hasMany問題 - 使用CheckBox而不是HTML在create.gsp中選擇

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那麼你最終像這樣不工作形式;

Scaffolding

我自己的努力得到這個工作,列舉了技能複選框,看起來像這樣;

Custom Create.gsp

但是當我保存志願者的技能爲空!

Failed to save Skills

這是我的保存方法的代碼;

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選擇,那麼它工作正常!如下面做出Create頁面;

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

回答

5

更換你創建。GSP<g:checkbox...>代碼由:

def volunteerInstance = new Volunteer(name: params.name) 
params.each { 
    if (it.key.startsWith("skill_")) 
    volunteerInstance.skills << Skill.get((it.key - "skill_") as Integer) 
} 

應該工作:

<g:checkBox name="skill_${skillInstance.id}"/> 

然後控制器的動作save內,通過更換def volunteerInstance = new Volunteer(params)。 (代碼沒有測試)

+0

救命稻草!這是如此頭痛 – gav 2010-05-17 10:43:13

+0

歡迎您:-) – fabien7474 2010-05-17 16:33:05

+0

小優化it.key.startsWith比it.key.contains – mmigdol 2011-11-01 07:40:24

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

這是很容易,不是嗎? :-)

+0

如果您的表單包含一些錯誤,您的解決方案將無法工作。你必須檢查你的命令 - 人 - 是否包含參考集合的每個元素 - 技能 - 如果是這樣,將你的複選框標記爲選中狀態。此外,調用域類的save方法將首先調用驗證,因此您也可以使用save來驗證您的域類。 – 2013-01-13 00:02:56

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

} 

凡< template_dir模板>應該是相對於/grails-app/views目錄。此外,模板應以_爲前綴。

現在您可以使用您的自定義標籤庫如下

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

一旦完成,裝訂過程中會發生自動。無需額外的代碼。

+0

這是一個很好的解決方法。請注意,綁定僅在「hasMany」設置關係時纔會自動生效 - 如果它是一個簡單的「List 技能」,綁定將不會爲您完成。 – elias 2013-07-31 22:20:08

+0

@elias是的,你可以。當我可以創建自己的解決方法時,我不會等待Grails。使用Google Guava中的BindEventListener和一個Function對象將請求轉換爲List。請參閱http://stackoverflow.com/a/13538222和http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/base/Function.html – 2013-07-31 23:06:19

+0

對不起,我意味着它不會自動爲您完成,而不是無法完成。但看到可以更進一步定製綁定的東西是很酷的,不知道這一點 - 感謝指針。 =) – elias 2013-07-31 23:17:39

0

GSP

<g:checkBox name="skills" value="${skillInstance.id}" checked="${skillInstance in volunteerInstance?.skills}"/> 

Groovy的

def volunteerInstance = new Volunteer(params).save()  
def skills = Skill.getAll(params.list('skills')) 
    skills.each{ volunteerInstance.addToSkills(it).save() } 
相關問題