JSF ui:repeat, включенный ui:include, обернутый в h:panelGroup с условным рендерингом

Исходный вопрос приведен ниже, но, поскольку я придумал более минимальный пример, чтобы продемонстрировать эту проблему, и решил, что он должен идти вверху.

В любом случае, похоже, что теги ui:repeat обрабатываются перед проверкой того, действительно ли отрисовываются родительские элементы. Чтобы воссоздать это, вот лицевая сторона (minimalTest.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 &lt;ui:repeat&gt; inside &lt;h:panelGroup rendered=&quot;false&quot;&gt;</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>

С использованием этого компонента (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" );
    }
}

Из этого примера вы можете видеть, что h:panelGroup, который содержит ui:repeat, статически установлен на rendered=false, что, как я полагаю, будет означать, что ни одно из выражений EL внутри этого h:panelGroup не будет выполнено. Выражения EL просто вызывают геттеры, которые вызывают исключение RuntimeException. Однако ui:repeat на самом деле вызывает геттер для своего списка, что вызывает исключение, даже если оно не должно отображаться в первую очередь. Если вы закомментируете элемент ui:repeat, исключений не будет (даже если другое выражение EL останется в h:panelGroup), как я и ожидал.

Чтение других вопросов здесь, в stackoverflow, заставляет меня поверить, что это, вероятно, связано с часто упоминаемым курица/яйцо, но я точно не знаю, почему и что с этим делать. Я предполагаю, что установка PARTIAL_STATE_SAVING на false может помочь, но хотелось бы избежать последствий для памяти.

---- ИСХОДНЫЙ ВОПРОС ----

По сути, у меня есть страница, которая условно отображает разделы с использованием <h:panelGroup rendered="#{modeXXX}">, обернутого вокруг <ui:include src="pageXXX.xhtml" /> (согласно этому ответ). Проблема в том, что если в одном из pageXXX.xhtml есть <ui:repeat> внутри, кажется, что он обрабатывается, даже если содержащий <h:panelGroup> содержит rendered=false. Это проблема, потому что некоторые из моих разделов полагаются на то, что они были инициализированы другими разделами, которые должны быть посещены до них. Почему включенная страницаXXX.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 &lt;ui:include&gt;</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>

Как видите, эта страница будет условно включать либо страницу modeOne, либо страницу modeTwo в зависимости от значения в bean-компоненте testBean. Тогда у вас есть modeOne (по умолчанию):

<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&apos;m ready.  Take me to </span>
  <h:commandLink action="#{testBean.setModeTwo}">mode two.</h:commandLink>
</ui:composition>
</html>

Что в моем реальном приложении было бы страницей, которая настраивает вещи, необходимые для modeTwo. После настройки действие на этой странице направит вас в 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>

Страница modeTwo в основном представляет детали для страницы modeOne в ui:repeat, поскольку фактическая информация находится в коллекции. Основной управляемый компонент (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;
        }
    }
}

Это bean-компонент для всех основных данных, а bean-компонент TestBeanToo будет для деталей:

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

person Lucas    schedule 23.09.2011    source источник


Ответы (1)


<ui:repeat> не проверяет атрибут rendered самого себя (на самом деле у него его нет) и своих родителей, когда представление должно быть отображено. Рассмотрите возможность использования Tomahawk <t:dataList> вместо этого.

person BalusC    schedule 25.09.2011
comment
Именно об этом я и начал думать. Спасибо за проверку. Это действительно похоже на ошибку JSF, поскольку нет способа остановить обработку ui:repeat. В моем случае предыдущая страница - это то, что инициализирует базовый объект, на который ссылается повтор, поэтому я не могу его использовать. Я рассмотрю томагавк, но в настоящее время тестирую пользовательский компонент (расширяющийся из UIComponentBase). - person Lucas; 25.09.2011