You need to sign in to do that
Don't have an account?
Yves 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}
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}
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
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>
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}
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
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
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
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
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
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).
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.
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}"/>
You have done the more difficult, don't give up.
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
<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.