Инициализировать составной компонент на основе предоставленных атрибутов
Я пишу свой пользовательский составной компонент таблицы с помощью Mojarra JSF. Я также пытаюсь привязать этот композит к вспомогательному компоненту. Цель состоит в том, чтобы иметь возможность указать количество элементов, которые таблица имеет в составном атрибуте, позже связанный вспомогательный компонент автоматически сгенерирует сами элементы, прежде чем будет отображено представление. У меня есть этот пример кода:
Главная страница:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:comp="http://java.sun.com/jsf/composite/comp">
<h:head />
<body>
<h:form>
<comp:myTable itemNumber="2" />
</h:form>
</body>
</html>
myTable.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://java.sun.com/jsf/composite"
xmlns:h="http://java.sun.com/jsf/html">
<h:body>
<composite:interface componentType="components.myTable">
<composite:attribute name="itemNumber"
type="java.lang.Integer" required="true" />
</composite:interface>
<composite:implementation>
<h:dataTable value="#{cc.values}" var="value">
<h:column headerText="column">
#{value}
<h:commandButton value="Action" action="#{cc.action}" />
</h:column>
</h:dataTable>
</composite:implementation>
</h:body>
</html>
MyTable.java:
@FacesComponent("components.myTable")
public class MyTable extends UINamingContainer {
private List<String> values = new ArrayList<String>();
public void action() {
System.out.println("Called");
}
@Override
public void encodeBegin(FacesContext context) throws IOException {
// Initialize the list according to the element number
Integer num = (Integer) getAttributes().get("itemNumber");
for (int i = 0; i < num; i++) {
values.add("item" + i);
}
super.encodeBegin(context);
}
public List<String> getValues() {
return values;
}
}
Проблема в том, что таблица отображается правильно (в данном случае с двумя элементами), но action
метод не вызывается при нажатии кнопки в строках.
Если я буду следовать вики-странице для составных компонентов, я смогу заставить ее работать таким образом, но при этом нужно инициализировать List
каждый раз getValues()
вызывается, вводя логику в метод получения:-(.
Есть идеи по этому поводу? Кажется, проблема связана с переопределением encodeBegin
метод. Я также попытался инициализировать его на markInitialState
, но атрибуты там пока недоступны...
Протестировано с Мохаррой 2.1.27 + Tomcat 6-7 и Мохаррой 2.2.5 + Tomcat 7
1 ответ
Что касается причины, UIComponent
экземпляры по сути являются областью запроса. Обратная передача эффективно создает новый экземпляр с такими свойствами, как values
переинициализирован по умолчанию. В вашей реализации он заполняется только во время encodeXxx()
, который вызывается намного позже decode()
при этом событие действия должно быть поставлено в очередь и, следовательно, слишком поздно.
Вам лучше заполнить его во время инициализации компонента. Если вы хотите @PostConstruct
крючок для UIComponent
экземпляры, то postAddToView
Событие - хороший кандидат. Это вызывается непосредственно после добавления экземпляра компонента в дерево компонентов.
<cc:implementation>
<f:event type="postAddToView" listener="#{cc.init}" />
...
</cc:implementation>
с
private List<String> values;
public void init() {
values = new ArrayList<String>();
Integer num = (Integer) getAttributes().get("value");
for (int i = 0; i < num; i++) {
values.add("item" + i);
}
}
(и удалите encodeBegin()
метод, если он больше не делает ничего полезного)
Альтернативой будет ленивая инициализация в getValues()
метод.
Более простым решением было бы хранить и извлекать values
в составе компонентов гос. Хранение может произойти во время encodeBegin
и получение может произойти непосредственно в получателе:
@FacesComponent("components.myTable")
public class TestTable extends UINamingContainer {
public void action() {
System.out.println("Called");
}
@Override
public void encodeBegin(FacesContext context) throws IOException {
// Initialize the list according to the element number
List<String> values = new ArrayList<>();
Integer num = (Integer) getAttributes().get("itemNumber");
for (int i = 0; i < num; i++) {
values.add("item" + i);
}
getStateHelper().put("values",values);
super.encodeBegin(context);
}
public List<String> getValues() {
return (List<String>)getStateHelper().get("values");
}
}
Чтобы не повторять логику в getValues()
в более сложных случаях может потребоваться дополнительный анализ, должен быть способ обработки и кэширования атрибутов сразу после того, как они станут доступны, хотя я не уверен, когда и как на этом этапе.
В любом случае - это казалось самым простым способом решения этой проблемы.