Wednesday, September 16, 2009

Example Grails data binding for one-to-many association

Data binding is an integral aspect of all web applications.  The responsibility for the data binding generally falls within the scope of the controller.  In this following example,  I will show all the 'moving parts' that are needed to easily bind a domain object with a one-to-many association within Grails. 

I looked around for a simple example of data binding to handle a one-to-many association and never really found a full example so I decided to create an example and share it.  The example uses a Person domain object with a one-to-many association of Address objects.   All of the data is captured on a single screen.   The example uses DHTML or Dom scripting to dynamically create multiple address form fields and submits all the data from one screen.


Domain objects: Person & Address
The Person constructor contains just a first and last name.  Notice the Address field implements Serializable and also implements the hashCode() and equals() methods as required for objects that are to be placed in a collection.

class Person {
    static hasMany = [addresses:Address]
    static constraints = {
    }
    
    Person() {
        addresses = []
    }
    
    String firstName
    String lastName
    List<Address> addresses
    
    String toString()  {
        firstName + " " + lastName + " Addresses: "+addresses
    }
    
}


class Address implements Serializable {
    static constraints = {
    }
    
    String street
    String city
    String zip
    
    String toString() {
        street + "\n" + city + ", " + zip
    }
    
    int hashCode() {
        street?.hashCode() + city?.hashCode() + zip?.hashCode()
    }
    
    boolean equals(o) {
        if (this.is(o)) return true
        if (!(o instanceof Address)) return false
        return street?.equals(o.street) && (city?.equals(o.city)) && (zip?.equals(o.zip))
    }    
}


Sample screen shot of create person screen

Below is a snapshot of a the create.gsp screen after clicking the 'Add Address' button twice and filling in the data.  The 'Add Address' button calls a javascript function (see further below) to create an additional address row in the address table.  (I wanted to provide the entire page .gsp source but could not get it formatted correctly - if anyone has suggestions, I would appreciate hearing from you.)  



Add Address javascript function

Below is the javascript function called from the 'Add Address' button on the form.  The important part here is that use of the hidden field, addrCount, to add an index to the address form field names and ids.

function addAddr(tblId)
{
  var tblBody = document.getElementById(tblId).tBodies[0];
  var newNode = tblBody.rows[0].cloneNode(true);
  var count = parseInt(document.getElementById("addrCount").value);
  count++;
  document.getElementById("addrCount").value = count;
  var cells = newNode.cells;
  for (var i=0; i
      if (cells[i].firstElementChild.id.indexOf("street") != -1) {
          cells[i].firstElementChild.id = "addresses["+(count-1)+"].street";
          cells[i].firstElementChild.name = "addresses["+(count-1)+"].street";
      } else if (cells[i].firstElementChild.id.indexOf("city") != -1) {
          cells[i].firstElementChild.id = "addresses["+(count-1)+"].city";
          cells[i].firstElementChild.name = "addresses["+(count-1)+"].city";
      } else if (cells[i].firstElementChild.id.indexOf("zip") != -1) {
          cells[i].firstElementChild.id = "addresses["+(count-1)+"].zip";
          cells[i].firstElementChild.name = "addresses["+(count-1)+"].zip";
      }
  }
  tblBody.appendChild(newNode);
} 
 
Here is the initial statement for the hidden addrCount index field.

<g:hiddenField name="addrCount"   value="1" />


PersonController
When the form is submitted, the save action is called.  I have created a helper method, bindPerson(), to handle the data binding.  The method iterates 'addrCount' times, creating a new Address object, calling the bindData() method provided by Grails with the params starting with the value 'addresses[i]'.  Lastly, the address is added to the collection and the Person object is returned.

The controller code below works party because my constructor for the Person object initializes the addresses field to an empty list.   Other options might be to create a list in the controller and then set the addresses field to that list, or keep the initialized list and call personInstance.addToAddresses(addr).

class PersonController

  def save = {
        def personInstance = bindPerson(params)
        if(!personInstance.hasErrors() && personInstance.save()) {
            flash.message = "Person ${personInstance.id} created"
            redirect(action:show,id:personInstance.id)
        }
        else {
            render(view:'create',model:[personInstance:personInstance])
        }
    }
    
    def Person bindPerson(params)  {
        def personInstance = new Person()
        def count = params.addrCount.toInteger()
        for (int i=0; i<count; i++) {
            def addr = new Address()
            bindData(addr, params["addresses["+i+"]"])
            personInstance.addresses[i] = addr
        }
        personInstance.properties = params  
        return personInstance
    } 
 
Results
Here you see the results on the show.gsp screen.


Wrap up:
  1. First you need to setup your domain objects, including implementing Serialization, hashCode() and equals(),  where required.
  2. Form fields need to be name using index, like addresses[0].city or addresses[3].zip
  3. Finally, the controller needs to bind the association data using the indexed fields.
Note:  After writing this up, I finally realized why my Address object looked strange, it is missing a field for state.  While important for a correct Address object, it isn't absolutely necessary for this example, so I decided not to go back and add it in.

Note: Please see an improved solution in Part 2

I hope this helps and as always, constructive criticism is appreciated! Due to the large number of requests, I have created a site over at Groovy sites and you should be able to download from here.  Download site is https://sites.google.com/site/mikesgroovystuff/

29 comments:

  1. Hi. I'm new to grails. your article helped me so much, but i wonder how to create that master-detail form (person form). Could you provide the source-code for those GSPs? if you cannot post here, please send to my email. thanks!

    wrajaka [at] yahoo [dot] com

    ReplyDelete
  2. great post. It helped a lot

    ReplyDelete
  3. Excelent! Could you provide the source-code for those GSPs? If you cannot post here, please sende to my email. thanks!

    ReplyDelete
  4. Marco - please send me your email address so that I can send you the GSPs.

    ReplyDelete
  5. Bravo!!! Could you provide the code for those GSP?

    acoronadoiruegas[AT]yahoo[dot]com

    ReplyDelete
  6. super ! Could you plz send me the code for those GSP ?

    my email is minhchau_mychinh@yahoo.com

    thank u so much !

    ReplyDelete
  7. super ! cuold u plz send me GSP code ?

    minhchau_mychinh[at]yahoo[dot]com

    thank u so much !

    ReplyDelete
  8. Perfect, could you please provide the source code for those GSP?

    chawki.mguedmini@gmail.com

    ReplyDelete
  9. Great, could you please provide me the source code for those GSP? I would really appreciate it

    vicapa99@gmail.com

    ReplyDelete
  10. hi,

    did anybody this example running ? would it be possible to provide the complete source ?

    cheers

    andreas

    ReplyDelete
  11. Andreas - just post your email address and I will send you the complete source. I have it all zipped up and ready to go.

    ReplyDelete
  12. hello mike,

    that would be great. pls, send it to andreas "at" habeebee "dot" net

    thanks in advance

    andreas

    ReplyDelete
  13. Mike - GREAT article. I'd like to see the source as well.

    kelly.humbert@gmail.com

    ReplyDelete
  14. I'd like to see it too, please.

    bdrhoa at gmail dot com

    Thanks!

    ReplyDelete
  15. Great post, would you please send me the sources too?

    ues@udo-esser.de

    Thx a lot.

    ReplyDelete
  16. Really great article; and like the others here would you mind sending me the sources as well to share what you've accomplished?

    sjlum at yahoo dot com

    One suggestion for you is to setup a project at googlecode(free) where people could SVN checkout.

    Thanks!

    ReplyDelete
  17. really great post! could u send me the gsp code as well

    ReplyDelete
  18. Mash - I need your email address to send the code to???

    ReplyDelete
  19. Please send me the source code too..
    my email id is vb1900 at gmail.

    ReplyDelete
  20. Hi! Great post! Could you please send the code to rcartas (at) hotmail.com

    Thanks

    ReplyDelete
  21. Great post. Would you please send the code to sultana.nashid.zaman@gmail.com

    ReplyDelete
  22. Thanks for the great post. My very limited knowledge of JS is really going to show here. This this method, how can I delete an address row I created when pressing the button but later decided I didn't need it.

    Thanks.

    ReplyDelete
  23. @Me - sorry but I am not going to be your best source for JS. The gist of what you want to do is to get the tbody element and the you can call the deleteRow(idx) method to delete the row referenced by the idx index.

    ReplyDelete
  24. Hi Mike, please can you send me full source code to vhac@myself.com
    thks
    Víctor Hugo

    ReplyDelete
  25. Hello Mikie, your example is exactly what i'm looking for. Can you send the source to swmw[at]poczta.fm

    Thanks
    Jan Luca

    ReplyDelete
  26. Hi,
    can you please share the gsp code.My id: shiuli3@gmail.com

    ReplyDelete
  27. This comment has been removed by the author.

    ReplyDelete
  28. Hi ,
    Can u please provide gsp pages source code . My id is ranidurga.lavanya@gmail.com

    ReplyDelete