Ibrahim
Ibrahim

Reputation: 1471

grails 2.3 one-to-many databinding with dynamic forms

I'm Using Grails 2.3.11. I have an issue in saving one-to-many dynamic forms.

I referred to this question Grails one-to-many databinding with dynamic forms

I'm posting as a new question since I could not add comment to the above referred post. So don't mark it as duplicate.

I created dynamic form based on this blog http://omarello.com/2010/08/grails-one-to-many-dynamic-forms/

The same worked for me in my previous application which using Grails 2.2.3

Domain Classes:

Contact Class

package blog.omarello

import org.apache.commons.collections.FactoryUtils
import org.apache.commons.collections.list.LazyList

class Contact {

    static constraints = {
        firstName(blank:false)
        lastName(blank:false)
    }
    String firstName
    String lastName
    String nickName
    List phones = new ArrayList()
    static hasMany = [ phones:Phone ]

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

Phone Class

package blog.omarello

class Phone {
    int index
    String number
    PhoneType type
    boolean deleted
    static transients = [ 'deleted' ]
    static belongsTo = [ contact:Contact ]

    /* Constraints & Enum */

}

Controller Action:

@Transactional
def save(Contact contactInstance) {
    println 'params: -> '+params
    println '------------------------------------------------------------'
    println 'params.phones: -> '+params?.phones
    println '------------------------------------------------------------'
    println 'params.firstName: -> '+params?.firstName
    println '------------------------------------------------------------'
    println 'params.lastName: -> '+params?.lastName
    println '------------------------------------------------------------'
    println 'params.nickName: -> '+params?.nickName

    if (!contactInstance.save(flush: true)) {
        flash.error = message(code: 'default.not.created.message', args: [message(code: 'contact.label', default: 'Contact')])
        render(view: "create", model: [contactInstance: contactInstance])
        return
    }

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

When i submit the form, I didnt get any error in console. Input form fields name are phones[0].number, phones[ 1].number, ...

Console:

params: -> [lastName:Developer, phones[0].number:123456789, phones[0]:[id:, deleted:false, new:true, number:123456789, type:H], phones[1].number:987654321, phones[1]:[id:, deleted:false, new:true, number:987654321, type:H], phones[1].deleted:false, create:Create, phones[0].id:, phones[0].deleted:false, phones[1].id:, phones[1].type:H, nickName:admin, phones[0].type:H, phones[0].new:true, firstName:Grails, phones[1].new:true, action:save, format:null, controller:contact]
------------------------------------------------------------
params.phones: -> null
------------------------------------------------------------
params.firstName: -> Grails
------------------------------------------------------------
params.lastName: -> Developer
------------------------------------------------------------
params.nickName: -> admin

I'm not sure why params.phone shows null in Grails 2.3.11 Someone help me to fix this issue. Thanks in advance.

Upvotes: 1

Views: 1573

Answers (2)

Nicholas Zoelle
Nicholas Zoelle

Reputation: 11

From what I've seen, the LazyList.decorate(...) cannot be used in Grails 2.3+ to bind dynamically created many-to-one objects. I have run into the exact same problem as you. I am upgrading a Grails 1.3 project to 2.7 and a specific portion of the project wasn't working. After much head to keyboard colissions, I realized an effective solution is to perform the binding within the controller. Due to the structure of the database underlying my project, I did not want to pass back a count of the dynamically created child objects, thus I use a loop with a sufficiently high index to capture all use cases. I could not for the life of me figure out how to interpolate a variable into the argument of the array in the param, e.g. param.'field[variable]'.

What does work, is to capture it as a list, as shown below. Then you can iterate without having to resort to a massive brute force piece of hideousness in your controller, for each array index.

Here is an example from my project . .

Domain

class CalculatorScenario {

List<"CalculatorScenarioIncome> calculatorScenarioIncome

}

Controller

def scenario = new CalculatorScenario()
        scenario.properties = params
        scenario.agent = getAgent()

        //Iterate through the income sources
        for (int i = 0; i < 30; i++) {

            //Due to the vagaries of the GrailsParameterMap, we must retrieve the incomeSource as a list of lists
            def incomeSourceList = params.list('expandableCalculatorScenarioIncome[' + i + ']')

            //Break down the lists
            def incomeSource
            for (each in incomeSourceList) {
                incomeSource = each
            }

            //Append the many Incomes to the Parent
            if (incomeSource) {
                CalculatorScenarioIncome income = new CalculatorScenarioIncome(scenario: scenario)
                income.amount = Double.parseDouble(incomeSource.amount)
                income.ownerType = incomeSource.ownerType
                income.incomeSourceType = incomeSource.incomeSourceType
                income.deleted = incomeSource.deleted
                income.startAge = Integer.parseInt(incomeSource.startAge)
                income.endAge = Integer.parseInt(incomeSource.endAge)
                scenario.calculatorScenarioIncome.add(income)
            }
        }

        log.info stop

It's messy, but it works.

Upvotes: 1

hakuna1811
hakuna1811

Reputation: 534

It's seem have a problem with data-binding. Try to remove id: in params if it's a new phone. Tested with grails 2.3.7 and using it in my project.

Here is what I changed in _phone.gsp

    <div id="phone${i}" class="phone-div" <g:if test="${hidden}">style="display:none;"</g:if>>
        <g:if test="${phone?.id != null}">
            <g:hiddenField name='phones[${i}].id' value='${phone?.id}'/>
        </g:if>
        ...
    </div>

Upvotes: 2

Related Questions