Saturday, 6 June 2009

CommandButton not calling action when using immediate and rendered

I came across a problem with using a custom command component, that when using immediate it wasn't calling the action I had bound to it. I started resolving the issue and discovered my decode code wasn't being called. I had create a custom button, cannibalising and extending the original CommandButton component. Due to my lack of understanding of setting up renderkits, I gave up and hacked in a renderer:
public class ImageButton extends HtmlCommandButton
{
   private ImageButtonRenderer render = new ImageButtonRenderer();
   
   @Override
   public void decode(FacesContext context)
   {
      render.decode(context, this);
   }
   
   @Override
   public void encodeBegin(FacesContext context) throws IOException
   {
      render.encodeBegin(context, this);
   }
This resulted in decode not always working. I came across this nice summary of how to write a custom component and altered my code accordingly. Decode worked but the action was still not called. Thought the cause was just with immediate="true". So I swtiched to using a standard commandButton for testing.
<h:inputhidden value="#{testBean.add}" immediate="true" />
<h:commandbutton value="Delete" immediate="true" rendered="#{!testBean.add}" />
However the command button was still not being decoded, as I thought immediate on the attribute should work but for the 2nd time I've misunderstood how it works for input components. Immediate on input components only changes priority of validation, it doesn't cause them to update the model any earlier.

UIComponentBase.processDecodes:1018 calls isRendered() - which returns false as the Update Model Values phase hasn't been applied. Which skips the decode of the the component. Which clearly isn't right. Whether it was rendered or not should be remembered from the Render phase during the decode(Apply Request Values) phase. In the following phases it should be calculated from the EL binding. This to me seems a bug in the specification or the implementation.

So how to use a model value for the rendered attribute for a button with immediate on?

The answer use Tomahawk's saveState tag. The state is maintained on the server side, and as long as it is put before the other tags that need it, it will restore the model/attribute to the same value it was after the Render phase during the Apply Request Values phase. Thus when it goes to decode the button, the same values that were used to calculate the rendered during the previous rendering to create this page are used.
<t:savestate value="#{testBean.add}" />
<h:commandbutton value="Delete" immediate="true" rendered="#{!testBean.add}" />

No comments:

Post a Comment