3

I am trying to create an editable data table with dynamically generated columns.

The table renders as it should and when clicked the cells go into edit mode, but when enter is press or when focus leaves the cell I get a null pointer exception on the server. The onCellEdit method gets called but the cell value is not set in car object.

  • Am I doing something weird in the EL expression for the value attribute for p:inputText or p:outputText? Should I use some other property for the var attribute on p:dataTable or p:columns? What kind of EL expressions are valid here?
  • Have I missed something else?
  • Or could this be a bug in PrimeFaces? I can see that the error occurs in CellEditFeature.encode when it expects to get a CellEditor but that turns out to be null.

I have tried this with Primefaces 4.0 and 5.0 with the same result. I'm using JSF 2.2, Glassfish 4.0 and Java 1.8.0_20.

If anyone can help me figure out the cause of the problem I would be most grateful. It would also be useful to have a working example of dynamic editable datatables on Stack Overflow.

Bellow is a minimal example using the Primefaces showcase example of dynamically generated columns to which I have tried to add editing, and the exception stack trace.

Web page:

<p:dataTable id="cars" var="car" value="#{dtColumnsView.cars}" widgetVar="carsTable"
    editable="true" editMode="cell" >

  <p:ajax event="cellEdit" listener="#{dtColumnsView.onCellEdit}" update=":form:msgs" />

  <p:columns value="#{dtColumnsView.columns}" var="column" columnIndexVar="colIndex" 
    styleClass="ui-editable-column">

    <f:facet name="header">
      <h:outputText value="#{column.header}" />
    </f:facet>

    <p:cellEditor>
      <f:facet name="output"><h:outputText value="#{car[column.property]}" /></f:facet>
      <f:facet name="input"><p:inputText id="modelInput" value="#{car[column.property]}" /></f:facet>
    </p:cellEditor>

  </p:columns>
</p:dataTable>

Backing bean:

@ManagedBean(name = "dtColumnsView")
@ViewScoped
public class ColumnsView implements Serializable {

  private static final long serialVersionUID = 1L;

  private List<ColumnModel> columns;
  private List<Car> cars;

  @ManagedProperty("#{carService}")
  private CarService service;

  @PostConstruct
  public void init() {
    cars = service.createCars(10);
    columns = Arrays.asList(
        new ColumnModel("Id", "id"), 
        new ColumnModel("Brand", "brand"), 
        new ColumnModel("Year", "year"));
  }

  public List<Car> getCars() {
    return cars;
  }

  public List<String> getBrands() {
    return service.getBrands();
  }

  public List<String> getColors() {
    return service.getColors();
  }

  public void setService(CarService service) {
    this.service = service;
  }

  public List<ColumnModel> getColumns() {
    return columns;
  }

  public void onCellEdit(CellEditEvent event) {
    Object oldValue = event.getOldValue();
    Object newValue = event.getNewValue();
    System.out.println("Cell Changed, Old: " + oldValue + ", New:" + newValue);
    if (newValue != null && !newValue.equals(oldValue)) {
      FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Cell Changed", "Old: " + oldValue + ", New:" + newValue);
      FacesContext.getCurrentInstance().addMessage(null, msg);
    }
  }

  public void dumpCars() {
    for (Car c : cars) System.out.println(c.brand);
  }

  static public class ColumnModel implements Serializable {

    private static final long serialVersionUID = 1L;

    private String header;
    private String property;

    public ColumnModel(String header, String property) {
      this.header = header;
      this.property = property;
    }

    public String getHeader() {
      return header;
    }

    public String getProperty() {
      return property;
    }
  }
}

Exception stacktrace:

java.lang.NullPointerException
at org.primefaces.component.datatable.feature.CellEditFeature.encode(CellEditFeature.java:54)
at org.primefaces.component.datatable.DataTableRenderer.encodeEnd(DataTableRenderer.java:76)
at javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:924)
at javax.faces.component.UIComponent.encodeAll(UIComponent.java:1863)
at com.sun.faces.context.PartialViewContextImpl$PhaseAwareVisitCallback.visit(PartialViewContextImpl.java:559)
at com.sun.faces.component.visit.PartialVisitContext.invokeVisitCallback(PartialVisitContext.java:183)
at org.primefaces.component.api.UIData.visitTree(UIData.java:675)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1700)
at javax.faces.component.UIForm.visitTree(UIForm.java:371)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1700)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1700)
at com.sun.faces.context.PartialViewContextImpl.processComponents(PartialViewContextImpl.java:399)
at com.sun.faces.context.PartialViewContextImpl.processPartial(PartialViewContextImpl.java:319)
at org.primefaces.context.PrimePartialViewContext.processPartial(PrimePartialViewContext.java:57)
at javax.faces.component.UIViewRoot.encodeChildren(UIViewRoot.java:1004)
at javax.faces.component.UIComponent.encodeAll(UIComponent.java:1856)
at com.sun.faces.application.view.FaceletViewHandlingStrategy.renderView(FaceletViewHandlingStrategy.java:417)
at com.sun.faces.application.view.MultiViewHandler.renderView(MultiViewHandler.java:131)
at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:337)
at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:120)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:219)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:647)
at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1682)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:318)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:160)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:734)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:673)
at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:99)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:174)
at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:357)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:260)
at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:188)
at org.glassfish.grizzly.http.server.HttpHandler.runService(HttpHandler.java:191)
at org.glassfish.grizzly.http.server.HttpHandler.doHandle(HttpHandler.java:168)
at org.glassfish.grizzly.http.server.HttpServerFilter.handleRead(HttpServerFilter.java:189)
at org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:119)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:288)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:206)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:136)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:114)
at org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:77)
at org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:838)
at org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:113)
at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:115)
at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.access$100(WorkerThreadIOStrategy.java:55)
at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy$WorkerThreadRunnable.run(WorkerThreadIOStrategy.java:135)
at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:564)
at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:544)
at java.lang.Thread.run(Thread.java:745)
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Lii
  • 11,553
  • 8
  • 64
  • 88

1 Answers1

4

The org.primefaces.component.columns.Columns class behind <p:columns> returns a hardcoded null on getCellEditor() method. See source code:

258    public CellEditor getCellEditor() {
259        return null;
260    }

That at least explains the NPE further in the chain, but as I'm not a PrimeFaces dev, I can't explain why it purposefully returns a hardcoded null. Your best bet is reporting a new issue to PrimeFaces. It doesn't seem to be already reported before.


In the meanwhile, you should theoretically be able to solve this awkward issue by replacing <p:columns> with <c:forEach><p:column>.

<c:forEach items="#{dtColumnsView.columns}" var="column" varStatus="loop">
  <p:column>

    <f:facet name="header">
      <h:outputText value="#{column.header}" />
    </f:facet>

    <p:cellEditor>
      <f:facet name="output"><h:outputText value="#{car[column.property]}" /></f:facet>
      <f:facet name="input"><p:inputText id="modelInput_#{loop.index}" value="#{car[column.property]}" /></f:facet>
    </p:cellEditor>

  </p:column>
</c:forEach>

Note: make sure that you manually assign the iteration index to any component ID inside the loop.

See also:

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thanks for the investigation and the suggestion. I was aware of the solution with `c:forEach` but I have read reports from people who seem to have gotten it to work with `p:columns`. – Lii Sep 03 '14 at 18:41
  • This is [mentioned](https://code.google.com/p/primefaces/issues/detail?id=1814) in the issue tracker for example. But reading that again makes me notice that they speak about *row* editing. So I have to try if this works as a work around. – Lii Sep 03 '14 at 18:48