Инициализировать составной компонент на основе предоставленных атрибутов

Я пишу свой пользовательский составной компонент таблицы с помощью 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()в более сложных случаях может потребоваться дополнительный анализ, должен быть способ обработки и кэширования атрибутов сразу после того, как они станут доступны, хотя я не уверен, когда и как на этом этапе.

В любом случае - это казалось самым простым способом решения этой проблемы.

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