1
votes

I would like to ask some help in understanding a particular behaviour that JSF shows when Validation Phase fails.

I'm using:

  • Tomcat 7.0.2
  • JSF 1.2_12
  • RichFaces 3.3.3

Problem description.

I wrote a form with 4 input fields: an inputText and 3 selectOneMenu. The inputText is required while the selectOneMenus don't require any validation. Attached to the first selectOneMenu (row 32), is an a4j:support tag so that, whenever the change event is fired, the list of items of the second and the third selectOneMenu (row 44 and 58) are correctly filled. In particular, after loading the two lists of items, the method forces the value of the second and the third selectOneMenu to null. This mechanism seems to work fine until I submit the form without filling the input text: as expected, JSF validation fails but when I change the value of the first selectOneMenu, the page keeps displaying the values specified before JSF validation failed for the second and the third selectOneMenu (note that the actionListener is still called and the values of the second and the third selectOneMenu are still forced to null).

Since I'm using a simple PhaseListener, I noticed the following: before JSF validation fails, every time I change the value of the first selectOneMenu, JSF life cycle always calls the get method for the second and the third selectOneMenu during the Render Response Phase. In this way, JSF is able to "see" that those values have been set to null during the Invoke Application Phase. After validation fails, JSF stops calling those getters when I change the value of the first selectOneMenu.

Here is my view

<?xml version='1.0' encoding='UTF-8' ?> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:a4j="http://richfaces.org/a4j"
      xmlns:rich="http://richfaces.org/rich">

<head>
  <title>Prove Rich</title>
</head>

<body>

  <h2>Prove Rich</h2>

  <f:view>

  <a4j:outputPanel ajaxRendered="true">
    <h:messages style="color:red" />
  </a4j:outputPanel>

  <h:form>

  <p>
    Input required: <h:inputText value="#{provaProbReplyBean.inputRequired}" required="true" />
  </p>

  <p>
    <h:outputText value="Scegli il canale:" />
    <h:selectOneMenu value="#{provaProbReplyBean.canale}">

       <f:selectItem itemLabel="--" itemValue="" />
       <f:selectItem itemLabel="Profamily" itemValue="Profamily" />
       <f:selectItem itemLabel="Captive" itemValue="Captive" />

       <a4j:support event="onchange" action="#{provaProbReplyBean.caricaProcBanche}" ajaxSingle="true" reRender="procedure, banche" />
    </h:selectOneMenu>
  </p>

  <p>
    <h:outputText value="Scegli la procedura:" />
    <h:selectOneMenu id="procedure" value="#{provaProbReplyBean.procedura}">

       <f:selectItem itemLabel="--" itemValue="" />

       <f:selectItems value="#{provaProbReplyBean.procedureList}" />

       <!-- immediately save the current value -->
       <a4j:support event="onchange" ajaxSingle="true" />

    </h:selectOneMenu>
  </p>

  <p>
    <h:outputText value="Scegli la banca:" />
    <h:selectOneMenu id="banche" value="#{provaProbReplyBean.banca}">

       <f:selectItem itemLabel="--" itemValue="" />

       <f:selectItems value="#{provaProbReplyBean.bancheList}" />

       <!-- immediately save the current value -->
       <a4j:support event="onchange" ajaxSingle="true" />

    </h:selectOneMenu>
  </p>

  <p><h:commandButton value="Submit" /></p>

  </h:form>

  </f:view>

</body>

</html>

And here is my model:

public class ProvaProbReply {

    private String inputRequired;

    private String canale;
    private String procedura;
    private String banca;

    private Map<String, List<SelectItem>> canaliProc = new HashMap<String, List<SelectItem>>();
    private Map<String, List<SelectItem>> canaliBanche = new HashMap<String, List<SelectItem>>();

    private List<SelectItem> procedureList = new ArrayList<SelectItem>();
    private List<SelectItem> bancheList = new ArrayList<SelectItem>();

    public ProvaProbReply() {

        List<SelectItem> l = new ArrayList<SelectItem>();
        l.add(new SelectItem("Cessione del quinto"));
        l.add(new SelectItem("Credito al consumo"));
        l.add(new SelectItem("Mutui"));

        canaliProc.put("Profamily", l);

        l = new ArrayList<SelectItem>();
        l.add(new SelectItem("Credito al consumo"));

        canaliProc.put("Captive", l);

        l = new ArrayList<SelectItem>();

        canaliBanche.put("Profamily", l);

        l = new ArrayList<SelectItem>();
        l.add(new SelectItem("BDL"));
        l.add(new SelectItem("BM"));
        l.add(new SelectItem("BPM"));
        l.add(new SelectItem("CRA"));

        canaliBanche.put("Captive", l);
    }

    public String getInputRequired() {

        return inputRequired;
    }

    public void setInputRequired(String ir) {

        inputRequired = ir;
    }

    public String getCanale() {

        return canale;
    }

    public void setCanale(String c) {

        canale = c;
    }

    public String getProcedura() {

        System.out.println("\ngetProcedura called\n");
        return procedura;
    }

    public void setProcedura(String p) {

        procedura = p;
    }

    public String getBanca() {

        System.out.println("\ngetBanca called\n");
        return banca;
    }

    public void setBanca(String b) {

        banca = b;
    }

    public List<SelectItem> getProcedureList() {

        return procedureList;
    }

    public List<SelectItem> getBancheList() {

        return bancheList;
    }

    public String caricaProcBanche() {

        System.out.println("\nListener called\n");

        procedureList.clear();
        bancheList.clear();

        if(canale != null && !canale.equals("")) {

            procedureList.addAll(canaliProc.get(canale));
            bancheList.addAll(canaliBanche.get(canale));
        }

        System.out.println("BEFORE setting:\n");

        System.out.println("\nProcedura: "+procedura+"\n");
        System.out.println("Banca: "+banca+"\n");

        procedura = null;
        banca = null;

        System.out.println("\n\n\nAFTER setting:\n");

        System.out.println("\nProcedura: "+procedura+"\n");
        System.out.println("Banca: "+banca+"\n");

        return "";
    }
}
1
did you solve this problem ?Julián Chamalé

1 Answers

2
votes

It sounds like you are observing the expected behaviour of EditableValueHolder types.

If validation or conversion of types fails, the form will be rerendered in the state it was submitted. This is hinted at in the documentation for the Apply Request Values phase (JSF 2 spec):

At the end of this phase, all EditableValueHolder components in the component tree will have been updated with new submitted values included in this request (or enough data to reproduce incorrect input will have been stored, if there were conversion errors).

If conversion or validation fails, the Update Model Values phase will not be run (none of the setters will be called on the beans.) If the user just submitted a complex form, they wouldn't expect all the fields to be wiped because one was wrong. The renderers emit the value submitted by the user and do not call the getters (see isValid() and getSubmittedValue()).