8 Replies Latest reply on Mar 18, 2010 3:43 PM by Manuel Palacio

    AJAX Form Inputs are not updated post validation failure

    Chris Kojouharov Newbie

      I'm building a very simple AJAX crud application. User has a list, clicks a list value. Value is loaded. User changes the entity and saves. All AJAX, all in a single screen.
      The problem I'm running into is that when the entity validation fails, and then a new item is selected, the form fields that did not fail the validation are not updated with the newly selected item's values. Very weird.


      Mapped Superclass:


      package com.metrics3.crud.entity;
      
      import java.io.Serializable;
      import java.util.Date;
      
      import javax.persistence.Column;
      import javax.persistence.GeneratedValue;
      import javax.persistence.GenerationType;
      import javax.persistence.Id;
      import javax.persistence.MappedSuperclass;
      import javax.persistence.PrePersist;
      import javax.persistence.PreUpdate;
      import javax.persistence.Temporal;
      import javax.persistence.TemporalType;
      
      import org.jboss.seam.security.Identity;
      
      @MappedSuperclass
      public abstract class MappedEntity implements Serializable {
           
           private static final transient String UNKNOWN_USER = "UNKNOWN_USER";
           
           private long id;
           
           private String createdBy;
           
           private Date createdOn;
           
           private String updatedBy;
           
           private Date updatedOn;
           
           @PrePersist
           public void prePersist() {
                createdBy = resolveUser();
                createdOn = new Date();
                updatedBy = createdBy;
                updatedOn = (Date) createdOn.clone();
           }
           
           @PreUpdate
           public void preUpdate() {
                updatedBy = resolveUser();
                updatedOn = (Date) createdOn.clone();
           }
           
           private String resolveUser() {
                if(Identity.instance() != null &&
                          Identity.instance().getUsername() != null)
                     return Identity.instance().getUsername();
                return UNKNOWN_USER;
           }
      
           @Column(name = "ID", nullable = false)
           @Id @GeneratedValue(strategy = GenerationType.AUTO)
           public long getId() {
                return id;
           }
      
           public void setId(long id) {
                this.id = id;
           }
      
           @Column(name = "CREATED_BY", nullable = false, length = 100)
           public String getCreatedBy() {
                return createdBy;
           }
      
           public void setCreatedBy(String createdBy) {
                this.createdBy = createdBy;
           }
      
           @Column(name = "CREATED_ON", nullable = false)
           @Temporal(TemporalType.TIMESTAMP)
           public Date getCreatedOn() {
                return createdOn;
           }
      
           public void setCreatedOn(Date createdOn) {
                this.createdOn = createdOn;
           }
      
           @Column(name = "UPDATED_BY", nullable = false, length = 100)
           public String getUpdatedBy() {
                return updatedBy;
           }
      
           public void setUpdatedBy(String updatedBy) {
                this.updatedBy = updatedBy;
           }
      
           @Column(name = "UPDATED_ON", nullable = false)
           @Temporal(TemporalType.TIMESTAMP)
           public Date getUpdatedOn() {
                return updatedOn;
           }
      
           public void setUpdatedOn(Date updatedOn) {
                this.updatedOn = updatedOn;
           }             
      }




      Entity:


      package com.metrics3.crud.entity;
      
      import javax.persistence.Column;
      import javax.persistence.Entity;
      import javax.persistence.Table;
      import javax.persistence.UniqueConstraint;
      
      import org.hibernate.validator.Length;
      import org.hibernate.validator.NotEmpty;
      import org.hibernate.validator.NotNull;
      
      @Entity
      @Table(name = "DIMENSION3S", uniqueConstraints = @UniqueConstraint(columnNames = "NAME"))
      public class Dimension3 extends MappedEntity {
           
           private static final long serialVersionUID = -1972282192725291237L;
      
           private String name;
           
           private String description;
      
           @Override
           public void prePersist() {
                fixDescription();
                super.prePersist();
           }
      
           @Override
           public void preUpdate() {
                fixDescription();
                super.preUpdate();
           }
      
           private void fixDescription() {
                System.out.println("Dimension3.fixDescription");
                if(getDescription() != null && getDescription().length() == 0)
                     setDescription(null);
           }
      
           @Column(name = "NAME", nullable = false, length = 100)
           @NotNull @NotEmpty @Length(min = 1, max = 100)
           public String getName() {
                return name;
           }
      
           public void setName(String name) {
                this.name = name;
           }
      
           @Column(name = "DESCRIPTION", nullable = true, length = 4000)
           public String getDescription() {
                return description;
           }
      
           public void setDescription(String description) {
                this.description = description;
           }
      }




      EntityHome:


      package com.metrics3.crud.session;
      
      import java.io.Serializable;
      
      import javax.faces.application.FacesMessage;
      import javax.persistence.EntityManager;
      
      import org.jboss.seam.ScopeType;
      import org.jboss.seam.annotations.Begin;
      import org.jboss.seam.annotations.End;
      import org.jboss.seam.annotations.FlushModeType;
      import org.jboss.seam.annotations.In;
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.RaiseEvent;
      import org.jboss.seam.annotations.Scope;
      import org.jboss.seam.faces.FacesMessages;
      
      import com.metrics3.crud.entity.Dimension3;
      
      @Name("dimension3Home")
      @Scope(ScopeType.CONVERSATION)
      public class Dimension3Home implements Serializable {
      
           private static final long serialVersionUID = -7705525888437978995L;
      
           @In private EntityManager entityManager;
           
           @In(create = true) private FacesMessages facesMessages;
           
           private Dimension3 instance;
           
           @Begin(join = true, flushMode = FlushModeType.MANUAL)
           public void create() {
                instance = new Dimension3();
           }
           
           @Begin(join = true, flushMode = FlushModeType.MANUAL)
           public void read(long id) {
                instance = entityManager.find(Dimension3.class, id);
           }
           
           public void update() {
                try {
                     entityManager.merge(instance);
                     entityManager.flush();
                } catch (Exception e) {
                     e.printStackTrace();
      FacesMessage("Dimension3Home.update.exception:"+e.getMessage()+":"+e.getClass()));
                }
           }
           
           @RaiseEvent("org.jboss.seam.afterTransactionSuccess.Dimension3")
           public void delete(long id) {
                entityManager.createQuery("delete from Dimension3 where id = :id").setParameter("id", id).executeUpdate();
                clear();
           }
           
           @End
           public void clear() {
                instance = null;
           }
      
           public Dimension3 getInstance() {
                return instance;
           }
      
           public void setInstance(Dimension3 instance) {
                this.instance = instance;
           }
      }





      View:


      <!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:rich="http://richfaces.org/rich"
           xmlns:a="http://richfaces.org/a4j"
           template="layout/template.xhtml">
           
           <ui:define name="body">
           <h:messages id="globalMessages" globalOnly="true" styleClass="message" />
           <a:form id="entityPanelForm" ajaxSubmit="true">
                <rich:panel id="entityPanel" rendered="#{dimension3Home.instance != null}" >
                  <f:facet name="header">Dimension 3 [#{dimension3Home.instance.id}]</f:facet>
                  <s:decorate id="nameDecoration" template="layout/edit.xhtml" >
                      <ui:define name="label">Name</ui:define>
                      <h:inputText id="nameField" required="true" value="#{dimension3Home.instance.name}" size="50" />
                  </s:decorate>
                  <s:decorate id="descriptionDecoration" template="layout/edit.xhtml" >
                      <ui:define name="label">Description</ui:define>
                      <h:inputTextarea id="descriptionField" value="#{dimension3Home.instance.description}" rows="10" cols="40" />
                  </s:decorate>
                  <div class="actionButtons" style="clear:both">
                       <a:commandButton value="Create AJAX" action="#{dimension3Home.update}" 
                                        reRender="entityPanelForm, entityTableForm, globalMessages"
                                        rendered="#{dimension3Home.instance.id == 0}" />
                       <a:commandButton value="Update AJAX" action="#{dimension3Home.update}" 
                                        reRender="entityPanelForm, entityTableForm, globalMessages" 
                                        rendered="#{dimension3Home.instance.id != 0}"/>
                       <a:commandButton value="Delete AJAX" immediate="true" 
                                       action="#{dimension3Home.delete(dimension3Home.instance.id)}"
                                     reRender="entityPanelForm, entityTableForm, globalMessages"
                                     rendered="#{dimension3Home.instance.id > 0}" />
                       <a:commandButton value="Close AJAX" reRender="entityPanelForm, globalMessages"
                                       action="#{dimension3Home.clear}" 
                                       immediate="true" >
                       </a:commandButton>
                       <input type="reset"/>
                      <br />
                      #{dimension3Home.instance.name}
                      <br />
                      #{dimension3Home.instance.description}
                   </div>
              </rich:panel>
           </a:form>
           <a:form id="entityTableForm" ajaxSubmit="true" ajaxSingle="true">
                <rich:dataTable id="entityTable" width="auto" rows="10"
                     value="#{dimension3Query.resultList}" var="e" >
                     <f:facet name="header">
                          <h:panelGroup>
                               Dimension3s [#{dimension3Query.resultList.size}]
                               <a:commandLink value="{Reload}" style="margin-left: 20px;" 
                                    action="#{dimension3Query.refresh}" reRender="entityTableForm, globalMessages" />
                          </h:panelGroup>
                     </f:facet>
                     <f:facet name="footer">
                          <a:commandLink      value="Create AJAX" limitToList="true" 
                                            action="#{dimension3Home.create}"
                                          reRender="entityPanelForm, globalMessages" />
                     </f:facet>
                     <h:column>
                          <f:facet name="header">Action</f:facet>
                          <a:commandLink  value="Open AJAX" limitToList="true" 
                                            action="#{dimension3Home.read(e.id)}"
                                          reRender="entityPanelForm, globalMessages" />
                  </h:column>
                     <h:column>
                          <f:facet name="header">Id</f:facet>
                      #{e.id}
                  </h:column>
                     <h:column>
                          <f:facet name="header">Name</f:facet>
                          #{e.name}
                     </h:column>
                </rich:dataTable>
                <rich:datascroller for="entityTable"/>
          </a:form>
           </ui:define>
      </ui:composition>


        • 1. Re: AJAX Form Inputs are not updated post validation failure
          System Administrator Expert

          Which form are you referring to when you say the fields in the form are not being updated?


          did you check the values of the getter methods in the debugger to see if it's the data that is not being set properly or a re-rendering problem?


          • 2. Re: AJAX Form Inputs are not updated post validation failure
            Chris Kojouharov Newbie
            The jsf page consists of two forms:
            1. Entity Panel Form - detailed information about a selected entity
            2. Entity Table Form - generic info about all entities from an executed query

            Here is the scenario:
            1. User goes to page - table loads with data
            2. User clicks on an item in the entity table form - via ajax, the entity panel form is updated with the details about a particular entity
            3. User deletes the input from the nameField (which is required). User leaves the description field as is. User clicks save via ajax button which should persist the entity.
                 - since the nameField is required, a validation error is thrown in JSF (the model is never updated)
            4. User clicks on another entity
                 - the entity panel form is rerendered with information from the new entity, however, the description field is rerendered with information from the old entity. In the form i have 1 control and 1 output field which hit the same field from the model (#{dimension3Home.instance.description}). The output field renders the correct information, but the inputTextArea field renders the information from the old model item. (somehow the request values are not cleared). Also, the validation errors from the previously loaded item are not cleared. Have in mind that I am reloading the whole form, the validation errors are within it, but are not cleared. The input field of for the model value that failed validation is rerendered with the correct value.

            In all:
               if a differnt entity is reloaded via ajax post failed validation of another entity, only the values that have failed are reloaded properly, and the error messages are not cleared

            • 3. Re: AJAX Form Inputs are not updated post validation failure
              Chris Kojouharov Newbie

              I have also tried via non ajax approach. I'm using Conversation backed been with begin join true and flushmode manual

              • 4. Re: AJAX Form Inputs are not updated post validation failure
                Chris Kojouharov Newbie
                Ended up fixing this on my own. Looks like broken server side state saving implementation for JSF 1.2
                If anyone else is interested, you need this in your web.xml file.

                `<context-param>
                      <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
                      <param-value>client</param-value>
                </context-param>`


                More info on this issue:


                `http://forum.java.sun.com/thread.jspa?threadID=605086&messageID=3424036`



                s:links work fine. h:anything & a:anything does not
                • 5. Re: AJAX Form Inputs are not updated post validation failure
                  Robin Young Newbie
                  Chris,

                  I have the same problem but switching to client STATE_SAVING_METHOD rather than server doesn't solve it for me.

                  I suspect I am using a faulty lib somewhere along the line:-

                  Jboss 4.2.2.GA
                  Seam 2.0.2.SP1
                  Facelets 1.1.14
                  Jsf 1.2_09-b02-FCS

                  Is it possible for you to post the versions of the libs you are using and your full web.xml file in case there is another setting that causes the fault in conjunction with the state saving method?

                  • 6. Re: AJAX Form Inputs are not updated post validation failure
                    Matthew Wright Newbie

                    In the last couple of days I have come up against this issue and just thought I'd post the work around that I have taken to using.


                    Basically it looks like the default behavior for JSF is that UIInputs that do not fail validation are sticky i.e. they retain their values through multiple requests. This behavior makes sense on many occasions as most of the time users will just correct the errors and resubmit the form, but on the odd occasion is not really desirable such as when using a form to display multiple objects selected by ajax requests.


                    Anyway the work around is to manually reset the values for the components that do not fail the validation in your validator method like:




                    public void validateCurrentBudget(FacesContext fc, UIComponent uic, Object obj) throws ValidatorException{
                    
                         BigDecimal currentBudget = (BigDecimal)obj;
                         BigDecimal budget = (BigDecimal)uic.findComponent("budget").getAttributes().get("value");
                              
                              
                         if(currentBudget.compareTo(budget) == 1){
                              fc.addMessage(uic.getClientId(fc),new FacesMessage(FacesMessage.SEVERITY_ERROR, messages.get("currentBudgetError"),
                                             messages.get("currentBudgetError")));
                    
                                    //reset the none failing components here
                              ((UIInput)uic.findComponent("budget")).setValue(null);
                              ((UIInput)uic.findComponent("groupName")).setValue(null);
                                 throw new ValidatorException(new FacesMessage());
                         }
                    }



                    This is a bit of a hack and if any knows a more elegant way to solve this please share the wealth!


                     

                    • 7. Re: AJAX Form Inputs are not updated post validation failure
                      Coral Featherstone Newbie

                      Can also clear fields with


                      FacesContext facesContext = FacesContext.getCurrentInstance();
                      UIViewRoot uiViewRoot = facesContext.getViewRoot();
                      uiViewRoot.findComponent("path:to:form").getChildren().clear()     ;