Skip to content


Grails one-to-many dynamic forms

After searching around trying to find a good way to implement one-to-many dynamic forms in Grails, I have finally come across this post which does a very good job at explaining the details.

What I was basically looking for is a clean way to implement saving my domain objects in the backend, rather than the hacked way I did by hand picking request parameters and manually setting up my domain objects (I’m still fairly new to Grails), and the official docs fail to shed the light on the subtle details I found in this post, which is why I decided to post my own version of the one-to-many dynamic forms but with a little bit more complex domain objects to illustrate the use of enums which also I found is a bit of a gray area in the docs (or at least maybe for me).
Make sure you head over and read the original post, as I will not go through all the details already mentioned over there. This example was developed using Grails version 1.3.3

The source code for everything I will be talking about can be grabbed as a .zip here


Update (28 Aug 2010): As Brad pointed out, removing a new phone that was still not persisted did not really remove the phone, this is because the dom elements were hidden instead of removed from the phone which caused the values to be submitted anyways. I’ve updated the source code in the post to fix that by adding a ‘new’ flag when it is still ‘true’ the form elements should be completely removed, while when it is false, they should stay since we want the deleted flag to be submitted.

Update (19 Oct 2010): Updated the source code to incorportate the fixes from the previous update as well as some other bugs. Make sure you grab this updated sources for a better reference. Also fixed a typo in the post as pointed out by Brad.

Update (11 Feb 2011): Added a column name mapping for the Phone domain class which caused problems creating the table in MySQL since the column name “index” is a reserved word


I will be building a simple ‘Phone Book’ application that allows you to create Contacts were each contact can have multiple phone numbers. I will start by defining the domain classes. First up, our Contact

package blog.omarello

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

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

    def getPhonesList() {
        return LazyList.decorate(
              phones,
              FactoryUtils.instantiateFactory(Phone.class))
    }

    def String toString() {
        return "${lastName}, ${firstName}"
    }
}

The important thing to note here is the LazyList and the cascade mapping. according to the above our contact will be able to store a list of phones of type Phone. Our next domain class is the Phone class which looks like this.

package blog.omarello

class Phone {
    public enum PhoneType{
        H("Home"),
        M("Mobile"),
        W("Work")

        final String value;

        PhoneType(String value) {
            this.value = value;
        }
        String toString(){
            value;
        }
        String getKey(){
            name()
        }
        static list() {
            [H, M, W]
        }
    }

    static constraints = {
        index(blank:false, min:0)
        number(blank:false)
        type(blank:false, inList:PhoneType.list(), minSize:1, maxSize:1)
    }

    static mapping = {
        index column:"phone_index"
    }

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

    def String toString() {
        return "($index) ${number} - ${type.value}"
    }
}

I have added an enum here to show how you can persist enums and use them in your constraints (there might be better ways to do this I am open to suggestions). The enum will define the phone types that I can use for each phone number, and will be rendered as a select list on the page. The index integer is just to keep track of the position of each phone number in the list (I might come back to this in a later post), the rest is quite similar to the original post. The deleted boolean will be used to keep track of which objects were deleted in the view so that they can be later deleted from the database which is why is defined in the transients list (since we don’t want to persist this property)

Now that we have defined that a contact can have many phone numbers and a phone number belongs to contact, we can go ahead and start writing our views. You can stub out a controller and the corresponding views using grails generate-all blog.omarello.Contact and then edit them accordingly.

What we will do now, is create three template .gsp files which we will use to keep things clean and avoid repetition in multiple files:

  • _contact.gsp : This will be used to render the shared input fields within the create.gsp and edit.gsp file. Keeps things cleaner and avoids repetition
  • _phones.gsp: This will contain out main javascript logic (using jquery) and a loop to render each phone entry
  • _phone.gsp : This will represent an individual phone entry

_contact.gsp will basically contain the view contents that were stubbed out by the generate-all command, make sure to check the source code for the full picture, but the most important part is rendering the _phones.gsp template in place of the default list we get from ‘generate all’, notice how we pass our contact instance object to the template.

....
....
<tr class="prop">
   <td valign="top" class="name">
      <label for="phones"><g:message code="contact.phones.label" default="Phones List" /></label>
   </td>
   <td valign="top" class="value ${hasErrors(bean: contactInstance, field: 'phones', 'errors')}">
       <!-- Render the phones template (_phones.gsp) here -->
       <g:render template="phones" model="['contactInstance':contactInstance]" />
       <!-- Render the phones template (_phones.gsp) here -->
   </td>
</tr>
....
....

Note that when rendering templates you do not have to include the underscore that is in the file name.

Now our dynamic javascript behavior is contained in the _phones.gsp file (javascript can be included in an external .js file if you want to) which looks something like this.

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

    function addPhone(){
      var clone = $("#phone_clone").clone()
      var htmlId = 'phonesList['+childCount+'].';
      var phoneInput = clone.find("input[id$=number]");

      clone.find("input[id$=id]")
             .attr('id',htmlId + 'id')
             .attr('name',htmlId + 'id');
      clone.find("input[id$=deleted]")
              .attr('id',htmlId + 'deleted')
              .attr('name',htmlId + 'deleted');
      clone.find("input[id$=new]")
              .attr('id',htmlId + 'new')
              .attr('name',htmlId + 'new')
              .attr('value', 'true');
      phoneInput.attr('id',htmlId + 'number')
              .attr('name',htmlId + 'number');
      clone.find("select[id$=type]")
              .attr('id',htmlId + 'type')
              .attr('name',htmlId + 'type');

      clone.attr('id', 'phone'+childCount);
      $("#childList").append(clone);
      clone.show();
      phoneInput.focus();
      childCount++;
    }

    //bind click event on delete buttons using jquery live
    $('.del-phone').live('click', function() {
        //find the parent div
        var prnt = $(this).parents(".phone-div");
        //find the deleted hidden input
        var delInput = prnt.find("input[id$=deleted]");
        //check if this is still not persisted
        var newValue = prnt.find("input[id$=new]").attr('value');
        //if it is new then i can safely remove from dom
        if(newValue == 'true'){
            prnt.remove();
        }else{
            //set the deletedFlag to true
            delInput.attr('value','true');
            //hide the div
            prnt.hide();
        }        
    });

</script>

<div id="childList">
    <g:each var="phone" in="${contactInstance.phones}" status="i">
        <!-- Render the phone template (_phone.gsp) here -->
        <g:render template='phone' model="['phone':phone,'i':i,'hidden':false]"/>
        <!-- Render the phone template (_phone.gsp) here -->
    </g:each>
</div>
<input type="button" value="Add Phone" onclick="addPhone();" />

I have used a different approach than the original post here, I am basically relying on cloning an existing dom object rather than creating the html by hand since this will get a bit messy when you have some non-trivial form. Using jquery selectors to update my input ids in the addPhone() function (e.g. The selector input[id$=deleted] will find all inputs with an id attribute that ends with deleted). Also I am using jQuery’s live() to bind click events to my remove button, I find this a little bit cleaner.
I will come back to the dom object that will be cloned in a second, but the hidden:’false’ part in the render is related to the clone.

The above template loops over all the phones that a contact has, and renders them using this template (_phone.gsp)

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

    <g:textField name='phonesList[${i}].number' value='${phone?.number}' />
    <g:select name="phonesList[${i}].type"
        from="${blog.omarello.Phone.PhoneType.values()}"
        optionKey="key"
        optionValue="value"
        value = "${phone?.type?.key}"/>

    <span class="del-phone">
        <img src="${resource(dir:'images/skin', file:'icon_delete.png')}"
            style="vertical-align:middle;"/>
    </span>
</div>

The important thing to note here is the use of classes that are used for the jQuery selectors (class=”phone-div” for the main div and class=”del-phone” for the delete button), also the use of the g:select tag to render the enum list with the appropriate key/value.

Now we need to add the html part that will be cloned, and for that we will render the _phone.gsp template in the create.gsp and edit.gsp files (outside of the g:form tag) but we will set the hidden attribute to true so that this div is not visible to the user, but can be cloned by our javascript code like so

......
......
        </g:form>
    </div>
    <!-- Render the phone template (_phone.gsp) hidden so we can clone it -->
    <g:render template='phone' model="['phone':null,'i':'_clone','hidden':true]"/>
    <!-- Render the phone template (_phone.gsp) hidden so we can clone it -->
  </body>
</html>

Our views are now ready for prime time. The most important thing that we have accomplished here is that we are keeping track of the deleted flag on the hidden input which will be used in the controller to delete the phones from the database.

Now we need to make a little change to the controller so that we can remove the phones marked as deleted , plus I have added an extra loop to reset/update the index numbers of my phones (not relevant now, might talk about it later). Note that due to the naming convention of our form inputs, grails can automatically bind the request parameters and auto-magically create the domain class object for us. The controller’s update action should contain the following

......
......

contactInstance.properties = params

// find the phones that are marked for deletion
def _toBeDeleted = contactInstance.phones.findAll {(it?.deleted || (it == null))}

// if there are phones to be deleted remove them all
if (_toBeDeleted) {
    contactInstance.phones.removeAll(_toBeDeleted)
}

//update my indexes
contactInstance.phones.eachWithIndex(){phn, i ->
    phn.index = i
}
if (!contactInstance.hasErrors() && contactInstance.save(flush: true)) {
......
......

That’s it, we should be all set. Let give it a spin…. grails run-app and fireup your browser. here are some action shots.

Create a new contact

Create Contact

Contact Created

Update contact

Update Contact

List contacts

List Contacts

One thing I wanted to show was validation, but I could not make it work for the phone numbers, as you can see in this image, I am getting a validation error for only one of the empty phone numbers, and I did not know how to highlight the input box that was triggering this error, so if you have an ideas please don’t be shy to share them.

Validation

I hope someone finds this useful, as I have struggled to find resources that outline how to persist enums as well as how to deal with request parameters for one-to-many relationships without having to hack your way into hand crafting the domain classes. Off course kudos to the original poster for his comprehensive example.

Update (28 Aug 2010): As Brad pointed out, removing a new phone that was still not persisted did not really remove the phone, this is because the dom elements were hidden instead of removed from the phone which caused the values to be submitted anyways. I’ve updated the source code in the post to fix that by adding a ‘new’ flag when it is still ‘true’ the form elements should be completely removed, while when it is false, they should stay since we want the deleted flag to be submitted.

Posted in Programming.

Tagged with , , , , .


113 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Jerome says

    Thanks a lot, that was very useful.

  2. Brad Rhoads says

    Thanks for the great article. One small problem – On create, if you add a phone and then delete it, you get a validation error and the screen refreshes with the the blank phone that was previously deleted.

  3. omarji says

    Thanks Brad, I’ve updated the post to fix this. The problem was that the div was being hidden instead of removed from the DOM which caused the inputs to submit to the server causing the validation error.

  4. thom says

    Excellent post! Thanks! :-)

  5. John Bertrand says

    It doesn’t look like your latest source code has the new flag on create. Could you update the source? Many thanks

    • omarji says

      I’ve uploaded the source code with the latest updates as well as some other bug fixes.

      • ramesh says

        great work omarji…
        could you please send phone-book update project to me?

  6. Brad Rhoads says

    It would be nice to have an updated application to download. But I did get it to work…

    You need to take everything from _phones.gsp. Then you need to update _phone.gsp to add

    Also note there is a minor error in the narrative. It say:

    The above template loops over all the phones that a contact has, and renders them using this template (_phones.gsp)

    but should say:

    The above template loops over all the phones that a contact has, and renders them using this template (_phone.gsp) // notice _phone.gsp vs _phones.gsp

  7. Mathew says

    Great! Thanks for help!

  8. ed209 says

    This is nice, but I can’t get it working with a MySQL DB. I’ve tweaked the DB dialect with no effect, and have the latest connector.jar Thoughts?

    2011-01-28 09:46:20,323 [main] ERROR hbm2ddl.SchemaExport – Unsuccessful: create table phone (id bigint not null auto_increment, version bigint not null, contact_id bigint not null, index integer not null, number varchar(255) not null, type varchar(255) not null, phones_idx integer, primary key (id))
    2011-01-28 09:46:20,325 [main] ERROR hbm2ddl.SchemaExport – You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘integer not null, number varchar(255) not null, type varchar(255) not null, phon’ at line 1
    2011-01-28 09:46:20,348 [main] ERROR hbm2ddl.SchemaExport – Unsuccessful: alter table phone add index FK65B3D6EA9F010F1 (contact_id), add constraint FK65B3D6EA9F010F1 foreign key (contact_id) references contact (id)
    2011-01-28 09:46:20,348 [main] ERROR hbm2ddl.SchemaExport – Table ‘genericdb.phone’ doesn’t exist

    • ben says

      I got the same error too…

      • ben says

        And it’s because the column “index” from the table phone seems to used a reserved word. Just need to modify index into indexo for example.

        Then in mysql if you do:
        create table phone (id bigint not null auto_increment, version bigint not null, contact_id bigint not null, indexo INTEGER not null, number varchar(255) not null, type varchar(255) not null, phones_idx integer, primary key (id));
        instead of
        create table phone (id bigint not null auto_increment, version bigint not null, contact_id bigint not null, index INTEGER not null, number varchar(255) not null, type varchar(255) not null, phones_idx integer, primary key (id));

        it will work,

        To fix that, you have to modify the following files:
        ContactController.groovy
        contactInstance.phones.eachWithIndexo(){phn, i ->
        phn.indexo = i
        phn.indexo = i

        Phone.groovy
        indexo(blank:false, min:0)
        int indexo
        return “($indexo) ${number} – ${type.value}”

        Et voila…

    • omarji says

      @ed What ben suggested would fix the problem since as he mentioned the column “index” is a reserved word.

      You could change the name as he mentioned. Or a better way to do it is change the column name mapping using the static mapping block.
      In the Phone domain class add the following

      static mapping = {
      index column:”phone_index”
      }

      I will update the original post to include this.

      Thanks for the feedback.

      • Kalps says

        hi! i do have same issue as like add the group of phone number , i have to add set of columns in grails its like table of records i have to do the same . can you guide me ? i am new to grails.

  9. Adrian says

    Great post!! You asked about an alternative way to use Enum, I’ve tried to do it using a persisted category list, as I want to be able to add/modify types without having to do any code change. But it keeps failing, I’m new to Grails, is there any obvious reason why it cannot be done that way?

    The error I get is: No signature of method: party.Phonetype.get() is applicable for argument types: (java.lang.String, java.lang.String) values: [445, 444] Possible solutions: get(java.lang.Object), getId(), getAt(java.lang.String), list(), getAll(), ident().

    I have updated my domain and views accordingly (replacing enum by Phonetype object):

    package party
    class Phone {

    Phonetype phonetype
    }
    class Phonetype {

    String typename
    }

    And then on _phone.gsp:

    For ${listItemsPhonetype} I’ve created a service to get the list of phone types, that works fine.

    Finally, on _phones.gsp I have:

    clone.find(“select[id$=phonetype]“)
    .attr(‘id’,htmlId + ‘phonetype.id’)
    .attr(‘name’,htmlId + ‘phonetype.id’);

    Any help much appreciated!

  10. Adrian says

    Hmm, the _phone.gsp code did not get posted. I’ll try again adding some spaces:

  11. Adrian says

    Once more:

    g:select name=”phonesList[${i}].phonetype.id”
    from=”${listItemsPhonetype}”
    optionKey=”id”
    optionValue=”typename”
    value = “${phone?.phonetype?.id}”

  12. Adrian says

    Just to comment on my previous post, in case anyone else wants to do it that way. My code works fine, there is just an error on _phones.gsp:

    clone.find(“select[id$=id]“)
    .attr(‘id’,htmlId + ‘phonetype.id’)
    .attr(‘name’,htmlId + ‘phonetype.id’);

    Once you update that you can get your phonetypes from the DB and do all the rest as usual.
    Thanks!!

    • omarji says

      Thanks for the info Adrian. Could be a good idea to add this information into another post maybe.

  13. Dustin Clark says

    Did anyone figure out a way to do this:

    One thing I wanted to show was validation, but I could not make it work for the phone numbers, as you can see in this image, I am getting a validation error for only one of the empty phone numbers, and I did not know how to highlight the input box that was triggering this error, so if you have an ideas please don’t be shy to share them.

    Thanks.

    • Brad Rhoads says

      I’ve been meaning to post this for awhile. You can loop through the detail records and check if each one is valid.

      def save = {
      log.debug "params:${params}"
      def lookupTableInstance = new LookupTable(params)

      def _toBeRemoved = lookupTableInstance.lookupValues.findAll {!it}
      log.debug "lookupTableInstance.lookupValues:${lookupTableInstance.lookupValues}"
      log.debug "_toBeRemoved:${_toBeRemoved}"

      // if there are lookupvalues to be removed
      if (_toBeRemoved) {
      lookupTableInstance.lookupValues.removeAll(_toBeRemoved)
      }

      def anyErrors = false
      lookupTableInstance.lookupValues.eachWithIndex(){lookupValue, i ->
      lookupValue.idx = i
      lookupValue.lookupTable = lookupTableInstance
      if (!regExpManagerService.testRegexp(lookupTableInstance.regexpression,lookupValue.thevalue)) {
      log.debug "reject!"
      lookupValue.errors.rejectValue("thevalue",
      "invalid.regexp",
      [lookupValue.thevalue,
      g.message(code:'regexName.'+ lookupTableInstance.regexpression.regexName,
      default:lookupTableInstance.regexpression.regexName
      ),
      g.message(code:lookupTableInstance.regexpression.theexpression + ".human",
      default:'Invalid format'
      )
      ] as Object[],
      "The value {0} does not match the regular expression."
      )
      log.debug "errors:${lookupValue.errors}"
      anyErrors = true
      }
      }

      if (!anyErrors && lookupTableInstance.save(flush:true)) {
      log.debug "did the save"
      flash.message = "lookupTable.created"
      flash.args = [lookupTableInstance.id]
      flash.defaultMessage = "LookupTable ${lookupTableInstance.id} created"
      redirect(action: "show", id: lookupTableInstance.id)
      }
      else {
      render(view: "create", model: [lookupTableInstance: lookupTableInstance,thisInstance:lookupTableInstance])
      }
      }

      But, I you can actually do it in the domain. After doing the above, I did the following in a different domain,but using the same technique as the article:


      static constraints = {
      setName(size: 1..45, blank: false)
      dateCreated(nullable: true)
      lastUpdated(nullable: true)
      customer(nullable: true)
      updatedByPerson(nullable: true)
      createdByPerson(nullable: true)
      setTagtypes(validator: {stts,obj,errors ->

      def valid = stts?.tagtype == stts?.tagtype?.unique()
      if (!valid) errors.rejectValue(
      "setTagtypes", "metadataSet.duplicate.tagtype", "Each Tagtype can only be used once.")
      return valid
      })
      }

      • omarji says

        Thanks Brad,

        I’ve actually forgot to update the post with this information after I had figured it out. I might write up a new post in order to show this. Might use some of the infromation you’ve presented if that’s ok.

    • Jiri says

      I tried this. I think it is not “clean” solution but it works.
      In controller I added this line
      contactInstance.phones.each {it.validate}
      just before
      if (!contactInstance.hasErrors() && contactInstance.save(flush: true)) {

      Which fill in errors property directly for each phones instance.
      Than in template you can normaly use (inside each)

      class=”value ${hasErrors(bean: phones, field: ‘phone’, ‘errors’)}”

      And thanks for very useful article!!!

      • Jiri says

        Sorry, small typo
        contactInstance.phones.each {it.validate()}

  14. Thomas says

    Where’s #phone_clone defined? Am not getting the phone.gsp template to be displayed for some reason.

    var clone = $(“#phone_clone”).clone()

    • Thomas says

      ouch found it, you should take your time to read the full post…

  15. Donal says

    You could remove PhoneType.values() and just use the PhoneType.values() which is present on all enums

  16. Brad Rhoads says

    If you’re using this technique with something like dependent list boxes, or more generally, need to have an event handler renamed dynamically, you can do it like this:

    oc = tagtypeSelect.attr(‘onchange’).toString().replace(“${i}”,childCount).replace(“onchange”,”");

    What this does is is retrieve the text of the onchange event of a select , in which I have I call a remoteFunction with update:’tagtype-options-${i}’. Then the ${i} is replaced with the childCount. And finally, function onchange(event){…} get’s changed to just function (event) {..} because jquery .change() expects just a function.

    For clarity, the entire select is:

    Once the string is set I do

    .change(eval(oc);

    Well that doesn’t work in IE, so I actually do:

    .change(eval_global(oc));

    with:

    function eval_global(codetoeval) {
    if (window.execScript)
    window.execScript(‘code_evaled = ‘ + ‘(‘ + codetoeval + ‘)’,”); // execScript doesn’t return anything`
    else
    code_evaled = eval(codetoeval);

    return code_evaled;
    }

  17. Dave says

    I ran your code and everything works great. I tried to convert it to my use-case and for some reason my List isnt getting populated in the create.

    here is a println of the params passed in in your code:
    [lastName:iohjl, phonesList[0].new:true, phonesList[0]:[id:, deleted:false, number:t786796, new:true, type:H], nickName:hh, phonesList[0].id:, phonesList[0].deleted:false, firstName:hjkh, phonesList[0].type:H, create:Create, phonesList[0].number:t786796, action:save, controller:contact]

    So phonesList should be saved as phones in the contact.

    what does the mapping from phonesList to phones?
    in my use-case i have the same println but my timeSlotList isnt getting saved in the timeSlot object in my Schedule.

    I believe i did everything as you did above but i must of missed something that maps the phonesList to phones object step.

    I have been staring at it for about 5 hours now…. any help would be greatly appreciated

    Thanks for the help

    • Dave says

      im sorry i just got it working… i guess all i had to do was ask for help ;) wish i had done that 5 hours ago

      • omarji says

        :) glad you got it working. It would be helpful to share if you had to do something special in order to fix it so I can update the post.

        • Dave says

          it was actually just me mistyping the LazyList part. Your doc is correct. The curse of dynamically typed languages ;)

  18. Amol says

    Hi,
    Thank you for this post! It was a life saver :-)
    However I was trying to check the behavior of Hibernate and the related DB schema.
    It seems like when we insert a child record, there is an insert into the child table with all the columns (including parents_idx column, which I guess GRAILS uses to map the indices of the children).
    However this is followed by an update of the child table to set the idx column above and the foreign key reference column. Any ideas why this is the case? It would be preferable to avoid this behavior if possible – an insert should be a single query. Any inputs?
    TIA!

  19. Dave says

    I am trying to nest templates essentially many to many dynamic forms but am running into issues.
    Essentially i want to have the ability to create a list of days and each day can have a list of timeslots.

    I can successfully create a list of days using this example and have been trying to insert a second template into each day(for the timeslots) Unfortunately every time i add a timeslot it inserts it into the first day.

    I believe the solution is to have a unique name for the timeslot div(corresponding to each day you dynamically add)

    I havent figured out how to pass this information to the template.

    How can i pass in a variable that i can reference in the template?

    Am i trying to approach this in the correct way? Thank you for the help

    • omarji says

      You can use the model attribute for the render tag to pass in variables. For example (from the code above)


      g:render template='phone' model="['phone':null,'i':'_clone','hidden':true]"

      This passes a null phone object, ‘i’ and ‘hidden’ variables to the template.

      Hope this helps.

  20. Bob says

    For some reasons some people uses Prototype & jQuery together.
    For this wonderful dynamic form to work they have to replace all $ (dollar signs) in the script in _phones.gsp with jQuery

    • Bob says

      and you have to write childCount = childCount + 1; instead of childCount++;

  21. Isaid Castri says

    Hi, I’m trying to reuse your technique for a class of photos instead of telephone, in view of the photo entry is a file type input, and when I do submit that taking the name of the picture, the get the name of the photo iterating getFileName () with getFile (fileName). OriginalFilename:

    request.getFileNames().each(){fileName ->
    def uploadedFile = request.getFile(fileName);

    if(!uploadedFile.empty && (uploadedFile.originalFilename != null)){
    def resource = new Photo(name : uploadedFile.originalFilename)

    def webRootDir = servletContext.getRealPath(“/”)
    def resDir = new File(webRootDir, “/res/uploads”)
    resDir.mkdirs()
    entryInstance.photos.add(resource)//here add photo to photo list of entryInstance
    if(resource.validate() && !resource.hasErrors() && entryInstance.validate() && !entryInstance.hasErrors() && resource.save()){

    uploadedFile.transferTo( new File( resDir, resource.id+”_”+resource.name))
    }

    }

    and manually added to the list of type photo of my container class, I mark the validation errors of the extension because the controller is adding the photo as if got it from of text input type, as I have to specify in the controller to pull the photo from the class name instead of request params?

    sorry for my bad English.

  22. Zvi says

    Great sample app – thanks!! We need to make this into a plug-in which will generate one-to-many forms automatically!

  23. sand says

    Thanks for this useful post.

    I was trying to run this application on Grails 2.0.0.RC1. I was able to create contact and phones with no problem. I was also be able to update a contact with NO phones, then add phones.

    However, when I tried to update a contact with existing phones, I got “Not a collection, array, or map: cannot resolve nested value types. Stacktrace follows:” exception at line: contactInstance.properties = params.

    I am wondering if anyone else have this problem?

  24. Alex says

    hey guys.

    I was wondering if you could help me.
    I am doing the same thing with grails 2.0.0.RC1 and on IE works fine, but in Firefox or Chrome the values never reach the server. From what i saw in the source of the generated HTML , the new components aren’t added to the DOM in Firefox/Chrome.
    Any hints on how to resolve this ?
    Thanx.

    • Alex says

      Never mind.
      I forgot the closing tag of a div in the code, and it was all messed up.

  25. Bob says

    I’am using Grails 2.0 and it throws the follwong exception in the save action:
    null id in test.TargetEvaluation entry (don’t flush the Session after an exception occurs)
    org.hibernate.AssertionFailure: null id in ….

    It works fine in 1.3.7. And the data sent is the same. Has anyone the same issue?
    Looks like the cascade does not work…

    • win says

      Me too

    • win says

      I test in 2.0.1. It work fine.
      when I change “phonesList” to “phones” in gsp files and remove getPhonesList method in Contact class.

  26. Paulo says

    Very good article, thanks for sharing. I’m new to Grails, so if anyone can help me, I’d really appreciate. I’m trying to extend this example by associating Phone with a third domain called “product”.
    so, in my phone domain I have a line for:
    static belongsTo = [contact: Contact, product:Product]

    In my Product domain:
    static hasMany =[phones: Phone]

    In my phone view:

    When I try to save, I get an error message:
    Property [product] of class [class com.easyinvt.Phone] cannot be null

    It seems that Grails is not automatically mapping my product id to the Phone class.
    Note: I’m using Grails 2.0

    Thank you for the help
    Paulo

  27. Stephane Rainville says

    Great post !
    Error validation seems to be an issue still.

    On a parent.save (with errors in child) the parent.errors contains duplicate entries in a weird format
    (and child objects errors are empty)
    childItem[0][1].description don’t understand
    childItem[1][2].description don’t understand
    childItem[0].description Great
    childItem[1].description Great

    If I loop thru the child, I get errors in the child object (yeah) but still duplicate entries in the parent when I call save.

    Any clues?

    So far my complex solution would be to loop thru the children, highlight the errors BEFORE calling save. Once validate on all child items passes THEN call save.

  28. Nont Banditwong says

    Thanks for article, very helpful for me.

    How did you handle the form data binding and constraints if some of phone index skipped, such as create
    4 empty phone number rows then delete 1st and 3rd record, fill in available record and submit.

    Now available phone map is phonesList[1] and phonesList[3].

    From Grails document ,
    “Grails will automatically create a new instance for you at the defined position. If you “skipped” a few elements in the middle:”
    http://grails.org/doc/latest/guide/theWebLayer.html#6.6%20Filters

    If you apply constraints on domain class, it would fail when binding data, if you faces this problem how did you solve it.

    Thanks.

  29. hjkhkjhkjh says

    Fix your code not to treat the hidden fields and the phone input field differently.

    .find().attr().attr() is fine. Don’t do var x = find(); x.attr().attr it is a visual blight.

  30. mexposito says

    Hi!

    I’ve tried to run the project in grails 2.0.3 with no luck: validation erros appear for no reason (all fields are filled) in phone records, even if I remove validation constraints in the domain class. Really weird…

    Also, I’m trying to implement my own set of domain classes following the example suggested in this blog. I have some doubts about params binding, I mean:

    The property for storing ‘phones’ in the domain class of Contact is a List called ‘phones’; however, the param filled with the introduced data is called ‘phonesList’. There is no map in the controller between ‘phonesList’ and the ‘phones’ property of class ‘contact’, how is the list saved?

    I’ve been trying to change the name ‘phonesList’ to ‘phones’ , as it is suggested in a previous comment , but I get the following error: java.lang.NumberFormatException: For input string: “_clone”

    Any thoughts?

    • Praveen says

      Hi
      I think you have kept the (for cloning via jQuery) inside the i.e. before . That’s why it is getting submitted to the server make sure that it is not inside your form.

      Please follow the following lines mentioned above.

      • Praveen says

        I’m sorry .. g:render tag for cloning via jQuery should be kept outside your form tag, otherwise it will be submitted to the server which leads to NumberFormatException: For input string: “_clone”

    • 大乐 says

      Having the same issue – upgraded the sample code to 2.0.4 – when the controller sets the contact instance parameters (contactInstance.properties = params) all phone number fields are null.

      • 大乐 says

        You can remove the LazyInit list and the getPhonesList method and operate directly on the phones list – requires some re-work in the .gsp files and javascript but the phone numbers are correcting created.

        • John says

          I am having the same issues with grails 2.0.4.
          Do you have a sample of what you changed to make it work?

          I would kindly appreciate it if you could provide the code somewhere.

          Thanks.

          • 大乐 says

            John, didn’t see your reply – do you still need this documented?

        • Rachel says

          If you could post your changes, that would be lovely. I’ve been struggling with this for awhile now

  31. Michael Morett says

    This piece of functionality has been giving people fits for over 2 years. I’ve used this code, but it’s very fragile and I’ve experienced almost all of the issues people left comments about. I’ve had the double validation error messages, the NFE For input string: “_clone”, the validation messages that appear even though the fields were filled in.

    This is sad.

    I’m trying to use it again for a different project and the NFE For input string: “_clone” is the current issue. Maybe when I get it working correctly, I’ll start a GitHub page and put the latest source code up there for people to download. As much as I appreciate the work here, the fact is the code has never worked “out of the box”. There’s always some small (or not so small) issue. Or the code, but not the blog (or vice versa) is updated.

    The Grails community badly needs this type of functionality. It shouldn’t be so hard or so brittle.

    • André Abreu says

      Have you succeeded? I’m new to grails, and I’m facing the same difficulties. Wuold be the problem of null values resolved with a bind function at save/controller?

      • André Abreu says

        Done, as posted above, the soluction is: change in Phone.groovy:
        class Contact {

        static constraints = {
        firstName(blank:false)
        lastName(blank:false)
        }

        String firstName
        String lastName
        String nickName

        List phones = LazyList.decorate(new ArrayList(), FactoryUtils.instantiateFactory(Phone.class))
        static hasMany = [ phones:Phone ]

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

        // def getPhonesList() {
        // return LazyList.decorate(
        // phones,
        // FactoryUtils.instantiateFactory(Phone.class))
        //}

        def String toString() {
        return “${lastName}, ${firstName}”
        }
        }

        And, in _phone.gsp and phones.gsp, change all “phonesList” by just “phones”.

  32. Adrian says

    Hi! I’m trying to do the same thing, but working with images. The thing is that I couldn’t make it so far. I don’t know why the save method don’t recognize my images in the list and don’t upload them.
    More details of my problem: http://stackoverflow.com/questions/11424632/one-to-many-images-grails

    I’d love some kind of help, please!

  33. André Abreu says

    Sorry, change in Contact.groovy!!!!!

  34. Federico says

    Thanks for the post!

    I’ve been strugging to find a simple and clean solution to this problem and always ended with complex and ugly code!

    :D

  35. Brandy says

    I’m truly enjoying the design and layout of your blog. It’s a
    very easy on the eyes which makes it much
    more enjoyable for me to come here and visit more often.
    Did you hire out a developer to create your theme?
    Great work!

  36. Rachele says

    Thanks for sharing your thoughts. I truly appreciate your efforts and I am waiting for your further post thanks once again.

  37. beitragsbemessungsgrenze private krankenversicherung 2011 says

    Hello There. I found your blog using msn. This is a very well written article.
    I will be sure to bookmark it and return to read more of your useful info.

    Thanks for the post. I’ll definitely comeback.

  38. chittibabu says

    thanks to post this. i have used this application but i have some problem here.

    * rejected value [Thu Dec 27 18:26:22 IST 2012]; if i try to insert date value from screen (ex: mm/dd/yyyy) and also try to save id of domain class like: name=”student.id” use g:select.

    can you help me to solve please……………………………………..

  39. how to get a free macbook pro says

    I think this is one of the most important information for me.
    And i am glad reading your article. But should
    remark on few general things, The web site style is great, the
    articles is really excellent : D. Good job, cheers

  40. free mac book air says

    Nice blog here! Additionally your web site so much up very
    fast! What web host are you the usage of?
    Can I get your affiliate hyperlink to your host?
    I desire my web site loaded up as quickly as
    yours lol

  41. how to get a free ipod touch says

    Greetings from Florida! I’m bored at work so I decided to browse your site on my iphone during lunch break. I enjoy the info you present here and can’t
    wait to take a look when I get home. I’m surprised at how fast your blog loaded on my mobile .. I’m
    not even using WIFI, just 3G .. Anyways, good site!

  42. how to win a iphone 4s says

    I really like it when individuals come together and share thoughts.
    Great site, keep it up!

  43. paleo diet free says

    Our ancestors had to worry about finding food to eat in restaurants and is not meant
    to eat corn may make them fatter faster, but we’re guessing that’s asking a lot.
    Plants have evolved with these characteristics in order to avoid developing amino
    acid deficiencies.

  44. blogging with john chow review says

    Hi there would you mind stating which blog platform you’re working with? I’m planning to start
    my own blog in the near future but I’m having a difficult time deciding between BlogEngine/Wordpress/B2evolution and Drupal. The reason I ask is because your design and style seems different then most blogs and I’m looking for something completely unique.
    P.S Sorry for being off-topic but I had to ask!

  45. interactive text says

    Nice blog! Is your theme custom made or did you download it from somewhere?
    A design like yours with a few simple tweeks would really
    make my blog jump out. Please let me know where
    you got your design. Bless you

    • Juan says

      I’m having problems with this project. Even making the changes suggested, it’s giving me null always when I insert a phone number, which is the problem?

      Thanks

  46. PMP Prep says

    Wow, this article is fastidious, my younger sister is analyzing these
    things, thus I am going to convey her.

  47. Tybee island vacation says

    Right here is the perfect webpage for everyone who would like
    to find out about this topic. You know so much its almost tough to argue with you (not
    that I personally will need to…HaHa). You definitely put a new spin on a topic that’s been written about for a long time. Wonderful stuff, just excellent!

  48. Julianne says

    zaawizował chrapliwym szeptem. – Również Julianne dziewice rzekomo, nie wiem,
    ile w
    tym zasady… Trochę, pomyślał sir Roger, fama gminna jacyś wyolbrzymia.

    W swoje funkcjonowanie nie spotkał w życiu
    smoka, kto.

  49. surveys paid review, says

    wonderful points altogether, you just gained a
    new reader. What might you suggest about your post that you simply made some
    days in the past? Any certain?

  50. reputation management says

    I used to be suggested this website by way of my cousin.

    I am no longer certain whether this publish is written by him as no one else know such designated about my
    problem. You’re incredible! Thank you!

  51. ndfsigjrsx says

    ‘s urban management surnamed Zhou,ray ban pas cher, deputy chief of the department Habitat the original households bungalow is dilapidated buildings,abercrombie pas cher,Ministry of Commerce, the Japanese side shall bear the bilat, the authorities had agreed to repair their cottage,ray ban, but did not expect to build buildings,ray ban sunglasses,Part of the Japanese companies in China factory managing return to work short-te. After the incident,toms outlet, the city management several times on-site coordination to no avail,hollister. Recently,ray ban pas cher, he said,abercrombie,An affair with a 48-year-old rich woman and a 28-year-old lover to the end of th, will be to investigate and punish,ray ban.

  52. toms shoes in knoxville says

    It’s a pity you don’t have a donate button! I’d definitely donate to this outstanding blog! I guess for now i’ll settle for book-marking and adding your RSS feed to my Google account. I look forward to new updates and will share this site with my Facebook group. Talk soon!

  53. Monte says

    Hey would you mind sharing which blog platform you’re using? I’m looking to start
    my own blog soon but I’m having a hard time deciding between BlogEngine/Wordpress/B2evolution and Drupal. The reason I ask is because your layout seems different then most blogs and I’m looking
    for something unique. P.S My apologies for getting off-topic
    but I had to ask!

  54. zombie movies says

    There are a few things we need to go over real quick. It
    let go with a shriek and fell back into the masses disappearing from sight “Freeeeeeeedddoooooooooom” Jeff yelled out lustily Micah
    raised an eyebrow and glanced over him, “That’s all I could come up with” he grinned sheepishly.

    The troops are torn apart by the same enemy soldiers they had killed a few minutes earlier in an ambush.

  55. Abhay says

    Gosh! great post, but the code doesn’t work as is… had to fix we patches… Here is the code if anyone gets stuck into it.

    //update contact.groovy with the below
    List phones = new ArrayList()
    List phonesList = LazyList.decorate(new ArrayList(), FactoryUtils.instantiateFactory(Phone.class))

    //Update ContactController.groovy in the eachWithIndex loop
    //update my indexes
    contactInstance.phones.eachWithIndex(){phn, i ->
    phn.index = i
    phn.setContact(contactInstance)
    }

  56. Video SEO says

    Hi there, just became alert to your blog through
    Google, and found that it’s really informative. I’m gonna watch out for brussels.

    I’ll appreciate if you continue this in future. Numerous people will be benefited from your writing. Cheers!

  57. rheumatic heart disease says

    Cool blog! Is your theme custom made or did you download it from
    somewhere? A design like yours with a few simple adjustements
    would really make my blog shine. Please let me know where you got your theme.
    Many thanks

  58. how to get traffic to my website says

    Wonderful article! We will be linking to this particularly great
    content on our site. Keep up the good writing.

  59. Internet Marketing for Attorneys says

    We stumbled over here by a different web address and thought I may as well check things out.
    I like what I see so now i am following you. Look forward to checking out your web page again.

  60. web traffic generator says

    Have you ever thought about writing an ebook or guest authoring on other sites?
    I have a blog based upon on the same information you discuss and would really like to have you share
    some stories/information. I know my visitors would value your work.
    If you’re even remotely interested, feel free to send me an e-mail.

  61. How to Do SEO says

    I every time used to study piece of writing in news papers but now
    as I am a user of web thus from now I am using net for articles, thanks to web.

  62. complete link building says

    I visited multiple web sites except the audio quality for audio songs present at this web site is truly marvelous.

  63. darmowe sex oferty says

    darmowe sex oferty Dziewczyny przyjaźniły się
    nuże od chwili dziecka, w pobliżu iż widywały się ale wręcz
    w odpoczynek a w święta. Nawet nie pochodziły spośród jednego miasta, jednak każde lato spędzały zupełnie obok
    swojej babci. Na osiedle wiejskie furt przyjeżdżał plus ich
    kuzyn Kazik. We trójkę spędzali Chronos na różnych pierdołach.
    W tym momencie Kazio został w domu i pomagał w czymś
    dziadkowi, oraz dziewczyny poszły na miasto. Podczas gdy dotarły w umówione punkt, chłopcy w tej chwili w ową
    stronę na nie czekali.
    Zobacz sam: darmowe sex oferty.

  64. internet marketing for local business says

    certainly like your web site however you have to check the spelling on several of your posts.

    Many of them are rife with spelling issues and I to find it very troublesome to tell
    the truth then again I’ll definitely come back again.

  65. increase traffic says

    It is perfect time to make a few plans for the longer term and it is time to be happy.
    I have learn this put up and if I may I desire to counsel you some attention-grabbing things or
    tips. Perhaps you could write subsequent articles relating to
    this article. I desire to read even more things about
    it!

  66. hampshire fireworks says

    Good day! Do you know if they make any plugins to help with SEO?
    I’m trying to get my blog to rank for some targeted keywords but I’m not seeing
    very good results. If you know of any please share.

    Kudos!

  67. one way link building services says

    Hi, Neat post. There’s a problem together with your web site in web explorer, would check this? IE still is the market chief and a huge part of other folks will leave out your wonderful writing due to this problem.

  68. she in money review says

    Hi, its nice piece of writing regarding media print, we all know media is
    a fantastic source of facts.

  69. z code system review says

    Hi! I realize this is sort of off-topic however I needed
    to ask. Does building a well-established blog like yours require a
    lot of work? I am completely new to writing a blog however I do write
    in my diary daily. I’d like to start a blog so I can easily share my own experience and views online. Please let me know if you have any ideas or tips for brand new aspiring bloggers. Appreciate it!

  70. smtp2go review says

    My spouse and I stumbled over here by a different page and thought I might as well check things out.

    I like what I see so i am just following you. Look forward to
    finding out about your web page for a second time.

Continuing the Discussion

  1. Blog bookmarks 08/23/2010 « My Diigo bookmarks linked to this post on August 23, 2010

    [...] Train of Thought | Grails one-to-many dynamic forms [...]

  2. Tweets that mention Grails one-to-many dynamic forms -- Topsy.com linked to this post on August 23, 2010

    [...] This post was mentioned on Twitter by kevin's bookmarks and Proggit Articles, omarello. omarello said: #grails how-to: one-to-many relationships with dynamic forms http://is.gd/eumw1 [...]

  3. Introduction to Grails Development - Grails Tutorial linked to this post on September 20, 2010

    [...] Grails one-to-many relationships with dynamic javascript forms by Omar Marji (Aug 2010) [...]

  4. grailstutorial.org linked to this post on January 13, 2011

    Grails one-to-many dynamic forms…

    After searching around trying to find a good way to implement one-to-many dynamic forms in Grails, I have finally come across this post which does a very good job at explaining the details. What I was basically looking for is a clean way to implement s…

  5. Important Links for Grails | JAVA developer and research blog linked to this post on July 20, 2011

    [...] Many-to-Many relations and dynamic form [...]

  6. One-to-many forms with Grails 2.1 | Object Partners Inc linked to this post on October 4, 2012

    [...] did, the following blog post helpful, although it seems to be a bit outdated for Grails 2.1 users: http://omarello.com/2010/08/grails-one-to-many-dynamic-forms/ . My approach is essentially a revision of omarello’s post, updated slightly for Grails 2.1. [...]

  7. Portal Muncii » Answers Archive » cheap nfl jerseys Mercedes-Benz owners to allow banks to remind others left over linked to this post on May 15, 2013

    [...] Jiangsu Golden Great Wall law firm lawyer Wang Jian, Zhang bank card forget teller machines belonging to the remnants, if this card did not exit, the other party can put the card immediately exit the alarm to the police, which is doing good, but his money out of this Cary, And one after another large withdrawals, withdrawals this behavior on the inside on suspicion of theft. Then the other party has said its own withdrawals in order to remind the Card Master can be set up? Wang lawyers believe that this is to do good, common sense, if the cue card owners, then within two minutes louboutin pas cher, three withdrawals prompted, it is difficult to say through As for not constitute a crime in conjunction with the other party was the real motive depends on the police investigation and the record of inquiry go qualitative. self-service withdrawals, please remember the simplicity of these seven strokes Related articles: louboutin Valentine’s Day of the older woman forced marriage is not to kill her boyfriend and burned his body nfl jerseys Consumers of leisure facilities crush got into his pants and mouse louboutin The wife to use the sleeping pills kill wishing to louboutin The wife to use the sleeping pills kill wishing to [...]



Some HTML is OK

or, reply to this post via trackback.