+ Start a Discussion
Yves Asselin 3Yves Asselin 3 

Basic Component BASICS: Connect components to events:

PLEASE HELP... 
I have been stuck on this for days!!! 

==TRAILHEAD DIRECTIONS... ===
Here’s the cool part. Sending an event looks almost the same as handling the update directly. Here’s the code for the clickReimbursed handler.

({
    clickReimbursed: function(component event helper) {
        var expense = component.get("v.expense");
        var updateEvent = component.getEvent("updateExpense");
        updateEvent.setParams({ "expense": expense });
        updateEvent.fire();
    }
})
================
MY QUESTION: Where do you paste this? In which controller???? I have tried pasting it in several places but to no avail... Because of the ({}) brackets it looks like it should go in a controller that is blank but that could just be a mistake in the directives...  how knows...


ERROR===
This page has an error. You might just need to refresh it.
Unknown controller action 'clickReimbursed'
Failing descriptor: {markup://c:expenseForm}
Alain CabonAlain Cabon
Hello,

It lacks some informations for this trail and it has become quite incomprehensible.

clickReimbursed : function was update: function previously in the documentation.

https://developer.salesforce.com/docs/atlas.en-us.206.0.lightning.meta/lightning/qs_aotp_app_step6_events.htm?search_text=reimbursed

expenseItem.cmp fires an event updateExpense when you click the toggle button Reimbursed ? 

so replace the entire controller of the component expenseItem.cmp with 
 
({
    clickReimbursed: function(component, event, helper) {
        var expense = component.get("v.expense");
        var updateEvent = component.getEvent("updateExpense");
        updateEvent.setParams({ "expense": expense });
        updateEvent.fire();
    }
})
Alain
Alain CabonAlain Cabon
clickReimbursed : function was update: function previously in the documentation with other components so it is not the same.
Yves Asselin 3Yves Asselin 3
Alain,

Thank you for your response. I followed the linkd to the Developer Documentation. Everything worked fine in that example. However, My issue is that I am following trails in Trailhead and would really like to just fix my issue in the Lesson I am trying to follow. All hell broke loose when they make us decompose our application... I think... anyway, here is the code. Everything works just fine except for the Reimboursed? fuction.. 

Can you take a look at this code?

Thanks again,

Yves



++++++++++++++++++++++++++++++++++++
expensesApp.app
++++++++++++++++++++++++++++++++++++

<aura:application extends="force:slds">
        <!-- This component is the real "app" -->

         <c:expenses /> 

</aura:application>


++++++++++++++++++++++++++++++++++++
expenses.cmp
++++++++++++++++++++++++++++++++++++

+++++ COMPONENT++++ (expenses.cmp)

<aura:component controller="ExpensesController">
    <aura:attribute name="expenses" type="Expense__c[]"/>
    <aura:handler name="init" action="{!c.doInit}" value="{!this}"/>
<aura:handler name="updateExpense" event="c:expensesItemUpdate" action="{!c.handleUpdateExpense}"/>
    <aura:handler name="createExpense" event="c:expensesItemUpdate" action="{!c.handleCreateExpense}"/>
    
 <!-- PAGE HEADER -->
 <lightning:layout class="slds-page-header slds-page-header--object-home">
        <lightning:layoutItem >
            <lightning:icon iconName="standard:scan_card" alternativeText="My Expenses"/>
        </lightning:layoutItem>
   <lightning:layoutItem padding="horizontal-small">
            <div class="page-section page-header">
                <h1 class="slds-text-heading--label">Expenses</h1>
                <h2 class="slds-text-heading--medium">My Expenses</h2>
            </div>
        </lightning:layoutItem>
    </lightning:layout>
<!-- / PAGE HEADER -->

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

        <c:expenseForm />

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

        <c:expensesList expenses="{!v.expenses}"/>

    </lightning:layoutItem>
    <lightning:layoutItem padding="around-small" size="6">
    </lightning:layoutItem>
</lightning:layout>

<c:expensesList expenses="{!v.expenses}"/>
</aura:component>


+++++ CONTROLLER++++ (expensesController.js)

({

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

    // 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);
},
    

handleUpdateExpense: function(component, event, helper) {
    var updatedExp = event.getParam("expense");
    helper.updateExpense(component, updatedExp);
},
    handleCreateExpense: function(component, event, helper) {
    var newExpense = event.getParam("expense");
    helper.createExpense(component, newExpense);
},
    

    
})

+++++ HELPER++++ (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);
},
})


++++++++++++++++++++++++++++++++++++
expenseForm.cmp
++++++++++++++++++++++++++++++++++++

+++++ COMPONENT++++ (expenseForm.cmp)

<aura:component >
    <aura:attribute name="newExpense" type="Expense__c"
     default="{ 'sobjectType': 'Expense__c',
                    'Name': '',
                    'Amount__c': 0,
                    'Client__c': '',
                    'Date__c': '',
                    'Reimbursed__c': false }"/>
    <aura:registerEvent name="createExpense" type="c:expensesItemUpdate"/>

    
<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 Expense
    </legend>

    <!-- CREATE NEW EXPENSE FORM -->
    <form class="slds-form--stacked">          
        <lightning:input aura:id="expenseform" label="Expense Name"
                         name="expensename"
                         value="{!v.newExpense.Name}"
                         required="true"/> 
        <lightning:input type="number" aura:id="expenseform" label="Amount"
                         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 aura:id="expenseform" label="Client"
                         name="expenseclient"
                         value="{!v.newExpense.Client__c}"
                         placeholder="ABC Co."/>
        <lightning:input type="date" aura:id="expenseform" label="Expense Date"
                         name="expensedate"
                         value="{!v.newExpense.Date__c}"/>
<lightning:input type="toggle" 
                        label="Reimbursed?"
                        name="reimbursed"
                        class="slds-p-around--small"
                        checked="{!v.expense.Reimbursed__c}"
                        messageToggleActive="Yes"
                        messageToggleInactive="No"
                        onchange="{!c.clickReimbursed}"/>
        <lightning:button label="Create Expense" 
                          class="slds-m-top--medium"
                          variant="brand"
                          onclick="{!c.clickCreate}"/>
    </form>
    <!-- / CREATE NEW EXPENSE FORM -->

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

</div>
</aura:component>


+++++ CONTROLLER++++ (expenseFormController.js)

({

        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);
        }
    },

})


+++++ HELPER++++ (expenseFormHelper.js)

({

createExpense: function(component, newExpense) {
    var createEvent = component.getEvent("createExpense");
    createEvent.setParams({ "expense": newExpense });
    createEvent.fire();
},

updateExpense: function(component, expense) {
    this.saveExpense(component, expense);
},
    
    saveExpense: function(component, expense, callback) {
    var action = component.get("c.saveExpense");
    action.setParams({
        "expense": expense
    });
   if (callback) {
        action.setCallback(this, callback);
    }
    $A.enqueueAction(action);
},
    
})


++++++++++++++++++++++++++++++++++++
expenseItem.cmp
++++++++++++++++++++++++++++++++++++

+++++ COMPONENT++++ (expenseItem.cmp)

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

    <lightning:card title="{!v.expense.Name}" iconName="standard:scan_card"
                    class="{!v.expense.Reimbursed__c ?
                           'slds-theme--success' : ''}">
        
        <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">
           Amount: <lightning:formattedNumber value="{!v.expense.Amount__c}" style="currency"/>
        </p>
        <p class="slds-p-horizontal--small">
            Client: {!v.expense.Client__c}
        </p>
        <p>
            <lightning:input type="toggle" 
                             label="Reimbursed?"
                             name="reimbursed"
                             class="slds-p-around--small"
                             checked="{!v.expense.Reimbursed__c}"
                             messageToggleActive="Yes"
                             messageToggleInactive="No"
                             onchange="{!c.clickReimbursed}"/>
        </p>
    </lightning:card>
</aura:component>


+++++ CONTROLLER++++ (expenseItemController.js)

({
    doInit : function(component, event, helper) {
        var mydate = component.get("v.expense.Date__c");
        if(mydate){
            component.set("v.formatdate", new Date(mydate));
        }
    },
    
    clickReimbursed: function(component, event, helper) {
        var expense = component.get("v.expense");
        var updateEvent = component.getEvent("updateExpense");
        updateEvent.setParams({ "expense": expense });
        updateEvent.fire();
    }

    

})

++++++++++++++++++++++++++++++++++++
expensesList.cmp
++++++++++++++++++++++++++++++++++++

<aura:component >

   <aura:attribute name="expenses" type="Expense__c[]"/>

    <lightning:card title="Expenses">
        <p class="slds-p-horizontal--small">
            <aura:iteration items="{!v.expenses}" var="expense">
                <c:expenseItem expense="{!expense}"/>
            </aura:iteration>
        </p>
    </lightning:card>

</aura:component>




++++++++++++++++++++++++++++++++++++
ExpensesController.apxc
++++++++++++++++++++++++++++++++++++


public with sharing class ExpensesController {

@AuraEnabled
public static List<Expense__c> getExpenses() {
    
 // Check to make sure all fields are accessible to this user
    String[] fieldsToCheck = new String[] {
        'Id', 'Name', 'Amount__c', 'Client__c', 'Date__c', 
        'Reimbursed__c', 'CreatedDate'
    };
    
    Map<String,Schema.SObjectField> fieldDescribeTokens = 
        Schema.SObjectType.Expense__c.fields.getMap();
    
    for(String field : fieldsToCheck) {
        if( ! fieldDescribeTokens.get(field).getDescribe().isAccessible()) {
            throw new System.NoAccessException();
            return null;
        }
    }
    
    // OK, they're cool, let 'em through
    return [SELECT Id, Name, Amount__c, Client__c, Date__c, 
                   Reimbursed__c, CreatedDate 
            FROM Expense__c];
}
    
 @AuraEnabled
    public static Expense__c saveExpense(Expense__c expense) {
        // Perform isUpdatable() checking first, then
        upsert expense;
        return expense;
    }
}
++++++++++++++++++++++++++++++++++++
expensesItemUpdate.evt
++++++++++++++++++++++++++++++++++++

<aura:event type="COMPONENT">
    <aura:attribute name="expense" type="Expense__c"/>
</aura:event>
Yves Asselin 3Yves Asselin 3
BTW: This is the error I get

This page has an error. You might just need to refresh it.
Action failed: c:expenseForm$controller$clickReimbursed [Cannot read property 'setParams' of null] Failing descriptor: {c:expenseForm$controller$clickReimbursed}
Alain CabonAlain Cabon
In fact, the error are quite clear.

1)  expenseForm.cmp :   This page has an error. You might just need to refresh it.
Action failed: c:expenseForm$controller$clickReimbursed [Cannot read property 'setParams' of null] Failing descriptor: {c:expenseForm$controller$clickReimbursed}

its controller should contain a function clickReimbursed (or remove the toggle button). 

 <lightning:input type="toggle" 
                                 label="Reimbursed?"
                                 name="reimbursed"
                                 class="slds-p-around--small"
                                 checked="{!v.expense.Reimbursed__c}"
                                 messageToggleActive="Yes"
                                 messageToggleInactive="No"
                                 onchange="{!c.clickReimbursed}"/>  // this method is missing in the controller of expenseForm


2)  expenses.cmp (handler of the event updateExpense)This page has an error. You might just need to refresh it.
Action failed: c:expenses$controller$handleUpdateExpense [helper.updateExpense is not a function]
Failing descriptor: {c:expenses$controller$handleUpdateExpense}

 <aura:registerEvent name="updateExpense" type="c:expensesItemUpdate"/>

expensesController.js

    handleUpdateExpense: function(component, event, helper) {
        var updatedExp = event.getParam("expense");
        helper.updateExpense(component, updatedExp);  // KO "updateExpense" doesn't exist in the helper.
    },
    handleCreateExpense: function(component, event, helper) {
        var newExpense = event.getParam("expense");
        helper.createExpense(component, newExpense);  // OK "createExpense" exists in the helper
    },

This module is one of the most tricky but you are near the goal.

Alain
Alain CabonAlain Cabon
In fact, the errors are quite clear but it is misleading because it is just the end of the error message that is important (not the consequence at the beginning or the misleading message about a "magic" refresh which will never create the missing methods here).
Alain CabonAlain Cabon
The most disturbing thing with the Lex components is that you can save a source code with calls for something that doesn't exist.
The checks of coherence are quite light. The source code has been saved with major errors of coherence (no warnings here) but there are some checks on the other hand furthermore with errors blocking the saving (sometimes the developer console refuses to save the code). It's hard not to be disappointed with this behavior at the beginning
Yves Asselin 3Yves Asselin 3
Hi Alain,

I added the helper updateExpense in the expensesHelper as well as the method but only got errors saving... 

I have to admit, of all the learning platforms I have used to learn code, this is the absolute worse... I mean, I am simply trying to finish a LESSON!!!... not a challenge... I noticed that in the new lessons, like in "Build Flexible Apps with Visualforce Pages and Lightning Components" the lessons are a little better written and easier to follow.... however, I still am running into frustrating error messages... To add to the frustration, one never knows if it is a bug in salesforce or an error in our code... 

Often, I simply use the same code and create a new ORG and it works... What do people do in real life situations that do not allow us to just create a new org if things are not working??

It seems to me that ALL LESSONS should include the final code at the end of each lesson so we can study and understand the working code requited to complete our challenges.... The forum is littered with frustration and Trailhead seems to do nothing about it... 

Finally, and only because I believe that this is a most important chapter in Ligntning Developement, if ever you did have the time to correct the code I posted  for the lesson: "Develop for Lightning Experience:Lightning Components Basics: Connect to Salesforce with Server-Side Controllers "), I am absolutely certain many others would benefit from it in the future...

Regards,

Newbie Yves
Alain CabonAlain Cabon
Hi Yves,

The final code in the lesson are the PROJECTS not the MODULES.

PROJECTS : easy (only copy / paste the solution directly and the final code is sometimes more complicated than for the modules and always works)

MODULES: more difficult but you have a pattern to follow. This module "Lex component basics" is one of the most diffcult.
Do not be deceived by the word "basics". It is one of the most difficult module of the trails.

SUPERBADGES : very difficult ( 8 - 12 hours each )

Alain
Yves Asselin 3Yves Asselin 3
I got that... However the goal is to learn. A lesson should be exactly like a project. Cut and paste. This way you can see the big picture and test everything.  The Challenge is the part that should require effort. In this particular case, I have what should be a simple example. Yet it does not work. Very frustrating. Obviouly, if one cannot even understand the lesson, how can he be expected to learn???

But I ask you again Alain... I have posted all the code and your answer seems pretty straight forward... Yet a little incomplete... do you think you can copy my code pasted above, edit the few small changes you suggest and repost it as an answer? It would be most appreciated...

Regards,

Yves
 
Alain CabonAlain Cabon
Correct, the lesson itself should be also "copy/pastable" ideally (I agree) and that was often the case but this module is a known exception (difficult even for the lesson itself). There was a real hope to find the complete solution for the lesson in the former documentation but there are not the same components exactly.

There are perhaps errors in the code of the lesson (typo) or even the missing of some lines of code (just explained but not writen clearly).
People could help you here but we need to see your current code. I don't have myself the source code of the lesson (we often copy/paste it changing the name of the variables and that could be sufficient for the challenge but after some dozens of changes, you need to understand what you are doing exactly).

If you have bugs with error messages, you have already some indications.

If only one letter is in lowercase instead of uppercase (and vice-versa), your code will never work (never, it is Javascript with few checks of coherence).  Apex is case-insentive and has strong checks of coherence (not javascript).
Alain CabonAlain Cabon
Yves,

1) Install the Expense Tracker App: for a complete training application.

Click the installation URL link: https://login.salesforce.com/packaging/installPackage.apexp?p0=04t1a000000EbZp

But it is now an "old" application (one year ago).

2) As I wrote above, you are very close to the end and I mentioned the two major errors.

2.1) expenseHelper.js : updateExpense was missing.
 
({
    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);
    },
    updateExpense: 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") {
                // do nothing!
            }
        });
        $A.enqueueAction(action);
    },
})

2.2) expenseHelper.js: remove the toggle button and replace it with a simple checkbox.

 <!-- lightning:input type="toggle" 
                                 label="Reimbursed?"
                                 name="reimbursed"
                                 class="slds-p-around--small"
                                 checked="{!v.expense.Reimbursed__c}"
                                 messageToggleActive="Yes"
                                 messageToggleInactive="No"
                                 onchange="{!c.clickReimbursed}"/ -->


<ui:inputCheckbox aura:id="reimbursed"
label="Reimbursed?" 
class="slds-checkbox"
labelClass="slds-form-element__label"
value="{!v.newExpense.Reimbursed__c}"/>
 
<lightning:input type="date" aura:id="expenseform" label="Expense Date"
                                 name="expensedate"
                                 value="{!v.newExpense.Date__c}"/>
                          
               <ui:inputCheckbox aura:id="reimbursed" label="Reimbursed?"
                              class="slds-checkbox"
                              labelClass="slds-form-element__label"
                              value="{!v.newExpense.Reimbursed__c}"/>
                
                <lightning:button label="Create Expense" 
                                  class="slds-m-top--medium"
                                  variant="brand"
                                  onclick="{!c.clickCreate}"/>
            </form>

You have done the more difficult, don't give up.

Alain
Yves Asselin 3Yves Asselin 3
Hi Alain,
Thanks again for your help : )

My question only applies to the NEW module in trailhead called "Lightning Components Basics", not the .app example you mentionned previously which uses <ui:input>. The reason that this is important is that Salesforce wants us to use <lightning:input>  instead of  <ui:input> from now on. I was able to build the app you referred to previously though without any problem.

Ref link to OLD example found on the developer site which use <ui:input>  :
https://developer.salesforce.com/docs/atlas.en-us.206.0.lightning.meta/lightning/qs_aotp_app_step6_events.htm?search_text=reimbursed

-Y
Yves Asselin 3Yves Asselin 3
Note
<lightning:input> is the Swiss army knife for input fields that’s infused with the goodness of SLDS styling. Use it whenever you find yourself reaching for the <ui:input> component variety like <ui:inputText>, <ui:inputNumber>, and others. Components in the ui namespace don’t come with SLDS styling and are considered to be legacy components.