[SalesForce] Remove item in dynamic list in Lightning Component

I'm trying to create remove item functionality using this How to remove items from a dynamic list in Lightning component? and this tutorial https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/apex_records_delete.htm
hovewer removing still does not work…

My components:

expenses.cmp

<aura:component controller="ExpensesController" implements="force:appHostable,flexipage:availableForAllPageTypes">

<aura:attribute name="expenses" type="Expense__c[]"/>
<aura:attribute name="newExpense" type="Expense__c"
 default="{ 'sobjectType': 'Expense__c',
                'Name': '',
                'Amount__c': 0,
                'Spending__c': 0,
                'CreatedAt__c': '' }"/>

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

<!-- PAGE HEADER -->
<lightning:layout class="slds-page-header slds-page-header--object-home">
    <lightning:layoutItem>
        <lightning:icon iconName="custom:custom17" alternativeText="My Spendings"/>
    </lightning:layoutItem>
    <lightning:layoutItem padding="horizontal-small">
        <div class="page-section page-header">
            <h1 class="slds-text-heading--label">Donkey Money</h1>
            <h2 class="slds-text-heading--medium">Service, that helps you manage your spendings.</h2>
        </div>
    </lightning:layoutItem>
</lightning:layout>
<!-- / PAGE HEADER -->

<!-- NEW EXPENSE FORM -->
<lightning:layout>
    <lightning:layoutItem padding="around-small" size="6">

        <div aria-labelledby="newexpenseform">

            <!-- BOXED AREA -->
            <fieldset class="slds-box slds-theme--default slds-container--small">

            <legend id="newexpenseform" class="slds-text-heading--small 
              slds-p-vertical--medium">
              Add incomings and spendings
            </legend>

            <!-- CREATE NEW EXPENSE FORM -->
            <form class="slds-form--stacked">          
                <lightning:input aura:id="expenseform" label="Name"
                                 name="expensename"
                                 value="{!v.newExpense.Name}"
                                 required="true"/> 
                <lightning:input type="number" aura:id="expenseform" label="Incomings"
                                 name="expenseamount"
                                 min="0.1"
                                 formatter="currency"
                                 step="0.01"
                                 value="{!v.newExpense.Amount__c}"
                                 messageWhenRangeUnderflow="Enter an amount that's at least $0.10."/>
                <lightning:input type="number" aura:id="expenseform" label="Spending"
                                 name="expenseamount"
                                 min="0.1"
                                 formatter="currency"
                                 step="0.01"
                                 value="{!v.newExpense.Spending__c}"
                                 messageWhenRangeUnderflow="Enter an amount that's at least $0.10."/>

                <lightning:input type="date" aura:id="expenseform" label="Created at"
                                 name="expensedate"
                                 value="{!v.newExpense.CreatedAt__c}"/>
                <lightning:button label="Create" 
                                  class="slds-m-top--medium"
                                  variant="brand"
                                  onclick="{!c.clickCreate}"/>
            </form>
            <!-- / CREATE NEW EXPENSE FORM -->

          </fieldset>
          <!-- / BOXED AREA -->

        </div>

    </lightning:layoutItem>

    <lightning:layoutItem padding="around-small" size="6">
        <c:spendingList expenses="{!v.expenses}"/>
    </lightning:layoutItem>
</lightning:layout>
<!-- / NEW EXPENSE FORM -->

expensesController.js

   ({
        // Load expenses from Salesforce
        doInit: function(component, event, helper) {

            var mydate = component.get("v.expense.CreatedAt__c");
            if(mydate){
                component.set("v.formatdate", new Date(mydate));
            }

            // Create the action
            var action = component.get("c.getExpenses");

            // Add callback behavior for when response is received
            action.setCallback(this, function(response) {
                var state = response.getState();
                if (state === "SUCCESS") {
                    component.set("v.expenses", response.getReturnValue());
                }
                else {
                    console.log("Failed with state: " + state);
                }
            });

            // Send action off to be executed
            $A.enqueueAction(action);
        }, 
        clickCreate: function(component, event, helper) {
            var validExpense = component.find('expenseform').reduce(function (validSoFar, inputCmp) {
                // Displays error messages for invalid fields
                inputCmp.showHelpMessageIfInvalid();
                return validSoFar && inputCmp.get('v.validity').valid;
            }, true);
            // If we pass error checking, do some real work
            if(validExpense){
                // Create the new expense
                var newExpense = component.get("v.newExpense");
                console.log("Create expense: " + JSON.stringify(newExpense));
                helper.createExpense(component, newExpense);
            }
        }
})

expensesHelper.js

  ({
    createExpense: function(component, expense) {
        var action = component.get("c.saveExpense");
        action.setParams({
            "expense": expense
        });
        action.setCallback(this, function(response){
            var state = response.getState();
            if (state === "SUCCESS") {
                var expenses = component.get("v.expenses");
                expenses.push(response.getReturnValue());
                component.set("v.expenses", expenses);
            }
        });
        $A.enqueueAction(action);
    }
})

spendingList.cmp

 <aura:component>

    <aura:attribute name="expenses" type="Expense__c[]"/>
    <aura:handler name="deleteExpenseItem" event="c:deleteExpenseItem" action="{!c.deleteEvent}" />

        <lightning:layout>
      <lightning:layoutItem padding="around-small" size="12">
            <lightning:card title="Spendings">
            <p class="slds-p-horizontal--small">
            <aura:iteration items="{!v.expenses}" var="expense">
              <c:spendingItem expense="{!expense}"/> <!--c:spendingItem is the sub-component -->
            </aura:iteration>
            </p>
            </lightning:card>
          </lightning:layoutItem>
        </lightning:layout>

</aura:component>

spendingListController.js

 ({
    deleteEvent : function(component, event, helper) {
    helper.deleteExpense(component, event.getParam("expense"));
  }
})

spendingListHelper.js

  ({
    deleteExpense : function(component, expense, callback) {
    // Call the Apex controller and update the view in the callback
    var action = component.get("c.deleteExpense");
    action.setParams({
      "expense": expense
    });
    action.setCallback(this, function(response) {
        var state = response.getState();
        if (state === "SUCCESS") {
            // Remove only the deleted expense from view
            var expenses = component.get("v.expenses");
            var items = [];
            for (i = 0; i < expenses.length; i++) {
                if(expenses[i]!==expense) {
                    items.push(expenses[i]);  
                }
            }
            component.set("v.expenses", items);
        }
    });
    $A.enqueueAction(action);
    }
})

spendingItem.cmp

<aura:component>
<aura:registerEvent name="deleteExpense" type="c:deleteExpenseItem"/>

<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
<aura:attribute name="formatdate" type="Date"/>
<aura:attribute name="expense" type="Expense__c"/>

<lightning:card title="{!v.expense.Name}" iconName="standard:quotes">
    <aura:set attribute="footer">
        <p>Date: <lightning:formattedDateTime value="{!v.formatdate}"/></p>
        <p class="slds-text-title"><lightning:relativeDateTime value="{!v.formatdate}"/></p>
    </aura:set>
    <p class="slds-text-heading--medium slds-p-horizontal--small">
       Incomings: <lightning:formattedNumber value="{!v.expense.Amount__c}" style="currency"/>
    </p>
    <p class="slds-text-heading--medium slds-p-horizontal--small">
        Spending: <lightning:formattedNumber value="{!v.expense.Spending__c}" style="currency"/>
    </p>

    <lightning:button label="Delete" onclick="{!c.delete}"/>
</lightning:card>

spendingItemController.js

 ({
    doInit : function(component, event, helper) {
        var mydate = component.get("v.expense.CreatedAt__c");
        if(mydate){
            component.set("v.formatdate", new Date(mydate));
        }
    },
    delete : function(component, evt, helper) {
        var expense = component.get("v.expense");    
        var deleteEvent = component.getEvent("deleteExpenseItem");
        console.log(expense)
        deleteEvent.setParams({ "expense": expense }).fire();
        }
})

When I try to remove, I have the following error:

This page has an error. You might just need to refresh it. Action
failed: c:spendingItem$controller$delete [deleteEvent is null] Failing
descriptor: {c:spendingItem$controller$delete}

console.log(expense)

in spendingItemController shows single (correct) object

Object { Id: "a020O00001M04xKQAR", Name: "gdfgn", Amount__c: 34, Spending__c: 90, CreatedAt__c: "2017-12-04", CreatedDate: "2017-12-13T15:48:41.000Z" }

deleteExpense.cls

public with sharing class deleteExpense {

@AuraEnabled
public static Expense__c deleteExpense(Expense__c expense) {
    // Perform isDeletable() check here 
    delete expense;
    return expense;
}}

deleteExpenseItem.evt

<aura:event type="COMPONENT">
    <aura:attribute name="expense" type="Expense__c"/></aura:event>

Best Answer

It looks like you are missing just one (important) item.

You haven't declared your component event on the component markup. It should look similar to this:

<aura:registerEvent name="deleteExpenseItem" type="c:deleteExpenseItem"/>

Put this in the spendingItem component markup.

Also, while it's nice to see some code to help diagnose the problem, putting it ALL there makes it harder - it's a lot of code for people to read.

Related Topic