You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 11:54:14