Доброго времени суток.
При работе с таблицами в библиотеке Primefaces вполне естественно может возникнуть необходимость в обновлении одной колонки на основе других. Например, как здесь, где значение последней колонки вычисляется как произведение двух других (пример чисто демонстрационный).
ManagedBean:
package com.tuneit.itc;
import lombok.AllArgsConstructor;
import lombok.Data;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Data
@ManagedBean
@ViewScoped
public class TableTestBean implements Serializable {
private List<TableRow> rows;
@PostConstruct
public void init() {
rows = fillData();
}
private List<TableRow> fillData() {
List<TableRow> result = new ArrayList<>();
for (int i = 0; i < 3; ++ i) {
int fst = 0;
int snd = 0;
result.add(new TableRow(i, fst, snd));
}
return result;
}
@Data
@AllArgsConstructor
public static class TableRow implements Serializable {
private long id;
private int fst;
private int snd;
public int getResult() {
return fst * snd;
}
}
}
Facelet-шаблон:
<?xml version="1.0"?>
<f:view
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets"
>
<h:head>
</h:head>
<h:body>
<h:form>
<p:dataTable id="table" value="#{tableTestBean.rows}" var="row">
<p:column headerText="Id">
<h:outputText value="#{row.id}"/>
</p:column>
<p:column headerText="First data">
<p:inputText value="#{row.fst}"/>
</p:column>
<p:column headerText="Second data">
<p:inputText value="#{row.snd}"/>
</p:column>
<p:column>
<p:commandButton value="*" update="@form" process="@form"/>
</p:column>
<p:column headerText="Result">
<h:outputText value="#{row.result}"/>
</p:column>
</p:dataTable>
</h:form>
</h:body>
</f:view>
Вычисление результата происходит в методе модели строки; при нажатии на кнопку происходит отправка формы, содержащей таблицу, и форма перерисовывается.
До отправки формы:

После нажатия на кнопку (в одной строке):

Такой подход работает, но приводит к отправке большого количества лишних данных: при изменении всего одной строки приходится отправлять и обновлять всю форму. Кроме того, ошибка конвертации/валидации в одной строке приведёт к тому, что значения в модели обновлены не будут (и результат для строки не будет пересчитан), что не всегда соответствует логике приложения. Благо, в Primefaces встроено большое количество различных выражений, которые можно писать в атрибутах update
и process
(Primefaces Search, [1], [2]), среди которых есть селектор @row
, который выбирает строку под заданным номером. Кроме того, у компонента p:dataTable
есть атрибут rowIndexVar
, через который можно задать имя переменной, доступной через EL и равной номеру текущей строки. Попробуем модифицировать Facelets-шаблон в соответствии с полученными знаниями.
<?xml version="1.0"?>
<f:view
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets"
>
<h:head>
</h:head>
<h:body>
<h:form>
<p:dataTable id="table" value="#{tableTestBean.rows}" rowIndexVar="idx" var="row">
<p:column headerText="Id">
<h:outputText value="#{row.id}"/>
</p:column>
<p:column headerText="First data">
<p:inputText value="#{row.fst}"/>
</p:column>
<p:column headerText="Second data">
<p:inputText value="#{row.snd}"/>
</p:column>
<p:column>
<p:commandButton value="*" update="@form:table:@row(#{idx})" process="@form:table:@row(#{idx})"/>
</p:column>
<p:column headerText="Result">
<h:outputText value="#{row.result}"/>
</p:column>
</p:dataTable>
</h:form>
</h:body>
</f:view>
Но, увы, при нажатии на кнопку ничего не происходит - значение в последней колонке не обновляется. Тем не менее, запрос при нажатии на кнопку отправляется:

А ответ содержит данные, похожие на то, что могло бы содержаться в одной строке таблицы:

Если внимательно посмотреть на последние элементы update
, то в них можно обнаружить правильный результат умножения 11 на 12.
<update id="_tabletestportlet_WAR_tabletestportlet_:j_idt3:table:0:j_idt13">132</update>
Значит, сервер сгенерировал новую разметку, которую клиентская часть приложения по какой-то причине не смогла применить к DOM'у.
Попробуем поискать элементы с id
, указанными в ответе, на странице. Получим интересный результат:

Получается, что по какой-то причине не всем элементам был присвоен идентификатор (а именно - h:outputText
'у). Попробуем форсировать назначение ему идентификатора путём задания соответствующего атрибута:
<p:column headerText="Result">
<h:outputText id="result" value="#{row.result}"/>
</p:column>
И, ура, это помогло!

Кроме того, видно, что селектор @row
возымел должный эффект - значение обновилось только в той строке, где была нажата кнопка.
Вместо заключения
Не один раз столкнувшись с этой проблемой, я взял себе за правило оборачивать весь контент колонки в какой-то контейнер (h:panelGroup
, например) и всегда задавать ему атрибут id
.
Засим откланиваюсь, прощайте.