[SalesForce] reRender problem: variable changes in controller, but not on the VF page

(I am building upon this question and the helpful answers there: Re-rendering section on picklist value change – picklist value is always null.)

Requirement:
On a Visualforce page, I have a picklist (apex:selectOption). When a user selects certain values, additional input fields have to appear. Otherwise, they should be hidden.

Implementation: The additional fields are in a pageBlockSection and a Boolean variable in rendered controls it's visibility. The onchange action on the picklist calls an actionFunction actUpdateUnlockFieldsVisibility which calls the controller method updateUnlockFieldsVisibility() which updates the Boolean variable unlockFieldsVisibility. The actionFunction's reRender should refresh a PageBlockSection that contains the additional input fields.

Problem: The variable is updated correctly in the controller extension (I see this in the debug log), but it is not updated in the Visualforce page. Basically, reRender in the actionFunction does not do anything. (I also tried having the actionRegion only around the PageBlockSection, but this did not change the behavior.) Any hints?

The page:

<apex:page standardController="CaseCard__c" extensions="CardExtension" recordSetVar="caseCards">
    <apex:form>
        <apex:actionFunction action="{!updateUnlockFieldsVisibility}" name="actUpdateUnlockFieldsVisibility" reRender="unlockFields" focus="unlockReasonPicklist"/>
        <apex:pageMessages />
        <apex:pageBlock >
            <apex:pageBlockButtons >
                <apex:commandButton action="{!issueInstruction}" value="Set Instructions"/>
            </apex:pageBlockButtons>
                    <apex:actionRegion>
                        <apex:selectList value="{!newInstruction}" multiselect="false" size="1" onchange="actUpdateUnlockFieldsVisibility()">
                            <apex:selectOptions value="{!instructions}"/>
                        </apex:selectList>
                        <apex:outputText>unlockFieldsVisibility: {!unlockFieldsVisibility}</apex:outputText>
                        <br/>
                        <apex:outputText>newInstruction: {!newInstruction}</apex:outputText>
                        <apex:pageBlockSection id="unlockFields" rendered="{!unlockFieldsVisibility}">
                            <apex:selectList value="{!newUnlockReason}" multiselect="false" size="1" id="unlockReasonPicklist">
                                <apex:selectOptions value="{!unlockReasons}" />
                            </apex:selectList>
                            <apex:inputTextarea value="{!newUnlockComment}" id="unlockComment"/>
                        </apex:pageBlockSection>
                    </apex:actionRegion>
            <apex:pageBlockTable value="{!cards}" var="card">
                [...]
            </apex:pageBlockTable>
        </apex:pageBlock>
    </apex:form>
</apex:page>

The controller extension:

public with sharing class CardExtension {
    public String newInstruction { get; set; }
    public String newUnlockReason { get; set; }
    public String newUnlockComment { get; set; }
    public Boolean unlockFieldsVisibility { get; set; }

    private Case currentCase;
    private ApexPages.StandardSetController standardController;

    public CardExtension(ApexPages.StandardSetController standardController) {
        this.standardController = standardController;
        unlockFieldsVisibility = false;
    }

    public PageReference updateUnlockFieldsVisibility() {
        System.debug('>>>> newInstruction: ' + newInstruction);
        System.debug('>>>> unlockFieldsVisibility: ' + unlockFieldsVisibility);
        //if unlock, show unlock reason picklist + unlock comment
        if (newInstruction.containsIgnoreCase('unlock')) {
            unlockFieldsVisibility = true;
        } else {
            unlockFieldsVisibility = false;
        }
        System.debug('>>>> unlockFieldsVisibility: ' + unlockFieldsVisibility);
        return ApexPages.currentPage();
    }

    //sets the selected instruction on all cards
    public PageReference issueInstruction() {
        //get the instruction and check if it is empty
        if (String.isEmpty(newInstruction)) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'The instruction is empty.'));
            return ApexPages.currentPage();
        }
        //business logic here
        return ApexPages.currentPage();  
    }
}

Best Answer

The issue here is you are trying to rerender a section which was previously not rendered on the page. "Rerender" attributes only renders those sections or panels which were rendered on the page initially or before the ajax request.

So you would have to rerender the "Apex:pageBlock" or put your selectList inside an outputPanel like this:

<apex:pageBlockSection id="unlockFields" >
      <apex:outputPanel rendered="{!unlockFieldsVisibility}">
                            <apex:selectList value="{!newUnlockReason}" multiselect="false" size="1" id="unlockReasonPicklist">
                                <apex:selectOptions value="{!unlockReasons}" />
                            </apex:selectList>
                            <apex:inputTextarea value="{!newUnlockComment}" id="unlockComment"/>
       <apex:outputPanel>
 </apex:pageBlockSection>