4 Replies Latest reply on May 26, 2011 2:20 PM by Leo van den berg

    Updating list of child from a entityHome

    Jean-Philippe Belanger Newbie

      I'm new to seam and trying to do something that I feel should be pretty straightforward. At least it normally is in JSF.


      I have a structure parent with one-to-many child. In this case a Resource containing a list of Notes.
      My resource.xhtml page allows me to update the resource and add notes.


      Click on the addNote and a new empty note is added to the list. The list allows update on new row (no ids) only.


      The problem is whenever I do an action (save in this case) the update value of the notes aren't persisted (and not even updated in the model)


      I'm using Seam 2.2.0.GA and Jboss 6.0.0. Help would be greatly appreciated. There is probably something I'm just missing. Thanks in advance.


      package com.noverka.nvkcrm.entity;
      
      import java.io.Serializable;
      import java.sql.Date;
      import java.util.List;
      
      import javax.persistence.Entity;
      import javax.persistence.EntityListeners;
      import javax.persistence.FetchType;
      import javax.persistence.GeneratedValue;
      import javax.persistence.Id;
      import javax.persistence.OneToMany;
      import javax.validation.constraints.Pattern;
      
      import org.hibernate.validator.Length;
      import org.hibernate.validator.NotNull;
      import org.hibernate.validator.constraints.Email;
      
      import com.noverka.nvkcrm.utils.EntityValueListener;
      
      @Entity
      @EntityListeners(value=EntityValueListener.class)
      public class Resource extends AbstractEntity implements Serializable
      {
           private static final long serialVersionUID = -3321381618797067306L;
      
           @Id @GeneratedValue
          private Long id;
           
          @Length(max=60)
          @NotNull
           private String firstname;
      
          @Length(max=60)
          @NotNull
          private String lastname;
      
          private Date availabledate;
      
          @Pattern(regexp="^((\\+\\d{1,3}(-| )?\\(?\\d\\)?(-| )?\\d{1,5})|(\\(?\\d{2,6}\\)?))(-| )?(\\d{3,4})(-| )?(\\d{4})(( x| ext| \\/|\\/)\\d{1,5}){0,1}$||()",
                      message="Invalid phone number")
          private String phonehome;
      
          @Pattern(regexp="^((\\+\\d{1,3}(-| )?\\(?\\d\\)?(-| )?\\d{1,5})|(\\(?\\d{2,6}\\)?))(-| )?(\\d{3,4})(-| )?(\\d{4})(( x| ext| \\/|\\/)\\d{1,5}){0,1}$||()",
                      message="Invalid phone number")
           private String phonecell;
      
          @Pattern(regexp="^((\\+\\d{1,3}(-| )?\\(?\\d\\)?(-| )?\\d{1,5})|(\\(?\\d{2,6}\\)?))(-| )?(\\d{3,4})(-| )?(\\d{4})(( x| ext| \\/|\\/)\\d{1,5}){0,1}$||()",
                      message="Invalid phone number")
          private String phonework;
      
          @Email
          private String emailhome;
      
          @Email
           private String emailwork;
      
          private String address1;
           private String address2;
           private String city;
           private String province;
           private String postalcode;
           private String country;
           
           private String functioncode;
           private String statuscode;
      
           private Integer salaryhour;
           private Integer salaryannual;
           private Integer vacationweeks;
      
           public Resource()
           {
                setProvince("QuŽbec");
                setCountry("Canada");
           }
           
           @OneToMany(fetch=FetchType.LAZY, targetEntity=CurriculumVitae.class)
           private List<CurriculumVitae> curriculaVitae;
      
           @OneToMany(fetch=FetchType.LAZY, targetEntity=Note.class)
           private List<Note> notes;
      
           @OneToMany(fetch=FetchType.LAZY, targetEntity=Reference.class)
           private List<Reference> references;
      
          public Long getId() {
              return id;
          }
      
          public void setId(Long id) {
              this.id = id;
          }
      
           public String getFirstname() {
                return firstname;
           }
      
           public void setFirstname(String firstname) {
                this.firstname = firstname;
           }
      
           public String getLastname() {
                return lastname;
           }
      
           public void setLastname(String lastname) {
                this.lastname = lastname;
           }
      
           public Date getAvailabledate() {
                return availabledate;
           }
      
           public void setAvailabledate(Date availabledate) {
                this.availabledate = availabledate;
           }
      
           public String getPhonehome() {
                return phonehome;
           }
      
           public void setPhonehome(String phonehome) {
                this.phonehome = phonehome;
           }
      
           public String getPhonecell() {
                return phonecell;
           }
      
           public void setPhonecell(String phonecell) {
                this.phonecell = phonecell;
           }
      
           public String getPhonework() {
                return phonework;
           }
      
           public void setPhonework(String phonework) {
                this.phonework = phonework;
           }
      
           public String getEmailhome() {
                return emailhome;
           }
      
           public void setEmailhome(String emailhome) {
                this.emailhome = emailhome;
           }
      
           public String getEmailwork() {
                return emailwork;
           }
      
           public void setEmailwork(String emailwork) {
                this.emailwork = emailwork;
           }
      
           public String getAddress1() {
                return address1;
           }
      
           public void setAddress1(String address1) {
                this.address1 = address1;
           }
      
           public String getAddress2() {
                return address2;
           }
      
           public void setAddress2(String address2) {
                this.address2 = address2;
           }
      
           public String getCity() {
                return city;
           }
      
           public void setCity(String city) {
                this.city = city;
           }
      
           public String getProvince() {
                return province;
           }
      
           public void setProvince(String province) {
                this.province = province;
           }
      
           public String getPostalcode() {
                return postalcode;
           }
      
           public void setPostalcode(String postalcode) {
                this.postalcode = postalcode;
           }
      
           public String getCountry() {
                return country;
           }
      
           public void setCountry(String country) {
                this.country = country;
           }
      
           public String getFunctioncode() {
                return functioncode;
           }
      
           public void setFunctioncode(String functioncode) {
                this.functioncode = functioncode;
           }
      
           public String getStatuscode() {
                return statuscode;
           }
      
           public void setStatuscode(String statuscode) {
                this.statuscode = statuscode;
           }
      
           public Integer getSalaryhour() {
                return salaryhour;
           }
      
           public void setSalaryhour(Integer salaryhour) {
                this.salaryhour = salaryhour;
           }
      
           public Integer getSalaryannual() {
                return salaryannual;
           }
      
           public void setSalaryannual(Integer salaryannual) {
                this.salaryannual = salaryannual;
           }
      
           public Integer getVacationweeks() {
                return vacationweeks;
           }
      
           public void setVacationweeks(Integer vacationweeks) {
                this.vacationweeks = vacationweeks;
           }
      
           public static long getSerialversionuid() {
                return serialVersionUID;
           }
      
           public List<CurriculumVitae> getCurriculaVitae() {
                return curriculaVitae;
           }
      
           public void setCurriculaVitae(List<CurriculumVitae> curriculaVitae) {
                this.curriculaVitae = curriculaVitae;
           }
      
           public List<Note> getNotes() {
                return notes;
           }
      
           public void setNotes(List<Note> notes) {
                this.notes = notes;
           }
      
           public List<Reference> getReferences() {
                return references;
           }
      
           public void setReferences(List<Reference> references) {
                this.references = references;
           }
           
           public String getFullName()
           {
                return getLastname() + ", " + getFirstname();
           }
      }
      





      package com.noverka.nvkcrm.entity;
      
      import java.io.Serializable;
      
      import javax.persistence.Entity;
      import javax.persistence.EntityListeners;
      import javax.persistence.GeneratedValue;
      import javax.persistence.Id;
      
      import org.hibernate.validator.Length;
      
      import com.noverka.nvkcrm.utils.EntityValueListener;
      
      @Entity
      @EntityListeners(value=EntityValueListener.class)
      public class Note extends AbstractEntity implements Serializable 
      {
           private static final long serialVersionUID = 3196629518629299940L;
      
           @Id @GeneratedValue
          private Long id;
           
           @Length(max=4000)
           private String note;
      
           public Long getId() {
                return id;
           }
      
           public void setId(Long id) {
                this.id = id;
           }
      
           public String getNote() {
                return note;
           }
      
           public void setNote(String note) {
                System.out.println("Setting note to: " + note);
                this.note = note;
           }
      
      }
      





      package com.noverka.nvkcrm.session;
      
      import org.jboss.seam.ScopeType;
      import org.jboss.seam.annotations.Begin;
      import org.jboss.seam.annotations.In;
      import org.jboss.seam.annotations.Logger;
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.Scope;
      import org.jboss.seam.annotations.web.RequestParameter;
      import org.jboss.seam.framework.EntityHome;
      import org.jboss.seam.log.Log;
      
      import com.noverka.nvkcrm.entity.Note;
      import com.noverka.nvkcrm.entity.Resource;
      
      @Name("resourceHome")
      @Scope(ScopeType.CONVERSATION)
      public class ResourceHome extends EntityHome<Resource>
      {
           private static final long serialVersionUID = 5737386867792221507L;
           @RequestParameter Long resourceId;
           @Logger Log log;
      
           @In(required=false) NoteListManager noteListManager;
      
           @Override
          public Object getId()
          {
              if (resourceId == null)
              {
                  return super.getId();
              }
              else
              {
                  return resourceId;
              }
          }
      
           @Override @Begin
          public void create() {
               log.info("Create called on resourceHome, starting a long running conversation");
              super.create();
          }
      
          @Override @Begin(join=true)
           public String persist() {
               super.persist();
      
                return "/resource.xhtml";
           }
       
           @Override @Begin(join=true)
           public String update() {
                log.info("updating the resourceHome with name: " + getInstance().getLastname() + ", " + getInstance().getFirstname());
      
                noteListManager.update();
      
                for (Note note : getInstance().getNotes()) {
                     log.info("checking persistence of note: " + note.getNote());
                     if ( !getEntityManager().contains(note) )
                     {
                          log.info("persisting a note: " + note.getNote());
                          getEntityManager().persist(note);
                     }
                     else
                     {
                          getEntityManager().merge(note);
                     }
                }
      
               super.update();
      
                return "/resource.xhtml";
           }
      
           @Override @Begin(join=true)
           public String remove() {
                super.remove();
      
                return "/resourceList.xhtml";
           }
      }
      





      package com.noverka.nvkcrm.session;
      
      import java.util.List;
      
      import org.jboss.seam.ScopeType;
      import org.jboss.seam.annotations.Begin;
      import org.jboss.seam.annotations.Factory;
      import org.jboss.seam.annotations.FlushModeType;
      import org.jboss.seam.annotations.In;
      import org.jboss.seam.annotations.Logger;
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.Scope;
      import org.jboss.seam.annotations.datamodel.DataModel;
      import org.jboss.seam.annotations.datamodel.DataModelSelection;
      import org.jboss.seam.log.Log;
      
      import com.noverka.nvkcrm.entity.Note;
      
      @Name("noteListManager")
      @Scope(ScopeType.CONVERSATION)
      public class NoteListManager {
           @Logger Log log;
      
           @In ResourceHome resourceHome;
      
           @DataModel List<Note> noteList;
           @DataModelSelection Note note;
      
           @Factory
           public void getNoteList() { 
                log.info("Initialising the note list");
               this.noteList = resourceHome.getInstance().getNotes();
           }
      
           @Begin(flushMode = FlushModeType.MANUAL, join=true )
           public void addNote()
           {
                log.info("adding a new note to the resource");
                resourceHome.getInstance().getNotes().add(new Note());
           }
      
           public void removeSelectedNote()
           {
                log.info("Removing " + note + " from the list " + noteList + " on resource " + resourceHome.getInstance());
                resourceHome.getInstance().getNotes().remove(note);
           }
      
           public void update() {
                for (Note note : noteList) {
                     log.info("update on noteListManager " + note.getId() + " with value " + note.getNote());
                }
           }
      
      }
      





      <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <ui:composition xmlns="http://www.w3.org/1999/xhtml"
          xmlns:s="http://jboss.com/products/seam/taglib"
          xmlns:ui="http://java.sun.com/jsf/facelets"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:a="http://richfaces.org/a4j"
          xmlns:rich="http://richfaces.org/rich"
          template="layout/template.xhtml">
      
      
      <ui:define name="body">
      
          <h:form id="resourceForm">
      
              <rich:panel>
                  <f:facet name="header">#{messages['label.resource.title']}</f:facet>
      
                  <s:decorate id="lastnameField" template="layout/edit.xhtml">
                      <ui:define name="label">#{messages['label.resource.lastname']}</ui:define>
                      <h:inputText id="lastnameValue" required="true" value="#{resourceHome.instance.lastname}"/>
                  </s:decorate>
      
                  <s:decorate id="firstnameField" template="layout/edit.xhtml">
                      <ui:define name="label">#{messages['label.resource.firstname']}</ui:define>
                      <h:inputText id="firstnameValue" required="true" value="#{resourceHome.instance.firstname}"/>
                  </s:decorate>
      
                          <rich:tabPanel switchType="client">
                               <rich:tab label="#{messages['label.resource.tab.address']}">
                            <s:decorate id="address1Field" template="layout/edit.xhtml">
                                <ui:define name="label">#{messages['label.resource.address1']}</ui:define>
                                <h:inputText id="address1Value" required="true" value="#{resourceHome.instance.address1}"/>
                            </s:decorate>
                            <s:decorate id="address2Field" template="layout/edit.xhtml">
                                <ui:define name="label">#{messages['label.resource.address2']}</ui:define>
                                <h:inputText id="address2Value" required="false" value="#{resourceHome.instance.address2}"/>
                            </s:decorate>
                            <s:decorate id="cityField" template="layout/edit.xhtml">
                                <ui:define name="label">#{messages['label.resource.city']}</ui:define>
                                <h:inputText id="cityValue" required="true" value="#{resourceHome.instance.city}"/>
                            </s:decorate>
                            <s:decorate id="provinceField" template="layout/edit.xhtml">
                                <ui:define name="label">#{messages['label.resource.province']}</ui:define>
                                <h:inputText id="provinceValue" required="true" value="#{resourceHome.instance.province}"/>
                            </s:decorate>
                            <s:decorate id="countryField" template="layout/edit.xhtml">
                                <ui:define name="label">#{messages['label.resource.country']}</ui:define>
                                <h:inputText id="countryValue" required="true" value="#{resourceHome.instance.country}"/>
                            </s:decorate>
                            <s:decorate id="postalcodeField" template="layout/edit.xhtml">
                                <ui:define name="label">#{messages['label.resource.postalcode']}</ui:define>
                                <h:inputText id="postalcodeValue" required="true" value="#{resourceHome.instance.postalcode}"/>
                            </s:decorate>
                               </rich:tab>
      
                               <rich:tab label="#{messages['label.resource.tab.phones']}">
                            <s:decorate id="phoneHomeField" template="layout/edit.xhtml">
                                <ui:define name="label">#{messages['label.resource.phone.home']}</ui:define>
                                <h:inputText id="phoneHomeValue" required="false" value="#{resourceHome.instance.phonehome}"/>
                            </s:decorate>
                            <s:decorate id="phoneMobileField" template="layout/edit.xhtml">
                                <ui:define name="label">#{messages['label.resource.phone.mobile']}</ui:define>
                                <h:inputText id="phoneMobileValue" required="false" value="#{resourceHome.instance.phonecell}"/>
                            </s:decorate>
                            <s:decorate id="phoneworkField" template="layout/edit.xhtml">
                                <ui:define name="label">#{messages['label.resource.phone.work']}</ui:define>
                                <h:inputText id="phoneworkValue" required="false" value="#{resourceHome.instance.phonework}"/>
                            </s:decorate>
                               </rich:tab>
           
                               <rich:tab label="#{messages['label.resource.tab.emails']}">
                            <s:decorate id="emailHomeField" template="layout/edit.xhtml">
                                <ui:define name="label">#{messages['label.resource.email.home']}</ui:define>
                                <h:inputText id="emailHomeValue" required="false" value="#{resourceHome.instance.emailhome}"/>
                            </s:decorate>
                            <s:decorate id="emailWorkField" template="layout/edit.xhtml">
                                <ui:define name="label">#{messages['label.resource.email.work']}</ui:define>
                                <h:inputText id="emailWorkValue" required="false" value="#{resourceHome.instance.emailwork}"/>
                            </s:decorate>
                               </rich:tab>
                               
                               <rich:tab label="#{messages['label.resource.tab.other']}">
                            <s:decorate id="availabilityField" template="layout/edit.xhtml">
                                <ui:define name="label">#{messages['label.resource.availability']}</ui:define>
                                <rich:calendar id="availabilityValuePicker" required="false" value="#{resourceHome.instance.availabledate}" />
                            </s:decorate>
                            <s:decorate id="functionField" template="layout/edit.xhtml">
                                <ui:define name="label">#{messages['label.resource.function']}</ui:define>
                                <h:inputText id="functionValue" required="false" value="#{resourceHome.instance.functioncode}"/>
                            </s:decorate>
                            <s:decorate id="statusField" template="layout/edit.xhtml">
                                <ui:define name="label">#{messages['label.resource.status']}</ui:define>
                                <h:inputText id="statusValue" required="false" value="#{resourceHome.instance.statuscode}"/>
                            </s:decorate>
                            <s:decorate id="rateField" template="layout/edit.xhtml">
                                <ui:define name="label">#{messages['label.resource.rate']}</ui:define>
                                <h:inputText id="rateValue" required="false" value="#{resourceHome.instance.salaryhour}"/>
                            </s:decorate>
                            <s:decorate id="salaryField" template="layout/edit.xhtml">
                                <ui:define name="label">#{messages['label.resource.salary']}</ui:define>
                                <h:inputText id="salaryValue" required="false" value="#{resourceHome.instance.salaryannual}"/>
                            </s:decorate>
                            <s:decorate id="holidayField" template="layout/edit.xhtml">
                                <ui:define name="label">#{messages['label.resource.holiday']}</ui:define>
                                <h:inputText id="holidayValue" required="false" value="#{resourceHome.instance.vacationweeks}"/>
                            </s:decorate>
                               </rich:tab>
                    </rich:tabPanel>
      
                  <div style="clear:both"/>
                     </rich:panel>
      
                     <rich:panel>
                          <f:facet name="header">#{messages['label.note.title']}</f:facet>
                          
                  <h:outputText value="#{messages['label.note.empty']}" rendered="#{noteList.rowCount eq 0}"/>
                          <rich:dataTable id="notes"
                                                    var="note"
                                                    rendered="#{noteList.rowCount gt 0}"
                                                    value="#{noteList}">
                                                    
                               <f:facet name="header">
                                    <rich:columnGroup>
                                         <rich:column>#{messages['label.note.header.timestamp']}</rich:column>
                                         <rich:column>#{messages['label.note.header.author']}</rich:column>
                                         <rich:column>#{messages['label.note.header.note']}</rich:column>
                                    </rich:columnGroup>
                               </f:facet>
                               <rich:column>#{note.created}</rich:column>
                               <rich:column>#{note.creator}</rich:column>
                               <rich:column>
                                    <s:decorate id="note" template="layout/edit.xhtml">
                                         <h:outputText    rendered="#{empty note.creator}" value="#{note.note}" />
                                         <h:inputTextarea rendered="#{not empty note.creator}"     value="#{note.note}" />
                              </s:decorate>
                               </rich:column>
                          </rich:dataTable>
                          
                          <h:panelGrid>
                               <s:button action="#{noteListManager.addNote()}" value="#{messages['label.note.button.add']}"  />
                          </h:panelGrid>
      
                          <div style="clear:both"/>
                     </rich:panel>
      
                     <rich:panel>
                          <f:facet name="header">#{messages['label.curriculumvitae.title']}</f:facet>
      
                          <h:outputText value="#{messages['label.curriculumvitae.empty']}" rendered="#{curriculumVitaeList.rowCount eq 0}"/>
                          <rich:dataTable id="cv"
                                                    var="cvItem"
                                                    rendered="#{curriculumVitaeList.rowCount gt 0}"
                                                    value="#{curriculumVitaeList}">
      
                               <f:facet name="header">
                                    <rich:columnGroup>
                                         <rich:column>#{messages['label.curriculumvitae.header.filename']}</rich:column>
                                         <rich:column>#{messages['label.curriculumvitae.header.type']}</rich:column>
                                         <rich:column>#{messages['label.curriculumvitae.header.action']}</rich:column>
                                    </rich:columnGroup>
                               </f:facet>
      
                               <rich:column>#{cvItem.name}</rich:column>
                               <rich:column>#{codeBean.getCodeDesc("cv_type", cvItem.cvtype)}</rich:column>
                               <rich:column><s:button value="#{messages['label.curriculumvitae.button.remove']}" action="#{curriculumVitaeListManager.removeSelectedCurriculumVitae()}" /></rich:column>
                          </rich:dataTable>
      
                          <h:panelGrid>
                               <s:button value="#{messages['label.curriculumvitae.button.add']}" />
                          </h:panelGrid>
                     </rich:panel>
                     
                     <rich:panel>
                          <f:facet name="header">#{messages['label.reference.title']}</f:facet>
                  <h:outputText value="#{messages['label.reference.empty']}" rendered="#{referenceList.rowCount eq 0}"/>
                          <rich:dataTable id="reference"
                                                    var="referenceItem"
                                                    rendered="#{referenceList.rowCount gt 0}"
                                                    value="#{referenceList}">
      
                               <f:facet name="header">
                                    <rich:columnGroup>
                                         <rich:column>#{messages['label.reference.header.name']}</rich:column>
                                         <rich:column>#{messages['label.reference.header.company']}</rich:column>
                                         <rich:column>#{messages['label.reference.header.created']}</rich:column>
                                    </rich:columnGroup>
                               </f:facet>
      
                               <rich:column>#{referenceItem.name}</rich:column>
                               <rich:column>#{referenceItem.company}</rich:column>
                               <rich:column>#{referenceItem.created}</rich:column>
                          </rich:dataTable>
      
                          <h:panelGrid>
                               <s:button value="#{messages['label.reference.button.add']}" />
                          </h:panelGrid>
                     </rich:panel>
                     
                     <div class="actionButtons">
                    <h:commandButton id="save"   value="Save"   action="#{resourceHome.persist}" rendered="#{!resourceHome.managed}" />
                  <h:commandButton id="update" value="Save"   action="#{resourceHome.update}"  rendered="#{resourceHome.managed}" />
                  <h:commandButton id="delete" value="Delete" action="#{resourceHome.remove}"  rendered="#{resourceHome.managed}" immediate="true" />
      
                  <s:button propagation="end" id="cancel" value="Cancel" view="/resourceList.xhtml"/>
              </div>
      
          </h:form>
      
      </ui:define>
      
      </ui:composition>