Find your content:

Search form

You are here

Javascript Remoting (Return cached result without re-query)

 
Share

I started writing some code using apex functions and then I thought let's try it out with Javascript remoting.

Remoting method was called from my VF page and all worked fine, until I tried to use local variable inside the remoting method. Here is my class:

global class MyClass
{
    public static List <Account> accounts {get; set;}

    public void init()
    {
        accounts = [SELECT Id, Name FROM Account]; 
    }

    @RemoteAction
    global static Account findAccount(String accountName)
    {
        for(Account acc : accounts)
        {
             if(acc.Name == accountName)
                 return acc;
        }

        return new Account();
    }
}

This is the code in my VF page:

var accountName = 'Test';

Visualforce.remoting.Manager.invokeAction(
    '{!$RemoteAction.MyClass.findAccount}',
    accountName,
    function(result, event)
    {
        if (event.status) 
        {
            console.log(result);
        }
        else if (event.type === 'exception')
        {
            alert('Error');
        }
        else
        {
            alert('Error');
        }
    }, 
    {escape: true}
);

When I debug the "accounts" in the remoting method, the list is empty. Is there anything that I'm missing here?

I have tried making the list static too and accessing it by referencing the class name but still no luck.

Please note that this is a simple example, the actual code is a bit different and the init method is called from the VF page through the action parameter of the page tag.

Appreciate your help!


Attribution to: Boris Bachovski

Possible Suggestion/Solution #1

A couple things stand out to me here.

1) Querying for all accounts and storing them in a global variable like that, you will hit a governor limit eventually.

2) I'm not familiar with that method of calling a remote action. I have always seen it in the form: ClassName.methodName([parameters], function(result, event))

Having said that, this should work for you:

Apex:

public with sharing class MyClass {

    @RemoteAction
    public static Account findAccount(String accountName) {
        return [SELECT Id, Name 
                  FROM Account 
                 WHERE Name = :accountName 
                 LIMIT 1];
    }

}

VF:

...
<script>
  var REMOTING_EXCEPTION = 'exception';

  function findAccount(accountName) {
    MyClass.findAccount(accountName, function(result, event) {
      if(event.type === REMOTING_EXCEPTION) {
        alert('something went wrong see console for more');
        console.log(event.message);
      } else if(result) {
        alert('successfully retrieved.  check console for more.');
        console.log(result);
      } else {
        alert('could not find specified account');
      }
    });
  }

  findAccount('Test');
</script>
...

Attribution to: Phil Rymek

Possible Suggestion/Solution #2

There are a couple of things.

  1. In Apex, init() methods are not called by default. Any code you want to run during the instantiation of an Object must go in the constructor, see Using Constructors. Since JavaScript Remoting requires your methods to be Static, setting Instance Variables will not do you any good.
  2. Your static method, "findAccounts" is trying to reference an instance variable, "accounts". I got a compile error when initially trying to save your class. If you want to reference the "accounts" variable from within another static method, make sure to define this as a static list.
  3. Since init() is never getting called, the list of accounts will be null. If you call this init() method within the findAccounts method, then the data will be populated. Note, that you must change init() to be a static method then, as well. As referenced above, it's generally not a best practice to query all objects into memory, as you will soon hit governor limits for the total number of records retrieved by SOQL queries of 50k query rows.

If you put all that together, this should get you at least a working example.

global class MyClass
{
    public static List <Account> accounts = new List <Account>();

    public static void init()
    {
        accounts = [SELECT Id, Name FROM Account]; 
    }

    @RemoteAction
    global static Account findAccount(String accountName)
    {
        init();
        for(Account acc : accounts)
        {
             if(acc.Name == accountName)
                 return acc;
        }

        return new Account();
    }
}

Note, that this will infact requery the database everytime findAccounts is called. A design pattern I like to use, and have seen a lot looks something like the following:

public static List <Account> accounts;
public static void setAccounts()
{
    if (accounts == null){
        accounts = new List<Account>();
        accounts = [SELECT Id, Name FROM Account]; 
    }
}

This will ensure that the query is only run once within the lifespan of the request.

You also may want to utilize a Map<String, Account> accountNameToAccountMap see Apex Maps, so that you can avoid iterating through the list every single time. But, I'll leave that as an exercise.


Attribution to: Mikey
This content is remixed from stackoverflow or stackexchange. Please visit https://salesforce.stackexchange.com/questions/4878

My Block Status

My Block Content