Last week, we took a look at a skeletal version of a class implementing the asynchronous aspects of Apex: batch, scheduled and email.  While powerful, it’s a corner of Apex developers don’t always have a use case to tread down into.  This week, let’s look at something a lot more common: Visualforce controllers – quite possibly the most common use of Apex on the planet.

But Josh, you might ask – how long could such a template be?  After all, your basic controller only has one real requirement – and that’s a constructor of some kind.  That would just be too easy, of course.  The idea behind these templates is to give developers a starting point for what these classes can really execute, and there are a lot of different things that you can execute on with a Visualforce page.  So while this template is going to be light on actual code, in agreement with the “no functionality” concept behind the templates – it is going to be full of commented code examples as quick reminders of how to accomplish commonly used functionality on a controller (or extension).

So let’s crack on some commented code.  The whole class is up on github if you’d like to follow along at home.  In the nofunc version, most of the code you’ll see here will be commented out – but I’ve left the comment blocks to the wayside in these examples for better code highlighting.

Setting Variables

Basic enough right?  Except that it is good to remember that how you set your variables in Apex has a direct impact on your Visualforce page, assuming you are going to have an apex:form component in use.  That’s because public variables will appear in the viewstate, which increases the size and time of your data transport back to the controller, while variables which are transient or static do not.

Also note the use of an interior class here as a custom datatype.  This is very handy in a few case: one, used in the controller example here, is to organize the data being sent back via JavaScript Remoting.  Second, you can also organize the data if you needed to specify how it would be formed in, for instance, an apex:repeat component.

public with sharing class VisualforceController {
	/* Example variables */

	//Transient data that does not get sent back, reduces viewstate
	transient String userName {get; set;}

	//Static and/or private vars also do not become part of the viewstate
	static private integer VERSION_NUMBER = 1;

	//Public variables for Apex:Form
	public Account testAccount {get; set;}
	public String newName {get; set;}

	//You don't need this to return results for RemoteAction, but creates structure
	public class JSONResponse {
		public string message;
		public Account data; //Modify to your actual datatype
		public JSONResponse() {}
	}

Also note that the class itself is using the with sharing keyword, which will force Apex to obey the sharing rules of the current user.  Most controllers should be using that keyword.

Constructors

Constructors on a Visualforce page are going to be mostly responsible for defining the initial state of the data that the user will see.   When I’m first writing code like that, it is tempting to just toss out some of SOQL statements, fill some arrays and then be along my way.  However, I usually find that won’t be sufficient down the road.  This is because I’m forgetting the following frequently used scenarios:

  • Detecting incoming query parameters
  • Validating incoming query parameters (important for blocking injection attacks)
  • Handling potential DML errors
  • Notifying the user of custom error scenarios via Apex Messages.

So here is a constructor with that set of examples:

//Custom Controller Constructor
	public VisualforceController() {
		//check the existence of the query parameter
		if(ApexPages.currentPage().getParameters().get('id') == null) {
			ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL,'No Id Found'));
			return;
		}
		
		//check the format of expected parameters - this is good for SOQL injection protection
		ID accountID = null;
		try {
		    accountID = ApexPages.currentPage().getParameters().get('id'); 
		}	catch(Exception ex) { 
			ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL,'Not a valid ID'));
			return;
		}
		accountID = ApexPages.currentPage().getParameters().get('id'); 
		List<Account> accountsForId = [SELECT ID, Name from ACCOUNT WHERE Id =: accountID LIMIT 1];
		if(accountsForId.size() == 1) {
			testAccount = accountsForId[0];
		} else { //build custom error for messages component
			ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR,'There is no account for that ID','Failed SOQL for '+ApexPages.currentPage().getParameters().get('id')));
		} 
		
		//No DML allowed here
	}

Note that last comment – it’s a helpful reminder.  You can’t perform DML within the constructor of a Visualforce page.  You can tie DML to a method referred to the action attribute of your page component if you need to perform DML when the page first loads.

Another habit I have gotten into is always creating two constructors – one for a controller, and one for an extension.  I might never use the second controller, but if I want to port this same logic over to a page with a StandardController set, it allows me to easily keep everything to one class:

	//Extension Constructor
	public VisualforceController(ApexPages.StandardController stc) {
		testAccount = (Account)stc.getRecord();

		//no DML allowed here
	}

Callback Methods

Now we just need to let our controller actually do something.  I generally think of how Visualforce and Apex will interact in three main types:

  • A method which will respond to changes via apex:param and return a PageReference
  • A method which will be send a specific data point back as a getter
  • A method to respond via JavaScript Remoting

Here are three methods which correspond to that:

// Example callback functions

	//Visualforce Form callback, or as an ACTION from the page component
	public PageReference cloneAccount() {
		Account clonedAccount = testAccount.clone();
		clonedAccount.Name = newName; //set via param components on Visualforce
		insert clonedAccount; //DML allowed here 

		//return:
		//null to stay on the same page
		//PageReference with setRedirect(false) = wizard style, viewstate intact
		//PageReference with setRidirect(true) = new page, viewstate flushed
		return null;
	}

	//getter - accessible as {!relatedAccounts} in VF
	public List<Account> getRelatedAccounts() {
		String likeString = '%'+testAccount.Name+'%';
		try {
			List<Account> accountsWithNameLike = [SELECT ID, Name from ACCOUNT where Name LIKE :likeString];
			return accountsWithNameLike;
			} catch (DMLException e) {
				System.debug(e.getMessage()); //accessible via Developer Console or Debug Logs
				return null;
			}

	}

	//Asyncronous JavaScript callback.  No viewstate.
	//RemoteAction is static, so has no access to Controller context or variables
	@RemoteAction
	public static VisualforceController.JSONResponse updateResponse(String accountID) {
		JSONResponse json = new JSONResponse();
		try {
			Account a = [SELECT ID, Name from ACCOUNT WHERE Id =:accountID LIMIT 1];
			json.data = a;
			json.message = 'Success';
		} catch (DMLException e) {
			json.message = e.getMessage();
		}

		return json;
	}

Now, there is one other kind of transaction which is less commonplace these days: cookies.  Once the end all and be all of controlling a session, client-side cookies have taken something of a backseat to server-side data management.  Heck, when I first starting with Apex it wasn’t even an option – but we do have the ability now, and the utilization looks like this:

	/* Cookie Methods */

	public PageReference setCookies() {
		//Cookie = new Cookie(String name, String value, String path, Integer milliseconds, Boolean isHTTPSOnly)
		Cookie companyName = new Cookie('accountName','TestCo',null,315569260,false); //10 year cookie
		ApexPages.currentPage().setCookies(new Cookie[]{companyName});

		return null;
	}

	public String getCookieValue() {
		return ApexPages.currentPage().getCookies().get('accountName').getValue();
	}

A thanks goes out to Keir Bowden, AKA Bob Buzzard for breaking down the cookie’s constructor method on StackExchange.

Unit Testing

You can’t escape unit testing, not if you want your code to eventually make it into production.  Testing Visualforce controllers is often very similar to testing normal Apex code, except when the page interaction itself is in question.  This usually materializes in the form of query parameters which aren’t normally set when constructing just the Apex class.  To solve this, Test has a method to set the current page being seen by the unit test context:

	/* Example Test Method */
	@isTest
	static public void testVisualforceController() {
		//Set test page
		Test.setCurrentPage(Page.VisualforcePage);

		//Set test data
		Account a = new Account(Name='TestCo');
		insert a;

		//Set test params
		ApexPages.currentPage().getParameters().put('id',a.Id);

		//Construct controller
		VisualforceController vc = new VisualforceController();
		vc.newName = 'TestCo 100';

		//Execute Code
		vc.cloneAccount();
		List<Account> accounts = vc.getRelatedAccounts();

		//Make assertions
		System.assertEquals(1,accounts.size());
	}

Summary

And there you have – an example of the most common use cases on a Visualforce controller.  You can follow the whole nofunc project on Github as it unfolds, and either start your own branch to suggest changes and updates – or as always leave us a note in the comment box below, or catch me on twitter @joshbirk.  Happy coding, gang.

tagged , , , , Bookmark the permalink. Trackbacks are closed, but you can post a comment.
  • Anonymous

    “ApexPages.currentPage().getParameters().get(‘id’).length() != 15 && ApexPages.currentPage().getParameters().get(‘id’).length() != 18″ – is this line valid? I think it should be OR insteadof AND I gues?

    • http://twitter.com/joshbirk Josh Birk

      Yeah, I think I got inverted there. But I poked around a little bit and found a better method than just length: http://stackoverflow.com/questions/9742913/validating-a-salesforce-id

      Updated

    • http://profile.yahoo.com/P6GHIYZMPWOMZNF7HHVWAHZTIY Bhupendra

      where i can get the list of parameters and methods available for ApexPages.currentPage().getParameters()
      ApexPages.currentPage()?

      Many Thanks

  • http://twitter.com/forceguru Ankit Arora

    Cool!

  • Keith Clarke

    Can you confirm that setting cookies is only supported where the page/controller is being used in a site as documentation like http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_sites_cookie.htm suggests?

    Thanks,
    Keith

    • http://twitter.com/joshbirk Josh Birk

      That does not seem to be the case. Those docs are for Force.com Sites, but it doesn’t seem like the functionality is limited to it (I drew some of the above from the normal docs as well).

      But a quick test from the template code shows that you can show {!CookieValue} on the VF page after SetCookies() is run.

  • http://www.facebook.com/sandeep.k.singhal Sandeep Kr Singhal

    Very Helpful..