Dynamic Visualforce Components in Summer’ 11

One of the cool new features in the upcoming Summer' 11 release is Dynamic Visualforce Components. Dave Carroll demoed this feature in the Summer' 11 Preview webinar that he and I did a couple of weeks back. You can watch a recording of the full webinar here, but if you just want to see the Dynamic VF components demo, jump to this point of the recording. You can also find detailed documentation for this feature in the VF Developers Guide for Summer' 11 (Note for visitors from the future: 1) who won the 2011 World Series? and b) this documentation link will change once Summer' 11 is GA).

Lets take a look at the sample code demoed during the webinar and dig a little deeper into this feature. Before we get to the code however, lets lay some groundwork.

1) So what are Dynamic Visualforce Components? This is a new VF feature introduced as Pilot in Summer' 11 (i.e. you need to contact Salesforce support to have it enabled in your Org). With this feature, you can generate VF markup in Apex controller/extension code and then insert that markup at runtime in the VF page to generate truly dynamic VF pages whose structure/content is determined at runtime. For example, you can generate an entire <apex:outputPanel> (including all its child elements) in the controller based on some business logic/conditions and then insert that panel into the VF page at runtime.

 

2) Why the new feature? The Visualforce team has been adding support for dynamic VF content over the last couple of releases. Dynamic VF bindings and Field Sets were the first pieces. Dynamic VF components is the next evolution in that journey and it allows developers and ISVs to generate truly dynamic VF pages.

 

3) Wait, can't I already generate dynamic VF pages by using the 'rendered' attribute and/or JavaScript DOM manipulation (e.g. with a JQuery library)? Yes and no. One could certainly implement a dynamic VF page that renders certain elements/sections based on a 'rendered' attribute that is controlled by business logic in the Apex controller/extension. For example, say you have a VF page with  three outputPanels, only one if which you render based on certain business conditions, and in each panel you have to render one of 3 possible dataTables. That is nine possible combinations that you would have to code in a complex markup if using the 'rendered' approach. You can however accomplish the same functionality with Dynamic VF Components in a more elegant and natural manner. Specifically, you could use a single <apex:dynamicComponent> tag and control which outputPanel and child dataTable gets inserted at runtime in the Apex controller/extension code. Another advantage of using Dynamic VF Components over the 'rendered' approach is performance. The size of the VF component tree should be reduced with Dynamic VF Components (vs multiple tags that use the 'rendered' attribute) and the platform will have to perform less computations (no 'rendered' values to evaluate) to render the page. This should result in some performance improvements for complex and large dynamic pages.

It is important to remember that using dynamic VF components is not always the automatic choice when developing a dynamic VF page. In most cases, using static VF markup with the 'rendered' attribute and/or some JavaScript DOM manipulation would still be the appropriate choice. Dynamic VF components are typically better suited for complex dynamic requirements that would otherwise require very complex and hard to maintain VF markup.

Lastly, Dynamic VF components allow you to implement functionality that is just not possible with the use of 'rendered' attributes. For example, if the page composition/structure is completely dynamic and only determined at runtime. The sample demoed during the webinar is a good example of this use case.

 

4) Speaking of, enough groundwork already! Show me some code. The webinar demoed a simple 'Top 10 open Opportunities' VF page with the Opportunities grouped by their respective Accounts and displayed in a tabbed format. Each tab on the page corresponds to an Account. Since the number of Accounts that the top 10 Opportunities could belong to is dynamic (could be a min. of 1 or a max. of 10), you can't create this page using static <apex:tabPanel> and <apex:tab> tags with 'rendered' attributes. Instead, you can use a Dynamic VF component and insert the appropriate number of tabs at runtime.

Here is the VF markup for the page.

<apex:page controller="TopOpportunityController">
<apex:stylesheet value="{!$Resource.CustomTableCSS}"/>
<apex:sectionHeader title="Top 10 Open Opprtunities"/>
<h1>Total Expected Revenue from Top 10 Opportunities:</h1> $
<apex:dynamicComponent componentValue="{!OppTotal}"/>
<br/><br/>
<apex:dynamicComponent componentValue="{!tabbedView}"/>
</apex:page>

And the corresponding Apex controller

public with sharing class TopOpportunityController {
private List<Opportunity> topOpportunities;
public Double opportunityTotal {get;set;}
public TopOpportunityController ()
{
topOpportunities = [SELECT closeDate, TotalOpportunityQuantity, Id, Name,
AccountId, Account.Name, Probability, ExpectedRevenue,
stageName, Amount, Account.Open_Opportunities_Total__c FROM
Opportunity where isClosed=false ORDER BY
Account.Open_Opportunities_Total__c DESC, Account.Name,
ExpectedRevenue DESC LIMIT 10];
}
public Component.Apex.TabPanel getTabbedView()
{
Component.Apex.TabPanel panel = new Component.Apex.TabPanel(
switchType = 'client',
title = 'Top 10 Opportunities');
String lastAccountId;
opportunityTotal = 0;
for (Integer i = 0; i< topOpportunities.size(); i++)
{
Opportunity o = topOpportunities[i];
opportunityTotal += o.ExpectedRevenue;
//If we have a new Account record, need to create a new Tab
if (lastAccountId != o.AccountId)
{
Component.Apex.Tab acctTab = new Component.Apex.Tab();
acctTab.label = o.Account.Name;
panel.childComponents.add(acctTab);
}
Component.Apex.OutputText oppText = new Component.Apex.OutputText(escape = false);
oppText.value = '';
//If this is a new tab, start a new <table>
if (lastAccountId != o.AccountId)
{
oppText.value += '<table class="customT"><thead class="customT"><tr class="customT"><th class="customT">Opportunity Name</th><th class="customT">Probability</th><th class="customT">Stage</th><th class="customT">Expected Revenue</th></tr><tBody class="customT"></thead><tBody class="customT">';
}
oppText.value += '<tr><td class="customT">' + o.Name + '</td>';
oppText.value += '<td class="customT">' + o.Probability + '</td>';
oppText.value += '<td class="customT">' + o.StageName + '</td>';
oppText.value += '<td class="customT">' + o.ExpectedRevenue + '</td>';
oppText.value += '</tr>';
if ( (i == topOpportunities.size() -1) ||
o.AccountId != topOpportunities[i+1].AccountId )
{
oppText.value += '</tBody></table>';
}
panel.childComponents.get(panel.childComponents.size() -1 ).childComponents.add(oppText);
lastAccountId = o.AccountId;
}
return panel;
}
public Component.Apex.OutputText getOppTotal()
{
Component.Apex.OutputText oppTotal = new Component.Apex.OutputText();
oppTotal.expressions.value='{!opportunityTotal}';
return oppTotal;
}
}

Lets walk-through the code. The VF markup is super simple – which is after all one of the reasons for using Dynamic VF components. The entire Tab Panel section of the page is represented with a single <apex:dynamicComponent> tag whose 'componentValue' attribute is linked to the 'getTabbedView' controller method that returns the dynamic VF markup at runtime.

Lets now look at where the real action is – the 'getTabbedView' controller method. Every standard VF tag (except for certain tags that aren't currently supported in Pilot) now has an equivalent Apex class representation – Component.Apex.<Component Name>. So for example the <apex:dataTable> tag is represented by Component.Apex.DataTable, <apex:TabPanel> by Component.Apex.TabPanel and so on. Every attribute that exists on a standard Visualforce tag is available as a property in the corresponding Apex class (e.g. the 'title' attribute of the TabPanel component). 

You generate dynamic VF markup in Apex by instantiating and manipulating these classes. Adding child nodes to a dynamic Visualforce component can be done via the 'childComponents' List property. That is exactly what the 'getTabbedView' controller method does (line 35) – generate a TabPanel component and its child Tab panels based on the runtime Opportunity data. You can also see how it's possible to insert HTML markup in a dynamic VF component by using Component.Apex.OutputText with the 'escape' attribute set to false. Finally, the 'getOppTotal' method (line 71) shows how you can use the 'expressions' property to set expressions, formulas and global variables (like '$User' etc.) in a dynamic VF component.

Sorry for the seemingly endless post and congratulations for making it this far! Remember to also check out the Summer' 11 home page for additional documentation and other resources for the upcoming release. Happy coding and as always, comments and questions are welcome.