PrimeFaces11嵌套DataTable行编辑后子行重复渲染问题
PrimeFaces 11 DataTable 行展开嵌套可编辑子表渲染异常问题
问题场景
- 实现带
rowExpansion行展开功能的订单主DataTable:点击行首展开箭头后,展开区域通过嵌套子DataTable展示对应订单的行项目明细 - 主表头部配置「+」按钮用于新增订单,子表头部配置「+」按钮用于为当前订单新增行项目,主、子表均开启
rowEdit行编辑功能
异常复现路径
- 点击行展开区域内子表的行编辑按钮,行正常进入编辑态
- 点击编辑态的保存对勾图标,对应行项目数据可正常更新,但父级订单主表区域会重复展示一条相同的行项目数据,原有展开的子表区域同时消失
- 刷新页面后重复数据行自动消失
咨询问题
- 该异常是否为PrimeFaces 11的已知缺陷?
- DataTable的行展开功能是否仅支持只读展示场景,不支持嵌套可编辑表格?
附:可复现代码
前端XHTML代码
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://xmlns.jcp.org/jsf/facelets" xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:p="http://primefaces.org/ui" xmlns:h="http://xmlns.jcp.org/jsf/html"> <h:head> <title>PrimeFaces Test</title> <h:outputScript name="test.js"/> <h:outputStylesheet name="test.css"/> </h:head> <h:body> <h1>#{testView.string}</h1> <h:form id="form"> <div class="card"> <h5>Row Editing</h5> <p:dataTable id="order" var="order" value="#{mainBean.orderVOs}" editable="true" style="margin-bottom:20px" styleClass="myclass" rowKey="#{order.orderNo}" rowIndexVar="orderIndex" showGridlines="true"> <p:ajax event="rowEdit" listener="#{mainBean.onRowEdit(order)}" update=":form:order"/> <p:ajax event="rowEditCancel" listener="#{mainBean.onRowCancel(order)}" update=":form:order"/> <p:column style="width:2rem"> <p:rowToggler/> </p:column> <p:column style="width:6rem"> <f:facet name="header"> <p:commandButton value="+" styleClass="rounded-button ui-button-plus" actionListener="#{mainBean.addOrder()}" update="order" oncomplete="editLastDatatableRow();"/> </f:facet> <p:rowEditor editTitle="Edit Row" cancelTitle="Cancel Edit" saveTitle="Save Row"/> </p:column> <p:column headerText="OrderNo"> <p:cellEditor> <f:facet name="output"> <h:outputText value="#{order.orderNo}"/> </f:facet> <f:facet name="input"> <p:inputText id="modelInput" value="#{order.orderNo}" style="width:100%"/> </f:facet> </p:cellEditor> </p:column> <p:column headerText="AddressLine1"> <p:cellEditor> <f:facet name="output"> <h:outputText value="#{order.addressLine1}"/> </f:facet> <f:facet name="input"> <p:inputText value="#{order.addressLine1}" style="width:100%" label="Name"/> </f:facet> </p:cellEditor> </p:column> <p:column headerText="GrandTotal"> <p:cellEditor> <f:facet name="output"> <h:outputText value="#{order.totalAmt}"/> </f:facet> <f:facet name="input"> <p:inputText value="#{order.totalAmt}" style="width:100%" label="Name"/> </f:facet> </p:cellEditor> </p:column> <p:rowExpansion> <div class="card"> <h5>Row Editing</h5> <p:dataTable id="lineItem" var="line" value="#{order.lineItems}" editable="true" style="margin-bottom:20px" rowKey="#{ineItem.productDescription}" rowIndexVar="lineIndex" styleClass="myClass${orderIndex}" size="small" showGridlines="true"> <p:ajax event="rowEdit" listener="#{mainBean.onRowEditLine(order,line)}" update="form:order:${orderIndex}:lineItem form:order"/> <p:ajax event="rowEditCancel" listener="#{mainBean.onRowCancelLine(order,line)}" update="form:order:${orderIndex}:lineItem"/> <p:column style="width:6rem"> <f:facet name="header"> <p:commandButton value="+" styleClass="rounded-button ui-button-plus" actionListener="#{mainBean.addLine(order)}" update="form:order:${orderIndex}:lineItem" oncomplete="editLastDatatableRowLine(${orderIndex});"/> </f:facet> <p:rowEditor editTitle="Edit Row" cancelTitle="Cancel Edit" saveTitle="Save Row"/> </p:column> <p:column headerText="productDescription"> <p:cellEditor> <f:facet name="output"> <h:outputText value="#{line.productDescription}"/> </f:facet> <f:facet name="input"> <p:inputText id="modelInput" value="#{line.productDescription}" style="width:100%"/> </f:facet> </p:cellEditor> </p:column> <p:column headerText="quantity"> <p:cellEditor> <f:facet name="output"> <h:outputText value="#{line.quantity}"/> </f:facet> <f:facet name="input"> <p:inputText value="#{line.quantity}" style="width:100%" label="Name"/> </f:facet> </p:cellEditor> </p:column> </p:dataTable> </div> </p:rowExpansion> </p:dataTable> </div> <script type="text/javascript"> function editLastDatatableRow() { jQuery('.myclass .ui-datatable-data tr').first().find( 'span.ui-icon-pencil').each(function() { jQuery(this).click() }); } function editLastDatatableRowLine(index) { jQuery('.myClass'+index+ " "+ '.ui-datatable-data tr').first().find( 'span.ui-icon-pencil').each(function() { jQuery(this).click() }); } </script> </h:form> </h:body> </html>
后端MainBean代码(@ViewScoped作用域)
package org.primefaces.test; import java.io.Serializable; 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 MainBean implements Serializable { private static final long serialVersionUID = 7536893702695858952L; List<OrderVO> orderVOs; private List<LineVO> lineItems; @PostConstruct public void init() { orderVOs = new ArrayList<OrderVO>(); lineItems = new ArrayList<LineVO>(); LineVO l1 = new LineVO("desc1", "d", "s", "s", "d"); lineItems.add(l1); OrderVO orderVO = new OrderVO(); orderVO.setOrderNo("10000"); orderVO.setAddressLine1("address1"); orderVO.setLineItems(lineItems); orderVOs.add(orderVO); } public List<LineVO> getLineItems() { return lineItems; } public void setLineItems(List<LineVO> lineItems) { this.lineItems = lineItems; } public List<OrderVO> getOrderVOs() { return orderVOs; } public void setOrderVOs(List<OrderVO> orderVOs) { this.orderVOs = orderVOs; } public void onRowEdit(OrderVO orderVO) {} public void onRowCancel(OrderVO orderVO, LineVO lineVO) {} public void onRowEditLine(OrderVO orderVO, LineVO lineVO) {} public void onRowCancelLine(OrderVO orderVO, LineVO lineVO) {} public void addOrder() { OrderVO orderVO = new OrderVO(); orderVOs.add(0, orderVO); } public void addLine(OrderVO orderVO) { LineVO lineVO = new LineVO("dd", "jjj", "kk", "ooo", "iiiii"); if (orderVO.getLineItems() == null) { orderVO.setLineItems(new ArrayList<LineVO>()); } orderVO.getLineItems().add(0, lineVO); } }
问题解答
- 关于是否为已知缺陷:这是PrimeFaces 11版本行展开+嵌套可编辑表格场景下的已确认渲染bug,触发原因是子表行编辑后更新整个主表时,组件未正确区分主表行和展开区的子表DOM节点,把子表行错误追加到主表数据行列表中,该问题在后续12版本中已做针对性修复。
- 关于行展开是否仅支持只读场景:行展开功能本身没有限制嵌套可编辑组件,出现异常是版本bug+代码配置问题共同导致,并非功能不支持。代码中存在两处明确配置错误会放大该异常:
- 子表
rowKey属性存在拼写错误,原代码写为#{ineItem.productDescription},正确应为#{line.productDescription},错误的rowKey会导致组件无法正确识别子表行的唯一标识 - 子表行编辑的ajax update配置直接更新整个主表
:form:order,会触发行展开区域的全量重渲染,丢失当前展开状态同时引发DOM错位
- 子表
- 修复方案:修正子表rowKey拼写错误,子表行编辑后不要直接更新整个主表,仅更新当前子表和主表中需要同步的金额列即可;如果必须更新主表,更新后需要通过JS主动触发展开行的重新展开逻辑。
内容的提问来源于stack exchange,提问作者SRK
相关产品推荐
相关产品推荐

