简体   繁体   中英

Grails Parent Child Form not saving child data

I am trying to create a parent child form with Author and Book domain classes. The view works fine and lets me enter books when I creating a new Author. However, when I add a new author and the books and check the database (dbconsole), I see a new record in the Author table but no records are added to the Book table. Can you please let me know what I am missing or doing wrong here?

Here are my domain classes:

AUTHOR:

package bookauthor1tomany

import org.apache.common.collections.list.*
import org.apache.commons.collections.ListUtils.*

class Author {

    static constraints = {
    }

    String name
    String category

    List<Book> books = new ArrayList<>()
    static hasMany = [ books:Book ]

    static mapping = {
        books cascade:"all-delete-orphan"
    }

    def getExpandableBookList() {
        return LazyList.decorate(books, FactoryUtils.instantiateFactory(Book.class))
    }

    String toString(){
        return "${name}" - "${category}"
    }

}

BOOK

package bookauthor1tomany

class Book {

    static constraints = {
    }

    String title
    boolean _deleted

    static transients = [ '_deleted' ]

    static belongsTo = [ author:Author ]

    def String toString() {
        return title
    }

}

AuthorController

I haven't changed anything with the controller. This is the default generated save method for the Author controller.

@Transactional
def save(Author authorInstance) {
    if (authorInstance == null) {
        notFound()
        return
    }

    if (authorInstance.hasErrors()) {
        respond authorInstance.errors, view:'create'
        return
    }

    authorInstance.save flush:true

    request.withFormat {
        form multipartForm {
            flash.message = message(code: 'default.created.message', args: [message(code: 'author.label', default: 'Author'), authorInstance.id])
            redirect authorInstance
        }
        '*' { respond authorInstance, [status: CREATED] }
    }
}

GSPs

create.gsp

<!DOCTYPE html>
<html>
    <head>
        <meta name="layout" content="main">
        <g:set var="entityName" value="${message(code: 'author.label', default: 'Author')}" />
        <title><g:message code="default.create.label" args="[entityName]" /></title>
    </head>
    <body>
        <a href="#create-author" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
        <div class="nav" role="navigation">
            <ul>
                <li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
                <li><g:link class="list" action="index"><g:message code="default.list.label" args="[entityName]" /></g:link></li>
            </ul>
        </div>
        <div id="create-author" class="content scaffold-create" role="main">
            <h1><g:message code="default.create.label" args="[entityName]" /></h1>
            <g:if test="${flash.message}">
            <div class="message" role="status">${flash.message}</div>
            </g:if>
            <g:hasErrors bean="${authorInstance}">
            <ul class="errors" role="alert">
                <g:eachError bean="${authorInstance}" var="error">
                <li <g:if test="${error in org.springframework.validation.FieldError}">data-field-id="${error.field}"</g:if>><g:message error="${error}"/></li>
                </g:eachError>
            </ul>
            </g:hasErrors>
            <g:form url="[resource:authorInstance, action:'save']" >
                <fieldset class="form">
                    <g:render template="authortemp"/>
                </fieldset>
                <fieldset class="buttons">
                    <g:submitButton name="create" class="save" value="${message(code: 'default.button.create.label', default: 'Create')}" />
                </fieldset>
            </g:form>
        </div>
    </body>
</html>

_form.gsp

<%@ page import="BookAuthor1ToMany" %>



<div class="fieldcontain ${hasErrors(bean: authorInstance, field: 'books', 'error')} ">
    <label for="books">
        <g:message code="author.books.label" default="Books" />

    </label>

<ul class="one-to-many">
<g:each in="${authorInstance?.books?}" var="b">
    <li><g:link controller="book" action="show" id="${b.id}">${b?.encodeAsHTML()}</g:link></li>
</g:each>
<li class="add">
<g:link controller="book" action="create" params="['author.id': authorInstance?.id]">${message(code: 'default.add.label', args: [message(code: 'book.label', default: 'Book')])}</g:link>
</li>
</ul>


</div>

<div class="fieldcontain ${hasErrors(bean: authorInstance, field: 'name', 'error')} required">
    <label for="name">
        <g:message code="author.name.label" default="Name" />
        <span class="required-indicator">*</span>
    </label>
    <g:textField name="name" required="" value="${authorInstance?.name}"/>

</div>

_authortemp.gsp

<div class="dialog">
    <table>
        <tbody>
            <tr class="prop">
                <td valign="top" class="name"><label for="name">Name:</label></td>
                <td valign="top" class="value ${hasErrors(bean:authorInstance,field:'name','errors')}">
                    <input type="text" id="name" name="name" value="${fieldValue(bean:authorInstance,field:'name')}"/>
                </td>
            </tr>
                        <tr class="prop">
                <td valign="top" class="name"><label for="category">Category:</label></td>
                <td valign="top" class="value ${hasErrors(bean:authorInstance,field:'category','errors')}">
                    <input type="text" id="category" name="category" value="${fieldValue(bean:authorInstance,field:'category')}"/>
                </td>
            </tr>
            <tr class="prop">
                <td valign="top" class="name"><label for="books">Books:</label></td>
                <td valign="top" class="value ${hasErrors(bean:authorInstance,field:'books','errors')}">
                    <g:render template="books" model="['authorInstance':authorInstance]" />
                </td>
            </tr>
        </tbody>
    </table>
</div>

_books.gsp

<script type="text/javascript">
    var childCount = ${authorInstance?.books.size()} + 0;

    function addChild() {
        var htmlId = "book" + childCount;
        var deleteIcon = "${resource(dir:'images/skin', file:'database_delete.png')}";
        var templateHtml = "<div id='" + htmlId + "' name='" + htmlId + "'>\n";
        templateHtml += "<input type='text' id='expandableBookList[" + childCount + "].title' name='expandableBookList[" + childCount + "].title' />\n";
        templateHtml += "<span onClick='$(\"#" + htmlId + "\").remove();'><img src='" + deleteIcon + "' /></span>\n";
        templateHtml += "</div>\n";
        $("#childList").append(templateHtml);
        childCount++;
    }
</script>

<div id="childList">
    <g:each var="book" in="${authorInstance.books}" status="i">
        <g:render template='book' model="['book':book,'i':i]"/>
    </g:each>
</div>
<input type="button" value="Add Book" onclick="addChild();" />

_book.gsp

<div id="book${i}">
    <g:hiddenField name='expandableBookList[${i}].id' value='${book.id}'/>
    <g:textField name='expandableBookList[${i}].title' value='${book.title}'/>
    <input type="hidden" name='expandableBookList[${i}]._deleted' id='expandableBookList[${i}]._deleted' value='false'/>
    <span onClick="$('#expandableBookList\\[${i}\\]\\._deleted').val('true'); $('#expandableBookList${i}').hide()">Delete</span>
</div>

You don't need the

List<Book> books = new ArrayList<>()

in your domain. The hasMany will give you a books collection by default. That might be causing some issues. Also, have you debugged in your controller to make sure that the books collection is populated when the save occurs.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM