【问题标题】:JSF2 - Why does render response not rerender component setting?JSF2 - 为什么渲染响应不重新渲染组件设置?
【发布时间】:2011-03-01 01:06:05
【问题描述】:

我遇到以下问题:

在我的视图恢复后,字段验证会导致 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>

按照 Brian 的建议进行编辑 (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 阶段重新评估。

【问题讨论】:

    标签: java jsf jsf-2


    【解决方案1】:

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

    使用&lt;h:panelGroup rendered="#{}"&gt; 标记而不是&lt;c: 标记再次尝试您的示例。

    查看这篇文章了解更多详情: http://drewdev.blogspot.com/2008/03/build-time-vs-render-time.html

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

    【讨论】:

    • 谢谢布赖恩。我尝试按照您的建议实施 field-component.xhtml。我还阅读了您的博客文章(刚刚对我来说很好但很难)。我用你的建议编辑了这个问题,但它仍然不起作用:-(
    • 我将 title 属性添加到 h:inputText 以表明在渲染响应阶段新值 (cc.attrs.fieldValue) 未应用于 h:inputText 属性。第一次填写“Hello”->Store。字段内容是“你好”,标题是“你好”。然后尝试清除内容->存储。字段内容为空但标题仍然是“你好”。这让我很困惑。
    • 似乎在 http 请求中发送的值仅适用于 h:inputText 的“value”属性,而不适用于 h:inputText 标签的任何其他属性。我对吗?这是正确的行为吗?
    • &lt;f:attribute&gt; 标签设置了 inputText 元素的属性,而不是自定义组件的属性。我认为您可以通过使用 `' 标签以标准方式实现所需的效果。
    【解决方案2】:

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

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

    托管 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() 返回的属性在Restore View 阶段填充(如果Process 验证阶段跳到Render response 阶段)。结果,我们得到了“更新”的值,该值仅从用户传递给 value 属性,其余的保持不变。

    如果流程验证阶段成功,不会导致直接跳到渲染响应阶段(如果用户填写任何非空值),则会调用 HtmlInputText 的新构造函数并 样式、标题等。 属性是从头开始填充的。这些属性由托管 bean 填充,该 bean 在阶段更新模型值中使用适当的数据进行了更新。

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

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

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

    【讨论】:

      【解决方案3】:

      我终于设法让验证工作了。

      我使用了可以访问 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 尚未更新,也会应用黄色背景,因为必需的验证器已经设置了常量 new ValueExpressionLiteral("background-color: yellow;", String.class)

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

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

      注意:在 h:inputText 中不能使用必需的属性。它会超过所需的验证器。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-08-12
        • 2022-08-18
        • 2020-06-16
        • 2020-05-06
        • 2017-07-12
        • 2020-10-15
        • 2020-03-08
        相关资源
        最近更新 更多