1
votes

I found that disabled attribute for any validator I use in JSF2 is evaluated only in the first cycle if my managed bean is ViewScoped.

But I would like to make use of disabled attribute for my validators based on data I get from 4th update phase. So I would expect it reevaluated in all cycles performed on the same view.

Example xhtml page:

<!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:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core">

<h:head></h:head>
<h:body>
    <h:form>
        <h:panelGrid id="pnlGrid">          
            <h:inputText id="someValueId" value="#{testPageBean.someValue}">                
                <f:validateLength minimum="2" maximum="4" 
                   disabled="#{testPageBean.disableValidateLength}"/>
            </h:inputText>
            <h:messages for="someValueId"/>

            <h:commandButton action="#{testPageBean.doSomething}" 
               value="Do something" />
        </h:panelGrid>
    </h:form>
</h:body>
</html>

and its view scoped managed bean

package cz.kamosh;

import java.io.Serializable;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

@ManagedBean
@ViewScoped
public class TestPageBean implements Serializable {

    boolean disableValidateLength = false;

    private String someValue;

    public TestPageBean() {
        System.out.println("Constructor TestPageBean");
    }

    public String getSomeValue() {
        return someValue;
    }

    public void setSomeValue(String someValue) {
        this.someValue = someValue;
    }

    /**
     *  Some action without any navigation as a result
     */
    public void doSomething() {
        disableValidateLength = !disableValidateLength;
        System.out.printf(
                "DoSomething, someValue: %1$s, disableValidateLength: %2$b\n",
                someValue, disableValidateLength);
    }

    public boolean isDisableValidateLength() {
        System.out.printf("IsValidateLength, disableValidateLength: %1$b\n", 
                disableValidateLength);
        return disableValidateLength;
    }
}

I know that I should blame implementation com.sun.faces.facelets.tag.jsf.ValidatorTagHandlerDelegateImpl:

private void applyNested(FaceletContext ctx,
                             UIComponent parent) {

        // only process if it's been created
        if (!ComponentHandler.isNew(parent)) {
            return;
        }
    ...
}

These three rows cause validator not being reevaluated its disabled attribute when I perform action:-(

Could some please give me a hint, what was the motivation for JSF2 guys to implement this way or even better how to solve my problem?

EDIT:

Version of JSF2: 2.0.3-SNAPSHOT

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">
    <display-name>JSF2Testing</display-name>
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>/faces/*</url-pattern>
    </servlet-mapping>

    <context-param>
        <param-name>javax.faces.PARTIAL_STATE_SAVING</param-name>
        <param-value>false</param-value>
    </context-param>

</web-app>


EDIT2 Attempt to follow BalusC' suggestion

Changes on page:

<h:inputText id="someValueId" value="#{testPageBean.someValue}">                
    <f:validator validatorId="myLengthValidator"/>
    <f:attribute name="disableMyLengthValidator" value="#{testPageBean.disableValidateLength}"/>
    <f:attribute name="minimum" value="2" />
    <f:attribute name="maximum" value="4" />
</h:inputText>

Own validator used instead of standard :

import java.io.Serializable;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.FacesValidator;
import javax.faces.validator.LengthValidator;
import javax.faces.validator.ValidatorException;

@FacesValidator("myLengthValidator")
public class MyLengthValidator extends LengthValidator implements Serializable {

    public MyLengthValidator() {
        System.out.println("MyLengthValidator constructor");
    }

    @Override
    public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {     
        if((Boolean)component.getAttributes().get("disableMyLengthValidator")) {
            return;
        }       
        setMinimum(Integer.valueOf((String)component.getAttributes().get("minimum")));
        setMaximum(Integer.valueOf((String)component.getAttributes().get("maximum")));
        super.validate(context, component, value);
    }
}

My notes about this solution:

  • requires own implementation even if for standard validators
  • is dependent on attributes on component (inputText) level
  • validator is triggered even if it should not be
  • I am pretty sure it will not behave as we expect when client side validation is provided for validator
  • I consider is as very ugly and not following JSF2 :-(
1

1 Answers

0
votes

You've become another victim of the chicken-egg issue with partial state saving and view scoped beans as described in issue 1492 which is to be fixed in the upcoming Mojarra 2.2.

You should also immediately have realized that something's not entirely right when you saw the constructor of a view scoped bean being invoked on every request instead of only once at the first request of a view.

One of the ways to fix it is to disable the partial state saving altogether:

<context-param>
    <param-name>javax.faces.PARTIAL_STATE_SAVING</param-name>
    <param-value>false</param-value>
</context-param>

See also: