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="<#{cc.attrs.element.id}>">
<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}"/>]
<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-объект с областью запроса воссоздается между отправкой формы и вызовом действия.