Take the 2-minute tour ×
Salesforce Stack Exchange is a question and answer site for Salesforce administrators, implementation experts, developers and anybody in-between. It's 100% free, no registration required.

I have added a Custom Button to the List View of the Custom Object. What is the best practice of calling an Apex Class on click of the Custom Button, and refresh the List View after execution of the Apex Class?

So far, I have used an internal Web Service to call the Apex Class. Then using setTimeout function, I have refreshed the List View on return of control from the Apex Class.

Should I have used internal Web Service? Or is that a bad practice? Should I write a Custom VF page instead?

function get(name) { 
    if (name = (new RegExp('[?&]' + encodeURIComponent(name) + 
        '=([^&]*)')).exec(location.search)) { 
        return decodeURIComponent(name[1]); 
    } 
}

{!REQUIRESCRIPT("/soap/ajax/10.0/connection.js")} 
{!REQUIRESCRIPT("/soap/ajax/10.0/apex.js")} 
setTimeout(function() { 
    sforce.apex.execute("ClassName", "functionName", 
    { 
        paramId0 : paramIds[0], 
        paramId1 : paramIds[1] 
    }); 
    ListViewport.instances[get('fcf')].refreshList(); 
}, 0);
share|improve this question
1  
The call is synchronous, can't you simply call it directly (without setTimeout)? –  eyescream Dec 10 '12 at 12:51
    
@eyescream, No. otherwise the refreshList() call executes before the Apex Class finishes executing. –  Suman Krishna Saha Dec 10 '12 at 12:53
2  
Weird, I'd swear it to be synchronous... salesforce.com/us/developer/docs/apexcode/Content/… I had no problem waiting for results of the call before calling location.reload() or when I had to work on one checkbox at a time I've been displaying "Processing 3 out of 10 records" as the button name... If you're sure then it might be tricky to work with... Does adding sforce.debug.trace=true; change anything? –  eyescream Dec 10 '12 at 13:00
3  
"Should I write a Custom VF page instead?" Yes. This gets my vote, its a more recent and more native implementation route for me. If your interested in this route take a look at my answer below. Enjoy! –  Andrew Fawcett Dec 10 '12 at 21:50
1  
Indeed, you have to consider the impact to your users should this happen and the cost to your future self in fixing / reworking it if they do. Some undocumented things are so widely used, there are almost documented and supported by that alone, though at the end of the day its hard to tell. If Salesforce have a compelling reason to change it, security or something like that, they will. –  Andrew Fawcett Feb 11 '13 at 8:09

4 Answers 4

JavaScript Custom Buttons. I have not been using JavaScript Custom Buttons for a while now, not only are they slower to respond in some cases, for reasons I've never really got to the bottom of. But they force us to expose global / web service classes for code that would otherwise be internal to our solutions.

Visualforce / Apex Custom Buttons. As you've considered in your question, using a Visualforce Page is another, much more direct and modern option in my view. It has now been available for sometime as an alternative, giving you a 100% Apex native solution without having to expose anything via Web Services.

By using the action attribute on the apex:page element you can invoke some Apex controller logic when your page is loaded by the button. Then return the PageReference you want to redirect the user to. Create a page as follows and then when creating the Custom Button select the List Button type and Visualforce option to select the desired page (note the use recordSetVar to denote a List View page).

<apex:page standardController="Test__c" extensions="ListViewActionController"
   recordSetVar="TestRecords" action="{!doSomething}"/>

The following action controller method performs the neccessary work and redirects back to the list view.

public with sharing class ListViewActionController {

    public ListViewActionController(ApexPages.StandardSetController stdController) { }

    public PageReference doSomething()
    {
        // Do something...
        insert new Test__c();

        // Invoke the list action to return back to the ListView
        return new ApexPages.Action('{!list}').invoke();
    }
}

Salesforce Security Review. Keep the following in mind, if your building a managed package and planning on taking it through this process. Action methods invoked on page load that perform database modifications, are generally (without further discussion) not accepted. Instead you need to modify your page to provide a button the user has to press.

<apex:page standardController="Test__c" extensions="ListViewActionController" recordSetVar="TestRecords">
    <apex:form >
        <apex:pageMessage summary="Click 'Do Something' to confirm." severity="Info" strength="2"/>
        <apex:commandButton value="Do Something" action="{!doSomething}"/>
        <apex:commandButton value="Back" action="{!list}"/>
    </apex:form>
</apex:page>

Image

Hope this helps, enjoy!

share|improve this answer

To get the JavaScript function which refreshes the list to execute after the Apex is finished, you should reference a callback function. The callback is the 4th argument of the sforce.apex.execute function.

This function (success or fail) will be called after the apex execution is finished (and returns its results). This will give you the behavior you are looking for instead of the refreshList() function call being executed as the next step immediately following the apex execute call.

function get(name) { 
    if (name = (new RegExp('[?&]' + encodeURIComponent(name) + 
        '=([^&]*)')).exec(location.search)) { 
        return decodeURIComponent(name[1]); 
    } 
}

{!REQUIRESCRIPT("/soap/ajax/10.0/connection.js")} 
{!REQUIRESCRIPT("/soap/ajax/10.0/apex.js")} 

var refreshList = function(result) {
    ListViewport.instances[get('fcf')].refreshList();
};

var handleError = function(error) {
    // handle any errors
    alert(error);
};

// use callback functions to be called when the apex is finished executing
sforce.apex.execute("ClassName"
                    , "functionName"
                    , { paramId0 : paramIds[0], paramId1 : paramIds[1] }
                    , { onSuccess: refreshList, onFailure: handleError });
share|improve this answer

I've asked a similar question previously around how to invoke an Apex method from a custom button without opening yourself up to cross site request forgery issues - See CSRF safe Custom button linked to Apex method.

The options put forward to solve this by Salesforce support were to:

  1. Create an intermediate form in a Visualforce page that triggers the sensitive Apex Code. Hence picking up the built in CSRF protection. Andrews answer covers this in good detail, including the need for an intermediate button that picks up the postback CSRF protection.
  2. Override the Opportunity Detail page (using apex:Details to display similar information). This new Visualforce page would include a similar form post back to option 1 to invoke the sensitive APEX code and get automatic CSRF protection.

Another interesting alternative was to embed/inline a Visualforce page within the standard page layout. This gives you a commandButton within the standard page layout that can invoke apex code without the risk of CSRF.

Finally, I created an idea for Invoking Apex code from the standard Entity Details pages with CSRF protection. It hasn't proved very popular to date so it may be most people can get by with one of the there methods above or JavaScript.

share|improve this answer

I have implemented my logic used webservice that returned me a string, that i can easily check in the custom button code:

Webservice:

global without sharing class MyWebservice {

    WebService static String myFunction(String param) {

        if (String.isNotBlank(param) {

            try {
                // Some logic here

                return 'OK';    
            }
            catch (Exception e) {
                return 'ERROR';
            }
        }
        else {
            return 'ERROR';
        }
    }
}

And my custom button:

{!REQUIRESCRIPT("/soap/ajax/20.0/connection.js")} 
{!REQUIRESCRIPT("/soap/ajax/10.0/apex.js")} 

var callout = "" + sforce.apex.execute(
    NamespacePrefix + "MyWebservice",
    "myFunction",
    {param:"{!MyObject__c.Id}"}); 

if (callout == 'OK') { 
    alert("Webservice call was ok."); 
    window.location.reload(); 
} 
else { 
    alert("Webservice call failed!"); 
}
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.