I have had a few occasions where I wanted to invoke Apex Code by clicking a button on a Page Layout. Everywhere I looked, it always said I needed to have the button call an s-Control, which would then invoke Apex Code that’s written as a Web Service. I am doing my best to avoid using s-Controls and figured there had to be a way using Visualforce instead of an s-Control. The key was the action attribute on the <apex:page tag.

Suppose you wanted to add a button on your Opportunity page that automatically did something when you pushed the button. You can’t execute Apex Code directly from a button. You need something in the middle. Let’s try to do it with a Visualforce page instead of an s-Control.

Controller

The controller has the main method in it to be executed. This method returns a PageReference (very important). Note that the method does not need to be a webservice.

public class VFController {
// Constructor - this only really matters if the autoRun function doesn't work right
private final Opportunity o;
public VFController(ApexPages.StandardController stdController) {
this.o = (Opportunity)stdController.getRecord();
}
// Code we will invoke on page load.
public PageReference autoRun() {
String theId = ApexPages.currentPage().getParameters().get('id');
if (theId == null) {
// Display the Visualforce page's content if no Id is passed over
return null;
}
for (Opportunity o:[select id, name, etc from Opportunity where id =:theId]) {
// Do all the dirty work we need the code to do
}
// Redirect the user back to the original page
PageReference pageRef = new PageReference('/' + theId);
pageRef.setRedirect(true);
return pageRef;
}
}

Visualforce

The Visualforce page is very simple. You really don’t need anything in there except the action parameter invokes code (the autoRun method) that runs before the page is initialized. That method returns a Page Reference that redirects the user back to the original page.

<apex:page standardController="Opportunity"
extensions="VFController"
action="{!autoRun}"
>
<apex:sectionHeader title="Auto-Running Apex Code"/>
<apex:outputPanel >
You tried calling Apex Code from a button. If you see this page, something went wrong. You should have
been redirected back to the record you clicked the button from.
</apex:outputPanel>
</apex:page>

Custom Button

All we need now is a custom Opportunity button. Because we used the Opportunity standard controller in the Visualforce page, we can simply have the button point to a the page we created.

// Code we will invoke on page load.
public PageReference autoRun() {
for (Opportunity o:[select id, name from Opportunity where id =:theController.getId()]) {
// Do all the dirty work we need the code to do
}
return theController.view().setRedirect(true);
}

Benjamin Pirih Said,

Have you attempted invocation of the autorun method during the construction of the controller instance? This would reduce the need to explicitly define action=”{!autoRun}” and would reduce the underlying overhead associated with handling an action.

That’d work too. I believe the action function runs before the constructor does, which means you don’t have the overhead. Not totally sure about that, though. If that’s the case, then it’s nice to have the constructor available for other things you might want other pages to do since you can re-use a constructor on multiple pages.

“In general you want any initialization code to happen in your constructor. That is the only place where you are guaranteed for that code to execute before anything else in your class (getters, setters, actions).

Actions are generally for doing some kind of redirection, DML, or state change (say you want a checkbox flipped from true to false when a button is clicked). If you need load logic to happen that should probably happen in your constructor.”

Very good stuff.. Thanks for the interesting information/discussion.. Ben

But if any of your autoRun code involves a record update, then calling that function from the constructor would result in a System.exception “DML not allowed here”, disqualifying it from being invoked from the constructor – am I correct?

david Said,

BTW – is there an easy way to display a message during the autoRun process? I’ve created a VF page to override the standard lead conversion button. The apex takes a while to run so I want to use apex:OutputText to display “please wait … conversion in progress”, but of course the autorun happens first and then I redirect to the “conversion successful” page.

Please wait … lead conversion in progress …

Thanks
David

DavidPSG Said,

Thanks very much for the quick reply.
My controller is virtually identical to your example, except it acts on the Contract object rather than Opportunity, and of course the method itself is different. The test method (I thought) was very straightforward:

DavidPSG Said,

BrianP Said,

Using you code examples above i am haivng trouble getting the test method to work correctly. When i run the test method the code coverage is very low, because the test method runs selects one row, but then the autorun() method returns a null value.

I’d have to see your code to know for sure. One thing I do know is that you should be creating your custom object data in your test class, not selecting existing data. If your autoRun code handles many different data scenarios, create custom object data in your test class to accommodate each and call autorun for each record.

If you want to, you can log a case and show me your code and we can go from there.

The default Action view is replace with my custom page that is identical to the original with the exception of the hidden form with the hidden actionFunction and the inclusion of the controller extension
When the custom button is pressed it invokes the actionFunction which in turn invokes the action on the controller extension.
The action on the controller returns a PageReference (with redirect attribute set) to direct the user to a new screen, or back to the current screen using ApexPages.currentPage()

@Andy, the blog is active. It’s been 6 months since a comment on this post, but the blog itself has received comments as recently as today.

The issue is in your pageRef line. First of all, put the pageReference lines above your Event currentEvent lines. Second, you need to create a reference to your Visualforce page. Should be something along the lines of:

GON Said,

Hi Scott,
I´m new to salesforce, and i´m trying to update a field when a user clicks a button in a layout page.
I´m using the code above, but i only get the same page without any update in the field.
I would really appreciate your feedback.
Here is my code:

Not sure exactly why you are doing what you are doing (I’d just do a mass update of the actual data and not need this code), but the reason nothing is sticking is that you are not updating the record and are then redirecting the user away from the page they are on and refreshing the page. You are doing the equivalent of going to a record, clicking Edit and then clicking the Back button on your browser without saving the record.

I tried to use Dan Fowlie’s way of adding a message (in my case I want to add it regardless of error). but I’m doing this with a standard page (the Case Support page to be exact). But a message doesn’t show.. So I’m not sure if I’m doing something wrong or of the page doesn’t have the apex:messages tag.. I can’t see the visiualforce code for a standard page (at least I don’t think I can).

So any suggestions on figuring out if its the message code or the page is missing the required tags?

Just a note.. the code of the controller calls a web service that spawns a job so it returns immediately.. I want to inform the user that the request was submitted..

this is the code I inserted in my custom controller that is called from the custom button..
// Redirect the user back to the original page
ApexPages.Message myMsg = new ApexPages.Message(ApexPages.Severity.INFO, ‘Request Submitted’);
ApexPages.addMessage(myMsg);
PageReference pageRef = new PageReference(‘/’ + theId);
pageRef.setRedirect(true);
return pageRef;

Thanks in advance!!

Rakesh Said,

Hi I tried this approach but it is throwing the error
“Insufficient Privileges
You do not have the level of access necessary to perform the operation you requested. Please contact the owner of the record or your administrator if access is necessary. ”

when clicking on the button please help

Sam Said,

I’m sure this is a dumb question, but what happens if someone clicks my custom button while they still have unsaved changes on the page?

For example, my staff have to put in several pieces of data on an Opportunity and then I want them to click my custom button that will use those pieces of new data to create a new related record. Do I have to train them to put in the new pieces of info and hit “Save”, wait for page to refresh, and then hit my custom button? Or, can they put in the new pieces and hit my custom button which will save any unsaved changes before doing its work?