JSF2: почему пустой рендеринг в рендеринге PanelGroup в составной части предотвращает вызов действия?

У меня возникают проблемы с получением действия управляемого компонента, который вызывается, когда некоторые другие элементы JSF из пользовательских составных компонентов присутствуют на той же странице, что и h: commandButton, который передает форму h:. Я могу сузить элементы страницы, которые вызывают проблемы, для тех, кто выполняет следующие безобидные (для меня) выглядящие тесты рендеринга:

        <h:panelGroup rendered="#{not empty cc.attrs.element}">

или же:

        <h:panelGroup rendered="#{empty cc.attrs.element}">

где 'element' - обязательный атрибут, загружаемый из сущности JPA по id. Что-то в этом тесте 'empty cc.attrs.element' или 'not empty cc.attrs.element' блокирует правильную отправку формы и блокирует вызов действия в любой h: форме, в которой она появляется.

Страница отправки формы debug.xhtml:

<f:view>
    <f:metadata>
        <f:viewParam name="id" value="#{elementController.id}"/>
    </f:metadata>
</f:view>

<h:head>
    <title>DEBUG</title>
</h:head>
<h:body>
<h:form>        
<util:view_link element="#{elementController.selected}"/>
<h:commandButton value="Apply" action="#{elementController.reflect}" /> 
        </h:form>
    </h:body>

Обратите внимание на параметр представления "id", который через ElementController.setId принудительно загружает "выбранный" или "текущий" элемент Element по идентификатору из JPA, здесь не показывается. Затем следует перейти к test.xhtml с помощью действия refle ():

@JSFaction
public Object reflect() {
    String $i = "reflect";
    log_debug($i);
    if (current==null) {
        log_warn($i, "can't generate reflect outcome for null current element");
        return null;
    }
    //return "reflect?faces-redirect=true&includeViewParams=true&id="+current.getId();
    //return "test?faces-redirect=true&includeViewParams=true&id="+current.getId();
    //return "reflect?id="+current.getId();
     return "/test?faces-redirect=true&id="+current.getId();
}

Вы можете видеть выше, что я экспериментировал с разными URL-адресами результатов, но оказалось, что проблема не имеет к этому никакого отношения, просто действие вообще не вызывается (когда тест {not empty cc.attrs.element} выполняется в другой элемент страницы.

Композитный компонент, который вызывает проблему, мой утилита:view_link::

    <composite:interface>
    <composite:attribute name="element" required="true" type="com.greensoft.objectdb.test.entity.Element"/>
    <composite:attribute name="name" required="false"/>
    <composite:attribute name="showOwnerAsText" required="false" default="false"/>
    <composite:attribute name="showModelClass" required="false" default="false"/>
    <composite:attribute name="showEntityClass" required="false" default="false"/>
    <composite:attribute name="showId" default="false"/>
    <!--composite:attribute name="showHelp" required="false" default="false"/-->
    <!--composite:attribute name="title" required="false"/-->
</composite:interface>

<composite:implementation>

    <h:panelGroup rendered="#{empty cc.attrs.element}">
        <span class="warn">NULL</span>
    </h:panelGroup>

    <h:panelGroup rendered="#{not empty cc.attrs.element}">
        <h:panelGroup rendered="#{cc.attrs.showOwnerAsText}">
            <h:outputText style="font-weight:bold" value="#{cc.attrs.element.owner.name}."/>
        </h:panelGroup>

        <h:panelGroup rendered="#{cc.attrs.showId}">
            <h:link outcome="/view" value="&lt;#{cc.attrs.element.id}&gt;">
                <f:param name="id" value="#{cc.attrs.element.id}"/>
            </h:link>
        </h:panelGroup>

        <h:link outcome="/view" 
                value="#{empty cc.attrs.name ? cc.attrs.element.name: cc.attrs.name}"
                >
            <f:param name="id" value="#{cc.attrs.element.id}"/>
        </h:link>
    </h:panelGroup>
</composite:implementation>

Вы можете видеть, что это просто повторно используемый способ создания ссылки на каждый элемент с идентификатором базы данных, переданным в качестве параметра. Если элемента нет, отображается "NULL".

Комментируя биты за раз (заключая в скобки), я могу доказать, что проблема связана с тестами rendered="#{empty cc.attrs.element} и rendered="#{not empty cc.attrs.element}, которые оба запретить отправку формы и действие с помощью a h:commandButton на той же составленной странице (см. выше debug.xhtml). Если, например, я просто использую простые элементы h:panelGroup без тестов 'rendered="#{empty cc.attrs.element}' и 'rendered="#{not empty cc.attrs.element}', все отображается нормально, и h: commandButton работает как положено, и действие называется ок, и навигация работает отлично!

В: Почему test 'rendered="#{empty cc.attrs.element}' или 'rendered="#{not empty cc.attrs.element}' препятствует правильной отправке формы и останавливает вызов действия?

Благодарен за идеи, вебель

РЕДАКТИРОВАТЬ: проблема возникает только когда я тестирую на elementController.selected, где.selected является элементом @Entity. Я попробовал это с простым классом Duummy со свойством String name и @RequestScoped DebugController, и он работал нормально (было вызвано действие).

<h:panelGroup rendered="#{not empty debugController.dummy}">
<h:outputText value="#{debugController.dummy.name}"/>
</h:panelGroup>
<h:commandButton value="Apply" action="#{elementController.reflect}" /> 

Отмечено как ошибка: http://java.net/jira/browse/JAVASERVERFACES-2343

1 ответ

Теперь я могу частично ответить на этот вопрос. Теперь я могу видеть, что происходит, и описывать это, но я все еще не до конца понимаю - с точки зрения фаз JSF и объема запроса - почему это происходит так, как есть. Это тонкая и хитрая проблема.

Чтобы объяснить это, я переписал контрольный пример, чтобы сделать его проще. Тестовая страница view.xhtml, которая принимает параметр запроса id:

<f:view>
    <f:metadata>
        <f:viewParam name="id" value="#{elementController.id}"/>
    </f:metadata>
</f:view>
<h:body>   
    <h:form>

       <h:panelGroup rendered="#{empty elementController.selected}">
           <div style="color:orange;">
               No Element found for id(#{elementController.id}) or page called without id query param.
           </div>
       </h:panelGroup>

       <h:panelGroup rendered="#{not empty elementController.selected}">
           [<h:outputText value="#{elementController.selected.id}"/>]&nbsp;
           <h:outputText value="#{elementController.selected.name}"/>
       </h:panelGroup>

        <br/><br/>

       <h:commandButton value="Apply" action="#{elementController.action}" />

    </h:form>
</h:body>

Где ElementController расширяет RequestController. При загрузке view.xhtml параметр запроса id устанавливается как viewParam с использованием setId, который загружает соответствующий объект Element по id (из базы данных) и устанавливает его как "текущий" (он же "выбранный") элемент, доступный как getSelected(), который также выполнил тест на нулевой ток, и это было причиной проблемы:

public void setId(Long id) {
    log_debug("setId","id",id);
    if (id != null) {
        this.id = id;
        T found = (T) getAbstractFacade().find(id);
        if (found == null) {
            String $error = "No object with id(" + id + ") found for class " + getManagedClass().getSimpleName();
            log_error($error);
        }
        setCurrent(found);
    }
}

public T getSelected() {
    log_debug("getSelected","current",current);        
    if (current == null) {
        //current = newElement(); //THIS WAS CAUSING PROBLEMS
        log_warn("getSelected","null current Element");
    }
    return current;
}

Проблема заключалась в том, что при отправке формы getSelected () вызывался дважды для каждого экземпляра теста '#{not empty elementController.selected}' или '#{empty elementController.selected}' до вызова действия и с полем "текущий" ноль.

В @RequestScoped @ManagedBean ElementController метод getSelected () проверил, является ли текущий (иначе выбранный) Элемент нулевым, и выполнял действие, которое приводило к ошибке, и это было коротким замыканием вызова действия для commandButton. когда был вызван "не пустой elementController.selected":

(Проблема, с которой я сталкиваюсь, не имеет отношения к тому, является ли "выбранный" элемент, подвергнутый тесту "не пустой" или "пустой") @Entity, или нет, я ошибочно полагал, что, поскольку проблема связана с ElementController, который обрабатывает сущности Элемента, и, конечно, этот вывод все равно не звучит правильно.)

Если я избавлюсь от присваивания newElement(), которое вызывало ошибку и было там, чтобы отлавливать случаи, когда страница просмотра вызывается без параметра id (что вызывает загрузку "текущей" сущности по id), и вместо этого Записав предупреждение, я вижу, что для каждого экземпляра теста "не пустой elementController.selected" или "empty elementController.selected" это предупреждение регистрируется дважды ДО того, как действие commandButton вызывается при отправке формы.

Каким-то образом - и это та часть, которую я не до конца понимаю - в @RequestScoped элемент, который был назначен переменной 'current' (он же "selected"), теряется, а тест "not empty elementController.selected" на странице просмотра (и, следовательно, getSelected()) вызывается дважды с current = null (с побочными эффектами).

Если я изменю область действия ElementController на @SessionScoped, проблема исчезнет; текущее состояние элемента сохраняется, и getSelected () никогда не вызывается с текущим значением null.

Я был бы признателен за любые ответные комментарии, которые объясняют в терминах фаз JSF и области запроса, почему моя переменная 'current' внезапно становится пустой и почему '#{not empty elementController.selected}' выполняется дважды перед вызовом действия. Я проверил, что setId вообще не вызывается в промежутке (только при начальной загрузке страницы представления), что-то еще сбрасывает текущий до нуля, или bean-объект с областью запроса воссоздается между отправкой формы и вызовом действия.

Другие вопросы по тегам