1

In very few words, I am hitting some sort of limit with a visualforce remote action, as I get the following error:

Visualforce Remoting Exception: Input too long

I reached out to Salesforce support, who told me to use an apex:actionFunction instead and see if that works out.

Things is, I'm having trouble understanding how to use this.

Here's the original code:

VF Page:

<apex:page controller="UNITA_EinsteinSecondPage" action="{!init}" showheader="false" sidebar="false" standardStylesheets="false" title="Creazione Case" >

<apex:stylesheet value="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"/>
<apex:stylesheet value="{!$Resource.cssStyle}"/>
    
                <script>
                    function newDoc() {
                      window.location = "/lightning/o/Case/list?filterName=" + 'All'
        }
    </script>

    <apex:includeScript value="https://code.jquery.com/jquery-3.1.0.min.js"/>
    <style>
        th {
            width: 50%;
        }
        h4 {
            font-size: 24px;
        }
        table {
            font-size: 20px;
            width: 100%;
        }
    </style>
    
    <div class="container-fluid"> 
        <apex:image styleClass="logoImg" id="theImage" value="{!$Resource.UNHCRimg}" width="200" height="200" />
        <h4 class="textCenter" id="message">Seleziono tutti i contatti...</h4>
    </div>
    
    <div class="hidden" id="myDIV">
          <input class="button3 btnMargin" id="thePage:theForm:theButton" type="submit" name="thePage:theForm:theButton" value="Vai ai case" onclick="newDoc()" />
    </div>
    
    <div class="container-fluid hidden">
        <table name="results" id="results" data-role="table" class="table-bordered table-striped table-responsive">
            <thead>
            <tr>
            <th>Contact</th>
            <th>Case</th>
            </tr>
            </thead>
            <tbody></tbody>
        </table>
    </div>
    
    <script>
        $(function() {
            $.ajaxSetup({
                headers: {"Authorization": 'Bearer {!$Api.Session_ID}'}
            });
            
            setTimeout(executeQuery, 30000); // was 1000
        });

/* Executes the SAQL query and displays the resulting contacts. Note: Contact.Name and ContactId referenced below refer to the dataset field names. Update them to match your dataset fields. */                
        function executeQuery() {
            var query = {};
            var campaignId = '{!idCampaign}';
            var caseType = '{!caseType}';
            var caseSubType = '{!caseSubType}';
            query.statements = "{!JSENCODE(query)}";
            var queryObj = {query: query.statements};

            $.ajax({
                type: 'POST',
                url: '/services/data/v39.0/wave/query',
                data: JSON.stringify(queryObj),
                contentType: 'application/json',
                success: function(data) {
                    $('#message').html('Procedo alla creazione dei Case e dei Campaign Members...');
                    var record = null;
                    var row = null;
                    $('#results tbody').empty();
                    
                    for (var i = 0; i < data.results.records.length; i++) {
                        record = data.results.records[i];
                        row = $('<tr>');
                        row.append($('<td>').html(record.Id));
                        row.append($('<td class="' + record.Id + '">').html(record.currentCaseId+'# Creating...'));
                        $('#results tbody').append(row);
                    }
                    
                    setTimeout(function() {createCases(data.results.records, campaignId, caseType, caseSubType);}, 1000);
                },
            });
        }

/* Calls the Apex controller method that creates Cases for each contact and returns the Case name for each contact to the HTML table. */
        function createCases(contactRecords, campaignId, caseType,caseSubType) {
            UNITA_EinsteinSecondPage.create(
            contactRecords, 
            campaignId,
            caseType,
            caseSubType,
            function(result, event) {
                if (event.status) {
                    for (var i = 0; i < contactRecords.length; i++) {
                        $('td.' + contactRecords[i].Id).html(result[contactRecords[i].Id]);
                    }
                    $('#message').html('Sono stati creati ' + contactRecords.length + ' Case e ' + contactRecords.length + ' Campaign Members.');
                    $('#myDIV').removeClass("hidden");
                }
                else {
                    $('#message').html('Error: ' + event.message);
                }
            });
        }
    </script>
</apex:page>

Apex controller:

public with sharing class UNITA_EinsteinSecondPage {
    public String idCampaign {get;set;}
    public String caseType {get;set;}
    public String caseSubType {get;set;}
    public String query {get;set;}
    public String strRecordId { get; set;}
    
    public UNITA_EinsteinSecondPage() {
        idCampaign = ApexPages.currentPage().getParameters().get('sel');
        caseType = ApexPages.currentPage().getParameters().get('type');
        query = ApexPages.currentPage().getParameters().get('query');
        strRecordId = ApexPages.currentPage().getParameters().get('id');
        caseSubType = ApexPages.currentPage().getParameters().get('subType');
        System.debug('### QUERY PAG 2 ' + query);
    }
    
    public PageReference init() {
        query = ApexPages.currentPage().getParameters().get('query');
        return null;
    }

    /* Takes the contact records from the SAQL query, creates a case for each contact, and then returns a map between contact ID and new case name.  Note: Contact.Name and ContactId referenced below refer to the dataset field names. Update them to match your dataset fields. */
    @RemoteAction 
    public static Map<String, String> create(List <Map<String, String>> contactRecords, String campaignId, String caseType, String caseSubType) {
        Map<String, String> result = new Map<String, String>();  
        List<Case> cases = new List<Case>();
        List<CampaignMember> cmList = new List<CampaignMember>();
        for (Map<String, String> contactRecord : contactRecords) {
            String contactId = contactRecord.get('Id ');
            if(campaignId != null && contactId != null){
                CampaignMember currentCamp = new CampaignMember(ContactId=Id.valueOf(contactId), CampaignId=campaignId);
                //Case currentCase = new Case(Status='In Progress', unig__Channel__c='TELEMARKETING', Origin='Web',unig__Campaign__c=campaignId, ContactId=Id.valueOf(contactId), UNITA_Case_Type__c =caseType, UNITA_Case_Subtype__c = caseSubType, UNITA_From_Einstein__c=true);
                Case currentCase = new Case(Status='In Progress', unig__Channel__c='TELEMARKETING', unig__Campaign__c=campaignId, ContactId=Id.valueOf(contactId), UNITA_Case_Type__c =caseType, UNITA_Case_Subtype__c = caseSubType, UNITA_From_Einstein__c=true);
                cases.add(currentCase);
                cmList.add(currentCamp);
                //System.debug('### currentCamp ' + currentCamp);
                
            }
        }

        Id batchInstanceId = Database.executeBatch(new InsertCases(cases),50);
        Id batchInstanceId1 = Database.executeBatch(new InsertCases(cmList),50);
            /*     
        try{
            //System.debug('### CAMPAING MEMBER LIST ' + cmList.size());
            //System.debug('### CASES LIST ' + cases.size());
            insert cmList;
            insert cases;
        }catch(Exception e){
            System.debug('### ERROR ' + e);
        }        
        */
        for (Case currentCase2 : cases) {
            String currentCaseContactId = currentCase2.contactId;
            String currentCaseId = currentCase2.Id;
            result.put(currentCaseContactId, currentCaseId);
        }
        return result;
    }

}

Now, I understand that I should put the following code in the vf page:

<apex:form>
        <apex:actionFunction name="createCases" action="{!create}"/>
</apex:form>

and delete the @remoteAction annotation in the controller. But if do this I get the following error:

Unknown method 'UNITA_EinsteinSecondPage.create()'

I'm totally new to Visualforce so I'm not really sure what I am doing here!

Any help would be much appreciated

4
  • Try removing static keyword from createMethod in apex. static is requirement of remote action which is stateless, when calling the method directly from Visualforce; the methods retain the state, hence such methods do not have to be static.
    – Raul
    Commented Jan 5, 2022 at 10:48
  • You also need apex:param to pass the parameters to apex method via apex:actionFunction
    – Raul
    Commented Jan 5, 2022 at 10:52
  • Yeah, got that. But apparently there's also another problem: lists can't be passed as apex:param, and I need to pass a list of maps in it. I'm afraid I can't achieve what I am trying to
    – robruf
    Commented Jan 5, 2022 at 11:08
  • Not exactly sure on what you are trying to do, one option is: you can convert the list or map to a string, send to apex when you can deserialize it back.
    – Raul
    Commented Jan 5, 2022 at 11:15

2 Answers 2

1

To convert the code, you need to move all the parameters to transient variables, make the method non-static, and remove the return type. You also need to serialize complex objects. Your Apex should look like:

public transient String results { get; set; }
public transient String errorMessage { get; set; }
public transient String contactRecordsJson { get; set; }
public transient String campaignId { get; set; }
public transient String caseType { get; set; }
public transient String caseSubType { get; set; }
public void create() {
    List<Map<String, String>> contactRecords = (List<Map<String, String>>)
        JSON.deserialize(contactRecordsJson, List<Map<String, String>>.class);
    // logic here
    // At the end, serialize to JSON
    results = JSON.serialize(result);
}

In your Visualforce code, you then assign all the properties with apex:param.

<apex:actionFunction name="createCasesRemote" action="{!create}" reRender="form" oncomplete="processResults()">
    <apex:param name="param1" assignTo="{!contactRecordsJson}" value="" />
    <apex:param name="param2" assignTo="{!campaignId}" value="" />
    <apex:param name="param3" assignTo="{!caseType}" value="" />
    <apex:param name="param4" assignTo="{!caseSubType}" value="" />
</apex:actionFunction>

To call this method, you do need to encode your data, so the remainder of the code looks like:

<apex:form id="form">
    <script>
        let results = JSON.parse("{!JSENCODE(results)}");
        let error = "{!JSENCODE(errorMessage)}";
        function processResults() {
            if(error) {
                $('#message').html('Error: ' + error);
                return
            }
            // use results to read results
            for (var i = 0; i < contactRecords.length; i++) {
                $('td.' + contactRecords[i].Id).html(result[contactRecords[i].Id]);
            }
            $('#message').html('Sono stati creati ' + contactRecords.length + ' Case e ' + contactRecords.length + ' Campaign Members.');
            $('#myDIV').removeClass("hidden");
        }
        function createCases(contactRecords, campaignId, caseType,caseSubType) {
            createCasesRemote(JSON.stringify(contactRecords), campaignId, caseType, caseSubType);
        }
    </script>
</apex:form>

There may be a few minor problems with this code, but it should address most of your primary concerns.

As Support theorizes, this method should allow a maximum payload of 6 MB to the server, and 15 MB returned to the client. It will be slightly less because of serialization/deserialization, but it should give you sufficiently more capacity to complete the task.

5
  • Thanks a lot for that. I'm not sure about where to put the last piece of code; I'm a total newbie and this is some code I didn't write myself that I'm dealing with, so I'm a bit lost. Should that be put instead of the former createCases() function? Inside the same script tag where the executeQuery() function is? Moreover, I'm getting the Variable does not exist: contactRecordsJson error (same thing with all the other variables) when I try to push my controller class code to the org
    – robruf
    Commented Jan 5, 2022 at 13:58
  • @robruf You need to put all those variables in your Apex code. And yes, the creaseCases method replaces the previous version.
    – sfdcfox
    Commented Jan 5, 2022 at 14:05
  • My bad, the error was due to the create() method still being static. Code gets pushed correctly now; however it doesn't seem to work yet. This is what I get when I try to run the whole process: ibb.co/WkYvJwy
    – robruf
    Commented Jan 5, 2022 at 15:00
  • @robruf sounds like a null value; maybe the attribute is mapped wrong?
    – sfdcfox
    Commented Jan 5, 2022 at 15:29
  • Not sure tbh. Opening the developer console in the browser I see the error "Uncaught SyntaxError: Unexpected end of JSON input at JSON.parse (<anonymous>) at UNITA_EinsteinSecondPage:102" I think I misunderstood how to properly write the code you suggested. Here's what I got now: dpaste.com/8D2EC9QL8 I think there's some sort of merging to do between the executeQuery() and the brand new createCases() javascript functions, just not sure how to do it
    – robruf
    Commented Jan 5, 2022 at 16:09
-2

I am amazed that instead of Changing the method from remote action to apex method action, why don't you use lazy loading with VF remoting method. Just pass and fetch the certain amount of data and again call method to fetch and send some more if needed.i have achieved it for one of the project development.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .