Lightning component client not sending data to server side controller - Answers - Salesforce Trailblazer Community
Trailblazer Community
Ask Search:
Eric OlsonEric Olson 

Lightning component client not sending data to server side controller

Good morning. I need help with this one. I replacing a Visual Force page with a lightning component to allow the User to update a list of Opportunities and certain pieces of data without opening each Opp to do so. The table is customized because lightning datatables do not support picklists or reference lookups for the moment so I had to do things this way. 

The issue is that once the User has edited the data, save is clicked and the server side just sees a list of sObjectTypes with their respective ID's and no other data. Alert statements and console logs all show that the data is there right before going to the server, but once it gets to the server, the data is gone. I am going to show all of my code so it's going to be a long post. 

Any ideas are welcome including redoing the entire project. However, I MUST have picklists and refreence fields in this component as required by our business partners using this component.

PARENT COMPONENT:
<aura:component implements="forceCommunity:availableForAllPageTypes,flexipage:availableForRecordHome,flexipage:availableForAllPageTypes,force:hasRecordId,force:hasSObjectName,force:appHostable" 
                access="global" 
                controller="ctrlGetOppsList">
    
    <aura:attribute name="opps" type="List"/>
    <aura:attribute name="columns" type="List"/>
    <aura:attribute name="dataCache" type="List"/>
    <aura:attribute name="errors" type="Object" default="[]"/>
    <aura:attribute name="draftValues" type="Object" default="[]"/>
    <aura:attribute name="isLoading" type="Boolean" default="false"/>
    <aura:attribute name="editing" type="Boolean" default="false"/>

    <aura:handler name="init" value="{!this}" action="{!c.doinit}"/>
    <aura:method name="reInit" action="{!c.doInit}" description="Runs the init method again" />
    <aura:handler event="force:refreshView" action="{!c.isRefreshed}" />

    <lightning:card title="My Opportunities">   

            <div class="slds-clearfix">
            <div class="slds-list_horizontal slds-float_right">
                <lightning:select aura:Id="oppFilter" label="Opportunity Filter" onchange="{!c.updateListWithFilters}">
                    <option value="mine">My Opportunities</option>
                    <option value="SE">My SE Opportunities</option>
                    <option value="STS">My STS Opportunities</option>
                </lightning:select>
                &nbsp;&nbsp;&nbsp;
                <lightning:select aura:Id="dateFilter" label="Date Range" onchange="{!c.updateListWithFilters}">
                    <option value="All">All Open</option>
                    <option value="-">Closing in past</option>
                    <option value="-120">Last 120 days</option>
                    <option value="Quarter1">Current quarter</option>
                    <option value="Quarter2">Prior quarter</option>
                    <option value="+30">Next 30 days</option>
                    <option value="+60">Next 60 days</option>
                    <option value="+120">Next 120 days</option>
                </lightning:select>
                &nbsp;&nbsp;&nbsp;
                <lightning:select aura:Id="probabilityFilter" label="Selected Probability" onchange="{!c.updateListWithFilters}">
                    <option value="Allprob">All Probability</option>
                    <option value="probUnder50">Probability less than 50%</option>
                    <option value="prob50AndUp">Probability greater than 50%</option>
                    <option value="prob75AndUp">Probability greater than 75%</option>
                    <option value="prob90AndUp">Probability greater than 90%</option>
                </lightning:select>
            </div>
        </div>

        <br />
        
        <div class="slds-table_edit_container slds-is-relative">
            <aura:if isTrue="{!v.isLoading}">
                <lightning:spinner alternativeText="Loading" />
            </aura:if>
            <table aria-multiselectable="true" class="cTable slds-table slds-no-cell-focus slds-table_bordered slds-table_edit slds-table_fixed-layout slds-table_resizable-cols" role="grid">
                <thead>
                    <tr class="slds-line-height_reset">
                        <aura:iteration items="{!v.cols}" var="col">
                            <th class="{!col.thClassName}" scope="col">
                                <span title="{!col.fieldName}" class="{!col.sortable ? 'slds-truncate slds-p-horizontal_x-small' : 'slds-hide'}">{!col.label}</span>
                            </th>
                        </aura:iteration>
                    </tr>
                </thead>
                <tbody>
                    <aura:iteration items="{!v.opps}" var="obj" indexVar="rowIndex">
                        <tr aria-selected="false" class="slds-hint-parent">
                            <aura:iteration items="{!obj.fields}" var="field" indexVar="fieldIndex">
                                <td scope="row">
                                    <span class="slds-grid slds-grid_align-spread">
                                        <aura:if isTrue="{!field.mode == 'view'}">
                                            
                                            <aura:if isTrue="{!field.type == 'url'}">
                                                <a href="{!field.value}" class="slds-truncate" target="{!field.target}">{!field.label}</a>
                                            </aura:if>
    
                                            <aura:if isTrue="{!field.type == 'date'}">
                                                <lightning:input id="{!rowIndex + '-' + fieldIndex}" type="date" class="slds-truncate ctInput" value="{!field.value}" onchange="{!c.startEdit}" />
                                            </aura:if>
    
                                            <aura:if isTrue="{!field.editable == false}">
                                                <span class="slds-truncate">{!field.value}</span>
                                            </aura:if>
    
                                            <aura:if isTrue="{!field.type == 'picklist'}">
                                                <lightning:select aura:id="{!rowIndex + '-' + fieldIndex}" value="{!field.value}" class="slds-truncate ctInput" onchange="{!c.startEdit}"> 
                                                    <aura:iteration items="{!field.selectOptions}" var="pl">
                                                        <option value="{!pl}">{!pl}</option>
                                                    </aura:iteration>
                                                </lightning:select>
                                            </aura:if>
    
                                            <aura:if isTrue="{!field.type == 'reference'}">
                                                <span class="ctInput">
                                                    <c:NewCustomLookup id="{!rowIndex + '-' + fieldIndex}" objectAPIName="User" IconName="standard:User" selectedRecord="{!field.value}" SearchKeyWord="{!field.value}" editing="{!v.editing}" />
                                                </span>
                                            </aura:if>
    
                                            <aura:if isTrue="{!field.type == 'textarea'}">
                                                <lightning:input id="{!rowIndex + '-' + fieldIndex}" class="ctInput" type="{!field.type}" value="{!field.value}" onchange="{!c.startEdit}" />
                                            </aura:if>
                                        </aura:if>                              
                                    </span>
                                </td>
                            </aura:iteration>
                        </tr>
                    </aura:iteration>
                </tbody>
            </table>
            <aura:if isTrue="{!v.editing}">
                <div class="ctFooter slds-modal__footer">
                    <div class="slds-grid slds-grid_align-center">
                        <lightning:button label="Save" onclick="{!c.saveChangedOpps}" />    
                        <lightning:button label="Cancel" onclick="{!c.cancelEdit}" />
                    </div>
                </div>
            </aura:if>
        </div>
    </lightning:card>

    <aura:if isTrue="{!v.isLoading}">
        <lightning:spinner alternativeText="Loading.." variant="brand"/>
    </aura:if>
</aura:component>

<!--<c:dataTable aura:id="datatableId" auraId="datatableId" data="{!v.opps}" columns="{!v.columns}" showRowNumberColumn="false"/>  
<c:newDataTable aura:id="tableForData" altAuraId="tblForData" objectData="{!v.opps}" tableCols="{!v.columns}" editing="{!v.editing}" /> -->
 
CONTROLLER:
({
	doinit : function(cmp, event, helper) {
		cmp.set("v.isLoading", true);
		helper.getOppsList(cmp);
	},
    saveChangedOpps: function (cmp, event, helper) {
		helper.updateOppList(cmp, event);
	},
	updateListWithFilters: function(cmp, event, helper) {
		// The User has selected something from one of the picklists. Update the list with the new filters.
		helper.getOppsList(cmp);
	},
	isRefreshed: function(cmp, event, helper) {
		location.reload();
	},
	cancelEdit : function(cmp, event, helper) {
		helper.cancelHelper(cmp, event);
	},
	startEdit : function(cmp, event, helper) {
		helper.editHelper(cmp, event);
	}
})
 
HELPER:
({
	getOppsList : function(component) {

        var userFields = {
            Inside_Sales_NEW__c: 'Inside_Sales_NEW__r',                                         // AE Fields (Client Ops Specialist)
            Sales_Engineer_NEW__c: 'Sales_Engineer_NEW__r',                                     // AE & SE Fields (Client Solutions Architect)
            GSD_CyberOps__c: 'GSD_CyberOps__r',                                                 // STS Fields
            GSD_Threat__c: 'GSD_Threat__c',                                                     // STS Fields
            GSD_IandI__c: 'GSD_IandI__r',                                                       // SE Fields
            Primary_Strategic_Services_Director__c: 'Primary_Strategic_Services_Director__r',   // STS Fields (Executive Advisor)
            Enterprise_Architect__c: 'Enterprise_Architect__r'                                  // SE Fields (Service Contracts)
        }

        // Reset the data for the data table
        component.set('v.opps', null);
        component.set("v.columns", null);

		var action = component.get("c.getOppsList");

        // Send the Opportunity Filter, Date Range, and Selected Probablity selections to the server as parameters
		action.setParams({"oppFilter" : component.find("oppFilter").get("v.value"),
			  		      "dateRange" : component.find("dateFilter").get("v.value"),
                          "probFilter" : component.find("probabilityFilter").get("v.value") });
        
        action.setCallback(this, function(response) {

            var state = response.getState();
		    if ( state === "SUCCESS" ) {
                // Set up the Name column as a Hyperlink column Set the column fieldName to a variable 
                // to be set up when looping through the returned opps. Set the Label for the link BEFORE
                // setting the variable on the fieldName.
                var cols = response.getReturnValue().lstDataTableColumns;
                cols.forEach(function (column) {
                    switch (column.fieldName) {
                        case 'Name':
                            column.type = 'url';
                            //column['typeAttributes'] = { label: { fieldName: column.fieldName },
                            //                             target: '_self'};
                            column.fieldName = 'linkName';                                                                                     
                            break;
                        default:
                            break;
                    }
                });

                // There are certain fields we need to get the associated name from the lookup.
                var opps = response.getReturnValue().lstDataTableData;
               
                if ( opps.length > 0 ) {
                    for ( var i = 0; i < opps.length; i++ ) {
                        
                        // Set the individual opp's hyperlink and get the names for the specific columns
                        var opp = opps[i];
                        opp.linkName = '/lightning/r/Opportunity/' + opp.Id + '/view';
                        for ( var key in userFields ) {
                            if ( opp[key] ) {
                                opp[key] = opp[userFields[key]].Name;
                            }
                        }                      
                    }
                }
                // Now that we have set the data correctly, set the page's variables to show the user the correct data.
                // component.set("v.columns", response.getReturnValue().lstDataTableColumns); 
                // component.set('v.opps', response.getReturnValue().lstDataTableData);      
                // console.log("========== UPDATE OPP HELPER COLUMNS: " + cols.length);
                // console.log("========== UPDATE OPP HELPER OPPS: " + opps.length);
                
                // ========================================================================================
                // component.set("v.columns", cols); 
                // component.set('v.opps', opps);
                // ========================================================================================

                this.setUpColumns(component, cols);
                this.setUpData(component, opps);

                // var table = component.find("tableForData");
                // var table = component.find("datatableId");
                // this.reInit();
            }
            else if ( state === "ERROR" ) {
                var errors = response.getError();
                if ( errors ) {
                    if ( errors[0] && errors[0].message ) {
                        console.log("========== GET MY OPPS ERROR: " + errors[0].message);
                    }
                } 
                else {
                    console.log("========== GET MY OPPS UNKNOWN ERROR");
                }
            }
            else {
                console.log("========== GET MY OPPS: Something went wrong.");
            }
		});
		component.set("v.isLoading", false);
        $A.enqueueAction(action);
    },
    setUpColumns : function (component, cols) {
		var tempCols = [];
		cols.forEach(function(col) {
			col.thClassName = "slds-truncate";
			col.thClassName += col.sortable === true ? " slds-is-sortable" : "";
			//col.sortable = false;
            tempCols.push(col);
		});
		component.set("v.columns", tempCols);		
	},
	setUpData : function(component, rows) {
		var tempData = [];
		rows.forEach(function(obj) {
			var cols = component.get("v.columns");
			var objRow = {};
			var objFields = [];
			cols.forEach(function(col) {
				var field = {};
				field.name = col.fieldName;
				field.value = obj[col.fieldName];
				field.mode = "view";
				field.editable = col.editable;
				field.type = col.type;
				field.sortable = false;
				if (col.type === 'currency' || col.type === 'percent') {
					field.editable = false;
				}
				if(col.type === 'picklist'){
					field.selectOptions = col.selectOptions;
				}    
				if(col.type === 'url'){
					field.label = obj.Name;
					field.target = '_self';
				}
				objFields.push(field);
			});	
			objRow.Id = obj.Id;
			objRow.fields = objFields;
			if ( tempData == null ) {
				tempData = [];
			}
			tempData.push(objRow);
		});
		component.set("v.opps", tempData);
    },
    editHelper : function(component, event) {
		// Start by setting the current list on the table into a cache to recall if the User cancels
		var dataSet = component.get("v.objectData");
		component.set("v.dataCache", dataSet);

		// Now show the save and cancel buttons
		component.set("v.editing", true);
	},
    updateOppList: function(cmp, event) {
        // Pull the changed opps from the draft values on the datatable
		var changedOpps = cmp.get('v.opps');

        alert('OPPS: ' + changedOpps);
        console.log(changedOpps);
		// Set up the call to the server to send the opps to and get the response back.
		var action = cmp.get("c.updateOppsList");
		action.setParams({ "myOpps" : changedOpps });
		action.setCallback(this, function(response) {
            alert('getting response');
			var state = response.getState();
			if (state === "SUCCESS") {
                alert('success');
				$A.get('e.force:refreshView').fire();
				// Set the returned updated Opportunities back into the datatable.
				cmp.set('v.opps', response.getReturnValue());				
			}
			else if (state === "ERROR") {
				var errors = response.getError();
				if (errors) {
					if (errors[0] && errors[0].message) {
						alert("Error message: " + errors[0].message);
					}
				}
				else {
                    alert('========== ERROR: ' + response.getReturnValue());
					console.log('========== ERROR: ' + response.getReturnValue());
				}
			}
			else {
                alert('========== Something went wrong.');
				console.log('========== Something went wrong.');
			}				
		});
		$A.enqueueAction(action);
    },
    cancelHelper : function(component, event) {
		// Reset the table
		component.set("v.isLoading", true);
		var data = component.get("v.dataCache");
		component.set("v.opps", data);
		component.set("v.editing", false);
		$A.get('e.force:refreshView').fire();
		component.set("v.isLoading", false);
	}
})
 
SERVER SIDE CONTROLLER:
public with sharing class ctrlGetOppsList {

	@AuraEnabled
    public static List<Opportunity> updateOppsList(List<Opportunity> myOpps) {
        System.debug('========== MADE IT TO THE SERVER');
        System.debug(myOpps);
        for ( Opportunity temp : myOpps) {
            System.debug(temp);
            if ( temp.Current_Quarter_Forecast_Category__c == null ) {
                System.debug('========== NULL');
            }
            else {
                System.debug('========== DATA: ' + temp.Current_Quarter_Forecast_Category__c);
            }
        }
        update myOpps;
        System.debug(myOpps);
        return myOpps;
    }
 }
Sergii GrushaiSergii Grushai
Eric,

The problem is that on the user side the Object which is transferred to the server side is not created correctly.
Your object looks like this: [{Id : 'bsbsbsbs', [list fields]}]. As such, only the ID field is available on the server side. Others are not initialized
The approximate structure of the object should look something like this:
without any arrays in the object.
Since I'm not sure what your object looks like before being sent to the server side ... You need to turn it into the correct form as in the previous screenshot
You will then be able to access the values via the API field nameUser-added image
Eric OlsonEric Olson

Thank you for your response. I really do appreciate this. I would like to ask a follow up question though: Do you have any suggestions on where to look to understand how to do that on the client side? Or maybe have a small code snippet that would get me started on modifying the client side to transform the list of objects to something the server will understand?

Thank you again. 

Sergii GrushaiSergii Grushai
Eric, 
I understand you have exposed only parts of the code ...
I don't know what your object looks like on the client side ... However, maybe this simple example will help you.
 
 You may need to add an additional method before sending data to the server side
 objFromFile = Your List of object.
 prepareObjectForSendToApexHelper : function (component, objFromFile) {
        //main Map
        var mapToApex = {};
        //get element from List
        for (var i in objFromFile){
                //create new Arr
                var newArr = [];
                //create new object
                var location = {}
                
                //Create object 
                location.Location_340B_ID__c = objFromFile[i].Location340BID;
                location.X340b_Status__c = objFromFile[i].Status340B;
                if (objFromFile[i].LocationName == ''){
                    location.Name = objFromFile[i].LocationAccountName;
                } else {
                    location.Name = objFromFile[i].LocationName;
                }
                location.Address_1__c = objFromFile[i].Address1;
                location.Address_2__c = objFromFile[i].Address2;
                location.City__c = objFromFile[i].City;
                location.State__c = objFromFile[i].State;
                location.Zip__c = objFromFile[i].Zip;
                location.Covered_Entity_Locatios__r = Account;
                //Add Object to arr
                newArr.push(location);
                //Filling map
                if (objFromFile[i].Parent340BID in mapToApex) {
                    mapToApex[objFromFile[i].Parent340BID].push(location);
                } else {
                    mapToApex[objFromFile[i].Parent340BID] = newArr;
                }
        }
        //send map to next step
        this.startHelper(component, mapToApex);
    },
    
    PS. If you want to pass an Object or a list of objects to the server side you must: 
    Create the same structure that you will see when you perform systems debugging the required object on the server side
    Try pasting the code below ...
    Controller:
    -----------
    doinit : function(cmp, event, helper) {
        cmp.set("v.isLoading", false);
        //helper.getOppsList(cmp);
        helper.testElement(cmp, event, helper);
    },
    ----------
    Hellper:
    testElement : function(cmp, event, helper) {
        var testObjectArr = [];
        for (var i = 0; i < 10; i++){
            var testObject = {};
            testObject.Id = '1234567' + i;
            testObject.Name = 'TestOppName' + i;
            //Other fields...
            testObjectArr.push(testObject);
        }
        console.log('xample of a list of Opportunities objects');
        console.log(testObjectArr);
        //then send to APEX
        var action = cmp.get("c.updateOppsList");
        action.setParams({ "myOpps" : testObjectArr });
        action.setCallback(this, function(response) {
            var state = response.getState();
            console.log(state);
            if (state === "SUCCESS") {
                console.log('view DEBUG on server side');                
            }
            else if (state === "ERROR") {
                var errors = response.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        alert("Error message: " + errors[0].message);
                    }
                }
                else {
                    alert('========== ERROR: ' + response.getReturnValue());
                    console.log('========== ERROR: ' + response.getReturnValue());
                }
            }
            else {
                alert('========== Something went wrong.');
                console.log('========== Something went wrong.');
            }                
        });
        $A.enqueueAction(action);
    }
    ----------
    APEX:
    @AuraEnabled
    public static List<Opportunity> updateOppsList(List<Opportunity> myOpps) {
        System.debug('========== MADE IT TO THE SERVER');
        System.debug(myOpps);
        for (Opportunity i : myOpps) {
            System.debug('record');
            System.debug(i);
            System.debug('record Id = ' + i.Id);
            System.debug('record Name = ' + i.Name);
            //...
        }
        
        return myOpps;
    }
    
    I hope this helps you understand