The setup
In simple terms, I am trying to call SOQL from a server-side controller and return a custom object record to the lightning component. The problem: This is failing in the lightning component's controller as it (products variable) is getting an undefined variable. I know the record isn't empty because I use System.debug
before returning the record and see it's populated. Do objects need to be converted from Apex to JavaScript or something? Is there a better approach I should be using? Any suggestions will be helpful. Thanks.
The Code
Component
<aura:component controller="QuickAddKBMaxController" implements="lightning:availableForFlowScreens" access="global">
<aura:attribute type="KBMaxProduct[]" name="products" />
<aura:attribute type="KBMaxProduct" name="product" />
<aura:attribute type="KBMaxProduct.KBMaxProductField[]" name="fields" />
<aura:attribute type="KBMaxProduct.KBMaxProductField" name="field" />
<aura:attribute type="Integer" name="indexNum" default="1" />
<lightning:card title="Add Products">
<lightning:layout multipleRows="true">
<lightning:layoutItem size="12" padding="around-small">
<c:AutoComplete aura:id="kbmax-record" label="KBMax Product" objectApiName="KBMax_Product__c" idFieldApiName="Id" valueFieldApiName="Name" /> <!-- This is just a fancy auto-complete dropdown field whose list is populated with all KBMax_Product__c records with the value mapped to the ID. Replaceable with regular dropdown. -->
</lightning:layoutItem>
<lightning:layoutItem size="12" padding="around-small">
<lightning:button label="Add Product" variant="brand" onclick="{!c.getSelectedValue}" /> <!-- Once the Add Product button is clicked, run the action from the lightning controller. -->
</lightning:layoutItem>
</lightning:layout>
<!--Dynamic list-->
<p>{!v.products}</p>
<aura:iteration var="product" items="{!v.products}"> <!-- Products list is updated from the getSelectedValue action in the lightning controller. Below is basic fields to show that it works, but currently does not. -->
<div class="slds-box slds-box_x-small" style="vertical-align: middle; padding: 5px;">
<lightning:layout multipleRows="false" verticalAlign="true" >
<lightning:buttonIcon iconName="utility:delete" alternativeText="Delete" title="Delete" onclick="{!c.deleteProduct}" value="{!v.product.name}" />
<lightning:buttonIcon iconName="utility:copy" alternativeText="Duplicate" title="Duplicate" />
<lightning:layoutItem size="1" padding="around-small">
<div class="header-column">
<p class="field-title" title="Field 1">Product</p>
<p>{!v.product.name}</p>
</div>
</lightning:layoutItem>
<!--<aura:iteration var="field" items="{!v.product.fields}">
<aura:if isTrue="{!v.field.visible}">
<lightning:layoutItem size="1" padding="around-small">
<div class="header-column">
<p class="field-title">{!v.field.name}</p>
<p>{!v.field.value}</p>
</div>
</lightning:layoutItem>
</aura:if>
</aura:iteration>-->
<lightning:layoutItem size="1" padding="around-small">
<lightning:input type="number" name="quantity" label="Quantity" value="{!v.product.quantity}" min="1" />
</lightning:layoutItem>
</lightning:layout>
</div>
</aura:iteration>
</lightning:card>
</aura:component>
Lightning Controller
({
/**
* Get Selected Value from AutoComplete Component
* @author Manish Choudhari
* */
getSelectedValue : function(component, event, helper) {
//find autocomplete component using aura id
const autoCompleteComponent = component.find("kbmax-record");
if(autoCompleteComponent){
//get selected option from auto complete component's selectedOption attribute
const selectedOption = autoCompleteComponent.get("v.selectedOption"); //Get ID from auto-complete dropdown field.
var action = component.get("c.getProductInfo"); //Run getProductInfo method from Apex Controller
action.setParams({ productId : selectedOption }); //Pass the ID from the dropdown to the method.
var products = component.get("v.products"); //Get current list of products from the Lightning page
action.setCallback(this, function(response){
var state = response.getState();
alert(state);
if (state === "SUCCESS") {
var p = response.getReturnValue(); //Get returned value (should be custom object KBMaxProduct which is KBMax_Product__c data with a list of KBMax_Product_Field__c data. Essentially bundling all related info together instead of dealing with a list of Products and a list of ProductFields.
alert("From server: " + p.name); //This is undefined, when it should exist.
products.push(p); //Add returned product to the Lightning page product list.
//set selected value in component attribute
component.set("v.products", products);
debugger; //Can see p = {} in Chrome Dev Tools. That's it. In an alert, it says [Object Object].
}
else if (state === "INCOMPLETE") { //Never reaches this.
// do something
}
else if (state === "ERROR") { //Never reaches this.
var errors = response.getError();
if (errors) {
if (errors[0] && errors[0].message) {
console.log("Error message: " +
errors[0].message);
}
} else { //Never reaches this.
console.log("Unknown error");
}
}
});
$A.enqueueAction(action);
var indexNum = component.get("v.indexNum"); //Used for differentiating multiple of the same product.
component.set("v.indexNum", indexNum++);
}
},
deleteProduct : function (component, event, helper) {
let value = event.getSource().get("v.value");
var values = component.get("v.products");
let pos = values.indexOf(value);
values.splice(pos,1);
//set selected value in component attribute
component.set("v.selectedValues", values);
}
})
Apex Controller
public class QuickAddKBMaxController {
@AuraEnabled
public static KBMaxProduct getProductInfo(Id productId) {
KBMax_Product__c product = [SELECT Id, Name, ProductId__c FROM KBMax_Product__c WHERE Id=:productId];
KBMaxProduct p = new KBMaxProduct(product.Name, product.ProductId__c, 1, getProductFieldsInfo(productId));
System.debug(p.name); //Returns 'Applecrate'
System.debug(p); //Returns object data 'KBMaxProduct:[fields=(fieldata), name=Applecrate, etc'
return p;
}
@AuraEnabled
public static List<KBMax_Product_Field__c> getProductFieldsInfo(Id productId) {
List<KBMax_Product_Field__c> products = [SELECT Name, FieldId__c, Required__c, Type__c, Value_List__c, Visible__c FROM KBMax_Product_Field__c WHERE KBMax_Product__c=:productId];
return products;
}
}
Apex Product Class
public class KBMaxProduct {
public decimal id;
public string name;
public integer quantity = 1;
public List<KBMaxProductField> fields;
public KBMaxProduct(string name, decimal id, integer quantity, List<KBMax_Product_Field__c> fields) {
//Name, FieldId__c, Required__c, Type__c, Value_List__c, Visible__c
this.name = name;
this.id = id;
this.quantity = quantity;
List<KBMaxProductField> fieldList = new List<KBMaxProductField>();
for(KBMax_Product_Field__c field: fields) {
fieldList.add(new KBMaxProductField(field.Name, field.FieldId__c, field.Type__c, field.Value_List__c, null, field.Required__c, field.Visible__c));
}
this.fields = fieldList;
}
public class KBMaxProductField {
public string name;
public string id;
public Object value;
public string fieldType;
public List<string> valueList;
public boolean required;
public boolean visible;
public KBMaxProductField(string name, string id, string fieldType, String valueListText, Object value, boolean required, boolean visible) {
this.name = name;
this.id = id;
this.fieldType = fieldType;
List<string> valueListTemp = (valueListText != null ? valueListText.split('\\n') : new List<string>());
this.value = value;
this.required = required;
this.visible = visible;
}
}
}