Showing posts with label Visualforce. Show all posts
Showing posts with label Visualforce. Show all posts

Wednesday, May 1, 2013

BUG REPORT: Dynamic Visualforce Bindings


Before I start, I encourage you to go over Salesforce’s Visualforce Developer’s Guide which you can find here.  There is a section called “Dynamic Visualforce Bindings” that already provides the syntax and more information about the topic that I am discussing today. 

If you don’t have time to read the Developer’s Guide, or if you just want to quickly glance at what I have to say today, then still please let me quickly talk about what exactly Dynamic Visualforce Binding is.  It is an extremely powerful tool to build dynamic pages at run time.

We all know that Visualforce is the “V” (view) part of the MVC paradigm that Salesforce uses.  It indicates what the user interface of a page is like.  A traditional Visualforce page denotes how the page should look like, what information is displayed, and in what order.  In other words, a Visualforce page dictates what information the users see and how they see it.  The person that wrote the Visualforce page is in the driver's seat.
  
However, imagine this use case.  What if you want to display a list of accounts, but you also want to give your users the flexibility to determine what information about the account they want to see.  You may want to present a list of account related fields, and the users can select what fields they want to see and in what order.  Having to program that logic can be a nightmare:  the users may choose one field or ten fields, the users may choose Name first this time and then Name last next time.  There are so many possibilities and combinations that, if not programmed carefully, your code can become nothing but an endless list of if-then-else statements.

Enters Dynamic Visualforce Binding.

Let's just quickly see what a regular Visualforce page looks like.  As an example I want to display the account name, account number, annual revenue and ticker symbol of a list of accounts.  To do that, all you need to do is a very simple Visualforce page like this:
<apex:page standardController="account" recordSetVar="accounts">
    
    <apex:pageBlock >
        <apex:pageBlockTable value="{!accounts}" var="a">
            <apex:column value="{!a.Name}" />
            <apex:column value="{!a.AccountNumber}" />
            <apex:column value="{!a.AnnualRevenue}" />
            <apex:column value="{!a.TickerSymbol}" />
        </apex:pageBlockTable>
    </apex:pageBlock>
    
</apex:page>

Your page will look like this:










In order to use Dynamic Visualforce Binding, you need a little help from a controller.  (I know it can be a turnoff,  but no pain no gain!)

Let's take a look at the controller (again, I don't want to divulge into the syntax.  Please refer to the Developer's Guide for the detailed syntax.  I just want to show you how little code is required to unleash the power of Dynamic Visualforce Binding.)


public class AccountListDynamic
{
    // constructor that simply adds the list of fields 
    // for the account.
    public AccountListDynamic(ApexPages.StandardSetController controller)
    {
        controller.addFields(accountFieldList);
    }

    // accountFieldList is a property.  It is a list of 
    // strings and each element in the list is the API 
    // name of the field that I want to display.  This list
    // is read-only.
    public List<String> accountFieldList
    {
        get
        {
            if (accountFieldList == null)
            {
                accountFieldList.add('Name');
                accountFieldList.add('AccountNumber');
                accountFieldList.add('AnnualRevenue');
                accountFieldList.add('TickerSymbol');
            }
            return accountFieldList;
        }
        private set;
    }

}

I hope the class is quite self-explanatory.  It is an extended controller (hence the ApexPages.standardSetController parameter in the constructor of the class).  All that we are achieving here is to pass an array of account related field names (API names) and expose the list to the Visualforce page.

The Visualforce page now looks like this:
<apex:page standardController="account" recordSetVar="accounts" extensions="AccountListDynamic">
    <apex:pageBlock >
        <apex:pageBlockTable value="{!accounts}" var="a">
            <apex:repeat value="{!accountFieldList}" var="f">
                <apex:column value="{!a[f]}" />
            </apex:repeat>
        </apex:pageBlockTable>
    </apex:pageBlock>
</apex:page>

The hardest part to understand is probably a[f] above.  "a" is a loop variable for each account in the list.  "f" is a loop variable for each field to display.  Therefore to simply put, a[f] refers to the value of a specific field for a specific account.

So, sure now you have the controller that makes things look a little more complicated, but did you notice the controller now handles the list of fields.  All the Visualforce page does is to simply take that list of fields and displays the relevant data; you do not have any field names hardcoded in the Visualforce page at all!

Now, going back to our use case.  Let's say you want to show the user a list of all account related fields, so that the user has the ability to choose which fields they want to display and the order the fields are to display.

You will need a separate Visualforce page for that.  Once you have built this new Visualforce page for that, then all you need to do is to capture the information in your controller.  Then the Visualforce page shown above again can be used to display the information.

This is exactly what the example in the Developer's Guide, in section "Using Dynamic References for a User-Customizable Page", is about.  It is an excellent read, and I urge you to take a look at this topic.

The most unfortunate thing is that this function is not stable.  I have got the code to work in some environments but fail in others.

I followed the example as per the Developer's Guide and this is the error I received:



I have talked to Salesforce and it is now officially under the "Known Issues" list.  If you are interested, you can take a look at this page for more details.  If you can replicate this error too, I strongly encourage you to click the "This Issue Affects Me" button.  Note that the example there is quite different, but it still exposes the defect related to Dynamic Visualforce Binding.




Tuesday, January 15, 2013

How to show data from Map collection in sorted order inside apex:repeat

I was going through Salesforce's Visualforce Developer's Guide and Apex Developer's Guide the last few weeks over the Christmas holiday.  In the Apex guide, for Map collection, the book warned that the "order of objects returned by maps may change without warning", and therefore we are asked to "not reply on the order in which map results are returned".

Then in the Visualforce guide, there is a section on "Referencing Apex Maps and Lists" section in the "Dynamic Visualforce Bindings" chapter.  It talked about how we can use data from map in combination with the <apex:repeat> to display data in the map.

What good is it if it cannot display the data properly, especially if the data set is large?

I'm going to use the example in the Visualforce guide as a demonstration.  It can be found on page 158 (if you are using Winter 13 of the Visualforce guide).  I am also going to add just a little more records so it's easier to see how data is sorted.

The following is the code for the class:

public class p158_a_Blog_Example
{
    public Map<String, String> directorMap {get; set;}
    
    public p158_a_Blog_Example()
    {
        // set up the data in this constructor
        directorMap = new Map<String, String>
                {
                    'Kieslowski' => 'Poland',
                    'Gondry' => 'France',
                    'del Toro' => 'Mexico',
                    'Lee' => 'Taiwan',
                    'Cameron' => 'Canada'
                };
        
    }
}

This is the Visualforce page that makes use of this controller.  I modified it a little so it displays the data in a table.
<apex:page controller="p158_a_Blog_Example">
    <apex:pageBlock title="Directors List">
        <apex:pageBlockTable value="{!directorMap}" var="dirKey" style='width:50%'>
            <apex:column value="{!dirKey}" />
            <apex:column value="{!directorMap[dirKey]}" />
        </apex:pageBlockTable>
    </apex:pageBlock>
</apex:page>

Using different DE orgs, I seem to get the results in different order, so the order of the data is indeed inconsistent.  This is how the data came out this morning:


I would like the data to show Cameron, del Toro, Gondry, Kieslowski and Lee, in this order.

I need to modify the code, but the first lesson of all, if you need to preserve the sorting order, use LIST in your <apex:repeat> tag, do NOT use the map variable.

Now let's go back to the class and turn the Map into a List, so we can display the data sorted.  It actually is very easy.  All you need to do is to add a new List variable, set it and then sort it (via the sort() method of list collection).

The new class is modified as this:
public class p158_a_Blog_Example
{
    public Map<String, String> directorMap {get; set;}
    // new variable to hold the list of directors.
    public List<String> directorList {get; set;}
    
    public p158_a_Blog_Example()
    {
        // set up the data in this constructor
        directorMap = new Map<String, String>
                {
                    'Kieslowski' => 'Poland',
                    'Gondry' => 'France',
                    'del Toro' => 'Mexico',
                    'Lee' => 'Taiwan',
                    'Cameron' => 'Canada'
                };
        // Set the new List variable to be the set of directors using the addAll() method.  After 
        // that simply all the sort function to sort the directors' names.                
        directorList = new List<String>();
        directorList.addAll(directorMap.keySet());
        directorList.sort();                
        
    }
}

The Visualforce also now needs to be modified so it is not using the map variable as the data set, but the list variable instead (which now contains a list of directors, sorted).  NOTE:  you still need to use the Map variable to access the country from which the director came from.  The bolded, underlined text is the ONLY change you need to make to the Visualforce page:
<apex:page controller="p158_a_Blog_Example">
    <apex:pageBlock title="Directors List">
        <apex:pageBlockTable value="{!directorList}" var="dirKey" style='width:50%'>
            <apex:column value="{!dirKey}" />
            <apex:column value="{!directorMap[dirKey]}" />
        </apex:pageBlockTable>
    </apex:pageBlock>
</apex:page>

Well...  still something is not quite right.  


The list is now almost all sorted, except the Mexican director is displayed at the end.  Turns out that when you call the sort() method on the List, it sorts the data based on the ascii code.  The lower case 'd' (ascii code = 100) comes after 'L' (ascii code = 76).  That is, the sort is case-sensitive.

In order to make the sort case-insensitive, what I found the easiest is to add two new variables.

A new Map variable (directorName) is created that maps between the director's name in upper case and the name intact.  A new List variable (directorListIgnoreCase) is created to hold the director's name in upper case for sorting purposes.  This List variable is used to determine the order of the directors.  However the data stored in this variable is all in upper case.  That's why you need the new Map variable directorName to find the director's name in its original form.  Then you can use the original Map variable directorMap to get the country information.

This is the final class.
public class p158_a_Blog_Example
{
    public Map<String, String> directorMap {get; set;}

    // new variable to map the directors from all uppercase to the original name.
    public Map<String, String> directorName {get; set;}
    public List<String> directorListIgnoreCase {get; set;}
    
    public p158_a_Blog_Example()
    {
        // set up the data in this constructor
        directorMap = new Map<String, String>
                {
                    'Kieslowski' => 'Poland',
                    'Gondry' => 'France',
                    'del Toro' => 'Mexico',
                    'Lee' => 'Taiwan',
                    'Cameron' => 'Canada'
                };
        
        directorName = new Map<String, String>();
        directorListIgnoreCase = new List<String>();
        for (String s : directorMap.keySet())
        {
            directorName.put(s.toUpperCase(), s);
            directorListIgnoreCase.add(s.toUpperCase());
        } 
        directorListIgnoreCase.sort();              
        
    }
}

The Visualforce now also is updated to reflect the changes:
<apex:page controller="p158_a_Blog_Example">
    <apex:pageBlock title="Directors List">
        <apex:pageBlockTable value="{!directorListIgnoreCase}" var="dirKey" style='width:50%'>
            <apex:column value="{!directorName[dirKey]}" />
            <apex:column value="{!directorMap[directorName[dirKey]]}" />
        </apex:pageBlockTable>
    </apex:pageBlock>
</apex:page>

Notice the repeating variable is the new List directorListIgnoreCase.  Do remember this is the director's name in upper case, so you don't want to display that.  To display the original director's name, you'll see to reference the new Map variable directorName, passing dirKey as the key.  That's why the director's name is directorName[dirKey].  Finally, to get the country information, we will need to reference the original Map variable diretorMap, passing directorName[dirKey] as the key.

The resulting page now looks exactly what I'm hoping for:


I felt like I had to jump through a lot of hoops to get to the final desired result.  If I have missed some steps, please let me know!





Friday, December 28, 2012

Using standard controller's "getRecord" method

There seems to be a common problem using the getRecord function, so I am going to write it down here.  This problem has haunted me a few times now.

Whenever you use a controller extension, extending a standard object, you can use the getRecord() to get the reference to the record.  For example, if you have an extension to the contact object, this can be what your Apex class looks like:

public class ContactExtension
{
    private Contact contact;

    public ContactExtension(ApexPages.StandardController sController)
    {
        this.contact = (Contact)sController.getRecord();
    }
}

Besides the obvious constructor, you will most likely have some other methods inside your class to manipulate the contact.  Let's say you want to provide a form so the user can change email address and phone number of the contact.  Your VisualForce class looks very simple

<apex:page standardController="Contact" extensions="ContactExtension">
    {!greeting}
    <apex:form>
        <apex:inputField value="{!contact.Email" />
        <apex:inputField value="{!contact.MobilePhone}" />
        <apex:commandButton action="{!save}" value="Save" />
    </apex:form>
</apex:page>

The {!greeting} is going to display some greeting messages.  That means we will need to write a getGreeting() method in the class.

If this is the definition of your getGreeting() inside the class,

public String getGreeting()
{
    return 'Hi ' + contact.FirstName + ' how have you been?  Please update the following.';
}

you will then get an error message that looks like this:

System.SObjectException: SObject row was retrieved via SOQL without querying the requested field: Contact.FirstName

Most of us think that as soon as we call the getRecord() method, then the entire record is loaded into the contact variable.  However if you have an object with 5000 fields, and you only need to use 2 or 3 in your Visualforce pages, do you think it really makes sense to load all 5000 fields for that record into the contact variable?  Probably not.

Then what can you do?  The easiest way is to do add this into your Visualforce page:
<apex:outputText value="{!contact.FirstName}"/>

If you say, but I don't want to display the contact name twice!  Then you can simply change this to:
<apex:outputText value="{!contact.FirstName}" rendered="false" />

By adding rendered="false", that will hide this variable.  Or perhaps use <apex:variable> instead, such as:
<apex:variable value="{!contact.FirstName}" var="contactFName" />

When you load this Visualforce page, the standard controller will scan through your Visualforce page and knows that you need this field, and when making the getRecord() method call, it automatically and implicitly adds this FirstName field into the SOQL.

Remember, if you want to use any field in your extension via the getRecord() method, you need to let it be known by making sure that the field exists in your Visualforce page!

(Another alternative is to write your own SOQL in your Apex class, but why make an extra SOQL call when you can avoid it?)

Sunday, November 11, 2012

Strange behaviour of apex:column

Let's say you have a custom field, say Age, on a custom object, say Artist, which is a numeric field with 0 decimal places.  If you want to display it in a table showing a list of artists, normally you would do something like this:


<apex:page standardController="Artist__c" recordSetVar="artists">
<apex:pageBlock >
  <apex:pageBlockTable value="{!artists}" var="c">
    <apex:column>
      {!c.Name} 
    </apex:column>
    <apex:column>
      {!c.Age__c}
    </apex:column>
</apex:pageBlockTable>
</apex:pageBlock>  

However, in the display you'll find out your Age column is displayed as a field with 1 decimal place.  (Interestingly, I looked at the NumberOfEmployees field, a numeric field in the standard Account object, and it does not have that problem.)  It can be understood because after all, all numeric fields in Salesforce are decimal fields.  (If you go back to the set up menu and create a field, you only specify a Numeric field and then define the number of decimal places.  So that means Salesforce does not distinguish between a decimal field and an integer field.  All numeric fields are decimal fields.)

So to circumvent that, all you need to do is to use the Round function.

    <apex:column>
      {!Round(c.Age__c, 0)}
    </apex:column>


Now, there is a value attribute for <apex:column> which you can also use.  So the above can be written as:
    <apex:column value="{!c.Age__c}">
       
Well, what do you know.  This time, the Age is displayed properly with no decimal points.  Not only that, the column, via the use of value attribute, will display the column header.

So, again, with this code,

<apex:page standardController="Artist__c" recordSetVar="artists">
<apex:pageBlock>
  <apex:pageBlockTable value="{!artists}" var="c">
    <apex:column >
      {!c.Name} 
    </apex:column>
    <apex:column >
      {!c.Age__c}
    </apex:column>
    <apex:column >
       {!Round(c.Age__c, 0)}
    </apex:column>
                
    <apex:column value="{!c.Age__c}"/>  <!-- this displays the Age correctly -->
                
  </apex:pageBlockTable>
</apex:pageBlock>    
</apex:page>


you'll get this:

Notice that when you use the value attribute, the column header is automatically displayed.

Now, what if the field has some decimal places?

Let's say we also have a Height field with 3 decimal places after the decimal point.  Let's also say for simplicity, all male artists are 1.80m tall and all female artists are 1.814m tall.

If you replace all the Age__c above with Height__c, you'll see a new table.  This time, however, worse yet, you see 1.8 and not 1.800 for the male artists.  This time even the Round function used in the second height column does not help matter.  The code is this,


<apex:page standardController="Artist__c" recordSetVar="artists">
<apex:pageBlock>
  <apex:pageBlockTable value="{!artists}" var="c">
    <apex:column >
      {!c.Name} 
    </apex:column>
    <apex:column >
      {!c.Height__c}
    </apex:column>
    <apex:column >
       {!Round(c.Height__c, 3)}
    </apex:column>

    <apex:column value="{!c.Height__c}"/>  <!-- again, this displays the Height correctly -->
                             
  </apex:pageBlockTable>
</apex:pageBlock>    
</apex:page>


and this is the screen display.


But once again, the value attribute for <apex:column> shows the correct answer.

Now, finally, to the part that really got me into writing this article.  From the above, you may feel that the value attribute is the right way to go because it seems to give the most desirable display of field values.  Now, what if you want to round the column so it only shows 2 decimal places?

Sure, wrap your formula expression for the value attribute with the Round function call, so the last line of the code is:

    <apex:column value="{!Round(c.Height__c, 2)}"/>

Let's run it!

Oh no, an error!


There is no syntax error for sure.  You know Salesforce won't even let you save your code unless it's syntactically correct.  The above change saves successfully.  The error really does not make any sense.  This to me looks like a bug in Salesforce.

With my experimentation, to successfully display the value correctly, you may still want to stay away from the value attribute.  Instead, make use of a combination of <apex:outputText> and <apex:param> to format your data.  This is the final code (Note:  you will have to use the headerValue attribute to specify the column name this way).

<apex:page standardController="Artist__c" recordSetVar="artists">
<apex:pageBlock>
  <apex:pageBlockTable value="{!artists}" var="c">
    <apex:column >
      {!c.Name} 
    </apex:column>
    <apex:column >
      {!c.Height__c}
    </apex:column>
    <apex:column >
       {!Round(c.Height__c, 3)}
    </apex:column>

    <apex:column value="{!c.Height__c}"/>  <!-- again, this displays the Height correctly -->
                    
    <apex:column headerValue="Height Column">
      <apex:outputText value="{0, number, #0.00}">
        <apex:param value="{!c.Height__c}"/>
      </apex:outputText>
    </apex:column>
                                
  </apex:pageBlockTable>
</apex:pageBlock>    
</apex:page>

and this is the final output.  The last column is what I really want.



Tuesday, September 25, 2012

Attributes for : showHeader, sidebar, standardStylesheets

As you know the most simplistic Visualforce page can look like this:

<apex:page>
<h1>main title</h1>
content
</apex:page>

You can provide attributes such as showHeader and sidebar to determine whether you want to show the header or sidebar, respectively, simply by setting the values to true or false, such as:


<apex:page showHeader="true" sidebar="false">
<h1>main title</h1>
content
</apex:page>


Do note that if showHeader is set to false, then sidebar will also be hidden!

Note also, that simply by setting showHeader = false, it does NOT mean you are not using the Salesforce's stylesheets (i.e. Salesforce stylesheets are still being applied).  If you do not want to use any Salesforce's stylesheets at all, then you also need to use the standardStylesheets attribute and set it to false.