2 Stimmen

JSF2 - Warum wird die Komponenteneinstellung beim Rendern der Antwort nicht wiederhergestellt?

Ich habe das folgende Problem:

Nachdem meine Ansicht wiederhergestellt ist, führt die Validierung des Feldes dazu, dass JSF die Render-Antwortphase überspringt (weil das erforderliche Feld leer ist). Aber auch wenn der aktuelle Wert (leere Zeichenfolge) gerendert wird, um dem Benutzer zu zeigen, dass er/sie nichts ausgefüllt hat, wird die folgende Anweisung nicht ausgeführt:

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

Ist es ein Fehler oder eine Funktion? Bitte helfen Sie uns.

Ein vollständiges Testbeispiel (Netbeans 6.8 Projekt) finden Sie hier: http://www.221b.cz/so/JSFTester.zip

Aus dem Lernprogramm: "Wenn es sich bei der Anfrage um einen Postback handelt und in der Phase apply request values, process validations phase oder update model values Fehler aufgetreten sind, wird die ursprüngliche Seite in der Phase Render response gerendert" ( http://java.sun.com/javaee/5/docs/tutorial/doc/bnaqq.html )

Bedeutet es, dass wenn die Ansicht in der Phase "Ansicht wiederherstellen" wiederhergestellt wird und dann jede Anwendungsanforderung/Validierung/Aktualisierungsmodellphase fehlschlägt und zur "Renderantwort" überspringt, dass die Renderantwort nur die wiederhergestellte Ansicht ohne Änderungen an den Client weitergibt?

Verwaltete Bohne (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;
}    

}

Zusammengesetzte Komponente (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>

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

EDIT, nach Brians Vorschlag (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>

Aber immer noch nicht funktioniert, auch wenn ich losgeworden JSTL: -( Scheint, dass nur Wert-Attribut mit neuen Wert von http-Anforderung in h:inputText aktualisiert wird, aber der Rest der Attribute sind nicht neu bewertet in Phase Render Response.

3voto

Brian Leathem Punkte 4580

En <c:choose> y <c:when> Tags, die Sie verwenden, sind JSTL-Tags und keine JSF-Tags. Das bedeutet, dass sie zur Erstellungszeit und nicht zur Renderzeit ausgewertet werden. Wenn Sie zurückkehren, wird der Komponentenbaum nicht neu aufgebaut, sondern neu gerendert, und die <c: Tags werden nicht neu bewertet.

Versuchen Sie Ihr Beispiel noch einmal mit <h:panelGroup rendered="#{}"> <c: Tags.

Weitere Einzelheiten finden Sie in diesem Bericht: http://drewdev.blogspot.com/2008/03/build-time-vs-render-time.html

Es ist sehr wichtig, daran zu denken, dass Sie keine Komponenten auf der Rückseite eines JSF-Formulars "wieder auftauchen" lassen können. Dies liegt daran, dass ein JSF-Komponentenbaum zwischen dem Speichern seines Zustands und der Wiederherstellung seines Zustands niemals geändert werden sollte. Dies ist sehr wichtig, also lassen Sie mich noch einmal sagen, dass ein JSF-Komponentenbaum zwischen dem Speichern seines Zustands und der Wiederherstellung seines Zustands niemals geändert werden darf.

1voto

Fekete Kamosh Punkte 361

Ich habe in den letzten zwei Tagen einige Untersuchungen und Fehlersuche durchgeführt, und das ist mein Ergebnis.

Zunächst einmal habe ich das Beispiel vereinfacht, um die zusammengesetzte Komponente wegzulassen und es so einfach wie möglich zu halten.

Verwaltete Bohne (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;
}
}

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

Wo liegt das Problem? Ich denke, es ist ein Problem von com.sun.faces.renderkit_html_basic.TextRenderer und seiner Methode 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 wird explizit an diese Methode übergeben, die von com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.encodeEnd aufgerufen wird

@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);

}

und wenn wir uns eine Methode getCurrentValue() genauer ansehen

/**
 * @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;

}

die Eigenschaft, die von getSubmittedValue() wird in der Phase Ansicht wiederherstellen ausgefüllt (wenn die Phase Prozessvalidierung in die Phase Antwort rendern übergeht). Infolgedessen erhalten wir einen "aktualisierten" Wert, der vom Benutzer nur für valor Attribut, der Rest bleibt unverändert.

Bei erfolgreicher Process Validation-Phase, die nicht zum direkten Übergang zur Render Response-Phase führt (wenn der Benutzer einen Nicht-Null-Wert eingibt), wird ein neuer Konstruktor von HtmlInputText aufgerufen und Stil, Titel usw. Attribute werden von Grund auf gefüllt. Diese Attribute werden aus der verwalteten Bean gefüllt, die in der Phase Update Model Values mit den richtigen Daten aktualisiert wurde.

OK, das ist kein Fehler, sondern eine Funktion. Es bestätigt nur meine These, dass in dem Satz etwas stinkt: "Wenn die Anfrage ein Postback ist und während der Phase apply request values, process validations phase oder update model values Fehler aufgetreten sind, wird die ursprüngliche Seite während der Render response phase gerendert".

Haben Sie eine Idee, wie ich diese Situation lösen kann, wenn ich wirklich einen gelben Hintergrund für Pflichtfelder wünsche?

Das aktualisierte Projekt finden Sie hier: http://www.221b.cz/so/JSFTester2.zip

1voto

Fekete Kamosh Punkte 361

Ich habe es endlich geschafft, die Validierung zum Laufen zu bringen.

Ich habe einen Validator verwendet, der Zugriff auf UIComponent hat. Im Falle einer fehlgeschlagenen Validierung wird ein spezieller Stil auf die Komponente angewendet. Dieser Stil wird auch in der Rendering-Antwortphase berücksichtigt.

Wie verhält es sich also?

  1. Ansicht wird wiederhergestellt mit style="#{empty testerBean2.someValue ? 'backg
  2. Die Validierung ist nicht erfolgreich. Daher wird testerBean2.someValue nicht aktualisiert (da die Phase "Modellwerte aktualisieren" übersprungen wird), aber die Konstante style wird mithilfe von RequiredValidator in h:inputText gesetzt - component.setValueExpression("style", new ValueExpressionLiteral("background-color:
  3. In der Render-Antwort wird der gelbe Hintergrund angewendet, auch wenn testerBean.someValue nicht aktualisiert wurde, da Required Validator bereits die Konstante new ValueExpressionLiteral("background-color: yellow;", String.class) gesetzt hat

Ich habe eigene erforderliche Validator (inspiriert von Bashan's Validator von http://www.codereye.com/2009/12/validating-empty-text-field-using-jsf.html ).

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));
    }
}
}

Die Zeile, die ich hinzugefügt habe: component.setValueExpression("style", new ValueExpressionLiteral("background-color: yellow;", String.class));

um die JSF-Trigger-Validierung für leere Felder zu erzwingen (web.xml):

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

um den Validator bei JSF zu registrieren (faces-config.xml):

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

und die Webseite mit dem erforderlichen Validator und 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>

HINWEIS: Das Attribut required kann nicht in h:inputText verwendet werden. Es würde den required-Validator übersteuern.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X