9 Replies Latest reply on Jul 28, 2008 9:52 AM by ericjava.eric.chiralsoftware.net

    Form fields not clearing after submit

    admin.admin.email.tld

      I'm doing a Seam presentation next week and have already asked JBoss dev support and they weren't able to solve this problem.


      I have a form (see xhtml below) that saves some fields via an Entity class and SFSB to the db (sql server or hsqldb).


      The persistence is fine.  When the page refreshes after clicking submit, the data in the form is still there in the JSF UI.  How can I clear it?  This is happening in another xhtml/form using the same entity class.


      thx.


      createContact.xhtml:


      <!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:ui="http://java.sun.com/jsf/facelets"
                             xmlns:h="http://java.sun.com/jsf/html"
                             xmlns:f="http://java.sun.com/jsf/core"
                             xmlns:a4j="http://richfaces.org/a4j"
                             xmlns:rich="http://richfaces.org/rich"
                       xmlns:s="http://jboss.com/products/seam/taglib"
                       template="layout/template.xhtml">
         
         <ui:define name="body">
                <a4j:log popup="true" level="ALL" style="width: 800px; height: 300px;"></a4j:log>
                <rich:panel header="Create a new contact">
                
                 <h:form id="form1">
                        <rich:messages errorClass="errors" infoClass="successful" rendered="#{createContacts.getDisplayMessages()}"/>    
                     <s:validateAll>                     
                             <h:panelGrid columns="3">   
                                  Name: <h:inputText id="contactname" value="#{contact.contactName}" required="true">
                                                <a4j:support ajaxSingle="true" event="onblur"/>
                                           </h:inputText>
                                           <rich:message for="contactname" styleClass="errors" />
                                                                   
                                  Email: <h:inputText id="email" value="#{contact.email}" required="true">
                                              <a4j:support ajaxSingle="true" event="onblur"/>
                                           </h:inputText>
                                           <rich:message for="email" styleClass="errors" />
                                                                                                
                                  Home phone:     <h:inputText id="homephone" value="#{contact.homePhone}" required="true">
                                                      <a4j:support ajaxSingle="true" event="onblur"/>
                                                  </h:inputText>
                                                  <rich:message for="homephone" styleClass="errors" />
                                                                
                             </h:panelGrid>                            
                      </s:validateAll>       
                                    
                      <h:commandButton value="Submit" action="#{createContacts.createContact}"/>
                      <h:commandButton value="Help">
                               <rich:componentControl for="mpHelp" event="onclick"     disableDefault="true" operation="show"/>                    
                        </h:commandButton>
                                                                    
                 </h:form>      
            </rich:panel>
            
            <rich:modalPanel  id="mpHelp" minHeight="300" minWidth="300">
                  <f:facet name="header">
                      <h:outputText value="Help Dialog" />
                  </f:facet>
                      <h:outputText value="This is a rich:modalPanel example." />
                       <h:commandButton value="Close">
                               <rich:componentControl for="mpHelp" event="onclick"     disableDefault="true" operation="hide"/>                    
                        </h:commandButton>
            </rich:modalPanel>  
            
            
         </ui:define>
            
         <ui:define name="sidebar">
                   
                             <p>This facelet features a template facelet, rich:panel, h:form, Hibernate Validators with dynamic AJAX validation, rich:modalPanel and a4j:log.</p>
                        
                             <p>Navigate to http://localhost:8080/Contacts/debug.seam to see the current LRC's and other interesting Seam contextual and component info.</p>
                             
                             <p>Seam automatically calls the validation API when a JSF form is submitted via s:validate tags.</p>
                             
                             <p>When using Seam with Hibernate Validators, like @Email and @Pattern, validation occurs on client-side and server-side. </p>
                        
                             <p>Using FF 3, press CTRL-SHIFT-L to display the a4j:log output and view the AJAX request/reponse.
                             When using a4j:log with IE7 there is a problem/bug: <h:outputLink value="https://jira.jboss.org/jira/browse/RF-4008"/></p>
                             
         </ui:define>
         
         
        
      </ui:composition>
      
      



      CreateContactsAction.java:


      package com.cox.contacts.session;
      
      import javax.ejb.Stateless;
      import javax.faces.application.FacesMessage;
      import javax.persistence.EntityManager;
      
      import org.jboss.seam.ScopeType;
      import org.jboss.seam.annotations.In;
      import org.jboss.seam.annotations.Logger;
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.Out;
      import org.jboss.seam.annotations.Scope;
      import org.jboss.seam.faces.FacesMessages;
      import org.jboss.seam.log.Log;
      
      import com.cox.contacts.entity.Contact;
      
      @Stateless
      @Scope(ScopeType.CONVERSATION)  //default scope is conversation for SFSB, this component uses a temporary conversation
      @Name("createContacts")
      public class CreateContactsAction implements CreateContacts
      {
      
         @In(create=true)
         private Contact contact;
                 
         //don't need create=true for entityManager injection, it's auto-created in components.xml
         @In
         private EntityManager entityManager;
         
         @In(create=true)
         private FacesMessages facesMessages;
         
         @Out(required=false)
         private Boolean displayMessages;
         
         @Logger
         private Log log;
         
         public void createContact()
         {        
              log.info(this.getClass().getName() + ": createContact() started");
              log.info(this.getClass().getName() + contact);
              entityManager.persist(contact);
              facesMessages.add(FacesMessage.SEVERITY_INFO, "New contact successfully created.");
              displayMessages = true;
         }
         
         public Boolean getDisplayMessages() {
              return displayMessages;
         }
         
      }
      



      Contact.java:


      package com.cox.contacts.entity;
      
      import java.io.Serializable;
      
      import javax.persistence.Column;
      import javax.persistence.Entity;
      import javax.persistence.GeneratedValue;
      import javax.persistence.GenerationType;
      import javax.persistence.Id;
      import javax.persistence.Table;
      
      import org.hibernate.validator.Email;
      import org.hibernate.validator.Length;
      import org.hibernate.validator.NotNull;
      import org.hibernate.validator.Pattern;
      import org.jboss.seam.ScopeType;
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.Scope;
      
      //Q: what is the default scope for an Entity class?
      //A: conversation
      //Q: how can you find out what context a particular Seam component is scoped to?
      //A: view server.log on startup/deployment of app
      @Entity
      @Name("contact")
      @Scope(ScopeType.CONVERSATION)
      @Table(name="contacts")
      public class Contact implements Serializable
      {
         private static final long serialVersionUID = 1881413500711441951L;
         
         private Long   contactId;
         private String contactName;   
         private String homePhone;
         private String email;
         
         public Contact(String contactName, String homePhone, String email)
         {      
            this.contactName = contactName;
            this.homePhone = homePhone;
            this.email = email;
         }
         
         public Contact() {}
         
         //NOTE: when using HSQLDB, comment out the getContactId() and setContactId(), so that the ContactId column is not generated
         //      during hibernate create-drop on startup.
         
         
         @Id //primary key designation
         @GeneratedValue(strategy=GenerationType.IDENTITY) //use SQL server identity seed
         @Column(name="ContactId")
         public Long getContactId() {
              return contactId;
         }
         
         public void setContactId(Long contactId) {
              this.contactId = contactId; 
         }
         
         
         //@Id //<-- use for HSQLDB only!
         @NotNull(message="Contact Name is required")
         @Length(min=5, max=15, message="Contact name must be 5 to 15 characters long")
         @Column(name="ContactName")
         public String getContactName()
         {
            return contactName;
         }
      
         public void setContactName(String contactName)
         {
            this.contactName = contactName;
         }
         
         @NotNull(message="Home phone is required")
         @Pattern(regex="^(?:\\([2-9]\\d{2}\\)\\ ?|[2-9]\\d{2}(?:\\-?|\\ ?))[2-9]\\d{2}[- ]?\\d{4}$", message="Home phone is not valid")
         @Column(name="HomePhone")
         public String getHomePhone()
         {
            return homePhone;
         }
      
         public void setHomePhone(String homePhone)
         {
            this.homePhone = homePhone;
         }
         
         @NotNull(message="Email is required")
         @Email(message="Email is not valid")
         @Column(name="Email")
         public String getEmail()
         {
            return email;
         }
      
         public void setEmail(String email)
         {
            this.email = email;
         }
            
         
         @Override
         public String toString() 
         {
            return "Contact(" + contactName + ", " + homePhone + ", " + email + ")";
         }
      }
      

        • 1. Re: Form fields not clearing after submit
          admin.admin.email.tld
          using Seam 2.0.0.GA and RF 3.2.0.SR1 with

          JBoss (MX MicroKernel) [4.3.0.GA_CP01 (build: SVNTag=JBPAPP_4_3_0_GA_CP01 date=200804211657)]
          • 2. Re: Form fields not clearing after submit
            ericjava.eric.chiralsoftware.net

            This is easy.  The form fields don't clear because the same entity is still there.  In createContact you persist the entity but you don't change the values, so the values show up again.


            There are two things you could do:



            1. In createContact you could make it set the entity to a new entity, by saying contact = new Contact().

            2. Better way is to have createContact return a string, like created, and then use pages.xml to specify a redirect for that outcome, so you can take the user to a page that says, thank you for signing up or something.  Or you could still have it return null, and use a from-action as the navigation rule, because that action won't be called unless the values validate.



            One other thing: you're using Seam 2.0.0 GA.  You should really update to the current version.  You should also update your JBoss AS.


            -------------


            JBoss Seam training

            • 3. Re: Form fields not clearing after submit
              admin.admin.email.tld

              Thanks for the reply.  you'd think it would be easy. 


              The use case is to redirect to the same page after submission so they can submit another new Contact.


              The backing bean is currently a SLSB with conversation scope.  I also tried SLSB with default scope (stateless) and SFSB with conversation scope.  Nothing worked to clear the form after submit.  I tried setting the contact instance to null as well.


              I didn't try:


              contact = new Contact()

              yet thinking I shouldn't have to do any of this kind of stuff b/c the SFSB/SLSB are using temporary conversations and are destroyed after the request/response cycle.  The entity class by default is conversation-scoped as well so I'm assuming that it should be destroyed as well after the request/response cycle.


              Either there's a bug in here somewhere or I'm not understanding how the context/conversation management works.


              I also did have something like:


              <page view-id="/createContacts.xhtml">
                      <navigation from-action="#{createContacts.createContact}">
                          <redirect view-id="/createContacts.xhtml"/>
                      </navigation>
               </page>



              in my pages.xml which didn't seem to do anything different that leaving that xml fragment out (by default it redirects to the same xhtml).


              when I tried a workaround to this problem that trying to redirect to the next screen (searchContacts.xhtml) as follows, Seam ignored it and redirected to createContacts.xhtml.  pretty weird unless I had a type I guess.


              <page view-id="/createContacts.xhtml">
                      <navigation from-action="#{createContacts.createContact}">
                          <redirect view-id="/searchContacts.xhtml"/>
                      </navigation>
               </page>



              Unfortunately, we have something called cox-gen which is a mod to seam-gen and it uses Seam 2.0.0.GA, perhaps I will try some of your suggestions and then Seam 2.0.1.GA to see if that helps.


              This is very frustrating, the JBoss tech sppt guy was unable to help as well (he asked me to add a @End annotation where there's no @Begin or LRC to begin with - that is not logical to me).

              • 4. Re: Form fields not clearing after submit
                ericjava.eric.chiralsoftware.net

                Hi Arbi,


                Help is on the way!


                You do want to do the


                contact = new Contact()



                thing.  Yes it's a temporary conversation, but the temporary conversation, and all the objects that go with it, exist until the end of the render response phase, so they exist long enough to fill in those fields.  So just creating a new Contact() will do the trick.


                However, because your Contact entity is also a Seam component (ie, has a @Name annotation), just creating a new Contact within the SB won't change the Seam component, so... see below.



                The entity class by default is conversation-scoped as well so I'm assuming that it should be destroyed as well after the request/response cycle.

                Yes.  However, I usually don't put a @Name annotation on entities.  I find it to be confusing.  I would rather have my Seam components (POJOs and SBs) manage entities by outjecting them if I want them to.  In this case, you could take the @Name annotation off the Contact entity, and instead refer to createContacts.contact, and let createContacts control the lifecycle of the Contact.  It can even outject it so you can refer to contact all by itself, but it's still under the lifecycle control of createContacts.


                Anyway, back to the problem:  For a page that re-displays like that, here's how I might do it:


                private Contact newContact;
                public Contact getNewContact() { return newContact; }
                
                public void createContact()
                   {        
                        entityManager.persist(contact);
                           newContact = contact;
                           contact = new Contact();
                        facesMessages.add(FacesMessage.SEVERITY_INFO, "New contact successfully created.");
                        displayMessages = true;
                   }
                



                And then in the XHTML side, you could say:


                <rich:panel rendered="#{createContacts.newContact != null}">
                <f:facet name="header">New contact created</f:facet>
                <h:panelGrid columns="2">
                <h:outputText value="Name:"/>
                <h:outputText value="#{createContacts.newContact.name}"/>
                (etc)
                </h:panelGrid>
                </rich:panel>



                Re: the page actions not working: They do work.  Something is wrong with the layout or filenames or something if they are not.



                Unfortunately, we have something called cox-gen which is a mod to seam-gen and it uses Seam 2.0.0.GA, perhaps I will try some of your suggestions and then Seam 2.0.1.GA to see if that helps.

                All the stuff above should work in Seam 2.0.0GA.  I only recommend the upgrade because there have been a lot of feature improvements and bug fixes since then.  It wouldn't be hard to modify even a customized seam-gen to do this.



                This is very frustrating, the JBoss tech sppt guy was unable to help as well

                Our JBoss services might be able to help as well.

                • 5. Re: Form fields not clearing after submit
                  admin.admin.email.tld

                  hey thanks again!


                  My demo app is now behaving properly (after fixing a few other bugs as well).


                  I used

                  contact = new Contact();

                  which forced the form fields to clear.  I remember trying
                  @End(beforeRedirect=true)

                  as well before and that didn't force the clearing either.


                  I found a snippet from Allen's book regarding this:



                  A long-running conversation remains active over a series of requests in correlation with the transfer of the conversation id. In the absence of a long-running conversation, Seam creates a temporary conversation to serve the current request. A temporary conversation is initialized immediately following the Restore View phase of the JSF life cycle and is destroyed after the Render Response phase.

                  So if that's true, then why doesn't setting

                  contact = null;

                  instead of
                  contact = new Contact();

                  achieve the same end result?  Wouldn't setting the instance to null force Seam to instantiate a new instance of Contact entity?


                  The only other thing I found that I thought was strange was that I had to move the @Begin to another method in the 2nd use case (for search, delete, edit contact).


                  The erroneous behavior was that based on the conversations listed in http://localhost:8080/Contacts/debug.seam, the LRC was only being terminated when I clicked delete, not on the save from editContact.xhtml.


                  After I moved the @Begin as follows, the LRC was being terminated properly when I clicked either button.  Anybody know why or is this a bug?


                  @Stateful
                  @Scope(ScopeType.CONVERSATION)  //default scope is conversation for SFSB
                  @Name("searchContacts")
                  public class SearchContactsAction implements SearchContacts
                  {
                  
                     @In(create=true)
                     @Out(required=false)
                     private Contact contact;
                     
                     @DataModel
                     private List<Contact> contactList;
                  
                     @DataModelSelection
                     @Out(required=false)
                     private Contact selectedContact;
                          
                     @In
                     private EntityManager entityManager;
                     
                     @In(create=true)
                     private FacesMessages facesMessages;
                        
                     @Logger
                     private Log log;
                     
                     //@Begin(join=true)
                     @Factory("contactList")
                     public void getContacts(){
                          log.info(getClassName() + ": begin searchContacts()");
                          contactList = entityManager.createQuery("SELECT c FROM Contact c ORDER BY c.contactName").getResultList();
                     }
                     
                     public void goToEditScreen() {
                          log.info(getClassName() + ": begin getSelectedContact()");
                          
                     }
                     
                     @Begin(join=true)
                     public void searchContacts(){
                          log.info(getClassName() + ": begin searchContacts()");
                          //execute a JPA query to get a list of Contacts from the database...
                          log.info("contact.contactname = "+contact.getContactName());
                          if (contact.getContactName() != null && !contact.getContactName().equals("")) 
                          {   
                               contactList = entityManager.createQuery("SELECT c "+
                                                                                    "FROM Contact c "+
                                                                                    "WHERE c.contactName LIKE '%" + contact.getContactName() + "%'")
                                                                    .getResultList();
                             }
                          else //retrieve all Contacts
                          {
                               contactList = entityManager.createQuery("SELECT c FROM Contact c ORDER BY c.contactName").getResultList();
                          }
                          
                          for (Contact contact : contactList) {
                               log.info(this.getClass().getName() + contact);
                          }
                          //In the absence of a LRC, Seam creates a temporary conversation to serve the current request.
                          //A temporary conversation is initialized immediately following the Restore View phase of the 
                          //JSF life cycle and is destroyed after the Render Response phase.
                          //In order to clear out the fields of the form, we need to instantiate a new Contact entity.
                          contact = new Contact();
                          
                     }
                     
                     @End
                     public void deleteContact() {
                          log.info(getClassName() + ": begin deleteContact()");
                          //merge the entity back into the persistence context prior to delete
                          Contact toDelete = entityManager.merge(selectedContact);
                          entityManager.remove(toDelete);
                          facesMessages.add(FacesMessage.SEVERITY_INFO, "Contact "+toDelete.getContactName()+" successfully deleted.");
                          contactList = entityManager.createQuery("select c from Contact c ORDER BY c.contactName").getResultList();      
                          contact = new Contact();
                     }
                     
                     @End
                     public void saveChanges() {
                          log.info(getClassName() + ": begin saveChanges()");
                          entityManager.merge(selectedContact);
                          facesMessages.add(FacesMessage.SEVERITY_INFO, "Contact "+selectedContact.getContactName()+" successfully modified.");  
                          contactList = null;
                          contact = new Contact();
                     }
                     
                     private String getClassName(){
                          //use Reflection API to get class name...
                          return this.getClass().getName();
                     }
                     
                     @Destroy @Remove
                     public void destroy() {
                          log.info(getClassName() + ": begin destroy()");
                     }
                     
                  
                  }
                  

                  • 6. Re: Form fields not clearing after submit
                    admin.admin.email.tld

                    more guidance on the original form values problem from Allen:



                    Note that the term 'end' is deceptive. Ending a conversation merely demotes it from long-running to temporary—it doesn’t destroy it outright. It’s terminated only after the view has been rendered. That means that whatever values were present in the conversation remain available in the Render Response phase that immediately follows the demotion.
                    • 7. Re: Form fields not clearing after submit
                      ericjava.eric.chiralsoftware.net

                      Wouldn't setting the instance to null force Seam to instantiate a new instance of Contact entity?

                      Again, when you reference createContacts.contact in the XHTML, you are not referencing the same object that is contact in the XHTML, because you are using @Name to create the component by Seam.


                      This is why I don't put @Name on my entity classes.  It can get confusing: who is managing it, Seam, or some of my classes?

                      • 8. Re: Form fields not clearing after submit
                        christian.bauer

                        Arbi Sookazian wrote on Jul 27, 2008 06:10:

                        contactList = entityManager.createQuery(
                          "SELECT c FROM Contact c WHERE c.contactName LIKE '%" + contact.getContactName() + "%'"
                        ).getResultList();
                        




                        This is SQL injection security hole.

                        • 9. Re: Form fields not clearing after submit
                          ericjava.eric.chiralsoftware.net

                          Good catch!  It sure is.  For Arbi: this is what it should look like:


                          contactList = entityManager.createQuery(
                            "SELECT c FROM Contact c WHERE c.contactName LIKE :s").
                          setParameter("s", "%" + contact.getContactName() + "%").
                          getResultList();