Monday, September 12, 2016

Visualforce Controllers - stay standard & extend? or go wild

Introduction

More than once I have seen a lot of people use the wrong controller more than once. At the beginning of my Apex days, it wasn't quite clear what exactly controllers were and when I should use them. So this post is for all new to Apex & Visualforce and those who might have already been programming in Apex but still get confused when making the decision about what controller to use. So let's begin by briefly answering the question, what is a contoller?

What is a VF Controller?


Simply put a VF Controller is a class that contains a bunch of methods and properties used to control the data, navigation and actions that can be performed on a Visualforce or Standard detail record page. Example of data includes for example what fields of a particular record are retrieved and shown on the UI. An example of navigation could be a User clicking on a link of a related record which opens this record on a new page or tab. And finally an example of an action could be a user clicking on an edit button of a standard detail page. 

There are 2 types of controllers; Standard Controller and a Custom Controller. Both Controllers can be extended by what we called Controller Extensions.

Standard Controller


Every time we create a new custom object, Salesforce creates a corresponding class for that object. That class determines what we see when we look at records of that object from the standard salesforce UI, also called the record detail page. It also  determines what happens when we perform an action, say click on edit, delete, clone or even on the new button when looking at a list view for that particular object. But this is not limited to objects we create. Standard Salesforce objects also have standard controllers. As an example, look at the standard detail page of a contact;





The information displayed here and the actions which take place when the buttons are clicked is controlled by the Standard Controller for Contact. The Standard Controller is always the name of the object.

In order to access the Standard Controller in Apex, we have to create a Visualforce page. This leads us to the next question, when exactly should we use a Standard Controller?

When to use a Standard Controller


Use a standard controller when you need to override the standard buttons for an object. The standard buttons you can override include New, View, Edit, Delete, Clone and Accept. Overriding any of these buttons will make sure that a Visualforce page which uses the Standard Controller is called when the overriden button is clicked and not the Standard Salesforce page for that particular button. Note that you have to override these buttons individually. This makes the ability to override standard pages very powerful because you can implement different functionality on a different page for each button. I have seen this feature abused quite a lot and therefore a word of caution, don't do something just because you can. The standard buttons will be enough in most situations and if extra functionality is really required, please think about adding a Visualforce section onto the page layout for an object rather than immediate overriding the "View" button and rebuilding the whole page. An example of such a situation can be when you need to retrieve data from an external System and show it on the record detail. Here you will also need to use the Standard Controller of the object whose record detail page is to be extended with the Visualforce section.

Before we look at an example, let's talk about controller extensions.

Extensions


The Standard Controller is a brilliant concept and coupled together with other Salesforce features such as field dependencies, record types, page layouts etc. it is enough in most situations and no additional code is required. However there are times when the functionality of the Standard Controller needs to be extended. This is when we use an extension and then put the custom functionality in the extension. The extension is simply an Apex class. In this class, we have access to the standard controller and all it's methods. Very important to note, although there can only be a single controller on a page, many extensions can be attached to the page.

Now let us look at an example;


 <apex:page standardController="Financial_Statement__c"   
      extensions="FinancialStatementControllerExt" showHeader="true" sidebar="true">  
      <apex:form>  
           <apex:sectionHeader title="Create Financial Statement"   
                subtitle="New Financial Statement" rendered="{!Financial_Statement__c.Id == null}" />  
           <apex:sectionHeader title="Update Financial Statement"   
                subtitle="{!Financial_Statement__c.Name}" rendered="{!Financial_Statement__c.Id != null}" />  
           <apex:pageBlock title="Financial Statement">  
                <apex:pageBlockButtons location="top">  
                     <apex:commandButton value="Save" action="{!saveFinancialStatement}" />  
                     <apex:commandButton value="Cancel" action="{!cancelOperation}" />  
                </apex:pageBlockButtons>  
                <apex:pageBlockSection title="Enter a new Financial Statement">  
                     <apex:inputField value="{!Financial_Statement__c.Name}" />  
                     <apex:inputField value="{!Financial_Statement__c.Account__c}" />  
                </apex:pageBlockSection>  
           </apex:pageBlock>  
      </apex:form>  
 </apex:page>  


Looking at this page, it is clear that this is a page used to override one of the standard buttons for an object called "Financial_Statement__c" or create a visualforce page to be added to the record detail page for Financial_Statement__c records. This means whenever a user clicks on that button for a particular record, the record or at least it's Id will be passed to this page. Because we are using the Standard Controller, we have direct access to the fields on the record e.g. through the expression "{!Financial_Statement__c.Name}.

Also notice that the page definition tag has an "extensions" attribute. Notice the "s" in extensions. More than 1 extension can be added to a single page. The extension "FinancialStatementControllerExt"  is used to add more functionality. For instance the "Save" and "Cancel" buttons do not call the save and cancel actions which exists in the Standard Controller but the "saveFinancialStatement" and "cancelOperation" actions which exists in the controller extension.

 public with sharing class FinancialStatementControllerExt {  
   public Financial_Statement__c financialStatement { get; set; }  
   public ApexPages.StandardController stdController { get; set; }  
   public FinancialStatementControllerExt(ApexPages.StandardController stdController){  
     this.financialStatement = (Financial_Statement__c)stdController.getRecord();  
     this.stdController = stdController;  
   }  
   public PageReference saveFinancialStatement(){  
     try {  
       if(this.financialStatement.Account__c == null)  
         this.financialStatement.Account__c = this.getDefaultAccountId();  
       insert this.financialStatement;  
       return new PageReference('/' + this.financialStatement.Id);  
     } catch(Exception e) {  
       ApexPages.addMessage(new ApexPages.Message(  
         ApexPages.Severity.Error,   
         'An error occurred while inserted a new financial statement record. Please contact your admin!')  
       );  
     }  
     return null;  
   }  
   public PageReference cancelOperation(){  
     return this.stdController.cancel();  
   }  
   private Id getDefaultAccountId(){  
     try {  
       return [SELECT Id FROM Account WHERE Name = 'Default Account' LIMIT 1].Id;  
     } catch (Exception e){  
       ApexPages.addMessage(new ApexPages.Message(  
         ApexPages.Severity.Error,   
         'An error occurred retrieving the default Account. Please contact your admin!')  
       );  
     }  
     return null;  
   }  
 }  

Notice that the cancel button "cancelOperation()" calls the standard controller "cancel()" method.

We can get access to the current record in our controller extension by using the methods defined in the Standard Controller. To get access to the Standard Controller, we have to write a constructor which will be passed an instance of the Standard Controller when the extension is initialised. The Standard Controller method getRecord() returns the current record. It is very important to note that only fields already referenced on the Visualforce page will be available in the controller extension, in our example only "Name" and "Account__c". So what do we do if we want to use fields in the controller extension, that do not necessarily need to be visible on the UI? There are basically two ways to do this that I have come across;

  1. Use hidden components on the page and reference the additional fields in those components. The hidden component to used is the "<apex:hiddenInput value="{!Financial_Statement__c.SomeValue}" />" whose value attribute binds the referenced value to the controller extension such that it is retrieved and available in the controller extension when "getRecord()" method of the Standard Controller is called.
  2. The second method is another Standard Controller method, "addFields(fieldList)"

Add Fields - addFields()


We can use the Standard Controller method "addFields(fieldList)" to add fields that should be retrieved together with fields referenced on the page when "getRecord()" is called. For example, the constructor which retrieves the record data would like look like this;

   public FinancialStatementControllerExt(ApexPages.StandardController stdController){  
     stdController.addFields(new List<String>{'fieldName1__c', 'fieldName2__c'});  
     this.financialStatement = (Financial_Statement__c)stdController.getRecord();  
     this.stdController = stdController;  
   }  

Notice that "addFields(fieldList)" is called before getRecords(), logical right?

I personally do not like hidden fields because I dislike the idea that someone can view the source of my page and get his hands on information, I may not want to expose, so I generally prefer using "addFields(fieldList)". The problem with "addFields(fieldList)" method is that it is not testable. Take a look at this idea StandardController AddFields method should be testable on the ideaExchange to learn more. You can see that the idea was created 5 years ago meaning others have lived with it for this long and so will you. Doesn't seem to be a priority to Salesforce to fix this. So what do you do? You will have to workaround this by making sure that "addFields(fieldList)" does not run if you are in a text class. Our constructor now looks like this; 

   public FinancialStatementControllerExt(ApexPages.StandardController stdController){  
     if (!Test.isRunningTest())  
        stdController.addFields(new List<String>{'fieldName1__c', 'fieldName2__c'});  
     }  
     this.financialStatement = (Financial_Statement__c)stdController.getRecord();  
     this.stdController = stdController;  
   }  


Depending on what you are testing, you might have to carefully craft your test class to avoid errors and to achieve the required coverage for the controller extension.

Also note that controller extensions can also be used to extend custom controllers. All you need to do in your extension is to provide a constructor which takes the custom controller you are extending as a parameter. This is similar to the constructor in our example above which takes the Standard Controller it is extending as a parameter.

Now that we have looked at the Standard Controller and controller extensions, let's briefly talked about Custom Controllers

Custom Controllers


These are classes that are not attached to any specific object the way Standard Controllers are. They are more like extensions in that they implement custom functionality that is available to the Visualforce page that uses them. So when should we use custom controllers?

When to use a Custom Controller?


A custom controller is mostly used when we need to expose a piece of functionality using Visualforce Tabs, custom buttons, and links. To stay with out Financial Statement example, let us create a custom button and add it to the Account detail page. This button should open a Visualforce page that can be used to create a financial statement record directly from the account. Here is the custom controller code;


 public with sharing class FinancialStatementController {  
      public String accountId { get; set; }  
      private String accountName { get; set; }  
      public Financial_Statement__c financialStatement { get; set; }  
      public FinancialStatementController() {  
           this.accountId = ApexPages.currentPage().getParameters().get('id');  
           this.accountName = ApexPages.currentPage().getParameters().get('name');  
           this.financialStatement = new Financial_Statement__c();  
           if(String.isNotBlank(this.accountId))   
                this.financialStatement.Account__c = this.accountId;  
           if(String.isNotBlank(this.accountName))   
                this.financialStatement.Name = this.accountName + ' FS';  
      }  
      public PageReference save(){  
           try {  
                insert this.financialStatement;  
                return new PageReference('/' + this.accountId);  
           } catch(Exception e){  
                ApexPages.addMessage(new ApexPages.Message(  
                     ApexPages.Severity.Error,  
                     'An error occurred while creating the Financial Statement. Please contact your admin!'  
                     )  
                );  
           }  
           return null;  
      }  
      public PageReference cancel(){  
           PageReference acctPage = new PageReference('/' + this.accountId);  
           acctPage.setRedirect(true);  
           return acctPage;  
      }  
 }  

And the page looks like this;


 <apex:page controller="FinancialStatementController" showHeader="true" sidebar="true" tabStyle="Account">  
      <apex:form>  
           <apex:pageBlock title="Create a new Financial Statement">  
                <apex:pageBlockButtons location="top">  
                     <apex:commandButton value="Save" action="{!save}" />  
                     <apex:commandButton value="Cancel" action="{!cancel}" />  
                </apex:pageBlockButtons>  
                <apex:pageBlockSection title="Enter Financial Statement details for {!financialStatement.Name}">  
                     <apex:inputField value="{!financialStatement.Name}" />  
                     <apex:outputField value="{!financialStatement.Account__c}" />  
                </apex:pageBlockSection>  
           </apex:pageBlock>  
      </apex:form>  
 </apex:page>  


For those of you who are completely new to Salesforce, you can create a custom button on account by navigating Setup -> Customize -> Accounts -> Buttons, Links, and actions and then clicking on "New Button or Link" button. You can then enter the details as shown below




Notice that we use URL as the content source for our link. We then call our Visualforce page and pass it parameters. We could also select Visualforce directly as the content source and then select our Visualforce page. If we do it this way, we will have no way of getting the Account Id to which the created Financial Statement should be attached. That is why we choose a link here, because it is of upmost importance to pass at the least the account Id. In some situations that we do not need to pass parameters and do not want to expose data in a URL, choosing Visualforce as a data type will be the appropriate choice here.

Please don't forget to add the button to the page layout by navigating to Setup -> Customize -> Accounts -> Page Layouts and editing the page. Click "Buttons" on the left panel on the fixed header, then drag and drop the "Create Financial Statement" button from the right panel onto the "Custom Buttons" section at the top of the Account Detail info.

If you click on the "Create Financial Statement" button on the account detail the following Visualforce page opens up.



You will notice that although we came from an account detail, the Financial Statement Tab is automatically selected. Well not really automatically. This happens because we set the tabStyle attribute on the "apex:page" component to "Financial_Statement__c" when defining our Visualforce page

Also notice that the account is read-only and also a hyperlink. This is because we used a "apex:outputField" component for the "Account__c" field. Also notice that the name defaults to the account name concatenated with "FS". This is exactly as defined in the custom controller.

Security & Data Access - With Sharing / Without Sharing

It is important to note that all Apex code always executes in System mode meaning that sharing is not applied. Eventhough Visualforce always enforces sharing such that Users wouldn't see Data they are not entitled to see, they would still be able to create records they are not allowed to create. For this reason, it is important to use the keywords "with sharing" in controller and controller extensions to make sure that sharing is applied and the Apex code runs within the context of the current user.

To know more about the dos and don'ts and how to correctly apply "with" & "without" sharing please read my blodpost Apex Sharing - "with sharing" & "without sharing"

Conclusion


As a recap, we have learned about the Standard Controller, controller extensions and the Custom Controller.

We learned that each object in Salesforce, be it Standard or Custom has a Standard Controller which controls the look & feel and functionality of the standard detail pages. We also learned that we could modify or customise this functionality by using extensions. In this case we could use Standard Controllers together with extensions to override standard buttons such as New, View, Edit etc. and to create Visualforce pages that can be added as sections to the standard detail record page.

Extensions can also be used to extend the functionality of custom controllers.

We also learned that we can use custom controllers to build stand-alone functionality for custom Visualforce tabs, buttons & links. The functionality behind this tabs, buttons and links is limited only by your use case and imagination. We looked at an example of how to create a Visualforce button for accounts.

I hope that you have understood when and how to use the different controller types after reading this blogpost. If not ask a questions in the comments and I will get back to you soon. Also take a look at the additional links below. As always I appreciate your feedback and critique, so don't be shy to leave a comment. Thanks for reading and lots of fun with learning salesforce.

Additional Links


No comments:

Post a Comment