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

Primefaces 12动态生成DataTable单元格编辑失效求助

动态生成PrimeFaces 12 DataTable时单元格编辑功能失效(NullPointerException)

使用PrimeFaces 12开发Web应用,静态定义的DataTable单元格编辑功能正常(填写一列后切换到另一列可触发编辑事件),但动态生成的DataTable该功能完全失效:无法进入onEditarCeldaTablaProc方法,同时抛出以下空指针异常:

GRAVE: java.lang.NullPointerException
at org.primefaces.event.CellEditEvent.resolveValue(CellEditEvent.java:106)
at org.primefaces.event.CellEditEvent.<init>(CellEditEvent.java:58)
at org.primefaces.event.CellEditEvent.<init>(CellEditEvent.java:62)
at org.primefaces.component.datatable.DataTable.queueEvent(DataTable.java:452)
at org.primefaces.behavior.ajax.AjaxBehaviorRenderer.decode(AjaxBehaviorRenderer.java:55)
at javax.faces.component.behavior.ClientBehaviorBase.decode(ClientBehaviorBase.java:134)
at org.primefaces.util.ComponentUtils.decodeBehaviors(ComponentUtils.java:256)
at org.primefaces.renderkit.CoreRenderer.decodeBehaviors(CoreRenderer.java:628)
at org.primefaces.component.datatable.DataTableRenderer.decode(DataTableRenderer.java:75)
at javax.faces.component.UIComponentBase.decode(UIComponentBase.java:533)
at org.primefaces.component.api.UIData.processDecodes(UIData.java:140)
at com.sun.faces.context.PartialViewContextImpl$PhaseAwareVisitCallback.visit(PartialViewContextImpl.java:629)
at com.sun.faces.component.visit.PartialVisitContext.invokeVisitCallback(PartialVisitContext.java:183)
at org.primefaces.component.api.UIData.visitTree(UIData.java:715)

正常工作的静态DataTable代码

<f:facet name="header">
    <p:toolbar id="tabla_BotoneraTablaProc" styleClass="toolbar-estilo-tabla-form-solicitud">
        <p:toolbarGroup>
            <p:outputLabel value="Pruebas" />
        </p:toolbarGroup>
        <p:toolbarGroup align="right">
            <p:tooltip for="botonAnadirFilaTablaProc"
                value="#{etiquetasSubvenciones['anadir.fila']}" showEffect="fade"
                hideEffect="fade" hideEvent="mouseleave click"/>
            <p:commandButton id="botonAnadirFilaTablaProc" styleClass="boton"
                icon="fas fa-plus"
                action="#{fichaFormularioConvocatoriaBean.onNuevaFilaTablaPruebaUno}"
                update="@([id$=tablaPrueba])"
                process="@this"
                <f:setPropertyActionListener
                    value="formFichaSolicitud:fichaSolicitud:tablaPrueba"
                    target="#{fichaFormularioConvocatoriaBean.idDataTableActual}" />
            </p:commandButton>
            <p:tooltip for="botonEliminarFilaTablaProc"
                value="#{etiquetasSubvenciones['borrar.fila']}" showEffect="fade"
                hideEffect="fade" hideEvent="mouseleave click"/>
            <p:commandButton id="botonEliminarFilaTablaProc" styleClass="boton"
                icon="fas fa-minus" 
                action="#{fichaFormularioConvocatoriaBean.onEliminarFilaTablaProc}"
                <p:confirm header="#{etiquetas['eliminar.titulo']}"
                    message="#{etiquetas['eliminar.mensaje']}"
                    icon="ui-icon-alert" />
            </p:commandButton>
        </p:toolbarGroup>
    </p:toolbar>
</f:facet>

<p:dataTable id="tablaPruebaUno" widgetVar="tablaPruebaUno" var="listaColsTablaProc"
    value="#{fichaFormularioConvocatoriaBean.lstPruebaUno}"
    lazy="false"
    rowKey="#{listaColsTablaProc.hashCode()}"
    selection="#{fichaFormularioConvocatoriaBean.pruebaSeleccionadaUno}"
    emptyMessage="#{etiquetas['emptyMessage']}" reflow="true"
    editable="true" editMode="cell"

    <p:ajax event="cellEdit" listener="#{fichaFormularioConvocatoriaBean.onEditarCeldaTablaProc}"/>

    <p:column selectionMode="single" style="width:4%;text-align:center"/>                           

    <p:column
        headerText="Columna1" style="text-align:center"
        <p:cellEditor>
            <f:facet name="output"
                <h:outputText id="listaColsTablaCol1" value="#{listaColsTablaProc.id}" />
            </f:facet>
            <f:facet name="input"
                <p:inputText value="#{listaColsTablaProc.id}" style="width: 100%;"
                </p:inputText>
            </f:facet>
        </p:cellEditor>
    </p:column>

    <p:column
        headerText="Columna2" style="text-align:center"
        <p:cellEditor>
            <f:facet name="output"
                <h:outputText id="listaColsTablaCol2" value="#{listaColsTablaProc.nombre}" />
            </f:facet>
            <f:facet name="input"
                <p:inputText value="#{listaColsTablaProc.nombre}" style="width: 100%;"
                </p:inputText>
            </f:facet>
        </p:cellEditor>
    </p:column>
</p:dataTable>

动态生成DataTable的相关代码

属性定义

private static final String EVT_AJAX_CELL_EDIT = "cellEdit";

AjaxBehavior pAjaxCellEdit;

String idDataTableActual;

List<Prueba> lstPrueba;
Prueba pruebaSeleccionada;

List<Prueba> lstPruebaUno;
Prueba pruebaSeleccionadaUno;

构造方法中的Ajax行为初始化

pAjaxCellEdit = new AjaxBehavior();
MethodExpression meEditCell = expFactory.createMethodExpression(elContext, "#{fichaFormularioConvocatoriaBean.onEditarCeldaTablaProc}", getClass(), new Class<?>[] {CellEditEvent.class});
pAjaxCellEdit.addAjaxBehaviorListener(new AjaxBehaviorListenerImpl(meEditCell, meEditCell));

动态添加组件的调用代码

tab.getChildren().add(toolBarCumplimentarDos("TablaPrueba", "1", PANEL_BOT_TBL_FORM_SOLICITUD, TOOLBAR_TBL_FORM_SOLICITUD));
tab.getChildren()
        .add(dataTableCumplimentarDos(
                "#{fichaFormularioConvocatoriaBean.lstPrueba}",
                "#{fichaFormularioConvocatoriaBean.pruebaSeleccionada}",
                lstPrueba));

工具栏和DataTable生成方法

private Panel toolBarCumplimentarDos(String titulo, String idTabla, String estiloPanel, String estiloToolBar) {
    Panel panel = new Panel();
    Toolbar toolBar = new Toolbar();
    ToolbarGroup toolBarGroupTitle= new ToolbarGroup();
    ToolbarGroup toolBarGroupButtons= new ToolbarGroup();
    OutputLabel outputLabel = new OutputLabel();
    Tooltip tooltipAdd = new Tooltip();
    Tooltip tooltipDel = new Tooltip();
    CommandButton comButAdd = new CommandButton();
    CommandButton comButDel = new CommandButton();
    ValueExpression target = expFactory.createValueExpression(elContext, "#{fichaFormularioConvocatoriaBean.idDataTableActual}", String.class);
    ValueExpression value = expFactory.createValueExpression(TABVIEW_SOLICITUDES + TABVIEW_BLOQUE_DATOS + ":" + TBL_SOLICITUD_PREF_ID_DT + idTabla, String.class);
    ActionListener handler = new SetPropertyActionListenerImpl(target , value);
    
    tooltipAdd.setFor(PREF_ETQ_BOTON_ANADIR_FILA + idTabla);
    tooltipAdd.setValue(VALUE_TOOLTIP_BOTON_ANADIR_FILA);
    tooltipAdd.setShowEffect(SHOWEF_TOOLTIP);
    tooltipAdd.setHideEffect(HIDEEF_TOOLTIP);
    
    comButAdd.setId(PREF_ETQ_BOTON_ANADIR_FILA + idTabla);
    comButAdd.setIcon(ICONO_ANADIR);
    comButAdd.setStyleClass("boton");
    comButAdd.setActionExpression(expFactory.createMethodExpression(elContext, "#{fichaFormularioConvocatoriaBean.onNuevaFilaTablaPrueba}", getClass(), new Class[0]));
    comButAdd.addActionListener(handler);
    comButAdd.setUpdate("@([id$=" + TBL_SOLICITUD_PREF_ID_DT + idTabla + "])");
    comButAdd.setProcess("@this");

    tooltipDel.setFor(PREF_ETQ_BOTON_BORRAR_FILA + idTabla);
    tooltipDel.setValue(VALUE_TOOLTIP_BOTON_BORRAR_FILA);
    tooltipDel.setShowEffect(SHOWEF_TOOLTIP);
    tooltipDel.setHideEffect(HIDEEF_TOOLTIP);

    comButDel.setId(PREF_ETQ_BOTON_BORRAR_FILA + idTabla);
    comButDel.setIcon(ICONO_ELIMINAR);
    comButDel.setStyleClass("boton");
    comButDel.setActionExpression(expFactory.createMethodExpression(elContext, "#{fichaFormularioConvocatoriaBean.onEliminarFilaTablaProc}", getClass(), new Class[0]));
    comButDel.setValueExpression("disabled", expFactory.createValueExpression(elContext, "#{fichaFormularioConvocatoriaBean.listaBloquesDatosFormularioCumplimentar.get(0).getObjetos().get(0).tablaBloqueDatosDto.detalles.filaSeleccionada == null}", Boolean.class));

    outputLabel.setValue(titulo);
    toolBarGroupTitle.getChildren().add(outputLabel);
    toolBarGroupButtons.getChildren().add(tooltipAdd);
    toolBarGroupButtons.getChildren().add(comButAdd);
    toolBarGroupButtons.getChildren().add(tooltipDel);
    toolBarGroupButtons.getChildren().add(comButDel);
    toolBar.getChildren().add(toolBarGroupTitle);
    toolBar.getChildren().add(toolBarGroupButtons);
    toolBar.setStyleClass(estiloToolBar);
    panel.setStyleClass(estiloPanel);
    panel.getFacets().put("header", toolBar);

    return panel;
}


private DataTable dataTableCumplimentarDos(String filasValueExpr, String filaSeleccionadaValueExpr, List<Prueba> tabla) {
    DataTable dataTable = new DataTable();
    List<UIColumn> columnas = new ArrayList<UIColumn>();
    CellEditor cellEd = new CellEditor();
    Column colUI;
    String var = TBL_SOLICITUD_PREF_VAR + "1";
    String idDataTable = TBL_SOLICITUD_PREF_ID_DT + "1";
    
    colUI = new Column();
    colUI.setSelectionMode(TBL_SOLICITUD_SEL_MODE);
    colUI.setStyle(TBL_SOLICITUD_SEL_STYLE);
    columnas.add(colUI);
    
    cellEd.getFacets().put("output", outputLabelCumplimentar("#{" + var + ".id}",""));
    
    cellEd.getFacets().put("input", uiComponentCumplimentarDos(TipoDato.EnumTipoDato.ENTERO, "#{" + var + "", ".id}", null, true));

    colUI = new Column();
    colUI.setStyle(TBL_SOLICITUD_CELL_STYLE);
    colUI.setHeaderText("Columna1");
    colUI.getChildren().add(cellEd);
    columnas.add(colUI);
    
    cellEd = new CellEditor();      

    cellEd.getFacets().put("output", outputLabelCumplimentar("#{" + var + ".nombre}",""));
    
    cellEd.getFacets().put("input", uiComponentCumplimentarDos(TipoDato.EnumTipoDato.ENTERO, "#{" + var + "", ".nombre}", null, true));

    colUI = new Column();
    colUI.setStyle(TBL_SOLICITUD_CELL_STYLE);
    colUI.setHeaderText("Columna2");
    colUI.getChildren().add(cellEd);
    columnas.add(colUI);

    dataTable.setColumns(columnas);
    dataTable.setValueExpression("value", expFactory.createValueExpression(elContext, filasValueExpr, ArrayList.class));
    dataTable.setLazy(false);
    dataTable.setEditable(true);
    dataTable.setEditMode("cell");
    dataTable.setVar(var);
    dataTable.setEmptyMessage(TBL_MSG_VACIA);
    dataTable.setReflow(true);
    dataTable.setValueExpression("rowKey", expFactory.createValueExpression(elContext, "#{" + var + ".hashCode()}", String.class));
    dataTable.setValueExpression("selection", expFactory.createValueExpression(elContext, filaSeleccionadaValueExpr, ArrayList.class));
    dataTable.setId(idDataTable);
    dataTable.setWidgetVar(idDataTable);
    dataTable.addClientBehavior(EVT_AJAX_CELL_EDIT, pAjaxCellEdit);
    
    return dataTable;
}

问题排查与解决建议

1. 修复selection属性的类型错误

静态DataTable中selection绑定的是单个Prueba对象,但动态代码中设置selection的ValueExpression时,类型指定为ArrayList.class,这和实际绑定的Prueba pruebaSeleccionada类型不匹配,会导致CellEditEvent处理时无法正确解析行数据,引发NPE。

修改dataTableCumplimentarDos方法中selection的ValueExpression代码:

dataTable.setValueExpression("selection", expFactory.createValueExpression(elContext, filaSeleccionadaValueExpr, Prueba.class));

2. 确保每个动态DataTable使用独立的AjaxBehavior

当前代码中pAjaxCellEdit是全局单例,多个动态DataTable共享同一个AjaxBehavior实例可能引发状态冲突。应改为在每个DataTable生成时创建独立的AjaxBehavior:

// 在dataTableCumplimentarDos方法内创建AjaxBehavior,替换全局pAjaxCellEdit
AjaxBehavior pAjaxCellEdit = new AjaxBehavior();
MethodExpression meEditCell = expFactory.createMethodExpression(elContext, "#{fichaFormularioConvocatoriaBean.onEditarCeldaTablaProc}", Void.class, new Class<?>[] {CellEditEvent.class});
pAjaxCellEdit.addAjaxBehaviorListener(new AjaxBehaviorListenerImpl(meEditCell, meEditCell));
dataTable.addClientBehavior(EVT_AJAX_CELL_EDIT, pAjaxCellEdit);

注意:MethodExpression的返回类型应指定为Void.class,而不是getClass(),因为onEditarCeldaTablaProc方法应该是void返回类型。

3. 修复ValueExpression拼接错误

在uiComponentCumplimentarDos调用中,存在字符串拼接错误:"#{" + var + "", ".id}",这会生成无效的EL表达式(比如#{var1, .id}),导致无法正确绑定数据,进而在单元格编辑时引发NPE。

修改为正确的拼接:

cellEd.getFacets().put("input", uiComponentCumplimentarDos(TipoDato.EnumTipoDato.ENTERO, "#{" + var, ".id}", null, true));
// 或者更清晰的写法:"#{" + var + ".id}"
// 检查uiComponentCumplimentarDos方法是否正确组合前缀和后缀,确保最终EL表达式格式正确

4. 验证rowKey的有效性

动态DataTable中rowKey使用hashCode(),但hashCode()可能存在碰撞,且如果Prueba对象没有正确重写hashCode()和equals(),会导致行识别错误。建议改为使用唯一标识字段(比如id)作为rowKey:

dataTable.setValueExpression("rowKey", expFactory.createValueExpression(elContext, "#{" + var + ".id}", Object.class));

同时确保Prueba类正确重写equals()和hashCode()方法,基于id字段。

5. 确保动态组件的状态保存

动态生成的组件需要在每个请求中重新创建(或从视图状态恢复),确保在init或viewScoped bean的合适阶段重建组件,避免组件状态丢失导致事件无法触发。

内容的提问来源于stack exchange,提问作者Javier

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 19:55:54