Friday, February 1, 2013

Two Visualforce Methods for Conditional Lists

Sometimes things which should be easy, and standard, don't actually exist and this can cause some level of consternation for a developer looking for them. A common requirement when displaying a list of records for instance, is to show the records should they exist, otherwise to display a message to the effect that there aren't any. This is easy enough to accomplish with Visualforce when using a custom controller or an extension, but not so with a standard controller. This does not mean, however, that it can't be done.

First up, a disclaimer (these seem to be getting somewhat common around here). The techniques explained here are unsupported by Salesforce. That is to say, the first one in particular has been highlighted to be so, while the second is a method I devised a few days ago (and have not seen elsewhere) but does come with a the 'hack' label firmly stapled onto it.


Counting Crows


Whilst trying to work out a Visualforce only method recently I thought about how I might be able to count the records in a list, remembering to my delight that you can modify the value of <apex:variable> variables (handy for creating indices), meaning of course, counting records becomes quite trivial:

<apex:variable var="count" value="{!0}"/>
<apex:repeat value="{!Account.Contacts}" var="v">
  <apex:variable var="count" value="{!count + 1}"/>
</apex:repeat>

<apex:outputText value="Found some contacts!" rendered="{!count != 0}"/>

No sooner had I discovered this method for myself, I came across a question on the Salesforce StackExchange site with an answer by Andrew Fawcett using the exact same idea, but I still wasn't entirely happy.

My major gripe with this technique was that it feels like it's inefficient, not to mention it requires an extra four lines of code per list. Having experimented for a few hours with various ideas, I can now demonstrate a more concise way (the jury is out on efficiency) to achieve this goal.


Another Way


When you output an empty list directly in a page, you simply get what looks to be an empty array, i.e. two square brackets containing absolutely nothing. Trying to simply compare against a string doesn't work since an empty array isn't a string, so rendered="{!Account.Contacts != '[]'}" is out.

You can't cast the list to a string value using TEXT(), which is a shame because then the solution would be simple, rendered="{!TEXT(Account.Contacts) != '[]'}"; but that's out too. Similarly, wrapping the list part in quotes to make it a string is no use as then we're just comparing the string 'Account.Contacts' with '[]', and we know they're not the same.

Once again, our friend <apex:variable> leaps into action and saves the day: in specifying its value we can put the merge expression inside some single quotes like so:

<apex:variable var="v" value="'{!Account.Contacts}'"/>

Meaning now the variable 'v' has the value '[]'—single quotes included—if there are no contacts for the account. We need to compare against a string which has the same value, again including the quotes. So far my attempts at escaping quotes directly in the comparison have proven unsuccessful, but we can avoid the need to do so by using a second <apex:variable>, thus:

<apex:variable var="v2" value="'[]'"/>

The upshot is now all we need do is perform a comparison between the two variables to decide whether or not the list has any records.

<apex:outputField value="Contacts established. Launching in T-10..." rendered="{!v != v2}"/>

A bonus is that the variable v2 above is reusable for each list, so all in we only need to add n+1 lines of markup to deal with n lists which helps keep our page source nice and tidy.


Related Posts

7 comments:

  1. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. Unfortunately not—although that works for lists exposed using a custom controller or extension, you can't even save a page that just uses the Account standard controller and the expression {!Account.Contacts.size}, you just get the compiler error: "Invalid field 'size' for SObject 'Contact'."

      Delete
    2. I have removed my comment after trying this on org...
      But I have tried something different and it seems it works:

      Delete
    3. This comment has been removed by the author.

      Delete
    4. Yeah I tried ISBLANK() too — it saves, but it always returns false!

      Delete
  2. If you're just trying to determine if a list is empty use rendered="{!List.empty}"

    ReplyDelete
    Replies
    1. Although this works for lists from a controller, you can't use it for a related list using a standard controller. So like the comment above, {!Account.Contacts.Empty} will not compile in the page: "Invalid field 'empty' for SObject 'Contact'".

      Delete