Pages

Monday, June 29, 2009

Triggering an Apex method with a Custom Button

Most often Salesforce.com developers want to write a custom logic such as sending a notification email, changing the status of a record (picklist), etc once a button is clicked on, in a standard layout.

The effort is minimized this way since you do not want to recreate the layout using a Visualforce page, all you need is to be able to launch a method once the button is clicked on.

In order to do so, you need to write your logic into an Apex class with following conditions:
  1. Firstly, your class should be marked as "Global"
  2. Secondly, the logic goes to a static method of this class which is marked as "WebService"
If an Apex class has the above characteristics, the method marked as web service can be called via javascript when the button is clicked on. Neat!

I think by now you have a good idea of where I am going with this, so let's dive into code and examine everything more closely.

Below I have created a Apex Class called "OutboundEmails" and added a method that has a keyword as "WebService".


global class OutboundEmails {

WebService static void SendEmailNotification(string id) {

//create a mail object to send a single email.
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();

//set the email properties
mail.setToAddresses(new string[] {'myemail@domain.com'});
mail.setSenderDisplayName('SF.com Email Agent');
mail.setSubject('A new reminder');
mail.setHtmlBody('an object with ID='+ id + ' is just clicked on.');

//send the email
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail } );

}
}




This method receives an object Id (this is just to demo purposes, so you can identify any parameters that you need) and sends an email notification subsequently once the method is called.

Now let's concentrate on the button that will actually call our WebService method.

Firstly, I create a detail page button let's say on Account object and name it "Send Me ID".
This button's behavior will be "Execute Javascript".
If you need more information about how you can add a custom button to Account object please click here. Then I add the following code to the body of the button edit page:



{!REQUIRESCRIPT("/soap/ajax/10.0/connection.js")}
{!REQUIRESCRIPT("/soap/ajax/10.0/apex.js")}
sforce.apex.execute("OutboundEmails","SendEmailNotification", {id:"{!Account.Id}"});
window.alert("Account Id is sent." );



Now It all works together!
Once you click on the button, in case you have not forgotten to change the sample email address with your own in the code, you should receive the email.

41 comments:

  1. Any reason that you've chosen to do this with a WebService and javascript in lieu of a Visualforce page or are these just two means to the same end?

    ReplyDelete
  2. Thanks Sam, this is exactly what I was looking to do. It's cleaner than the round-about way of calling a visual force page that calls the apex code. The only thing I added was a simple javascript page refresh to see the changes that were made by my Apex code. It's not as nice as returning a PageRefresh object but it doesnt seem like that works in this method anyways.

    ReplyDelete
  3. Hi,

    The code was working fine till i packaged the App .
    Now its giving error
    faultcode:'soapenv:client' faultstring:'No service available for class 'SendEmailNotification'

    please help

    ReplyDelete
  4. You need to prefix the package name in the call.

    sforce.apex.execute("MyPackage/MyClass".....

    ReplyDelete
  5. Many institutions limit access to their online information. Making this information available will be an asset to all.

    ReplyDelete
  6. Hi Sam,
    Thank you for this post. I'm using this approach to run some custom code on my SFDC system and I was wondering how you test the web service for deployment to production? I've read a few other posts about that but nothing is working for me so far.

    Thank you,
    Ian

    ReplyDelete
  7. This is good but can you also provide an example of how to write test methods for these classes that have web service methods.

    Thanks

    ReplyDelete
  8. Is this possible in Professional Edition? I know you can't have classes, but is there a way around it?

    ReplyDelete
  9. How do you write a test case for this?

    ReplyDelete
  10. The JavaScript method does not need test code. Only apex classes need test code.

    ReplyDelete
    Replies
    1. Salesforce only enforces testing of apex classes, but that doesn't mean you shouldn't test your JavaScript code as well. QUnit is a simple JavaScript testing framework. Jasmine is also very popular.

      Delete
  11. Thanks for the reply. I have gone through and added test functions for all of the features in the global class. But SF does not seem to pick it up as it being tested. My button creates a "case" from the "opportunity" and fills in the case with as much details as possible from the opportunity.

    ReplyDelete
  12. I think I figured it out... You need to basically call that class and function from one of your test cases.

    ReplyDelete
  13. Once you push the button and the Apex code runs, how do you tell the Apex code to open a page in their browser and point them back the object you just created? In my case I am creating a Case from the Opportunity. Once I press the button the Opportunity, how do I open the Case that I just created?

    ReplyDelete
  14. This is one way to skin the cat, but it seems like a hugely expensive way of making a call to an Apex class from within Salesforce. Instead, just define the button to call a VisualForce page.

    You should be able to define a one-line VF page with your Apex as the controller, and your method invoked in the Action of the tag.

    It also gives you some flexibility in that the controller can redirect the UI to a different location than the launch page depending on your logic.

    There's slightly more work if you want to pass parameters, but it's very simple, less awkward, doesn't cost roundtrips, etc.

    Also, you may not want to define all these Apex classes as globals and webservices which have different security considerations.

    Best, Steve B.

    ReplyDelete
  15. Hi,
    I´m not sure if this post is still alive...
    I´m trying to make a button just change the value of a field (Picklist). Could you please give me some advise on how to make the web service do this??
    Thanks on advance.

    ReplyDelete
  16. Now, with Visual Force, this is much easier. Just create a VF page that is associated with a standard *and* a custom controller. In the custom controller, define a method called "autorun" and in the apex page tag, set action="{!autoRun}". Then define the custom button as a detail page button defined as a VF page. Have the autorun method redirect the page as appropriate if the action succeeds (to a confirmation message, eg), and have it display an error message if the action fails.

    ReplyDelete
    Replies
    1. Very helpful comment and very useful article. The comment should be added as required update for the article.
      Thanks

      Delete
  17. Thank you for this post! I wrote a great Button/Apex combo that solved a huge need.

    Unfortunately, while it works perfectly in the Developer org I wrote it in, it does nothing in production when clicked. I launched it into production through a Managed Package. Any way to make it work in my installed production environment?

    ReplyDelete
  18. Simple.

    Elegant.

    Easy.

    One line of code to call a class?! Nice!

    ReplyDelete
  19. Thanks Sam, this is exactly what I was looking to do. It's cleaner than the round-about way of calling a visual force page that calls the apex code. It would be great as per administrative perpose.

    ReplyDelete
  20. When I use a button to a VF page that run's some apex to create a new case, or a new contact, etc; if the user hits the back button in the browser, it goes to the VF page, which then fires the apex code and creates another new case or contact.

    Using JS avoids the VF page, but can I direct the user to the new case or contact page?

    ReplyDelete
  21. If an Org is using name-space then , syntax has to be like this>>

    sforce.apex.execute('OutboundEmails','SendEmailNotification', {id:'{!Account.Id}'});

    ReplyDelete
  22. Hi ,

    If your Org has NameSpace then syntax has to be like this >>

    sforce.apex.execute('TPSF/OutboundEmails','SendEmailNotification', {id:'{!Account.Id}'});

    Thanks,
    Mayank Joshi

    ReplyDelete
  23. Hi

    I need to call batch apex on custom button click.

    Can i do it?

    ReplyDelete
  24. Can't tell you how helpful this blog post has been to us. Go to sleep tonight knowing that there is at least one person in the world who is ultra thankful for your contributions to the Salesforce community.

    Thank you Sam!!!!!
    David

    ReplyDelete
  25. Very educational, both specifically and generally.

    ReplyDelete
  26. Hi,

    Very useful. Is there a way to add an 'Are you sure?' type dialog so the user can accept before going through with the actions performed by the button?

    Thanks!

    ReplyDelete
  27. ISVforce partners WARNING!!

    This will not work for Professional edition and lower. Salesforce does not allow you to call a webservice from such orgs, does not matter if your app is a managed app.

    ReplyDelete
  28. Works good...but what if email address is not valid?
    How to get the respond from mail server about delivery notification failure and inform a sender?

    ReplyDelete
  29. Salesforce is handling the email sending, check Salesforce documentation to see how they handle the invalid emails.

    ReplyDelete
  30. if there is possible to call the trigger in the same way

    ReplyDelete
    Replies
    1. No, You cannot call a trigger, Salesforce database does that when an event takes place such as insert, update or delete.

      Delete
    2. Hello sam,

      I want to set current time for a field when the new button was clicked.I have some doubt in this,if i click button now with out completion of the event,i have clicked button again after 4 minutes then the old value will replaced with new time value or does the value remain same.

      Delete
    3. The value remain same

      Delete
  31. Nice post. Thanks!

    ReplyDelete
  32. Very useful post indeeed. I was looking for exactly the same functionality and am a newbie to SF. My org does not want to create VF pages, so they want all these calls to happen in onclick javascript. I spent long time in researching and all the solutions I found needed VF pages. Your post was really helpful.
    Thanks,
    MK2013

    ReplyDelete
  33. I am calling my webservice in the same way described in the post. I have one question though. Being a newbie to SF this might be a dumb question. I call the webservice in onclick javascript button which is a custom button on a custom object. This object is placed in standard opportunity layout and does not have a VF page of it's own. The question is I need to refresh the page after the webservice call returns, this will select some checkboxes in the custom object. I tried using:
    parent.location.href = url;
    where url = parent.location.href;
    This did not work so I tried
    window.location.reload();
    This works but the page reload happens before the webservice returns so corresponding UI change in the checkbox does not happen.
    Gurus, can you give me some hint on how to get the page refresh AFTER the webservice call returns?

    Thanks,
    mk2013

    ReplyDelete
  34. Very helpful post Sam.So is it possible to update any field by clicking on this button.

    Thanks,
    Baji Shaik

    ReplyDelete
  35. Hi Sam,

    How can I pass values which will be recired by a webservice method having list of wrapper class as a parameter.

    global without sharing class WebsiteWS
    {
    global class wsAttendeeDetails
    {
    webservice String websiteOrderId;
    webservice Id productId;
    }

    webService static wsOutputs wsPopulateOppEventContactDetails(List attendeeDetails)
    {
    system.debug('Inside wsPopulateOppEventContactDetails');
    system.debug('wsAttendeeDetails attendeeDetails - ' + attendeeDetails);
    return null;
    }
    }

    How should is populate sforce.apex.execute("WesiteWS", "wsPopulateOppEventContactDetails", "????") for "????" here.

    Please reply.

    Best Regards,

    Rahul

    ReplyDelete