学习目标
完成本单元后,您将能够:
- 创建可以从Lightning组件代码远程调用的Apex方法。
- 从Lightning组件调用远程方法。
- 使用回调函数异步处理服务器响应。
- 伸展目标:解释“c。”,“c:”和“c。”之间的区别。
服务器端控制器的概念
到目前为止,我们所做的一切都是严格的客户端。我们还没有将开支节省回到Salesforce。创建一些费用,然后打重装,会发生什么?没错,所有的费用都消失了。呜呼,免费的钱!
除了刚才所说的会计,他们对这种事情都是有些冷淡的。而实际上,我们是不是想要报销那些从我们口袋里拿出来的费用呢?哎呀!我们需要将我们的数据保存到Salesforce,不再有任何延误!
开玩笑,现在是时候把服务器端控制器添加到我们的应用程序。我们一直把这个拿回来,而我们的基础知识倒了。现在你已经准备好了,让我们潜入!
进入一些图片,就是。让我们确保我们知道我们的目标,并且在我们上路之前,我们的油箱已经满了。
首先,让我们重温一下我们在这个模块中看到的第一个图表,一个非常高层次的Lightning组件应用程序的体系结构。
到目前为止,我们所看到的一切都在这张照片的客户端上。 (注意我们通过在这里合并控制器和帮助器来简化。)虽然我们引用了一个自定义的对象类型,Expense__c,它是在服务器端定义的,但我们并没有直接触及服务器。
请记住我们如何将不同的元素连接起来以创建一个完整的电路?我们在最后一个单位建立的费用形式可能看起来像这样。
该电路以创建按钮开始,该按钮连接到clickCreate操作处理程序(1)。当动作处理程序运行时,它会从表单字段(2)中获取值,然后向费用数组(3)添加新的开销。当数组通过设置更新时,触发费用列表(4)的自动重新显示,完成电路。很简单,对吧?
那么,当我们在服务器端访问时,图表会变得更加复杂。更多的箭头,更多的颜色,更多的数字! (暂时解释一切吧。)
而且,这个电路没有相同的平滑,同步的控制流程。服务器通话费用很高,可能需要一些时间。情况良好时为毫秒,网络拥塞时为长时间。 Lightning组件不希望应用程序在等待服务器响应时被锁定。
在等待时保持响应的解决方案是异步处理服务器响应。这意味着什么,当你点击Create Expense按钮时,你的客户端控制器触发一个服务器请求,然后继续处理。它不仅不等待服务器,它忘记了它提出的要求!
然后,当响应从服务器返回时,与请求打包在一起的代码(称为回调函数)将运行并处理响应,包括更新客户端数据和用户界面。
如果你是一个有经验的JavaScript程序员,异步执行和回调函数可能是你的面包和黄油。如果你之前没有和他们一起工作,这将是新的,也许是非常不同的。这也很酷。
从Salesforce查询数据
我们将从阅读Salesforce数据开始,在费用应用程序启动时加载现有费用列表。
public with sharing class ExpensesController {
// 斯特恩讲关于这里什么都没有了
@AuraEnabled
public static List<Expense__c> getExpenses() {
return [SELECT Id, Name, Amount__c, Client__c, Date__c,
Reimbursed__c, CreatedDate
FROM Expense__c];
}
}
- 方法声明之前的@AuraEnabled注解。
“Aura”是Lightning组件核心的开源框架的名称。您已经看到它在一些核心标签的命名空间中使用,如<aura:component>。现在你知道它来自哪里。 - 静态关键字。所有@AuraEnabled控制器方法都必须是静态方法,可以是公共范围或全局范围。
如果这些要求提醒您使用Visualforce的JavaScript远程功能的远程方法,那不是巧合。要求是一样的,因为架构在关键点非常相似。
另一件值得注意的事情是该方法没有做任何特殊的事情来打包Lightning组件的数据。它只是直接返回SOQL查询结果。 Lightning组件框架处理大多数情况下涉及的所有编组/解组工作。太好了!
从Salesforce加载数据
下一步是将费用组件连接到服务器端的Apex控制器。这很容易,可能会让你头晕目眩。将费用组件的开头<aura:component>标记更改为指向Apex控制器,如下所示。
<aura:component controller="ExpensesController">
但是,指向Apex控制器并不实际加载任何数据,或者调用远程方法。就像组件和(客户端)控制器之间的自动连线一样,这个指向只是让这两个组件“彼此了解”。这个“知道”甚至采取了同样的形式,另一个价值提供者,我们稍后会看到。但是自动布线只有这么远。完成电路仍然是我们的工作。
在这种情况下,完成电路意味着以下。
- 当费用组件被加载时,
- 查询Salesforce的现有费用记录,和
- 将这些记录添加到费用组件属性。
我们将依次采取每一个。第一个项目,当费用组件首次被加载时触发行为,要求我们编写一个init处理程序。这只是一个连接到组件的init事件的动作处理程序的简称,当组件首次创建时会发生这种情况。
你需要的接线是一行标记。将以下内容添加到组件的属性定义之下的费用组件。
<aura:handler name="init" action="{!c.doInit}" value="{!this}"/>
调用服务器端控制器方法
一步下去,两步走。剩下的步骤都在doInit动作处理器中进行,所以让我们来看看它。将以下代码添加到费用组件的控制器。
// 从Salesforce中加载费用
doInit: function(component, event, helper) {
// 创建动作
var action = component.get("c.getExpenses");
// 添加回复行为的时候收到响应
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);
}
});
// 发送动作执行
$A.enqueueAction(action);
},
- 创建一个远程方法调用。
- 设置远程方法调用返回时应该发生的事情。
- 排队远程方法调用。
这听起来很简单,对吧?也许代码的结构或特性是新的,但是需要发生什么的基本要求再次被熟悉。
这听起来像我们真的很鼓舞人心?比如说,也许我们试图通过一个粗糙的补丁来指导你?那么,我们需要谈论一些艰难的事情。该问题出现在函数的第一行代码中。
var action = component.get("c.getExpenses");
识别 |
上下文 |
含义 |
---|---|---|
c. |
组件标记 |
客户端控制器 |
c. |
控制器代码 |
服务器端控制器 |
c: |
标记 |
默认命名空间 |
好的,回到我们的代码。在我们偏离之前,我们正在看这条线。
var action = component.get("c.getExpenses");
$A.enqueueAction(action);
- 它将服务器请求排队。
- 就你的管制员行动而言,这是结束了。
- 你不能保证什么时候,或者如果,你会听到回来。
这是我们搁置的代码块的地方。但在我们谈论这个之前,有一点流行文化。
服务器调用,异步执行和回调函数
Carly Rae Jepsen的单曲“Call Me Maybe”于2011年发布,获得批判和商业成功,在十多个国家中名列第一。到目前为止,它已经在全球销售了超过1800万份,显然是有史以来最畅销的数字单曲之一。从合唱中最难忘的一行是“这是我的号码。所以也许打电话给我。“除了乐观和危险的吸引力之外,这是Lightning组件处理服务器调用的一个比喻。
听我们出来。让我们看看我们在伪代码中的动作处理器。
doInit: function(component, event, helper) {
// 从Salesforce中加载费用
var action = component.get("c.getExpenses");
action.setCallback(
// 这是我的号码,也许打电话给我
);
$A.enqueueAction(action);
},
action.setCallback(this, function(response) { ... });
action.setCallback(scope, callbackFunction);
总体效果是创建请求,打包请求完成时要执行的代码并将其发送到执行。在这一点上,动作处理器本身停止运行。
这是另一种方法来包围你的头。你可以把你的孩子捆绑上学,然后把他们上课后要回家的杂事交给他们。你在学校放弃他们,然后你去工作。当你在工作的时候,你正在做你的工作,确保你的孩子,作为一个好孩子,当他们从学校回来的时候,会完成你分配给他们的工作。你自己不这样做,而且你不知道什么时候会发生。但它确实如此。
这是看最后一个方法,再次用伪代码。此版本“展开”回调函数以显示更为线性的操作处理程序版本。
// 不是真正的代码!不要剪贴!
doInit: function(component, event, helper) {
// 创建服务器请求
var action = component.get("c.getExpenses");
// 发送服务器请求
$A.enqueueAction(action);
// ... 时间流逝 ...
// ...
// ...危险主题扮演...
// ...
// ...在不确定的未来某个时候
// 处理服务器响应
var state = action.response.getState();
if (state === "SUCCESS") {
component.set("v.expenses", action.response.getReturnValue());
}
},
处理服务器响应
现在我们已经得到了创建一个服务器请求的结构,让我们来看看我们的回调函数实际处理响应的细节。这里只是回调函数。
function(response) {
var state = response.getState();
if (state === "SUCCESS") {
component.set("v.expenses", response.getReturnValue());
}
}
在这个特定的回调函数中,我们执行以下操作。
- 获取响应的状态。
- 如果状态是成功的,也就是说,我们的要求按计划完成,那么:
- 将组件的费用属性设置为响应数据的值。
你可能有几个问题,比如:
- 如果响应状态不是成功会发生什么?
- 如果回应永远不会到来会发生什么? (打电话给我,可能的话。)
- 我们如何才能将响应数据分配给我们的组件属性?
前两个答案不幸的是,我们不打算在这个模块中介绍这些可能性。他们当然是你需要知道的事情,并考虑在你的真实世界的应用程序,但我们只是没有空间。
最后一个问题在这里是最相关的,但也是最容易回答的。我们为费用属性定义了一个数据类型。
<aura:attribute name="expenses" type="Expense__c[]"/>
public static List<Expense__c> getExpenses() { ... }
Apex 控制器的闪电组件
在我们开发应用程序的下一步之前,让我们深入一点Apex控制器。下面看看下一个版本,我们需要处理创建新记录,以及更新报销?复选框在现有的记录。
public with sharing class ExpensesController {
@AuraEnabled
public static List<Expense__c> getExpenses() {
// 先执行isAccessible()检查,然后
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) {
// 首先执行isUpdatable()检查
upsert expense;
return expense;
}
}
@AuraEnabled
public static List<Expense__c> getExpenses() {
// 检查以确保所有的字段都可以被这个用户访问
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;
}
}
// 好,他们很酷,让他们通过
return [SELECT Id, Name, Amount__c, Client__c, Date__c,
Reimbursed__c, CreatedDate
FROM Expense__c];
}
好, </stern-lecture>.
将数据保存到Salesforce
在我们实施“添加费用”表单之前,不要作弊,我们先来看看创建新记录与阅读现有记录是不同的挑战。使用doInit(),我们只需读取一些数据,然后更新应用程序的用户界面。直截了当,即使我们必须让Carly Rae参与解释。
创建新记录涉及更多。我们将从表单中读取值,在本地创建新的费用记录,发送该记录以保存在服务器上,然后当服务器告诉我们已保存时,使用返回的记录更新用户界面服务器。
这是否会使它听起来像是很复杂?比如,也许我们需要滚石乐队和一整首歌曲来帮助我们解释下一个问题。
让我们来看看一些代码,你可以自己决定。
首先,确保已经保存了Apex控制器的更新版本,包括saveExpense()方法的先前版本。
请记住,当我们向您展示如何处理表单提交?当至少有一个字段是无效的,你会看到一个错误消息,表单不提交。当所有字段都有效时,错误消息将被清除。
因为我们把所有的细节(和所有的作弊)都放到了辅助函数createExpense()函数中,所以我们不需要在控制器中做任何其他的修改。到目前为止,这么容易?
所以,我们所需要做的就是在助手中更改createExpense()函数,以完成前面提到的所有复杂的事情。这是代码。
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);
},
需要注意的事项
虽然我们已经介绍了将客户端Lightning组件代码与服务器端Apex代码连接起来的所有必要事项,但是有几件事值得你在知道的地方咬你之前指出。
第一个问题是区分大小写,这一般归结为Apex和Salesforce通常不区分大小写,但JavaScript区分大小写。也就是说,“Name”和“name”在Apex中是相同的,但在JavaScript中是不同的。
这可能会导致绝对疯狂的错误,即使是在你的面前是完全不可见的。特别是如果您一直在Salesforce上使用非Lightning组件代码一段时间,您可能根本不会再考虑对象和字段名称,方法等等的情况。
因此,对于您来说,这是一个最佳实践:始终使用每个对象,字段,类型,类,方法,实体,元素,大象或您有什么的确切API名称。总是在任何地方,即使没有关系。这样,你就不会有问题。或者,至少不是这个问题。
我们希望引起你注意的另一个问题是“必需的”的性质。我们不能拒绝重复一句着名的引语:“你继续使用这个词。我不认为这意味着你的想法。“
在我们迄今为止编写的代码中,我们已经看到至少两种不同的“必需”。在“添加费用”表单的标记中,您会看到使用两种方式的单词。例如,在费用名称字段上。
<lightning:input aura:id="expenseform"
label="Expense Name"
name="expensename"
value="{!v.newExpense.Name}"
required="true"/>
var validExpense = component.find('expenseform').reduce(function (validSoFar, inputCmp) {
// 显示无效字段的错误消息
inputCmp.showHelpMessageIfInvalid();
return validSoFar && inputCmp.get('v.validity').valid;
}, true);