如何在JSF Facelets(PrimeFaces DataTable)中动态访问c:set定义的值?
问题描述
通过<c:set>定义的值引用了DataTable迭代变量var(指代每条数据的变量名),但DataTable的列值无法正常显示。现有代码示例如下:
<c:set var="column1_label" value="Id" scope="view" /> <c:set var="column1_value" value="#{data.id}" scope="view" /> <c:set var="column2_label" value="Code" scope="view" /> <c:set var="column2_value" value="#{data.code}" scope="view" /> <c:set var="column3_label" value="Name" scope="view" /> <c:set var="column3_value" value="#{data.name}" scope="view" /> <p:dataTable id="dataTableId" value="#{testView.products}" var="data"> <c:forEach begin="1" end="3" var="idx"> <p:column headerText="#{viewScope['column' += idx += '_label']}"> <h:outputText value="#{viewScope['column' += idx += '_value']}" /> </p:column> </c:forEach> </p:dataTable>
背景:我们有上百个包含DataTable的视图,因此构建了DataTable模板用于跨页面引入。希望通过<c:set>定义列(含列值)并循环生成,由于列需要复杂配置(格式、输入框、转换器等),需在各视图用<c:set>完成。但目前无法获取列值,<ui:param>不适用,<c:set>也无法与迭代变量名配合工作。
解决方案
问题核心是<c:set>在视图构建阶段就会解析#{data.id}这类表达式,但此时DataTable的迭代变量data还未初始化(DataTable的迭代是在渲染阶段执行的),导致表达式无法正确绑定到迭代数据。以下是两种可行的解决方式:
方式一:将列值定义为EL表达式字符串,渲染时动态求值
把<c:set>的value设为EL表达式的字符串内容(去掉#{}包裹),然后通过后台方法动态解析表达式并绑定当前迭代数据:
- 修改
<c:set>的定义:
<c:set var="column1_label" value="Id" scope="view" /> <c:set var="column1_value" value="data.id" scope="view" /> <!-- 仅保留表达式内容,去掉#{} --> <c:set var="column2_label" value="Code" scope="view" /> <c:set var="column2_value" value="data.code" scope="view" /> <c:set var="column3_label" value="Name" scope="view" /> <c:set var="column3_value" value="data.name" scope="view" />
- 在后台Bean中添加表达式解析方法:
import javax.el.ExpressionFactory; import javax.el.ValueExpression; import javax.faces.context.FacesContext; public Object evaluateColumnValue(String expr, Object currentData) { FacesContext facesContext = FacesContext.getCurrentInstance(); ExpressionFactory exprFactory = facesContext.getApplication().getExpressionFactory(); // 创建要解析的ValueExpression ValueExpression valueExpr = exprFactory.createValueExpression( facesContext.getELContext(), "#{" + expr + "}", Object.class ); // 将当前迭代的data对象绑定到EL上下文的"data"变量 facesContext.getELContext().setVariable( "data", exprFactory.createValueExpression(currentData, currentData.getClass()) ); return valueExpr.getValue(facesContext.getELContext()); }
- 页面中调用方法获取列值:
<p:dataTable id="dataTableId" value="#{testView.products}" var="data"> <c:forEach begin="1" end="3" var="idx"> <p:column headerText="#{viewScope['column' += idx += '_label']}"> <h:outputText value="#{testView.evaluateColumnValue(viewScope['column' += idx += '_value'], data)}" /> <!-- 复杂场景示例:带转换器的输入框 --> <!-- <p:inputText value="#{testView.evaluateColumnValue(viewScope['column' += idx += '_value'], data)}"> <f:convertNumber pattern="0.00" /> </p:inputText> --> </p:column> </c:forEach> </p:dataTable>
方式二:使用自定义列模型类,统一管理列配置
这种方式更适合复杂列配置的场景,避免页面上的字符串拼接,也更易维护:
- 创建列模型类,封装列的所有配置:
public class TableColumnConfig { private String headerText; private String valueExpr; // 比如"data.id" private boolean editable; // 是否可编辑 private String converterId; // 转换器ID(可选) // 可添加更多配置项:渲染器、样式、排序字段等 // 构造函数、getter和setter public TableColumnConfig(String headerText, String valueExpr) { this.headerText = headerText; this.valueExpr = valueExpr; } // getter & setter public String getHeaderText() { return headerText; } public void setHeaderText(String headerText) { this.headerText = headerText; } public String getValueExpr() { return valueExpr; } public void setValueExpr(String valueExpr) { this.valueExpr = valueExpr; } public boolean isEditable() { return editable; } public void setEditable(boolean editable) { this.editable = editable; } public String getConverterId() { return converterId; } public void setConverterId(String converterId) { this.converterId = converterId; } }
- 在视图对应的Bean中初始化列配置列表:
import java.util.ArrayList; import java.util.List; import javax.annotation.PostConstruct; import javax.faces.view.ViewScoped; import javax.inject.Named; @Named @ViewScoped public class TestView { private List<TableColumnConfig> columns; private List<Product> products; // 你的业务数据列表 @PostConstruct public void init() { // 初始化列配置 columns = new ArrayList<>(); columns.add(new TableColumnConfig("Id", "data.id")); columns.add(new TableColumnConfig("Code", "data.code")); columns.add(new TableColumnConfig("Name", "data.name")); // 示例:添加带转换器的可编辑列 // TableColumnConfig priceCol = new TableColumnConfig("Price", "data.price"); // priceCol.setEditable(true); // priceCol.setConverterId("javax.faces.Number"); // columns.add(priceCol); // 初始化业务数据products... } // 复用方式一中的evaluateColumnValue方法 public Object evaluateColumnValue(String expr, Object currentData) { // 实现同方式一 } // getter & setter public List<TableColumnConfig> getColumns() { return columns; } public List<Product> getProducts() { return products; } }
- 页面上直接循环列配置生成列:
<p:dataTable id="dataTableId" value="#{testView.products}" var="data"> <c:forEach items="#{testView.columns}" var="col"> <p:column headerText="#{col.headerText}"> <c:if test="#{col.editable}"> <p:inputText value="#{testView.evaluateColumnValue(col.valueExpr, data)}"> <c:if test="#{not empty col.converterId}"> <f:converter converterId="#{col.converterId}" /> </c:if> </p:inputText> </c:if> <c:if test="#{not col.editable}"> <h:outputText value="#{testView.evaluateColumnValue(col.valueExpr, data)}"> <c:if test="#{not empty col.converterId}"> <f:converter converterId="#{col.converterId}" /> </c:if> </h:outputText> </c:if> </p:column> </c:forEach> </p:dataTable>
核心原理说明
- JSF的标签分为视图构建阶段(如
<c:forEach>、<c:set>)和渲染阶段(如<p:dataTable>),两者的执行时机不同:<c:set>会在页面构建时就解析所有EL表达式,而DataTable的var变量要等到渲染时才会为每条数据创建。 - 动态求值的方式,是在渲染阶段将当前迭代的
data对象绑定到EL上下文,再解析提前保存的表达式字符串,从而实现正确的属性绑定。
内容的提问来源于stack exchange,提问作者Andrew Churchill
相关产品推荐
相关产品推荐

