2

我遇到以下问题:

在我的视图恢复后,字段验证会导致 JSF 跳到渲染响应阶段(因为必填字段为空)。但是即使呈现当前值(空字符串)以向用户显示他/她没有填写任何内容,也不会执行以下语句:

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

它是错误还是功能?请帮忙。

完整的测试示例(Netbeans 6.8 项目)在这里:http ://www.221b.cz/so/JSFTester.zip

来自教程:“如果请求是回发并且在应用请求值阶段、处理验证阶段或更新模型值阶段遇到错误,则在呈现响应阶段呈现原始页面”(http://java.sun .com/javaee/5/docs/tutorial/doc/bnaqq.html

这是否意味着如果视图在“恢复视图”阶段恢复,然后任何应用请求/验证/更新模型阶段失败并跳到“渲染响应”,渲染响应仅通过恢复的视图而不对客户端进行任何更改?

托管 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;
}    

}

复合组件(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">
<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">

<!-- 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>

页面(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">
  <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">

<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>

编辑,遵循布赖恩的建议(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">
<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">

<!-- 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>

但是即使我摆脱了 JSTL 仍然无法正常工作:-( 似乎只有 value 属性是用 h:inputText 中的 http 请求的新值更新的,但其余属性不会在 Render Response 阶段重新评估。

4

3 回答 3

3

您使用的<c:choose>and标记是 JSTL 标记,而不是 JSF 标记。<c:when>这意味着它们是在构建时评估的,而不是在渲染时。当您回发时,不会重建组件树,而是重新渲染它,并且<c:不会重新评估标签。

<h:panelGroup rendered="#{}">使用标签而不是标签再次尝试您的示例<c:

有关更多详细信息,请参阅此文章:http: //drewdev.blogspot.com/2008/03/build-time-vs-render-time.html

请务必记住,您不能让组件“重新出现”在 JSF 表单的回发中。这是因为 JSF 组件树不应该在保存其状态和恢复其状态之间进行更改。这非常重要,所以让我再说一遍,JSF 组件树不应该在保存其状态和恢复其状态之间进行更改。

于 2010-06-10T06:22:11.463 回答
1

这两天我做了一些调查和调试,这是我的结果。

首先,我简化了示例以省略复合组件并使其尽可能简单。

托管 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;
}
}

测试页面 (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">
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html">

<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>

哪里有问题?我认为这是 com.sun.faces.renderkit_html_basic.TextRenderer 及其方法 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 
}

currentValue参数被显式传递到从 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);

}

如果我们仔细看看 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;

}

getSubmittedValue()返回的属性在恢复视图阶段填充(如果流程验证阶段跳到渲染响应阶段)。结果,我们得到“更新”的值,该值仅从用户传递给属性,其余的保持不变。

如果 Process Validation 阶段成功,不会导致直接跳到 Render Response 阶段(如果用户填写任何非空值),则会调用 HtmlInputText 的新构造函数,并从头开始填充样式、标题等属性。这些属性是从在阶段更新模型值中使用适当数据更新的托管 bean 填充的。

好的,这不是错误,而是功能。它只是肯定了我的论点,即句子中有异味:“如果请求是回发,并且在应用请求值阶段、流程验证阶段或更新模型值阶段遇到错误,则在渲染响应阶段呈现原始页面” .

如果我真的渴望必填字段的黄色背景,任何线索如何解决这种情况?

更新项目在这里:http ://www.221b.cz/so/JSFTester2.zip

于 2010-06-11T09:05:45.187 回答
1

我终于设法让验证工作。

我使用了可以访问 UIComponent 的验证器。如果验证失败,组件会应用特殊样式。在渲染响应阶段也考虑了这种风格。

那么它的表现如何呢?

  1. 视图已恢复,包括样式 style="#{empty testerBean2.someValue ? 'background-color: yellow;' :''}"
  2. 验证不通过。因此 testerBean2.someValue 没有更新(因为跳过了更新模型值阶段),但是使用 RequiredValidator 将常量样式设置为 h:inputText - component.setValueExpression("style", new ValueExpressionLiteral("background-color: yellow;", String.班级));
  3. 在渲染响应中应用黄色背景,即使 testerBean.someValue 尚未更新,因为Required Validator 已经设置了常量 new ValueExpressionLiteral("background-color: yellow;", String.class)

我已经实现了自己需要的验证器(受到来自http://www.codereye.com/2009/12/validating-empty-text-field-using-jsf.html的 Bashan 验证器的启发)。

必需的Validator.java

package cz.test;

import javax.faces.application.FacesMessage;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
import org.apache.el.ValueExpressionLiteral;

public class RequiredValidator implements Validator {

public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
    if (value == null || "".equals(value.toString().trim())) {
        FacesMessage message = new FacesMessage();
        String messageStr = (String) component.getAttributes().get("message");
        if (messageStr == null) {
            messageStr = "Please enter data";
        }
        message.setDetail(messageStr);
        message.setSummary(messageStr);
        message.setSeverity(FacesMessage.SEVERITY_ERROR);
        component.setValueExpression("style", new ValueExpressionLiteral("background-color: yellow;", String.class));
        throw new ValidatorException(message);
    } else {
        component.setValueExpression("style", new ValueExpressionLiteral("", String.class));
    }
}
}

我添加的行: component.setValueExpression("style", new ValueExpressionLiteral("background-color: yellow;", String.class));

强制对空字段 (web.xml) 进行 JSF 触发器验证:

....
<context-param>
    <param-name>javax.faces.VALIDATE_EMPTY_FIELDS</param-name>
    <param-value>true</param-value>
</context-param>
....

向 JSF (faces-config.xml) 注册验证器:

<validator>
    <validator-id>RequiredValidator</validator-id>
    <validator-class>cz.test.RequiredValidator</validator-class>
</validator>

和使用所需验证器和 TesterBean2 (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">
<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>
    <title>Testing page</title>
</h:head>
<h:body>
    <h:form>
        <h:messages/>
        <h:inputText id="fieldValue"                          
                     title="#{testerBean2.someValue}"
                     value="#{testerBean2.someValue}"                         
                     style="#{empty testerBean2.someValue ? 'background-color: yellow;' : ''}">
            <f:validator validatorId="RequiredValidator"/>
            <f:attribute name="message" value="Field is mandatory"/>
        </h:inputText>

        <h:commandButton value="Store" action="#{testerBean2.storeValue}"/>
        <h:commandButton value="Erase" action="#{testerBean2.eraseValue}"/>
    </h:form>
</h:body>
</html>

NOTICE: There cannot be used required attribute in h:inputText. It would outrun required validator.

于 2010-06-14T14:28:47.467 回答