function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
MellycooksMellycooks 

Connect Components with Events -- Instructions out of order

It looks to me like the insructions on part of this trail are out of order.  About halfway down you are creating the "handleCreateExpense" action handler. These are the instructions:
We’ll start with the handleUpdateExpense action handler. Here’s the code, and be sure to put it riiiiiight under the handleCreateExpense action handler.

It tells us to put it under the "handleCreateExpense" handler, but we haven't created that one yet.  The instructions to create it are further down in the instructions.  About another quarter of the way through we finally get to this:
Finally, for the last step, create the handleCreateExpense action handler in the expenses controller. Add this code right above or below the handleUpdateExpense action handler.​
 
Wanted to share since it caught me up for a bit.  Thought I had missed something in an earlier trail and went searching for it.
NagendraNagendra (Salesforce Developers) 
Hi Melly,

Firstly thanks a lot for your interest on trail head.

Appreciate your post.May I suggest you send feedback on this by clicking on submit feedback button on the same page of the module so that trailhead team would get noticed and do the appropriate.

Thanks,
Nagendra.
 
MellycooksMellycooks
Will do.  I didn't see that the submit feedback feature had moved, but I spotted it now.
Patrick McClellanPatrick McClellan
Glad to see I'm not the only one confused by this. Now, I just need to figure out WHERE to create those two handlers...
Patrick McClellanPatrick McClellan
Here's the full code for the Expenses project. The validation is on camping, so I'm not giving anything away here, just consolidating several modules worth of instructions. This is also useful as you do the Camping project so you don't have to keep jumping around in Dev Console. If you find this useful, please mark it Best Answer.

expensesApp.app (this is my harness app, your name may vary)
<aura:application extends="force:slds">
    <c:expenses /> 
</aura:application>

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="createExpense" event="c:expensesItemUpdate"
        action="{!c.handleCreateExpense}"/>
    <aura:handler name="updateExpense" event="c:expensesItemUpdate"
        action="{!c.handleUpdateExpense}"/>

    <!-- PAGE HEADER -->
    <div class="slds-page-header" role="banner">
      <div class="slds-grid">
        <div class="slds-col">
          <p class="slds-text-heading--label">Expenses</p>
          <h1 class="slds-text-heading--medium">My Expenses</h1>
        </div>
      </div>
    </div>
    <!-- / PAGE HEADER -->

    <!-- NEW EXPENSE FORM -->
    <div class="slds-col slds-col--padded slds-p-top--large">

        <c:expenseForm />

    </div>
    <!-- / NEW EXPENSE FORM -->

    <!-- EXISTING EXPENSES -->
    <div class="slds-grid slds-m-top--large">

        <!-- EXPENSES LIST -->
        <div class="slds-col slds-col-rule--right slds-p-around--small
            slds-size--8-of-12">
            <c:expensesList expenses="{!v.expenses}"/>
        </div>
        <!-- / EXPENSES LIST -->

        <!-- SOMETHING COOL -->
        <div class="slds-col slds-p-left--large slds-size--4-of-12">
            <!-- Bonus lesson, coming soon.
                 Watch this space for details. -->
        </div>
        <!-- / SOMETHING COOL -->

    </div>
    <!-- / EXISTING EXPENSES -->

</aura:component>

expensesController.js   (I have included some console.log lines throughout my js that I used in debugging. These aren't required.)
({
    // Load expenses from Salesforce
    doInit: function(component, event, helper) {
        
        // Create the action, instructions to call the Apex controller method getExpenses()
        var action = component.get("c.getExpenses");
        
        // Add callback behavior for when response is received; a property of 'action'
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (component.isValid() && state === "SUCCESS") {
                component.set("v.expenses", response.getReturnValue());
            }
            else {
                console.log("Failed with state: " + state);
            }
        });
        
        // Send action off to be executed
        $A.enqueueAction(action);
    },
    
    
    handleCreateExpense: function(component, event, helper) {
        var newExpense = event.getParam("expense");
        helper.createExpense(component, newExpense);
    },
    
    
    handleUpdateExpense: function(component, event, helper) {
        var updatedExp = event.getParam("expense");
        console.log("expensesController.js is handleUpdateExpense now.")
        helper.updateExpense(component, updatedExp);
        
    },
    
    
})

expensesHelper.js
({
    saveExpense: function(component, expense, callback) {
        console.log("saveExpense in expensesHelper.js")
        var action = component.get("c.saveExpense");
        action.setParams({
            "expense": expense
        });
        if (callback) {
            action.setCallback(this, callback);
        }
        $A.enqueueAction(action);
    },
    
    
    createExpense: function(component, expense) {
        console.log("createExpense in expensesHelper.js")
        this.saveExpense(component, expense, function(response){
            var state = response.getState();
            if (component.isValid() && state === "SUCCESS") {
                var expenses = component.get("v.expenses");
                expenses.push(response.getReturnValue());
                component.set("v.expenses", expenses);
            }
        });
    },
    
    updateExpense: function(component, expense) {
        console.log("updateExpense in expensesHelper.js")
        this.saveExpense(component, expense);
    },
    
    
    
    
})

expensesList.cmp  - this is the mark-up for the list of expenses at the bottom of the expenses.cmp, below the new expense form. Key feature is the aura:iteration tag, which repeatedly uses the expensesItem markup that follows.
<aura:component >

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

    <div class="slds-card slds-p-top--medium">
        <header class="slds-card__header">
            <h3 class="slds-text-heading--small">Submitted Expenses</h3>
        </header>
        
        <section class="slds-card__body">
            <div id="list" class="row">
                <aura:iteration items="{!v.expenses}" var="expense">
                    <c:expenseItem expense="{!expense}"/>
                </aura:iteration>
            </div>
        </section>
    </div>

</aura:component>

expenseItem.cmp  - this is the markup for each individual expense item, called in the iteration on expensesList.cmp.
<aura:component >
    <aura:attribute name="expense" type="Expense__c"/>
    <aura:registerEvent name="updateExpense" type="c:expensesItemUpdate"/>
    
    <div class="slds-card">
        
        <!-- Color the item green if the expense is reimbursed -->
        <div class="{!v.expense.Reimbursed__c == true ?
                    'slds-theme--success' : 'slds-theme--warning'}">
            
            <header class="slds-card__header slds-grid grid--flex-spread">
                <a aura:id="expense" href="{!'/' + v.expense.Id}">
                    <h3>{!v.expense.Name}</h3>
                </a>
            </header>
            
            <section class="slds-card__body">
                <div class="slds-tile slds-hint-parent">
                    <p class="slds-tile__title slds-truncate">Amount:
                        <ui:outputCurrency value="{!v.expense.Amount__c}"/>
                    </p>
                    <p class="slds-truncate">Client:
                        <ui:outputText value="{!v.expense.Client__c}"/>
                    </p>
                    <p class="slds-truncate">Date:
                        <ui:outputDate value="{!v.expense.Date__c}"/>
                    </p>
                    <p class="slds-truncate">Reimbursed?
                        <ui:inputCheckbox value="{!v.expense.Reimbursed__c}"
                                          click="{!c.clickReimbursed}"/>
                    </p>
                </div>
            </section>
        </div>
    </div>
    
</aura:component>

expensesForm.cmp  - this is the markup for the new expense form. NOTE the aura:registerEvent line just below the newExpense attribute. That's essential for broadcasting the createExpense event.
<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">
                
                <div class="slds-form-element slds-is-required">
                    <div class="slds-form-element__control">
                        <ui:inputText aura:id="expname" label="Expense Name"
                                      class="slds-input"
                                      labelClass="slds-form-element__label"
                                      value="{!v.newExpense.Name}"
                                      required="true"/>
                    </div>
                </div>
                
                <div class="slds-form-element slds-is-required">
                    <div class="slds-form-element__control">
                        <ui:inputNumber aura:id="amount" label="Amount"
                                        class="slds-input"
                                        labelClass="slds-form-element__label"
                                        value="{!v.newExpense.Amount__c}"
                                        required="true"/>
                        
                    </div>
                </div>
                
                <div class="slds-form-element">
                    <div class="slds-form-element__control">
                        <ui:inputText aura:id="client" label="Client"
                                      class="slds-input"
                                      labelClass="slds-form-element__label"
                                      value="{!v.newExpense.Client__c}"
                                      placeholder="ABC Co."/>
                    </div>
                </div>
                
                <div class="slds-form-element">
                    <div class="slds-form-element__control">
                        <ui:inputDate aura:id="expdate" label="Expense Date"
                                      class="slds-input"
                                      labelClass="slds-form-element__label"
                                      value="{!v.newExpense.Date__c}"
                                      displayDatePicker="true"/>
                    </div>
                </div>
                
                <div class="slds-form-element">
                    <ui:inputCheckbox aura:id="reimbursed" label="Reimbursed?"
                                      class="slds-checkbox"
                                      labelClass="slds-form-element__label"
                                      value="{!v.newExpense.Reimbursed__c}"/>
                </div>
                
                <div class="slds-form-element">
                    <ui:button label="Create Expense"
                               class="slds-button slds-button--brand"
                               press="{!c.clickCreateExpense}"/>
                </div>
                
            </form>
            <!-- / CREATE NEW EXPENSE FORM -->
            
            
        </fieldset>
        <!-- / BOXED AREA -->
        
    </div>
    <!-- / CREATE NEW EXPENSE -->
    
    
</aura:component>

expenseFormController.js  - I added some code to clear the expense form after submitting a new expense.
({
	clickCreateExpense: function(component, event, helper) {
        if(helper.validateExpenseForm(component)){
            // Create the new expense
           console.log("Validated, clickCreateExpense reached")
            var newExpense = component.get("v.newExpense");
            helper.createExpense(component, newExpense);
     //clear the form
            component.set("v.newExpense", { 
                			'sobjectType': 'Expense__c',
                            'Name': '',
                            'Amount__c': 0,
                            'Client__c': '',
                            'Date__c': '',
                            'Reimbursed__c': false });
        }
    },
})

expenseFormHelpeer.js
({
    validateExpenseForm: function(component) {
        
        // Simplistic error checking
        var validExpense = true;
        
        // Name must not be blank
        var nameField = component.find("expname");
        var expname = nameField.get("v.value");
        if ($A.util.isEmpty(expname)){
            validExpense = false;
            nameField.set("v.errors", [{message:"Expense name can't be blank."}]);
        }
        else {
            nameField.set("v.errors", null);
        }
        console.log("Name is: " + expname +" validExpense is: " + validExpense)
        // Amount must be set, must be a positive number
        var amtField = component.find("amount");
        var amt = amtField.get("v.value");
        if ($A.util.isEmpty(amt) || isNaN(amt) || (amt <= 0.0)){
            validExpense = false;
            amtField.set("v.errors", [{message:"Enter an expense amount."}]);
        }
        else {
            // If the amount looks good, unset any errors...
            amtField.set("v.errors", null);
        }
        console.log("Amount is: " + amt +" validExpense is: " + validExpense)
        return(validExpense);
    },
    
    createExpense: function(component, expense) {
        console.log("createExpense in expenseFormHelper.js")
        var createEvent = component.getEvent("createExpense");
        createEvent.setParams({ "expense": expense });
        createEvent.fire();
    },


    
})

expenseForm.css - fixes the datePicker position
.THIS .uiInputDate .datePicker-openIcon {
    position: absolute;
    left: 95%;
    top: 55%;
}

ExpensesController.apxc - note the filename of this APEX controller has a capital E, in comparison to the javascript controller called expensesController.js. This appears to be a naming convention that is useful if you notice it.
public with sharing class ExpensesController {

    @AuraEnabled
    public static List<Expense__c> getExpenses() {
        // Perform isAccessible() checking first, then
        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  this one event is used by both updateExpense and createExpense, but requires two separate aura:handlers with different names to catch the two events.
<aura:event type="COMPONENT">
    <aura:attribute name="expense" type="Expense__c"/>
</aura:event>


 
Patrick McClellanPatrick McClellan
Sorry, forgot this one:

expenseItemController.js
({
	clickReimbursed: function(component, event, helper) {
        var expense = component.get("v.expense"); //gets the expense
        var updateEvent = component.getEvent("updateExpense"); //create the event
        updateEvent.setParams({ "expense": expense }); //package the expense in the event
        updateEvent.fire(); //fire the event
        
    },
})

 
saasthiisaasthii
Thanks @Patrick McClellan, you made my life easy.. appreciate your posting..