[SalesForce] Rerender page components from apex:component

I have a Visualforce component (apex:component) where I would like to trigger the rerendering of an apex component on the containing page. In this case the component to rerender is an apex:pageBlockButtons, but it could equally be an apex:outputPanel.

Page

<apex:page showHeader="true" sidebar="true" controller="AdBookInventoryCheckController">
    <apex:Form id="theForm">
        <!-- ... -->
        <apex:pageBlockButtons id="pAddFilterButton">
            <apex:commandButton action="{!addFilter}" value="Add Filter" id="theButton" disabled="{! addFilterButtonDisabled }"  /> 
             <apex:outputText value="{!NOW()}" /> <!-- Just for debugging -->
        </apex:pageBlockButtons>
        <!-- ... -->
        <c:TheComponent id="pbsPosition" />
        <!-- ... -->
    </apex:Form>
</apex:page>

Component

<apex:component controller="TheComponentController" >
    <apex:outputPanel id="pl1">
        <apex:outputPanel id="pl1">
            <apex:SelectList value="{!pl1Sel}" size="1">
                <apex:actionSupport event="onchange" action="{!changePl1}" rerender="pl,pAddFilterButton" status="counterStatus1"/>
                <apex:selectOptions value="{!pl1}"></apex:selectOptions>
            </apex:SelectList><apex:actionStatus startText=" Loading..." id="counterStatus1"/><br/>
        </apex:outputPanel>
    </apex:outputPanel>
</apex:component>

Directly using the pAddFilterButton id in the actionSupport rerender attribute doesn't cause the pageBlockButtons to rerender.

I've also tried some of the ideas from Rerender Visualforce Parent Component Panels from another component around passing the page components Id to the Visualforce component via an attribute. None of the following worked:

  • Just the component id: pAddFilterButton
  • The $Component notation: {!$Component.pAddFilterButton}
  • The colon separated path: mediaPageBlock:mediaForm:pAddFilterButton

How can I get an action in the component to cause an Ajax update to the parent page?

Best Answer

Here is one possible solution that can be filed under - It's Ugly, But It Works.

  1. On the Visualforce page, add an actionFunction that is connected to a no op method and rerenders the pageBlock that contains the pageBlockButtons. This gives us a javascript method we can call to rerender the buttons.

actionFunction for the Page:

 <apex:actionFunction name="checkAddFilterButton" rerender="selectPositionsPageBlock"/>
  1. Call the resulting javascript function from the onComplete attribute of the actionSupport in the Visualforce component.

Now when the actionSupport call finishes in the compoent it will call the javascript method to update the buttons. It's ugly on a number of levels, such as multiple calls to the server, the slight delay before the button gets enabled and having to refresh the entire page block.

  1. Set the apex:pageBlockButtons location to be either "top" or "bottom", but not "both". This will result in just a single set of controls to be turned on/off.

There is an undesirable side effect of this approach. Between the actionSupport completing and the actionFunction completing there is a period of time where the user can make changes to the controls within the pageBlock. When the actionFunction completes those changes will be reversed.

From the users perspective they start changing something, and then it reverts back to the value it had when they changed the select.

Update: I fixed this by hacking an apex:outputPanel into the apex:pageBlockButtons and then having the actionFunction rerender that instead of the entire pageBlock. ... Hacks all the way down...


I'd be happy to accept a more refined answer than this solution.

Related Topic