Find your content:

Search form

You are here

Calling Apex Class from Custom Button, And Then Refreshing List View

 
Share

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);

Attribution to: Shumon Saha

Possible Suggestion/Solution #1

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.


Attribution to: Daniel Ballinger

Possible Suggestion/Solution #2

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!"); 
}

Attribution to: Sergej Utko

Possible Suggestion/Solution #3

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!


Attribution to: Andrew Fawcett

Possible Suggestion/Solution #4

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 });

Attribution to: Mark Pond
This content is remixed from stackoverflow or stackexchange. Please visit https://salesforce.stackexchange.com/questions/5223

My Block Status

My Block Content