Friday, December 13, 2013

A StandardSetController Gotcha

Recently I've developed something of a penchant for using standard set controllers and dynamic Visualforce binding to make life easier, and most of the time it does just that. I did, however, hit a particular quirk twice in the space of a week so wanted to share it with others in case it can save some hair.


The Scenario


Suppose you have a custom controller that utilises a standard set controller like so:

ssc = ApexPages.StandardSetController([select Id, Name from Account limit 1000]);

and you use that controller to populate a public map that would be used for checkbox input fields like so:

for(Account a : ssc.GetRecords())
{
  // yes, this is another trivial example ;)
  accountMap.Put(a.Id, false);
}


Now if you were intending to put the list of accounts on screen with a checkbox for each you might do something like this:

<apex:pageBlockTable value="{!ssc.Records}" var="a'>
  <apex:column>
    <apex:inputCheckbox value="{!accountMap[a.Id]}"/>
  </apex:column>
  </apex:column>
  <apex:column value="{!a.Name}"/>
</apex:pageBlockTable>

Easy, right?


The Sticky Bit


This will work great if you've got fewer records than the current page size for the controller, once you've got more than that you'll get an error about value not being found in the map, so what gives? Well it turns out that for some reason although the method GetRecords() respects the page size set on the controller when called via Apex, it does not do so when invoked via Visualforce as in the page above. 


The Workaround


To fix the page you can simply write a wrapper method around GetRecords() to call it and return the resulting list.

public List<Account> GetAccounts()
{
  return ssc.GetRecords();
}


And Another Thing...


In my scenario I want the whole map populated so that selections can be maintained as the user flicks between pages of records (because you did include pagination, didn't you?). The easiest way to do this is to just call SetPageSize() before the loop that populates the map and pass it the same value that you used for the limit (1000 in this case). After the loop set it back to a more user-friendly number such as 25.


Hopefully this will avoid tears when working with the standard set controllers in this manner!


Related Posts




2 comments:

  1. I'll pile onto this and mention salesforce has specifically denied support for StandardSetController issues when used outside of visualforce contexts. If you think you found a neat way to use list views as criteria in batch apex jobs you'll encounter a whole bunch of pain.

    TL;DR: Avoid standard set controllers in use cases more complex than the workbook examples they're used in.

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete