June 27, 2011

apex:repeat and apex:variable, Pain With apex:selectRadio, actionSupport and onchange

Having just wasted a morning trying to figure out why some code wasn't working, I thought it'd be a good time for a blog post so that others may avoid the same trap. As may be derived from the title, this concerns the use of <apex:variable> and <apex:repeat>, and more specifically, their use along side <apex:selectRadio> and pals.

In our controller we had a class, and originally we used a list of instances of this class on our page, each one receiving the input from from an <apex:selectRadio> tag element (queue contrived code example):

// Controller 
class CThing 
{ 
    public string strValue {get; set;} 
} 

public list<CThing> liThings {get; set;} 

ControllerConstructor() 
{ 
    liThings = new list<CThing>{new CThing(), new CThing(), newCThing()}; 
} 

// VF Page 
<apex:repeat var="c" value="{!liThings}"> 
    <apex:selectRadio value="{!c.strValue}"> 
        <apex:selectOptions value="{!liOptionsFromTheController}"/> 
        <apex:actionSupport action="{!DoSomething}" event="onchange"/> 
    </apex:selectRadio> 
</apex:repeat>

Now this all functioned as desired, running through the list of CThing instances in the controller in the DoSomething action we could read all the values fine and dandy and everybody lived happily ever after. Then one of our developers adapted this code for another scenario, where we wanted to only display one CThing at a time; they chose the obvious route of replacing the <apex:repeat> with an <apex:variable> and made a bunch of other changes also, and it was a long time before we noticed that we were no longer receiving input in the strValue field of our CThing instances.

Considering many of the changes focused around JQuery additions and all manner of other quirky bits I started pulling the changes out to see what the cause might be—eventually I discovered it was the use of <apex:variable>. Maybe I've not read enough documentation (though what I have read doesn't suggest that we should have encountered this problem) but it would appear that our problem was caused by replacing the <apex:repeat> element with the aforementioned <apex:variable>:

<apex:variable var="c" value="{!oTheSingleInstance}">

The outcome of this was that when you changed the option in the <apex:selectRadio> list, the setters and getters on the controller would fire, the page would refresh as normal (even status panels worked as they should if we used those) but the DoSomething> action would not be invoked. As is often the case with Salesforce quirks I have no idea why this behaves differently, I'm sure there is an architectural reason or similar but if I actually had enough time to investigate all of these things in detail it'd probably mean that I am unemployed; therefore we'll skip the in-depth discussion, and as is my wont, jump to the quick fix and put it in a separate paragraph so it's easy to spot.

Use a list of instances in the controller, use <apex:repeat>, and just put one item in the list. Couldn't be simpler.