Primefaces 12动态生成DataTable单元格编辑失效求助
使用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

