[SalesForce] Re-rendering section on picklist value change – picklist value is always null

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. There are a couple of examples for this, but I could not get it to work.

I have an actionSupport element on the picklist that calls the method updateUnlockfieldsVisibility() in the extension that evaluates the picklist value and sets a flag (unlockFieldsVisibility) that decides if the additional input fields are rendered.

My problem is that the variable that contains the picklist value (newInstruction) is always null. When the command button is clicked, is has a value and the form works fine. But when the action is called, it is null.
Why is it not set to the value selected in the picklist?

Here is my Visualforce page:

<apex:page standardController="CaseCard__c" extensions="CardExtension" recordSetVar="caseCards">
    <apex:form >
        <apex:pageMessages />
        <apex:pageBlock title="Select the instruction for these cards">
            <apex:pageBlockButtons >
                <apex:commandButton action="{!setInstruction}" value="Set Instructions"/>
            </apex:pageBlockButtons>
            <apex:pageBlockSection id="inputSection">
                <apex:actionRegion>
                    <table style="width:100%">
                        <tr>
                            <td>New instruction:</td>
                            <td><apex:selectList value="{!newInstruction}" multiselect="false" size="1">
                                    <apex:selectOptions value="{!instructions}"/>
                                    <apex:actionSupport event="onchange" action="{!updateUnlockfieldsVisibility}" reRender="inputSection" immediate="TRUE"/>
                                </apex:selectList>
                            </td> 
                        </tr>
                        <apex:pageBlockSection id="unlockFields" rendered="{!unlockFieldsVisibility}">
                        <tr>
                            <td>Unlock reason:</td>
                            <td><apex:selectList value="{!newUnlockReason}" multiselect="false" size="1">
                                <apex:selectOptions value="{!unlockReasons}" />
                                </apex:selectList>
                            </td> 
                        </tr>
                        <tr>
                            <td>Unlock comment:</td>
                            <td><apex:inputTextarea value="{!newUnlockComment}" id="unlockComment"/></td> 
                        </tr>
                        </apex:pageBlockSection>
                    </table>
                </apex:actionRegion>
            </apex:pageBlockSection>
        </apex:pageBlock>
    </apex:form>
</apex:page>

And here is part of 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 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
##### NullPointerException on next line; newInstruction is null #####
        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 setInstruction() {
        //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 specific reason for your error is the use of immediate="true"

Per the SFDC Doc on VF Page Order of Execution,

  1. During a postback request, the view state is decoded and used as the basis for updating the values on the page.

Note A component with the immediate attribute set to true bypasses this phase of the request. In other words, the action executes, but no validation is performed on the inputs and no data changes on the page.

  1. After the view state is decoded, expressions are evaluated and set methods on the controller and any controller extensions, including set methods in controllers defined for custom components, are executed.

Hence, the value of merge field {!instructions} is not sent to the controller on the onchange event.

You can also see more on this including a workaround using actionRegion in this SFSE answer

Related Topic