tags:

views:

164

answers:

3

I am getting the following problem:

After my view is restored, validation of field causes JSF to skip to Render response phase (because required field is empty). But even though the current value (empty string) is rendered to show to user that he/she did not fill anything, the following statement is not executed:

 <c:if test="#{empty cc.attrs.fieldValue}">
     <f:attribute name="style" value="background-color: yellow;"/>
 </c:if>

Is it a bug or a feature? Please help.

Complete test example (Netbeans 6.8 project) is here: http://www.221b.cz/so/JSFTester.zip

From the tutorial: "If the request is a postback and errors were encountered during the apply request values phase, process validations phase, or update model values phase, the original page is rendered during Render response phase" (http://java.sun.com/javaee/5/docs/tutorial/doc/bnaqq.html)

Does it mean that if view is restored in "Restore View" phase and then any apply request/validation/update model phase fails and skips to "Render response" that Render response only passes restored view without any changes to client?

Managed Bean (TesterBean.java):

package cz.test;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

@ManagedBean
@RequestScoped
public class TesterBean {

// Simple DataStore (in real world EJB)
private static String storedSomeValue = null;

private String someValue;

public TesterBean() {
}

public String storeValue() {
    storedSomeValue = someValue;
    return "index";
}

public String eraseValue() {
    storedSomeValue = null;
    return "index";
}

public String getSomeValue() {
    someValue = storedSomeValue;
    return someValue;
}

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

}

Composite component (field-component.xhtml):

<?xml version='1.0' encoding='ISO-8859-1' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:composite="http://java.sun.com/jsf/composite"
  xmlns:c="http://java.sun.com/jsp/jstl/core"&gt;

<!-- INTERFACE -->
<composite:interface>
    <composite:attribute name="currentBehaviour" type="java.lang.String" required="true"/>
    <composite:attribute name="fieldValue" required="true"/>
</composite:interface>

<!-- IMPLEMENTATION -->
<composite:implementation>
    <h:panelGrid columns="3">
        <c:choose>
            <c:when test="#{cc.attrs.currentBehaviour == 'READONLY'}" >
                <h:outputText id="fieldValue" value="#{cc.attrs.fieldValue}">
                </h:outputText>
            </c:when>
            <c:when test="#{cc.attrs.currentBehaviour == 'MANDATORY'}" >
                <h:inputText id="fieldValue" value="#{cc.attrs.fieldValue}" required="true">
                    <f:attribute name="requiredMessage" value="Field is mandatory"/>
                    <c:if test="#{empty cc.attrs.fieldValue}">
                        <f:attribute name="style" value="background-color: yellow;"/>
                    </c:if>
                </h:inputText>&nbsp;*
            </c:when>
            <c:when test="#{cc.attrs.currentBehaviour == 'OPTIONAL'}" >                    
                <h:inputText id="fieldValue" value="#{cc.attrs.fieldValue}">                        
                </h:inputText>                    
            </c:when>
        </c:choose>
        <h:message for="fieldValue" style="color:red;" />
    </h:panelGrid>
</composite:implementation>

Page (index.xhtml):

<?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"&gt;
  <html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:h="http://java.sun.com/jsf/html"      
   xmlns:ez="http://java.sun.com/jsf/composite/components"&gt;

<h:head>
    <title>Testing page</title>
</h:head>
<h:body>
    <h:form>                        
        <h:outputText value="Some value:"/>
        <ez:field-component currentBehaviour="MANDATORY" fieldValue="#{testerBean.someValue}"/>           
        <h:commandButton value="Store" action="#{testerBean.storeValue}"/>
        <h:commandButton value="Erase" action="#{testerBean.eraseValue}" immediate="true"/>
    </h:form>

    <br/><br/>
    <b>Why is field's background color not set to yellow?</b>
    <ol>
        <li>NOTICE: Field has yellow background color (mandatory field with no value)</li>
        <li>Fill in any value (eg. "Hello") and press Store</li>
        <li>NOTICE: Yellow background disappeared (as mandatory field has value)</li>
        <li>Clear text in the field and press Store</li>
        <li><b>QUESTION: Why is field's background color not set to yellow?</b></li>
        <li>Press Erase</li>
        <li>NOTICE: Field has yellow background color (mandatory field with no value)</li>
    </ol>
</h:body>

EDIT, following Brian's suggestion (field-component.xhtml)

<?xml version='1.0' encoding='ISO-8859-1' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:composite="http://java.sun.com/jsf/composite"&gt;

<!-- INTERFACE -->
<composite:interface>
    <composite:attribute name="currentBehaviour" type="java.lang.String" required="true"/>
    <composite:attribute name="fieldValue" required="true"/>
</composite:interface>

<!-- IMPLEMENTATION -->
<composite:implementation>
    <h:panelGrid columns="3">
        <h:outputText rendered="#{cc.attrs.currentBehaviour == 'READONLY'}" id="fieldValue1" value="#{cc.attrs.fieldValue}" />

        <h:inputText rendered="#{cc.attrs.currentBehaviour == 'MANDATORY'}" id="fieldValue2" title="#{cc.attrs.fieldValue}" value="#{cc.attrs.fieldValue}" required="true" style="#{empty cc.attrs.fieldValue ? 'background-color: yellow;' : ''}">
            <f:attribute name="requiredMessage" value="Field is mandatory"/>
        </h:inputText>&nbsp;*

        <h:inputText rendered="#{cc.attrs.currentBehaviour == 'OPTIONAL'}" id="fieldValue3" value="#{cc.attrs.fieldValue}"/>

        <h:message for="fieldValue" style="color:red;" />
    </h:panelGrid>
</composite:implementation>

But still not working even if I got rid of JSTL:-( Seems that only value attribute is updated with new value from http request in h:inputText but the rest of attributes are not reevaluated in phase Render Response.

+2  A: 

The <c:choose> and <c:when> tags you are using are JSTL tags, not JSF tags. This means they are evaluated at build time, rather than at render time. When you post back, the component tree does not get rebuilt, rather it get's re-rendered, and the <c: tags don't get re-evaluated.

Try your example again using <h:panelGroup rendered="#{}"> tags instead of the <c: tags.

See this write-up for more details: http://drewdev.blogspot.com/2008/03/build-time-vs-render-time.html

It is very important to remember that you cannot have components "re-appear" on post back of a JSF form. This is because a JSF component tree should never be altered between having its state saved and when its state is restored. This is very important, so let me say again, a JSF component tree should never be altered between having its state saved and when its state is restored.

Brian Leathem
Thank you Brian. I tried to implement field-component.xhtml following your advice. I also read through your blog article (nice but difficult for me just now). I edited the question with your suggestion, but it still does not work:-(
I added title attribute to h:inputText to show that in Render response phase new value (cc.attrs.fieldValue) is not applied to h:inputText attributes. For the first time fill in "Hello"->Store. Field content is "Hello", title is "Hello. Then try to clear content->Store. Field content is empty but title is still "Hello". This is what puzzles me.
It seems that value sent in http request is applied only to "value" attribute of h:inputText and not to any other attribute of h:inputText tag. Am I right? Is it correct behaviour?
The `<f:attribute>` tag set an attribute of the inputText element, not an attribute of the custom component. I think you can achieve you're desired effect in a standard way by using an `<h:errorMessage>' tag.
Brian Leathem
A: 

Hello again. I did some investigation and debugging in the last two days and this is my result.

First of all, I simplified example to omit composite component and to have it as simple as possible.

Managed bean (TesterBean2.java)

package cz.test;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

@ManagedBean
@RequestScoped
public class TesterBean2 {

// Simple DataStore (in real world EJB)
private static String storedSomeValue = null;

private String someValue;

public TesterBean2() {
}

public String storeValue() {
    storedSomeValue = someValue;
    return "index";
}

public String eraseValue() {
    storedSomeValue = null;
    return "index";
}

public String getSomeValue() {
    someValue = storedSomeValue;
    return someValue;
}

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

Testing page (index.xhtml)

<?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"&gt;
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"&gt;

<h:head>
    <title>Testing page</title>
</h:head>
<h:body>
    <h:form>
        <h:inputText id="fieldValue" requiredMessage="Field is mandatory" title="#{testerBean2.someValue}" value="#{testerBean2.someValue}" required="true" style="#{empty testerBean2.someValue ? 'background-color: yellow;' : ''}"/>
        <h:commandButton value="Store" action="#{testerBean2.storeValue}"/>
        <h:commandButton value="Erase" action="#{testerBean2.eraseValue}"/>
    </h:form>
</h:body>

Where is the problem? I think it is problem of com.sun.faces.renderkit_html_basic.TextRenderer and its method getEndTextToRender:

protected void getEndTextToRender(FacesContext context,
                                  UIComponent component,
                                  String currentValue)
      throws IOException {

    ResponseWriter writer = context.getResponseWriter();
    assert(writer != null);
    boolean shouldWriteIdAttribute = false;
    boolean isOutput = false;

    String style = (String) component.getAttributes().get("style");
    String styleClass = (String) component.getAttributes().get("styleClass");
    String dir = (String) component.getAttributes().get("dir");
    String lang = (String) component.getAttributes().get("lang");
    String title = (String) component.getAttributes().get("title");
    if (component instanceof UIInput) {
        writer.startElement("input", component);
        writeIdAttributeIfNecessary(context, writer, component);
        writer.writeAttribute("type", "text", null);
        writer.writeAttribute("name", (component.getClientId(context)),
                              "clientId");

        // only output the autocomplete attribute if the value
        // is 'off' since its lack of presence will be interpreted
        // as 'on' by the browser
        if ("off".equals(component.getAttributes().get("autocomplete"))) {
            writer.writeAttribute("autocomplete",
                                  "off",
                                  "autocomplete");
        }

        // render default text specified
        if (currentValue != null) {
            writer.writeAttribute("value", currentValue, "value");
        }

   // Rest of code omitted 
}

A currentValue parameter is explicitly passed into this method called from com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.encodeEnd

@Override
public void encodeEnd(FacesContext context, UIComponent component)
      throws IOException {

   rendererParamsNotNull(context, component);

    if (!shouldEncode(component)) {
        return;
    }

    ResponseWriter writer = context.getResponseWriter();
    assert(writer != null);

    // NOTICE currentValue getter
    String currentValue = getCurrentValue(context, component);
    if (logger.isLoggable(Level.FINE)) {
        logger.log(Level.FINE,
                   "Value to be rendered {0}",
                   currentValue);
    }
    // NOTICE currentValue
    getEndTextToRender(context, component, currentValue);

}

and if we look closer at a method getCurrentValue()

/**
 * @param context the FacesContext for the current request
 * @param component the UIComponent whose value we're interested in
 *
 * @return the value to be rendered and formats it if required. Sets to
 *  empty string if value is null.
 */
protected String getCurrentValue(FacesContext context,
                                 UIComponent component) {

    if (component instanceof UIInput) {
        Object submittedValue = ((UIInput) component).getSubmittedValue();
        if (submittedValue != null) {
            // value may not be a String...
            return submittedValue.toString();
        }
    }

    String currentValue = null;
    Object currentObj = getValue(component);
    if (currentObj != null) {
        currentValue = getFormattedValue(context, component, currentObj);
    }
    return currentValue;

}

the property returned by getSubmittedValue() is filled in Restore View phase (if Process validation phase skips to Render response phase). As a consequence we get "updated" value which was passed from user only for value attribute, the rest remain unaltered.

In case of successful Process Validation phase, which does not cause direct skip to Render Response phase (if user fills in any not null value), a new constructor of HtmlInputText is called and style, title etc. attributes are filled from the scratch. These attributes are filled from managed bean which was updated with proper data in phase Update Model Values.

OK, this is not a bug, but the feature. It only affirms my thesis that something smells in the sentence: "If the request is a postback and errors were encountered during the apply request values phase, process validations phase, or update model values phase, the original page is rendered during Render response phase".

Any clue how to solve this situation if I really long for yellow background for mandatory fields?

Updated project is here: http://www.221b.cz/so/JSFTester2.zip

A: