Как мне реализовать NamingContainer? Все дети получают одинаковый идентификатор клиента

Я пытаюсь написать свой собственный компонент дерева. Узел дерева отображается как div, содержащий дочерние компоненты компонента дерева, например:

<my:tree id="extendedTree"
         value="#{controller.rootNode}"
         var="node">
    <h:outputText id="xxx" value="#{node.name}" />
    <h:commandLink value="Test" actionListener="#{controller.nodeSelectionActionListener}" />
</my:tree>

Пока все хорошо - все работает как положено, но h:outputText получает один и тот же идентификатор несколько раз.
Итак, я реализовал свой компонент javax.faces.NamingControllerперезаписывая getContainerClientId():

@Override
public String getContainerClientId(FacesContext context) {
    String clientId = super.getClientId(context);
    String containerClientId = clientId + ":" + index;
    return containerClientId;
}

index устанавливается и обновляется во время итерации по узлам. Но getContainerClientId() вызывается только один раз для каждого потомка (не для каждой итерации и не для каждого потомка, как я ожидал). Это приводит к тому, что каждому дочернему идентификатору присваивается одинаковый идентификатор контейнера:

form:treeid:0:xxx

То же самое для перезаписи getClientId(),

Что я упустил?

2 ответа

Решение

Ответ скрыт в нижней части главы 3.1.6 спецификации JSF 1.2:

3.1.6 Идентификаторы клиента

...

Значение, возвращаемое этим методом, должно быть одинаковым на протяжении всего времени существования экземпляра компонента, если только setId() вызывается, и в этом случае он будет пересчитан при следующем вызове getClientId(),

Другими словами, результат getClientId() по умолчанию кэшируется компонентом JSF, как это реализовано в UIComponentBase#getClientId() (см. также проверку нуля в строке 275, как в Mojarra 1.2_15), и этот кэш сбрасывается, когда UIComponentBase#setId() называется (см. также строку 358, как в Мохарре 1.2_15). Пока вы не сбросите кэшированный идентификатор клиента, он будет возвращать одно и то же значение на каждом getClientId() вызов.

Таким образом, при оказании детей в encodeChildren() реализация вашего компонента или средства визуализации, которое, скорее всего, будет выглядеть следующим образом,

for (UIComponent child : getChildren()) {
    child.encodeAll(context);
}

ты должен за каждого ребенка звонить UIComponent#setId()с результатом UIComponent#getId() сбросить внутренний кэшированный идентификатор клиента перед кодированием дочернего элемента:

for (UIComponent child : getChildren()) {
    child.setId(child.getId());
    child.encodeAll(context);
}

UIData класс позади <h:dataTable> реализация делает это, кстати, также (см. строку 1382, как в Мохарре 1.2_15). Обратите внимание, что это не специфично для JSF 1.x, это же относится и к JSF 2.x (а также к UIRepeat класс позади Facelets <ui:repeat>).

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

<my:tree id="extendedTree"
         value="#{controller.rootNode}"
         var="node">
  <h:panelGroup layout="block" id="nodeBlock">
    <h:outputText id="xxx" value="#{node.name}" />
    <h:commandLink value="Test" actionListener="#{controller.nodeSelectionActionListener}" />
  </h:panelGroup>
</my:tree>

Идентификатор <panelGroup> выходит ОК после применения исправления BalusC, описанного выше, но все подкомпоненты получают значение 0 в итераторе.

Чтобы исправить это, также переберите все уровни дочерних элементов и обновите их кешированные идентификаторы. Так:child.setId(child.getId()); становится uncacheId(child); где uncacheId функция определяется:

private void uncacheId(UIComponent el) {
    el.setId(el.getId());
    el.getChildren().forEach(this::uncacheId);
}

Это может быть очевидно, но мне потребовалось время, чтобы понять, так что...

h:outputText id дает вам то же самое, что вы не сделали его динамическим. Вы можете создать это как:

 <h:outputText id="xxx_#{node.id}" value="#{node.name}" />

Предположим, что узел имеет атрибут id, который является уникальным.

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