JSF/PrimeFaces: programmatic on not firing

commandbuttondatatablejsfjsf-2primefaces

We have a requirement to allow users to configure the order of columns in all datatables, including the columns that have action buttons on them, here p:commandButtons.

Hence we are using binding for all our columns which we must instantiate manually. For all columns that only display some strings, booleans, dates, and numbers all works fine, however problems start when adding p:commandButtons to the columns that have one or more <f:setPropertyActionListener>s on them.

    ELContext elContext = FacesContext.getCurrentInstance().getELContext();
    ExpressionFactory factory = FacesContext.getCurrentInstance().getApplication().getExpressionFactory();

    CommandButton button = new CommandButton();
    button.setIcon( "ui-icon ui-icon-pencil" );
    button.setProcess( "@this" );
    button.setUpdate( ":compliance-case-dialog :compliance-case-form:data" );
    button.setOncomplete( "complianceCaseDialog.show();" );

    ValueExpression targetExpression = factory.createValueExpression( elContext, "#{complianceCaseManager.selectedComplianceCaseWithRefresh}", ComplianceCase.class );
    ValueExpression valueExpression = factory.createValueExpression( elContext, "#{coca}", ComplianceCase.class );

    button.addActionListener( new SetPropertyActionListenerImpl( targetExpression, valueExpression ) );

    column.getChildren().add( button ); // add to Column instance

This button "correctly" displays a <p:dialog>, but should call ComplianceCaseManager class' setSelectedComplianceCaseWithRefresh( ComplianceCase selectedComplianceCase ) method with the datatable's current entity (as defined by var="coca") before showing the dialog.

<p:dataTable id="data"
             widgetVar="resultTable"
             value="#{complianceCaseManager.complianceCases}"
             var="coca"
             ...>
</p:dataTable>

Debugging shows the setSelectedComplianceCaseWithRefresh( ComplianceCase selectedComplianceCase ) method is never called.

Q:

What's wrong? How do you fix this?

PS: config is PrimeFaces 3.4.2, Mojarra 2.1.22, GlassFish 3.1.2.2, Java 7


Update 1:

Here's the p:commandButton I want to translate to programmatic:

<p:commandButton icon="ui-icon ui-icon-pencil"
                 title="#{msg['complianceCaseManager.data.edit.button.hint']}"
                 process="@this"
                 update=":compliance-case-dialog :compliance-case-form:data"
                 oncomplete="complianceCaseDialog.show();">
    <p:resetInput target=":unlocked-form" />
    <f:setPropertyActionListener target="#{complianceCaseManager.selectedComplianceCaseWithRefresh}" value="#{coca}" />
    <f:setPropertyActionListener target="#{complianceCaseManager.mode}" value="EDIT" />
</p:commandButton>

The line

    <f:setPropertyActionListener target="#{complianceCaseManager.selectedComplianceCaseWithRefresh}" value="#{coca}" />

is the one I must get to work.

The solution by kolossus to create a MethodExpressionActionListener did not work for my code:

    String targetExpressionString = "#{complianceCaseManager.selectedComplianceCaseWithRefresh}";
    String valueExpressionString = "#{coca}";

    MethodExpression targetMethodExpression = factory.createMethodExpression( elContext, targetExpressionString, null, new Class<?>[]{ ComplianceCase.class } );
    MethodExpression valueMethodExpression = factory.createMethodExpression( elContext, valueExpressionString, ComplianceCase.class, new Class<?>[0] );

    button.addActionListener( new MethodExpressionActionListener( targetMethodExpression, valueMethodExpression ) );

AFAIK is made so that on the target the setter is called and on the value the setter is called, so I'd assume the ValueExpressions to be sufficient.


Update 2:

Trying to set the current entity via EL 2.2 method call also doesn't work.

Code:

    String methodExpressionString = "#" + "{complianceCaseManager.selectedComplianceCaseWithRefresh(coca)}";
    MethodExpression methodExpression = factory.createMethodExpression( elContext, methodExpressionString, null, new Class<?>[]{ ComplianceCase.class } );

    button.addActionListener( new MethodExpressionActionListener( methodExpression ) );

Nothing is called.


Update 3:

Here's the correct <:setPropertyActionListener> code:

    // traditional <f:setPropertyActionListener target="#{complianceCaseManager.selectedComplianceCaseWithRefresh}" value="#{coca}" />
    ValueExpression targetExpression = factory.createValueExpression( elContext, "#{complianceCaseManager.selectedComplianceCaseWithRefresh}", ComplianceCase.class );
    ValueExpression valueExpression = factory.createValueExpression( elContext, "#{coca}", ComplianceCase.class );

    button.addActionListener( new SetPropertyActionListenerImpl( targetExpression, valueExpression ) );

Big thanks to kolossus. Remember to call setId() when instantiating a PrimeFaces CommandButton.

Best Answer

An actionListener should be created as a MethodExpression not a ValueExpression :

  1. I'm assuming that factory instanceOf ExpressionFactory. Use the createMethodExpression factory

    MethodExpression targetExpression = factory.createMethodExpression( elContext, "#{complianceCaseManager.selectedComplianceCaseWithRefresh(coca)}",null,new Class[]{ComplianceCase.class} );
    
  2. Adding the listener to the component:

    button.addActionListener( new MethodExpressionActionListener(targetExpression));
    
  3. Not entirely related to the current problem, you also omitted the id attribute of the component. To be safe, add

    button.setId("theButton");
    

    EDIT: You absolutely have to set the id attribute when dynamically creating command components

Related Topic