Dynamic Visualforce Components, why they scare the ish out of me

06/08/2011

Dynamic Visualforce Components are coming and they scare me…a lot. Before we can even begin to discuss why we need to review the basics of the model-view-controller pattern. If you aren’t familiar with the MVC pattern here is the basic run down when it comes to web apps that use MVC. Model, this represents your database and contains information on they fields in your database, validation rules, relationships, etc. View, this is the visual display of a model or many models. It says output the fields of a record in HTML on some web page and style them this way. This is where you can visually control how the data is displayed to the user. Controller, this is the code that retrieves and manipulates data from the Model and then prepares it for the View to use. A web app will most likely consist of many Models, Views, and Controllers. Some people may want to overcomplicated the MVC pattern, and it does vary slightly based on the platform or framework, but for the most part this is exactly how it works 97% of the time. So in the force.com world it looks a little like this:

Model
These would be custom objects, you can add fields, create validation rules, and build relationships with other objects.

View
Visualforce pages are the view and allow you to output information from the model as an HTML webpage.

Controller
One or many Apex classes associated with a Visualforce page. Here you can query and manipulate data before it is sent to the view for output.

So what does any of this have to do with Dynamic Visualforce Components (DVCs)? The reason is that DVCs allow developers to completely mangle and destroy the model-view-controller design pattern. They allow you to completely define the display from the controller. So basically MVC becomes M(VehCiv) or perhaps Model Viewroller as the View and the Controller start to get all mixed up. Big flipping deal you might say, all of the other hip cool web technologies (Rails) allow you to inject markup from the controller. Yes, they do, and there are people in those communities that feel the exact same way I do about mixing aspects of MVC, just because you can doesn’t mean you should.

Here is a very basic example of how mixing up the MVC can cause trouble. Let’s say you are working on a project where you have a web designer who is great at making spiffy looking websites and a developer that understands all the back end work. If you keep the View and the Controller separate these people can work individually on their part of the project. When you start to combine the View and the Controller the web design guy might be waiting for the developer to get the output setup so he can style it. Or maybe even worse you end up have a web designer starting to poke around in controller code, not ideal.

Mixing the View and the Controller also makes things more complicated and difficult for others to understand. When looking through a controller you now have to start mentally separating the view code and the logic code. For the consultants out there that sometimes have to inherit other’s code I do not envy you when you have to dig through someone else messy code, DVCs will make it messier.

Okay Jason, we get the point, but there are times when we need DVCs to meet the project requirements! Are there? Do you really need to use them? Doubtful. Yes, there are some use cases out there that can only be solved with DVCs but these are actually pretty rare. So if you think you need to use DVC’s, think again, and the ask someone else, perhaps on the force.com developer forums, and then if you still think you need them go for it. Personally, I still can’t think of a super great example for DVCs, but I’m sure after this post I’ll be inundated with use cases where only DVCs can save the day!

So here is an example of using DVC vs not using them. This is actually the example taken from the recent Force.com Summer 11 webinar. The goal is to get an output that looks like this:

An unknown number of tabs is the dynamic piece that needs to be solved. Here is one way to do it with DVCs. First the page. One thing I will point out right way is that when you look at this page you have no idea what constitutes the dynamic components. There is nothing that lets you know if they are a table, a div, text, etc. You would need to go look in the controller to see exactly what the dynamic component is made of. It is less markup but it is also inherently less descriptive and as mentioned before more difficult to understand. This is one of the core advantages to staying true to the MVC pattern. On a View it should be dead simple to understand the generated markup and output.

<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>

Next the controller. Notice that we are querying the data but also directly manipulating and defining the view in this controller. For me, when these two areas start to combine it can become blurring on exactly what code is interacting with the Model and what code is interacting with the View.

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;
    }
}

So how would you do it mister smarty pants? Oh, I’m glad you asked. I would still use Dynamic Visualforce, just not Dynamic Visualforce Components. In my approach I want to look at the controller first. Notice that this controller has nothing related to the structure and style of the View. It only queries and process information from the Model. This should make it easier to understand exactly what the code is doing from a logic standpoint as you do not need to mentally separate the View logic. Some of the variables control how the information will be displayed on the page and this is okay, this is exactly what a controller should do. One thing to notice is the use of Maps. Map support was recently added to Visualforce as Dynamic Visualforce and is it is fan-freakin-awesome. I would bet it will solve 95% of your dynamic Visualforce needs. The other 5% is for Dynamic Visualforce Components.

public class TopOppsController {
 
    public List<Id> accountIds {get; set;}
    public Map<Id,String> acctIdNameMap {get; set;}
    public Map<Id,List<Opportunity>> acctIdToOppsMap {get; set;}
    public Opportunity totalOppAmount {get; set;} //User Opportunity object amount field as wrapper for total, outputField will format currency
 
    public TopOppsController(){
        buildOppList();
    }
 
    public void buildOppList(){
        //Instantiate variables
        accountIds = new List<Id>();
        acctIdNameMap = new Map<Id,String>();
        acctIdToOppsMap = new Map<Id,List<Opportunity>>();
        totalOppAmount = new Opportunity(Amount = 0);
 
        //Query the Opps
        List<Opportunity> opps =    [SELECT CloseDate, Id, Name, AccountId, Account.Name, Probability, StageName, Amount 
                                    FROM Opportunity 
                                    WHERE isClosed=false ORDER BY Account.Open_Opportunities_Total__c DESC, Account.Name, Amount DESC LIMIT 10]; 
 
        //Process the opps
        for(Opportunity opp : opps){
            //Increment total amount
            totalOppAmount.Amount += opp.Amount;
 
            //Add accountId and Name to map, these Id -> Name map values can be used in Visualforce page
            acctIdNameMap.put(opp.AccountId,opp.Account.Name); 
 
            //Add Opps to Account ID -> List<Opps> map, the List values in this map can be use in Visualforce datatables
            if(acctIdToOppsMap.get(opp.AccountId) == null){
                acctIdToOppsMap.put(opp.AccountId,new List<Opportunity>());   
                accountIds.add(opp.AccountId); //We add account Ids to list and then we can iterate over this in page and pull values from Maps in the controller
            }
 
            acctIdToOppsMap.get(opp.AccountId).add(opp);
        } 
    }
}

Next up is my Visualforce page. One of the first things you will notice is that I had to use a little bit if jQuery magic. In my first attempt I tried to use an apex:repeat component inside of an apex:tabPanel but this simply does not work. So a bit unwillingly I turned to jQueryUI to address the tab issue. The good news is that using the jQuery UI tab panel is ridiculous easy: 1) Create a div that will represent the tab panel. 2) In this div create list of <a> links where href is the Id of a seperate div representing the corresponding panel. 3) Four lines of jQuery javascript. I’ll admit this is not 100% native where as DVCs are but this approach is actually very simple. It also makes it easier to see what the view is doing, and actually provides complete control over the tab panel style, and interaction. Even if you did use the native tab panel, it is doing the tab switching with javascript anyway ;-P .

So all that aside the page should be pretty easy to understand. With out any type of inline comments you’d be able to see we have a list of <a> links that is outputting an account name. Then for each one of these names we also have a corresponding data table. Keeping the View out of the controller makes it much easier to see how the page is formatted….or at least I think it makes it easier. You lose this type of natural documentation when using DVCs.

You can see a working example by clicking here.

<apex:page controller="TopOppsController" >
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js"/>
    <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.12/jquery-ui.min.js"/>
    <apex:stylesheet value="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.0/themes/ui-lightness/jquery-ui.css"/>
 
    <script type="text/javascript">
         var j$ = jQuery.noConflict();
 
        j$(document).ready(function() {
            j$("#tabs").tabs();
        });
    </script>
 
    <apex:sectionHeader title="Top 10 Opps!"/>
 
    <h1>Total Amount from Top 10 Opportunities: </h1> <apex:outputField value="{!totalOppAmount.Amount}"/><br/><br/>
 
    <div id="tabs">
        <ul>
            <apex:repeat value="{!accountIds}" var="acctId"> <!-- Loop through list of account Ids -->
                <li><a href="#tabs-{!acctId}">{!acctIdNameMap[acctId]}</a></li> <!-- use account Id to get values from acctIdNameMap -->
            </apex:repeat>
         </ul>
           <apex:repeat value="{!accountIds}" var="acctId"> <!-- Look through list of account Ids -->
               <div id="tabs-{!acctId}">
                   <apex:pageBlock mode="edit">
                       <apex:pageBlockTable value="{!acctIdToOppsMap[acctId]}" var="opp" width="100%" > <!-- use account Id to get list of opps from acctIdToOppsMap -->
                           <apex:column value="{!opp.Name}"/>
                           <apex:column value="{!opp.Probability}"/>
                          <apex:column value="{!opp.StageName}"/>
                          <apex:column value="{!opp.Amount}"/>
                     </apex:pageBlockTable>  
                 </apex:pageBlock>
             </div>
         </apex:repeat>
    </div>
 
</apex:page>

So in the end DVCs have a purpose but more often then not you will probably not need them to meet your specific requirements. The real danger is using them when you really don’t need to and I’m hoping everything I’ve listed in this post explains why you should make a conscious effort to only use them when absolutely necessary. In the end this will make your apps easier to understand and then when you leave your company to go start the next million dollar start up the poor sap that inherits your code won’t go insane trying to understand the mess that is your controllers.