0

I know this question has been asked before, but I've run across different code that supposedly works, but isn't working for me.

The error I keep getting is: "Uncaught Action failed: [...] [rowItemList.push is not a function].

I’ve tried a number of things to figure out what the issue is including changing parameters, altering the attribute in the component, and moving the JavaScript code from a handler to the controller (where it is currently located). None of these changes makes the code run.

Thanks in advance for any help you all can offer.

Here's the code (I'm sure there are other newbie issues with the code, but nothing else should be causing this error):

COMPONENT

<aura:attribute name="opportunity" type="Opportunity" default="{ 'sObjectType': 'Opportunity' }"/>

<aura:attribute name="newEst"  type="Crop_Estimate__c"
                default="{ 'sObjectType': 'Crop_Estimate__c',
                     'Name': '',
                     'Crop__c': '',
                     'Acres__c': '',
                     'Year__c': ''
                   }"/>

<!-- JUST HARDCODING VALUES UNTIL I FIGURE OUT HOW TO DRAW ON THE LIST IN THE LOOKUP FIELD-->
<aura:attribute name="cropTypes" type="String[]" default="Red Lentils, Wheat, Canola, Peas, Barley" />

<!--doInit action to auto populate Opportunity Details-->
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />

<lightning:layout verticalAlign="center">
    <lightning:layoutItem flexibility="auto" padding="around-medium">
        <lightning:card class="slds-card__body slds-card__body_inner"
                        iconName="custom:custom5" title="Enter New Crop Plan">

            <lightning:input label="Opportunity A"
                             name="opportunity"
                             aura:id="opportunity"
                             value="{!v.opportunity.Name}" />

            <lightning:input label="Crop Plan Name"
                             name="CropPlanName"
                             aura:id="CropPlanName"
                             value="{!v.newEst.Name}" /> 

            <lightning:input label="Year"
                             type="number"
                             name="number"
                             aura:id="Year"
                             value="{!v.newEst.Year__c}" />


            <br/>

            <lightning:select name="Crop"
                              aura:id="Crop"
                              label="Crop:" >

                <aura:iteration items="{!v.cropTypes}" var="cropType" >
                    <option value="{!cropType}" text="{!cropType}" />
                </aura:iteration>

            </lightning:select>

            <lightning:input label="Acres"
                             type="number"
                             name="number"
                             aura:id="Acres"
                             value="{!v.newEst.Acres__c}" />


            <br/>

            <lightning:button variant="neutral"
                              label="Add Crop"
                              onclick="{!c.newEstPlan}" />
            <br/><br/>
            <lightning:button variant="brand"
                              label="Save Crop Plan"
                              onclick="{!c.saveEstPlan}" />

        </lightning:card>
    </lightning:layoutItem>
</lightning:layout>

APEX CONTROLLER

 public with sharing class NewEstCropPlanController {
    @AuraEnabled
    public static Crop_Estimate__c newEstimateDB(Crop_Estimate__c cropEst) {
        insert cropEst;
        return cropEst;
    }

    @AuraEnabled
    public static Opportunity getOpportunity(Id opportunityId) {
        return [SELECT Id, Name FROM Opportunity WHERE Id = :opportunityId];
    }

COMPONENT CONTROLLER

({
    doInit : function(component, event, helper) {
        var action = component.get("c.getOpportunity");
        action.setParams ({ opportunityId : component.get("v.recordId") });
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                component.set("v.opportunity", response.getReturnValue());
                console.log(response.getReturnValue());
            } else {
                console.log('Did not work');
            }
        });
        $A.enqueueAction(action);
    },


    saveEstPlan : function (component, event, helper) {
        helper.createEstCropPlan(component);

    },

    //addNewPlan

     newEstPlan : function (component, event, helper) {
        var rowItemList = component.get("v.newEst");
        rowItemList.push( { 'sobjectType': 'Crop_Estimate__c',
                            'Crop__c': '',
                            'Acres__c': ''});
        component.set("v.newEst", rowItemList);
    }
)}

HELPER (Doesn't really matter for this question)

({
   createEstCropPlan : function (component, event) {
        var newPlan = component.get("v.newEst");
        var action = component.get("c.newEstimateDB");
        action.setParams ({ "cropEst" : newPlan });
        action.setCallback(this, function (response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                var name = response.getReturnValue();
                alert("Save Successful" + name);

            } else {
                console.log("Did not Save");
            }
        });
        $A.enqueueAction(action);
    }
)}

UPDATE AFTER @sfdcfox COMMENTS BELOW. This shows how I have the iteration

            <lightning:input label="Opportunity"
                             name="opportunity"
                             aura:id="opportunity"
                             value="{!v.opportunity.Name}" />



            <lightning:input label="Crop Plan Name"
                             name="CropPlanName"
                             aura:id="CropPlanName"
                             value="{!v.newEst.Name}" />

            <lightning:input label="Year"
                             type="number"
                             name="number"
                             aura:id="Year"
                             value="{!v.newEst.Year__c}" />


            <aura:iteration items="{!v.newEst}" var="est">


                <lightning:input label="Acres"
                                 type="number"
                                 name="number"
                                 aura:id="Acres"
                                 value="{!est.Acres__c}" />
            </aura:iteration> [CROP ITERATION AND BUTTONS FOLLOW ]
4
  • newEst is not a list. Why would you expect to be able to push a new item into it?
    – sfdcfox
    Commented Feb 13, 2018 at 22:56
  • Good question. I assumed it would push into the Crop_Estimate__c object that is in the newEst attribute. So, I need a list like the one in the Apex Controller in this post (from which I copied the code)? Commented Feb 13, 2018 at 23:09
  • Both the server and client-side code would have to be adapted to use list elements, if that's your intent. What exactly are you trying to accomplish?
    – sfdcfox
    Commented Feb 13, 2018 at 23:17
  • I'm creating a component that will allow input for what we call initial crop plans from a farmer. These are very general plans for the next season. Farmers will plant different numbers of crops over a different number of acres. In the end, we want to be able to add rows based on the number of crops and acres a farmer is seeding. So, Farmer 1 plants Canola: 3000 Acres, Wheat: 2500 Acres, while Farmer 2 seeds Canola: 5000 Acres, Lentils: 2500 Acres, Barley: 1200 Acres, Peas: 2000 Acres, and so on. It's basically like a grocery receipt: different numbers of products and different prices. Commented Feb 13, 2018 at 23:24

2 Answers 2

1

At minimum, you'll need to use a list, which will have further effects on your code.

To get started, change the attribute to a list:

<aura:attribute name="newEst"  type="Crop_Estimate__c[]" default="[]" />

Next, you'l need to iterate over this list:

<aura:iteration items="{!v.newEst}" var="est">
  <lightning:layoutItem flexibility="auto" padding="around-medium">
   ...

Which, in turn, will alter your inputs:

        <lightning:input label="Crop Plan Name"
                         name="CropPlanName"
                         aura:id="CropPlanName"
                         value="{!est.Name}" /> 

You now essentially have a multiple row setup, so the you should alter your server-side code as well:

@AuraEnabled
public static Crop_Estimate__c[] newEstimateDB(Crop_Estimate__c[] cropEst) {
    upsert cropEst;
    return cropEst;
}

Note that I use upsert so you can insert and update records at the same time. If you don't want to do this, then you'll have to arrange for a more complicated method of detecting which record is being inserted (and make the other rows read-only).

There's a few other minor problems in your code (I don't think that "crop type" is actually bound to an input), so you'll need to clean that up, too, but this should get you most of the way.

8
  • This is really helpful! I'm completely new to developing, in general, and Salesforce, in particular as of 6 months ago, so I'm in a steep learning curve. I love it, though. Commented Feb 14, 2018 at 15:31
  • Great news is that rows are now being added! Here are a couple of challenges that I now notice. (1) I only want 'Crop' and 'Acres' rows added. I took the 'Name' and 'Year' out of the iteration, so only 'Acres' is added. (I'll post that code after this comment.) That's fine. I'm not sure how to get the Crop in that iteration, though. I have an idea to try. [Crops are stored in their own object in our data model, if that matters] (2) When adding, the Crop Plan Name field is cleared. Acres is not, though. (3) Acres does not appear when action is launched, only after clicking 'Add Crop'. Commented Feb 14, 2018 at 15:48
  • After a bit more testing: (1) The estimate did not save with upsert, but it does save with insert; (2) It saves, however, as two distinct records. I'd like it to save as one record. In this way Farmer 1 could have 5 crops/acres on his (one) estimate, Farmer 2 could have 3 crops/acres on his (one) estimate, Farmer 3 could have 7 crops/acres on his estimate, etc. This is where the receipt analogy was helpful to me: different number of products at different prices, but on record. Commented Feb 14, 2018 at 16:43
  • 1
    @Jstig You need a parent object. See First Normal Form for the reasoning behind this. If a user has seven crops, there are seven records. If you want them all associated together, you have a parent record (which I at first assumed was your opportunity).
    – sfdcfox
    Commented Feb 14, 2018 at 16:54
  • 1
    @Jstig (0) Opportunity probably makes more sense; all of the CE can also be available on the account, too, just add a lookup field. (1) With a lookup field. (2) IMO, Year should be unnecessary if the Opportunity represents the year, or use another custom object (Receipt?). (3) Yes, you can write code for this, probably in your controller. You'll want to load the account record as a variable so you can access that data.
    – sfdcfox
    Commented Feb 15, 2018 at 21:09
0

Actually, the error you are getting is because of these lines of code:

rowItemList.push( { 'sobjectType': 'Crop_Estimate__c',
                            'Crop__c': '',
                            'Acres__c': ''});
        component.set("v.newEst", rowItemList);

here you're pushing the Sobject type attribute value in var rowItemList. you can do something like this, it will resolve your error:

var rowItemListArray =[];
          rowItemListArray.push( { 'sobjectType': 'Crop_Estimate__c',
                            'Crop__c': '',
                            'Acres__c': ''});
        component.set("v.newEst", rowItemListArray);

You must log in to answer this question.

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