[SalesForce] Creating Generic Related List Lightning Component

I am trying to create a simple, reusable component that allows me to list related records in an easy to read way. I want to be able to use this component a few times on a single record, and I would like it to be able to be used for different target objects.

My basic thought is to pull the current record, define target parameters, do a server query, and then return the results through aura:iteration. I need to be able to create the server side controller in such a way that it can query for any object and return them the same way.

I have the code built, but right now I am getting a really generic Uncaught Error in $A.getCallback() [result is not defined] message. I've been googling what that could mean in my case and it seems to always be a typo or inconsistent variable names. If that is my problem, I am unable to find the issue.

I do wonder if the problem is the way I return the information from the server. Could the problem be the wrapper class being put into a list? That doesn't seem right….

Also, somewhat related. As you can tell from the name of the component, I had planned on iterating over lightning:tile because I thought that would look nice. But I can't seem to find an example of how that would be accomplished. Does anyone have any examples of iterating over lightning:tile?

Here is the code I have:


relRecordTiles.cmp

<aura:component controller="relRecordTilesController" access="global">
<!--<aura:attribute name="resultItems" type="Object" access="public"
                description="This attribute can be used by parent component to read selected records"/>   -->

<aura:attribute name="currentID" type="String" access="public" 
                description="ID of current record (which is the parent of the return values)"/>
<aura:attribute name="objectName" type="String" access="public" 
                description="Name of Object to be searched"/>
<aura:attribute name="lookupField" type="String" access="public" 
                description="Name of field that connects the child object to the parent (current record)"/>    

<aura:attribute name="field1" type="String" access="public" 
                description="API Name of the first field, to be used to show text"/>
<aura:attribute name="field1Label" type="String" access="public" 
                description="Label for the value returned"/>
<aura:attribute name="field2" type="String" access="public" 
                description="API Name of the second field, to be used to show text"/>
<aura:attribute name="field2Label" type="String" access="public" 
                description="Label for the value returned"/>
<aura:attribute name="field3" type="String" access="public" 
                description="API Name of the third field, to be used to show text"/>
<aura:attribute name="field3Label" type="String" access="public" 
                description="Label for the value returned"/>

<aura:attribute Name="serverResult" type="list" /> 

<aura:handler name="init" value="{!this}" action="{!c.doInit}" />    

<br/>
relRecordTiles should go here:
<div class="salesforce slds">
    <ul class="slds-has-dividers--around-space">
        <aura:iteration items="{!v.serverResult}" var="item">
            <li class="slds-item">
                <div class="slds-tile slds-tile--board">
                    <h3 class="slds-truncate"><a href="{! '# ' + objectName + '/' + item.Id}"> {!objectName}: {!item.Name}</a>  </h3>
                    <div class="slds-tile__detail slds-text-body--small">
                        <aura:if isTrue="{!not(empty(v.field1))}">
                            <p>{!field1Label}: {!item.field1}</p> <br/>
                        </aura:if>
                        <aura:if isTrue="{!not(empty(v.field2))}">
                            <p>{!field2Label}: {!item.field2}</p> <br/>
                        </aura:if>
                        <aura:if isTrue="{!not(empty(v.field3))}">
                            <p>{!field3Label}: {!item.field3}</p> <br/>
                        </aura:if>
                    </div>
                </div>
            </li>
        </aura:iteration>
    </ul>
</div>


relRecordTiles.js

({
doInit : function(component, event, helper) {
    console.log("relRecordTiles is starting up...");

    //Pull values from component 
    //var currentID = component.get("v.currentID");
    //var objectName = component.get("v.objectName");
    //var lookupField = component.get("v.lookupField");
    //var field1 = component.get("v.field1");
    //var field2 = component.get("v.field2");
    //var field3 = component.get("v.field3");

    //call relRecordTilesController.getResults server side controller
    var action = component.get('c.getResults');

    //Set the variables for the server side controller
    action.setParams({
        currentID : component.get("v.currentID"),
        objectName : component.get("v.objectName"),
        lookupField : component.get("v.lookupField"),
        field1 : component.get("v.field1"),
        field2 : component.get("v.field2"),
        field3 : component.get("v.field3")
    });

    // Set up the callback
    action.setCallback(this, $A.getCallback(function (response) {
        var state = response.getState();
        var resultsToast = $A.get("e.force:showToast");
        if(state === "SUCCESS"){
            //if successful stores query results in serverResult
            component.set('v.serverResult', response.getReturnValue());
        } else if (state === "ERROR") {
            //otherwise write errors to console for debugging
            alert('Problem with connection. Please try again. Error Code: relRecordTiles.doInit.action.setCallback');
            resultsToast.setParams({
                "title": "Error",
                "message": "relRecordTiles failed to load due to: " + JSON.stringify(result.error)
            });
            resultsToast.fire();
            var errors = response.getError();
            console.error(errors);
        }
    }));

    $A.enqueueAction(action);
}            

})


relRecordTilesController.apxc

public class relRecordTilesController {
@AuraEnabled
public static List<tileWrapper> getResults(id currentID, string objectName, string lookupField, string field1, string field2, string field3) {
    System.debug('getResults starting...');

    //add in query fields if they are not null
    string queryfields;
    if(field1 != null){queryfields = ', ' +field1;}
    if(field2 != null){queryfields = queryfields+ ', ' +field2;}
    if(field3 != null){queryfields = queryfields+ ', ' +field3;}

    //build the string we will query
    string queryString = 'SELECT id, name' +queryfields+ ' FROM ' +objectName+ ' WHERE ' +lookupField+ '=' +currentID;

    //get the data into a list
    List<sObject> queryOutput = new List<sObject>();
    queryOutput = database.query(queryString);
    system.debug('queryOutput = ' + queryOutput);

    //Iterate over the list and put the values into a wrapper class defined below
    List<tileWrapper> returnList = new List<tileWrapper>();
    for(sObject s: queryOutput){
        tileWrapper tile = new tileWrapper();
        tile.recID = String.valueOf(s.id);
        tile.recName = String.valueOf(s.get('name'));
        if(String.valueOf(s.get(field1)) != null ){tile.field1 = String.valueOf(s.get(field1));}
        if(String.valueOf(s.get(field2)) != null ){tile.field2 = String.valueOf(s.get(field2));}
        if(String.valueOf(s.get(field3)) != null ){tile.field3 = String.valueOf(s.get(field3));}
        returnList.add(tile);
    }

    system.debug('returnList = ' + returnList);
    return returnList;        
}


public class tileWrapper{
    public id recID{get;set;}
    public string recName{get;set;}
    public string field1{get;set;}
    public string field2{get;set;}
    public string field3{get;set;}
}

}


Calling component from parent Component

                    <lightning:formattedText value="Related Divisions" />
                    <c:relRecordTiles currentID="{!v.selItem}"
                                      objectName="Division2Contact__c"
                                      lookupField="Contact__c"
                                      field1="Full_Division_Name_Backward__c"
                                      field1Label="Division"
                                      field2="Division_Campus__c"
                                      field2Label="Campus"
                                      field3="Status__c"
                                      field3Label="Status"
                                      />

Best Answer

I recommend using the Developer Console to look at the apex debug logs when encountering errors with your apex actions in lightning.

In this particular instance I believe that you are receiving an error with your query at this line of your apex controller:

string queryString = 'SELECT id, name' +queryfields+ ' FROM ' +objectName+ ' WHERE ' +lookupField+ '=' +currentID;

The issue is with your WHERE clause. The ID should be surrounded by single quotes, so it will look like this

string queryString = 'SELECT id' +queryfields+ ' FROM ' +objectName+ ' WHERE ' +lookupField+ '=\'' +currentID+'\'';

In addition, as mentioned in the comments of your question, you are referencing result.error in your error toast code without ever declaring result as a variable. You most likely want to call response.getError() there instead, which is being passed into your callback function as a variable.

Related Topic