10

I am trying to implement a simple paginator that uses a wrapper object to move between pages, but I am not sure how to get the wrapper class to work in the component controller. Specifically my questions are:

  1. How to invoke the inner-class method void next() from the component controller, given it is not a getter?
  2. Can I call non-Aura methods from the component controller?
  3. Can I call non-static methods from the component controller?

The documentation clearly answers questions 2 & 3 with a "no," but I wanted to be sure just in case there were exceptions to the rule.

The following code is not compiling with this error:

AuraEnabled methods must be named with a prefix 'get'

public class PageController
{
    @AuraEnabled
    public static QueryCursor getAccounts()
    {
        return new QueryCursor();
    }

    public class QueryCursor
    {
        private Integer m_pageNumber;

        public QueryCursor()
        { 
            m_pageNumber = 1; 
        }

        @AuraEnabled
        public Account[] accounts
        { 
            get { return queryAccounts(m_pageNumber); } 
            private set; 
        }

        @AuraEnabled
        public void next() 
        { 
            m_pageNumber++; 
        }

        private Account[] queryAccounts(pageNumber)
        {
            // return Accounts with an offset
        }
    }
}

I understand why I'm getting the error, but I don't want to change the signature from void next() to Account[] getNext(). What's the best way to go about this?

  • 1
    You may want to change your title to reflect you question to get more traffic. As it is it sounds like someone asking a question that is easily answered by the docs and does not really reflect the actual issue you posed – Eric Jun 23 '17 at 0:49
  • The salesforce SOAP and REST APIs have good support for defining a query and retrieving specific pages from the results, but if your query has more than 2000 records and is beyond the limit of the SOQL OFFSET keyword and is beyond the 10,000 limit of the StandardSetController, there isn't really an APEX equivalent. – martin Jun 23 '17 at 2:39
15

Pagination isn't accomplished server-side in Lightning; but instead done by client-side code. See my gist for an example that loads up to 50,000 rows. If you need more than that, you simply need to load the data from the server in pieces, as demonstrated here; basically, just query X number of records, and if there's still more data to get, use the ID as a condition for the next query to get the next "page" of data. Lightning is fast enough that you should be sorting and paging client-side without resorting going back to the server all the time.

| improve this answer | |
  • It's awesome that Lightning opens up this possibly (paginating without a server call between pages). Out of curiosity have you done any tests on this pattern for the performance difference for a scenario like "Find a record on the second page of results"? Paginating obviously becomes way faster with this model but I am wondering what the hit to initial load time is to pull all 50k records down. – dsharrison Jun 23 '17 at 2:52
  • 3
    @dsharrison There's a cost performance for having to hit the server. 4k records took about ~1/3 second, 16k took about ~1/2 second, and ~64k records took about 1 second. The more data you send back at once, the better compression you'll get, and ultimately better performance. But even at 4k records, the users will hardly notice the load time. – sfdcfox Jun 23 '17 at 3:09
  • Great findings. Yeah traditional server side paginating has always been super slow for me on Salesforce. Good to see this approach has such an improvement in performance. – dsharrison Jun 23 '17 at 3:35
  • @sfdcfox So are you saying that server-side controllers should not be used to maintain state in Lightning? In other words if I see myself going down this path I should stop and instead tweak my design to let the client-side controller hold the state? Essentially only use Apex to perform CRUD and aggregate the results in a presentable manner. In the long run simple CRUD is soon going to be replaced by Lightning Data Service. Am I right? – Mossi2 Jun 26 '17 at 18:04
  • @user45041 That's precisely correct. Lightning makes no attempt to arrange for state to be passed from client to server (which is why all methods are static, while in Visualforce, they typically are not). I expect that, at some point, Apex Code will become mostly/completely obsolete as more stuff moves to the client-side, which is the point of having a SPA (Single Page Application) like Lightning is able to provide. The less work on the servers, the more cool features we can have. – sfdcfox Jun 26 '17 at 18:17
3

The Dreamhouse App has some code for server-side Pagination. The initial page load is faster than loading 1000+ records, but the first time you load the next / previous page it is slower. With action.setStorable(), subsequent visits to the pages are much faster. Like anything, there are trade-offs. For the full implementation, install the Dreamhouse app in a Developer org.

Here's my modified version of the Apex Class and an @AuraEnabled method that gets records using the PagedResult class.

public with sharing class PagedResult {
    @AuraEnabled
    public Integer pageSize { get; set; }

    @AuraEnabled
    public Integer page { get; set; }

    @AuraEnabled
    public Integer total { get; set; }

    @AuraEnabled
    public List<Object> results { get; set; }
}

then in your Apex Controller for your Lightning component you'd have

@AuraEnabled
public static PagedResult getPagedRecords(String sObjectName, String fieldNames, Decimal pageSize, Decimal pageNumber) {
    Integer pSize = (Integer)pageSize;
    Integer offset = ((Integer)pageNumber - 1) * pSize;
    PagedResult pr =  new PagedResult();
    String totalQuery = 'SELECT Id FROM ' + sObjectName;
    pr.pageSize = pSize;
    pr.page = (Integer) pageNumber;
    String query = 'SELECT ' + fieldNames + ' FROM ' + sObjectName;
    pr.total = Database.query(totalQuery).size();
    pr.results = Database.query(query + ' LIMIT :pSize OFFSET :offset');
    return pr;
}
| improve this answer | |
-2

I would say consider looking at the standard set controller, it has build in methods for pagination so you could have aura exposed methods that call the corresponding method on the standard set controller. And probably have the std set controller as a private property

| improve this answer | |
  • 1
    This would be a reasonable solution if the asker was developing in Visualforce and Apex, but this question is specific to Lightning Component development which is stateless with Apex, so StandardSetController isn't as applicable. – Doug Ayers Jun 23 '17 at 4:46
  • That isn't to say that the @AuraEnabled method doesn't simply execute the core SOQL query and accept a page and pageSize parameters to then specify on the StandardSetController to fast forward/backward to specific pages. – Doug Ayers Jun 23 '17 at 4:47
  • @DougAyers -- I had the same thought. I'm using the StandardSetController's getListViewOptions method to grab the object listviews that the logged-in user has access to. The AuraEnabled method works fine when I run it from anonymous Apex, but running it from the Lightning component throws an ever-so-vague "Internal Salesforce.com" error. I posted the problem here. I love it if you could take a look. salesforce.stackexchange.com/q/224558/4180 salesforce.stackexchange.com/q/224558/4180 – Shane Steinfeld Jul 11 '18 at 2:22
  • @ShaneSteinfeld Please see my answer at salesforce.stackexchange.com/a/224560/987 – Doug Ayers Jul 11 '18 at 3:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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