JSF ui: repeat incluido por ui: incluir envuelto en h: panelGroup con rendering condicional... Un mouthfull
include conditional (1)
La pregunta original está debajo, pero como he encontrado un ejemplo más mínimo para demostrar este problema, y pensé que debería ir en la parte superior.
De todos modos, parece que las etiquetas ui:repeat
se procesan antes de verificar para ver si los elementos principales se representan realmente. Para volver a crear esto, aquí está el facelet (minimumTest.xhtml):
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Test JSF <ui:repeat> inside <h:panelGroup rendered="false"></title>
</h:head>
<h:body>
<h:form>
<h1>Testing</h1>
<h:panelGroup rendered="false">
<span>#{minimalTestBean.alsoThrowsException}</span>
<ul>
<ui:repeat value="#{minimalTestBean.throwsException}" var="item">
<li>#{item}</li>
</ui:repeat>
</ul>
</h:panelGroup>
</h:form>
</h:body>
</html>
Con el uso de este bean (MinimalTestBean.java):
package com.lucastheisen.beans;
import java.io.Serializable;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class MinimalTestBean implements Serializable {
private static final long serialVersionUID = 9045030165653014015L;
public String getAlsoThrowsException() {
throw new RuntimeException( "rendered is false so this shouldnt get called either" );
}
public List<String> getThrowsException() {
throw new RuntimeException( "rendered is false so this shouldnt get called" );
}
}
A partir de este ejemplo, puede ver que h:panelGroup
que contiene ui:repeat
está establecido estáticamente en rendered=false
lo que supondría que significaría que ninguna de las expresiones EL dentro de ese h:panelGroup
sería ejecutada. Las expresiones EL solo llaman a getters que arrojan una RuntimeException. Sin embargo, el ui:repeat
está llamando realmente al getter para su lista, causando la excepción, aunque en primer lugar no debería ser procesada. Si comenta el elemento ui:repeat
, no se h:panelGroup
excepciones (aunque la otra expresión EL permanezca en h:panelGroup
) como era de esperar.
Leer otras preguntas aquí en Stackoverflow me lleva a pensar que probablemente esté relacionado con el tema frecuentemente referido de pollo / huevo , pero no estoy seguro exactamente por qué, ni qué hacer al respecto. Me imagino que configurar el PARTIAL_STATE_SAVING en falso podría ayudar, pero me gustaría evitar las implicaciones de la memoria.
---- PREGUNTA ORIGINAL ----
Básicamente, tengo una página que condicionalmente procesa secciones utilizando <h:panelGroup rendered="#{modeXXX}">
envuelto alrededor de <ui:include src="pageXXX.xhtml" />
(según esta respuesta ). El problema es que si una de las páginasXXX.xhtml tiene un <ui:repeat>
dentro, parece que se procesa incluso cuando el <h:panelGroup>
contiene rendered=false
. Esto es un problema porque algunas de mis secciones dependen de haber sido inicializadas por otras secciones que deberían visitarse antes que ellas. ¿Por qué se procesa la página pageXXX.xhtml incluida?
Este es un error doloroso e increíblemente difícil de resumir en un pequeño ejemplo, pero este es el caso más mínimo que pude construir que demuestra el problema. Primero una página base:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Test JSF <ui:include></title>
</h:head>
<h:body>
<h:form>
<h1>#{testBean.title}</h1>
<h:panelGroup rendered="#{testBean.modeOne}">
<ui:include src="modeOne.xhtml" />
</h:panelGroup>
<h:panelGroup rendered="#{testBean.modeTwo}">
<ui:include src="modeTwo.xhtml" />
</h:panelGroup>
</h:form>
</h:body>
</html>
Como puede ver, esta página incluirá de forma condicional la página de modeOne o la página de modeTwo basándose en el valor del bean testBean. Entonces tienes modeOne (por defecto):
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<ui:composition>
<span>Okay, I'm ready. Take me to </span>
<h:commandLink action="#{testBean.setModeTwo}">mode two.</h:commandLink>
</ui:composition>
</html>
Lo que en mi aplicación del mundo real sería una página que configura las cosas que necesita modeTwo. Una vez configurada, una acción en esta página lo dirigirá a modeTwo:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<ui:composition>
<div>Here is your list:</div>
<ui:repeat value="#{testBeanToo.list}" var="item">
<div>#{item}</div>
</ui:repeat>
</ui:composition>
</html>
La página de modeTwo presenta básicamente los detalles de la página de modeOne en una interfaz de usuario: repita como la información real está en una colección. El frijol administrado principal (TestBean):
package test.lucastheisen.beans;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class TestBean implements Serializable {
private static final long serialVersionUID = 6542086191355916513L;
private Mode mode;
@ManagedProperty( value="#{testBeanToo}" )
private TestBeanToo testBeanToo;
public TestBean() {
System.out.println( "constructing TestBean" );
setModeOne();
}
public String getTitle() {
System.out.println( "/ttb.getTitle()" );
return mode.getTitle();
}
public boolean isModeOne() {
return mode == Mode.One;
}
public boolean isModeTwo() {
return mode == Mode.Two;
}
public void setModeOne() {
this.mode = Mode.One;
}
public void setModeTwo() {
testBeanToo.getReadyCauseHereICome();
this.mode = Mode.Two;
}
public void setTestBeanToo( TestBeanToo testBeanToo ) {
this.testBeanToo = testBeanToo;
}
private enum Mode {
One("Mode One"),
Two("Mode Two");
private String title;
private Mode( String title ) {
this.title = title;
}
public String getTitle() {
return title;
}
}
}
Es el frijol para todos los datos principales, y el frijol TestBeanToo sería para los detalles:
package test.lucastheisen.beans;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class TestBeanToo implements Serializable {
private static final long serialVersionUID = 6542086191355916513L;
private ObjectWithList objectWithList = null;
public TestBeanToo() {
System.out.println( "constructing TestBeanToo" );
}
public String getTitle() {
System.out.println( "/ttb2.getTitle()" );
return "Test Too";
}
public List<String> getList() {
System.out.println( "/ttb2.getList()" );
return objectWithList.getList();
}
public void getReadyCauseHereICome() {
System.out.println( "/ttb2.getList()" );
objectWithList = new ObjectWithList();
}
public class ObjectWithList {
private List<String> list;
public ObjectWithList() {
list = new ArrayList<String>();
list.add( "List item 1" );
list.add( "List item 2" );
}
public List<String> getList() {
return list;
}
}
}
<ui:repeat>
no comprueba el atributo rendered
de sí mismo (en realidad no tiene ninguno) y sus padres cuando se va a representar la vista. Considere usar Tomahawk''s <t:dataList>
lugar.