2010-05-10 17 views
7

Vấn đề của tôi là: Tôi muốn tạo một thể hiện miền grails, xác định trường hợp 'Nhiều' của một tên miền khác mà nó có. Tôi có nguồn thực tế trong một số Google Code Project nhưng sau đây sẽ minh họa sự cố.Grails - Simple hasMany Problem - Sử dụng CheckBox thay vì HTML Chọn trong 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) 
    } 
} 

Nếu bạn sử dụng mô hình này và def scaffold cho hai Bộ điều khiển thì bạn kết thúc bằng một biểu mẫu như thế không hoạt động;

Scaffolding

nỗ lực riêng của tôi để có được điều này để làm việc liệt kê các kỹ năng như hộp kiểm và trông như thế này;

Custom Create.gsp

Nhưng khi tôi lưu các tình nguyện viên những kỹ năng là null!

Failed to save Skills

Đây là mã dành cho phương pháp lưu của tôi;

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

Đây là đầu ra nhật ký của tôi (Tôi có phương thức toString() tùy chỉnh);

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

Lưu ý rằng trong dòng đăng nhập cuối cùng, kỹ năng đã được chọn và là một phần của đối tượng. Khi tình nguyện viên được lưu, 'Kỹ năng' bị bỏ qua và không được cam kết với cơ sở dữ liệu mặc dù phiên bản bộ nhớ được tạo rõ ràng không có các mục. Không thể vượt qua các kỹ năng tại thời điểm xây dựng? Phải có một cách vòng này? Tôi cần một hình thức duy nhất để cho phép một người đăng ký nhưng tôi muốn chuẩn hóa dữ liệu để tôi có thể thêm nhiều kỹ năng hơn sau này.

Nếu bạn nghĩ rằng điều này nên 'chỉ hoạt động' thì một liên kết đến một ví dụ làm việc sẽ là tuyệt vời.

Nếu tôi sử dụng HTML Chọn thì nó hoạt động tốt! Chẳng hạn như những điều sau đây để tạo trang Tạo;

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

Nhưng tôi cần nó để làm việc với các hộp kiểm như thế này;

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

Kết quả nhật ký chính xác giống nhau! Với cả hai kiểu dáng của thể hiện Tình nguyện được tạo ra với các kỹ năng được tham chiếu chính xác trong biến 'Kỹ năng'. Khi lưu, sau này thất bại với một ngoại lệ tham chiếu null như được hiển thị ở đầu câu hỏi này.

Hy vọng điều này có ý nghĩa, cảm ơn trước!

Gav

Trả lời

5

Thay thế tạo.GSP<g:checkbox...> mã bởi:

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

Sau đó bên trong save hành động của điều khiển của bạn, thay thế def volunteerInstance = new Volunteer(params) bởi:

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

nên làm việc. (mã không được kiểm tra)

+0

Lifesaver! Đây là một nhức đầu như vậy – gav

+0

Bạn hoan nghênh :-) – fabien7474

+0

Tối ưu hóa nhỏ- it.key.startsWith tốt hơn it.key.contains – mmigdol

3

Tôi sẽ đọc danh sách id của bạn có nhiều yếu tố vì điều này có thể dễ dàng được gán theo mặc định trong Grails. .gsp của bạn sẽ giống như thế:

<g:each in="${skills}" var="skill"> 
      <input type="checkbox" 
        name="skills" 
        value="${skill?.id}" 
      </g:each> 

và trong điều khiển của bạn, bạn chỉ có thể lưu trữ các giá trị như thế này:

person.properties = params 
person.validate() 
person.save() 

Nó khá dễ dàng, không phải là nó? :-)

+0

Nếu biểu mẫu của bạn có lỗi, giải pháp của bạn sẽ không hoạt động. Bạn phải kiểm tra xem lệnh của bạn - người - có chứa từng phần tử của bộ sưu tập tham chiếu - kỹ năng - và nếu có, đánh dấu hộp kiểm của bạn là đã chọn. Ngoài ra, gọi phương thức lưu trên một lớp miền sẽ gọi xác thực đầu tiên, vì vậy bạn cũng có thể sử dụng lưu để xác thực các lớp miền của bạn. –

3

Grails không cung cấp hỗ trợ ràng buộc dữ liệu khi bạn sử dụng hộp kiểm và bạn muốn ràng buộc ToMany liên kết. Ít nhất, tối đa phiên bản 2.2.0

Giải quyết sự cố?

1º tùy chọn - Viết mã GSP mà cư xử như một thành phần chọn

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

Tạo TagLib của riêng bạn

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

} 

đâu <TEMPLATE_DIR> nên tương đối so với thư mục /grails-app/views . Hơn nữa, các mẫu nên được bắt đầu bằng _.

Bây giờ bạn có thể sử dụng TagLib tùy chỉnh của bạn như sau

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

Sau khi thực hiện, quá trình ràng buộc sẽ xảy ra tự động. Không cần thêm mã.

+0

Đó là một giải pháp tốt. Lưu ý rằng ràng buộc chỉ hoạt động tự động nếu mối quan hệ được thiết lập với 'hasMany' - nếu nó là một kỹ năng' Danh sách 'đơn giản thì ràng buộc sẽ không được thực hiện cho bạn. – elias

+0

@elias Có, bạn có thể.Tôi không chờ đợi Grails khi tôi có thể tạo cách giải quyết riêng của mình. Sử dụng BindEventListener và đối tượng Hàm từ Google Guava để chuyển đổi yêu cầu cho bạn Danh sách. Xem http://stackoverflow.com/a/13538222 và http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/base/Function.html –

+0

Xin lỗi, tôi có nghĩa là nó đã không được thực hiện tự động cho bạn, không phải là nó không thể được thực hiện. Nhưng nó mát mẻ để thấy rằng nó có thể tùy chỉnh các công cụ ràng buộc hơn nữa, đã không nhận thức được điều đó - cảm ơn cho các con trỏ. =) – elias

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() } 
Các vấn đề liên quan