0

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;
        }
    }
}
2

When using a Wrapper class, you need to use the @AuraEnabled annotation:

public class KBMaxProductField {
    @AuraEnabled public string name;
    @AuraEnabled public string id;
    @AuraEnabled public Object value;
    @AuraEnabled public string fieldType;
    @AuraEnabled public List<string> valueList;
    @AuraEnabled public boolean required;
    @AuraEnabled public boolean visible;
}

Also, make sure you check the case of your variables, if applicable. In Lightning/JavaScript, everything is cAsE-sEnSiTiVe.

3
  • For the @AuraEnabled here: this is because they used <aura:attribute type="KBMaxProduct" right? if they did a more generic type="Object" it'd work, correct?
    – stackasaur
    Apr 22 '20 at 19:40
  • Thanks, sfdcfox! The properties show up in the debugger now, but the component still shows up empty ({!v.product.name} for example above the commented out iteration section). I made sure to match the case-sensitivity with all lowercase variables which matches the aura:attributes and the Apex class property names.
    – icebird76
    Apr 22 '20 at 19:57
  • Figured it out. Iteration variables don't need to start with 'v.'. It's just {!product.name}.
    – icebird76
    Apr 22 '20 at 20:10

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.