Lightning-组件(9)下一步

学习目标

完成本单元后,您将能够:

  • 列出可更深入探索的Lightning组件的五个方面。
  • 计划您可以对“费用”应用进行三项改进。
  • 赚。这个。徽章!

恭喜!

你做到了!我们为你感到兴奋,给你留下深刻的印象,并为你感到骄傲。严重的是,这是一个很难的模块,赚取这个徽章说了很多。恭喜!

这个单位的挑战就在于你和那个徽章之间的所有关系,毕竟你迄今为止所付出的努力,我们正在把这个简单化。但是,请不要跳过!虽然这个单位在挑战意义上是“容易的”,但是如果你想成为Lightning Components的开发者,那么这个单位就和所有其他单位一样重要。

Lightning组件基础模块教导了使用Lightning组件开发应用程序的基础知识,但这只是一个开始。只要这个模块已经到了这一步,实际上还有更多的东西要学习。至少,如果你想成为Lightning组件的主人。

在本单元中,我们将介绍两个主题。首先,我们将对这个模块中没有空间的事物进行调查,并提供可以开始了解这些事情的指针。之后,我们会建议您可以采取几个作业风格的项目。我们的小费用应用程序中有许多“显而易见”的补充,您可以尝试自己做。学习的最好方法就是在做!

什么我们掠过或压缩过去

当我们构建费用应用程序时,我们不得不说“我们不会在这里覆盖”这个次数超过了我们可以计算的次数。我们讨厌每一个人,你也可能也这样做。但是我们相信,如果我们把这个模块填充到八个小时以上,你会更加讨厌我们。

Salesforce闪电设计系统

SLDS是一个强大的,灵活的,全面的系统,在您的应用程序中实施Lightning Experience风格。您可以在Lightning组件,Visualforce中使用它,甚至在普通标记中使用它。 Salesforce文档小组的一位作者用它来创建…的原型,但是我们现在还不能告诉你。

SLDS是有趣的学习,甚至更有趣的使用。有一个专用于它的Trailhead模块(使用Visualforce)以及说明其用途的Trailhead项目的数量。还有一个哇哇网站专门把它放到人们的手中。

你可以在哪里使用闪电元件

我们以一系列的截图开始了这个话题,我们从来没有回头。但Lightning组件可以在Salesforce中以许多方式使用,甚至在其外部使用。

特别是,我们花了我们所有的时间来建立一个独立的“my.app”。您一定要学习如何将您的应用程序添加到Salesforce应用程序和闪电体验。好消息是,这很容易,这会让你想知道为什么我们没有掩盖它。 (请阅读,合作伙伴。)

“闪电组件开发人员指南”是关于在哪里以及如何使用Lightning组件应用程序的最佳信息来源。

调试

我们只涉及最原始的调试技术。学习调试你的Lightning组件应用程序,使用几种不同的复杂工具来做这件事,这将会带来好处,以你生命中的几个小时的形式回报,头发不会被拉出。

特别是,我们建议您学习Chrome的丰富DevTools套件以及在其中运行的Salesforce Lightning Inspector。 Apex开发人员也希望了解Apex提供的调试工具,其中许多工具直接位于开发者控制台中。

数据类型

我们简单地提到了一些可以用于属性的“特定于框架”的数据类型。主要是用于小平面,特别是身体的小平面。而且我们故意跳过了方面,特别是机身,因为它们很复杂,而且对于基本的Lightning Components开发来说并不是必不可少的。但是,最终你会想要了解所有这些概念,因为能够设置组件的主体(或其他方面)是一种强大的技术,可以为您节省大量的代码和尴尬。

助手

你在这个模块中写了一些相当有帮助的代码,但是还有一些帮助者的高级用法是值得了解的。由于助手是共享可重用代码的主要方式,因此这是一个重要的领域。

服务器请求生命周期和处理

我们介绍了如何使用$ A.enqueueAction()来创建服务器请求,以及如何处理成功的响应。还有其他的可能性,你真的需要知道如何处理它们。还有很多不同类型的请求,在某些情况下使用正确的请求可以显着提高应用程序的性能。

安全,安全,安全,安全

我们之前讲过,所以我们不会在这里。但是有很多东西需要知道。

应用程序事件

我们提到了这些,而且它们对于更大的应用程序很重要当我们介绍事件基础知识的时候,有很多东西需要了解。您无法使用Lightning组件构建复杂的应用程序,而无需了解所有事件。

我们什么都没有覆盖

还有一些我们没有提到的话题。其中有些是复杂的,有些是先进的,有些是两个。我们在这里列出一些您的考虑。

其他捆绑资源类型

我们介绍了四个核心Lightning组件捆绑资源类型。还有四个人。虽然设计和SVG资源有一些专门用途,但文档和渲染器资源在任何Lightning组件中都可能是有用的。

导航

你会被原谅,认为这是根本。在任何复杂的,真实世界的应用程序。在很多情况下,如果您正在使用Lightning Experience或Salesforce应用程序,开发导航实际上非常简单。但是,看下一个项目。

动态创建组件

在Lightning组件应用程序中“导航”的主要方式之一是动态创建新组件,以响应用户操作。这整个地区是丰富而强大的,而且足够复杂,你会在这个模块上一个星期的时间。我们会给你一个搜索词,你可以开始探索 $A.createComponent().

错误处理

有处理服务器错误,处理错误是纯粹的客户端。为了本单元的目的,我们假设你永远是成功的。可悲的是,在现实世界中,错误和怪异都以惊人的可靠性发生。伟大的工程师说得最好:任何可能出错的地方都会出错。所以你不妨尽可能地计划处理和从错误中恢复过来。

force:命名空间组件和事件

当您的Lightning组件应用程序在Lightning Experience和Salesforce应用程序中运行时,可以使用许多非常酷的组件和事件。其中一些组件在其他环境下工作,但其中许多组件在Lightning Experience和Salesforce应用程序中都很容易使用,所以我们没有看到它们。

系统事件

从技术上讲,我们以init处理程序的形式介绍了这一点,但是我们没有解释init事件是在组件或应用程序生命周期中可以捕获的一系列系统事件之一。 (对于这个问题,我们也没有谈论这个生命周期。)有一大堆,你可以用它们来让你的组件在它们存在的特定点上“做些什么”,或者处理特定的情况。

练习冒险

我们希望我们已经激发了更多的Lightning组件学习的胃口。这里有几个想法,我们可以做的事情,你可以做的费用应用程序,这将使有趣的探索,这自然适合你刚刚学到的东西。

提交后清除表格

现在,当你点击创建费用按钮,表单保持填充状态。将字段设置为空字符串并不困难,但是什么时候应该这样做?想想你想要的行为,可用性,以及服务器响应方面的各种可能性。 (即除了成功之外)

一旦你决定行为,你把代码放在哪里?听起来很简单,首先,你意识到服务器的响应处理是在开销中,而表单字段在expenseForm中。这听起来像一个工作?

显示“费用保存”消息

当您的费用成功保存到服务器上时,最好向用户显示一点成功信息。也许这消息是不同的更新报销?复选框与创建新的费用。或者,也许有时你根本不显示消息。

处理失败

如果字段上的数据验证规则阻止您保存服务器上的记录,会发生什么情况?或者发生其他一些错误?现在什么都没有该应用程序默默地失败。为了报销?复选框,该应用程序可以显示一个不正确的状态的费用。这两个都不好!

处理简单的故障实际上并不是那么多的代码行。但是在开始之前,你需要做一些阅读。

允许费用记录编辑

这是一种先进的,但你可以分阶段解决它。首先,当您单击费用列表中的费用时,请填写相应的费用值。然后让它将创建费用按钮的文本更改为保存费用。 (不要欺骗,并说它节省费用!)然后改变表单触发更新现有费用,而不是创建一个新的事件。

而就此,我们今天就离开你了。再次恭喜,我们希望您能在您的Lightning组件应用开发冒险中找到名气,财富和兴奋!

Lightning-组件(8)事件

学习目标

完成本单元后,您将能够:

  • 为您的应用定义自定义事件。
  • 从组件控制器创建和激发事件。
  • 创建动作处理程序来捕获和处理其他组件发送的事件。
  • 将一个大的组件重构成更小的组件。

连接组件与事件

在本单元中,我们将解决我们的小开支应用程序中最后一个未完成的功能:报销?复选框。你可能认为实现一个复选框将是一个简短的话题。我们当然可以采取一些捷径,并将其作为一个很短的话题。

但是除了使复选框工作之外,这个单元还有关于删除我们已经采取的所有快捷方式。我们要花这个单位“正确地做”,其中有几个地方意味着我们之前做过的重构工作。

在开始之前,我们先来谈谈我们采取的捷径,正确的道路,为什么正确的道路(有点)更难,但也更好。

组成和分解

如果您以源代码的形式看看我们的小费用应用程序,并列出单独的代码工件,您将得到如下内容。

  • expenses component
    • expenses.cmp
    • expensesController.js
    • expensesHelper.js
  • expensesList component
    • expensesList.cmp
  • expenseItem component
    • expenseItem.cmp
  • ExpensesController (server-side)
    • ExpensesController.apex

以下是所有内容如何组合在一起的情况,以及稍后您接线的createExpense和updateExpense事件。

The expenses app is made up of many smaller components.但是,如果你看屏幕上的应用程序,你看到了什么?你应该看到什么,你看到的最终会看到什么,是应用程序分解成更多的组件。您会看到,您可以将我们的应用程序进一步分解为更小的碎片,比迄今为止所做的更多。至少,我们希望您看到“添加费用”表单实际上应该是它自己的独立组件。 (这就是为什么我们在用户界面上画一个盒子的原因!)

为什么我们不把这个表格作为一个单独的组件?不这样做是迄今为止我们在这个模块的过程中最大的捷径。在软件设计方面,这比我们称之为“恶心”的黑客更糟。构建Lightning组件应用程序的正确方法是创建独立的组件,然后将它们组合在一起以构建新的更高级别的功能。为什么我们不采取这种方法?

我们采取了快捷方式,因为它将主要费用数组组件属性和影响它的控制器代码保存在同一个组件中,所以我们在“主费用”组件中保留了“添加费用”表单。我们希望createExpense()辅助函数能够直接触碰费用数组。如果我们将“添加费用”表单移到单独的组件中,那是不可能的。

为什么不?我们很早就介绍了这个原因,但是现在我们想要真正的琢磨一下。闪电组件应该是独立的。它们是封装所有基本功能的独立元素。一个组件不允许触及另一个组件,甚至是一个子组件,并且改变它的内部组件。

有两种主要的方式与另一个组件进行交互或影响其他组件。第一种方法就是我们已经看到并做了相当多的工作:在组件的标签上设置属性。组件的公共属性构成了其API的一部分。

与组件交互的第二种方式是通过事件。与属性一样,组件声明它们发出的事件以及它们可以处理的事件。和属性一样,这些公共事件构成了组件的公共API的一部分。我们实际上已经使用和处理了事件,但事件隐藏在一些便利功能之后。在这个单元中,我们将把事件拖入光明中,并创建一些我们自己的事物。

布线电路隐喻再次

这两种机制(属性和事件)是API“套接字”,即将组件连接在一起形成完整电路的方式。事件也在幕后,流经电路的电子。但这只是事件与属性不同的一种方式。

当您将<lightning:button>上的onclick属性设置为组件控制器中的操作处理程序时,将在这两个组件之间创建一个直接关系。他们是联系在一起的,当他们使用公共API保持彼此独立时,他们仍然是耦合的。

事件是不同的。组件不会将事件发送到其他组件。事实并非如此。组件广播特定类型的事件。如果有一个组件对这种类型的事件作出响应,并且该组件“听到”了你的事件,那么它就会采取行动。

您可以将属性和事件之间的区别视为有线电路和无线电路之间的区别。我们在这里不是说无线电话。一个组件不能获得另一个组件的“编号”并调用它。那将是一个属性。不,事件就像无线广播。你的组件到达收音机,并发出一条消息。有没有人打开收音机,并调整到正确的频率?你的组件没有办法知道 – 所以你应该以这样的方式编写你的组件,如果没有人听到他们广播的事件。 (也就是说,事情可能不起作用,但什么都不应该崩溃。)

从组件发送事件

好的,足够的理论,让我们做一些特定的应用程序,看看事件如何在代码中工作。我们将开始实施报销?复选框。然后,我们将采取我们所学到的,并用它来重构“添加费用”表格到自己的组件,这是伟大的工程师的意图。

首先,我们将重点放在Reimbursed__c字段的<lightning:input>上的点击处理程序。

<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:input>必须提供的内容。 type =“toggle”实际上是一个带有切换开关设计的复选框。类使您可以应用自定义CSS样式或使用SLDS实用程序。 messageToggleActive和messageToggleInactive为选中和未选中的位置提供自定义标签。这些便捷的属性只是<lightning:input>上的其他几个属性。最后,<lightning:input>的onchange属性为我们提供了一个简单的方法,可以将切换开关连接到一个动作处理程序,当您向右滑动(选中)或向左滑动(未选中)时更新记录。

现在,让我们考虑一下选中或不选中时会发生什么。根据我们编写的代码来创建新的费用,更新费用可能是这样的。

  1. 获取已更改的费用项目。
  2. 创建一个服务器操作来更新基础费用记录。
  3. 将费用计入行动。
  4. 设置一个回调来处理响应。
  5. 触发操作,将请求发送到服务器。
  6. 当响应到来并且回调运行时,更新费用属性。

呃,什么费用属性?再看看我们的组件标记。没有开支,只是一个单一的开支。嗯,对,这个组件只是一个单一的项目。在费用清单组件上有一个费用属性…但这甚至不是“真正的”费用。真正的是顶级费用组件中的一个组件属性。嗯。

有没有一个component.get(“v.parent”)?或者,它必须是component.get(“v.parent”)。get(“v.parent”) – 让我们得到父母的父母的引用,所以我们可以在那里设置费用?

停止。对。那里。

组件不会触及其他组件,并在其上设置值。没有办法说“嘿,祖父母,我会更新费用。”组件保持自己的手。当一个组件想要一个祖先组件改变某些东西时,它会问。奈斯利。通过发送一个事件。

这是最酷的部分。发送事件看起来几乎与直接处理更新相同。这是clickReimbursed处理程序的代码。

({
    clickReimbursed: function(component, event, helper) {
        var expense = component.get("v.expense");
        var updateEvent = component.getEvent("updateExpense");
        updateEvent.setParams({ "expense": expense });
        updateEvent.fire();
    }
})
哇。这很简单!它看起来有点像我们上面所设想的。 clickReimbursed的上述代码执行以下操作:
  1. 获取更改的费用。
  2. 创建一个名为updateExpense的事件。
  3. 将费用打包到事件中。
  4. Fires 事件。

回调的东西丢失,但否则这是熟悉的。但是…什么是处理调用服务器和服务器响应,并更新主费用数组属性?我们怎么知道这个updateExpense事件呢?

updateExpense是一个自定义事件,也就是我们自己写的一个事件。你可以这样说,因为与获取服务器动作不同,我们使用component.getEvent()而不是component.get()。另外,我们所得到的没有价值提供者,只是一个名字。我们将在一瞬间定义这个事件。

至于什么是处理调用服务器和处理响应,让我们来谈谈它。我们可以在costItem组件中实现服务器请求并处理响应。然后,我们会发送一个事件,只是放弃依赖于费用数组的事情。这将是一个完全有效的设计选择,并将保持费用项目组件完全独立,这是可取的。

但是,正如我们将看到的,创建新费用的代码和更新现有费用的代码非常相似,足以避免重复的代码。因此,我们所做的设计选择是发送一个updateExpense事件,主费用组件将处理。后来,当我们重构我们的表单时,我们也会创建一个新的开销。

通过让所有子组件委托处理服务器请求和管理费用数组属性的责任,我们打破封装一点。但是,如果将这些子组件视为费用组件的内部实现细节,那也没关系。主要费用部分是独立的。

您可以选择:合并关键逻辑或封装。您可以在Lightning Components中进行权衡,就像在任何软件设计中进行权衡一样。只要确保你记录的细节。

定义一个事件

我们要做的第一件事是定义我们的自定义事件。在开发者控制台中,选择 File | New | Lightning Event, 并将事件命名为“updateEvent”。用下面的标记替换默认的内容。

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

有两种类型的事件,组件和应用程序。这里我们使用了一个组件事件,因为我们想要一个祖先组件来捕捉和处理这个事件。祖先是组件层次结构中的一个组件“之上”。如果我们想要一个“通用广播”类事件,任何组件都可以接收它,我们将使用应用程序事件。

应用程序和组件事件的完全区别和正确的用法并不是我们能够在这里得到的。这是一个更高级的话题,有足够复杂的细节,这是我们在这个模块中的目的。当你准备好了更多,资源将帮助你。

另外要注意的是事件的定义是多么紧凑。我们在创建时命名了这个事件,其中包括了费用更新日期,它的标记是一个开始和结束的<aura:event>标记和一个<aura:attribute>标记。事件的属性描述了它可以携带的有效载荷。在clickReimbursed操作处理程序中,我们通过调用setParams()来设置有效载荷。在事件定义中,我们看到事件参数是如何定义的,并且没有其他有效的参数。

而这几乎都是定义事件。您不要将实现或行为细节添加到事件本身。他们只是包。事实上,有些事件根本没有任何参数。他们只是消息。 “发生了这种情况!”发生和接收事件的组件中定义了“如果发生这种情况”的所有行为。

发送事件

我们已经看过如何在clickReimbursed操作处理程序中实际触发一个事件。但是要做到这一点,我们需要做最后一件事,那就是注册事件。将这一行标记添加到费用项组件,正好在其属性定义下方。

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

这个标记说我们的组件触发了一个名为“updateExpense”的类型为“c:expensesItemUpdate”的事件。但是,当我们定义它时,不是“expensesItemUpdate”这个事件的名字吗?而组件或应用程序事件类型发生了什么?

你认为这有点令人困惑 – 这实际上是一个开关式的转换。这可能有助于将“应用程序”和“组件”视为Lightning Components框架事件类型,而来自您定义的事件名称的类型是自定义事件类型或事件结构类型。也就是说,当你定义一个事件时,你可以定义一个包的格式。当你注册发送一个事件时,你声明了它使用的格式。

定义和注册一个事件的过程可能看起来有些奇怪,所以让我们先看一下。这里在expenseItem中,我们将发送一个名为updateExpense的事件。稍后在expenseForm中,我们将发送一个名为createExpense的事件。这两个事件都需要包含一笔费用才能保存到服务器上。因此,他们都使用c:expensesItemUpdate事件类型或包格式发送事件。

在接收方,我们的主要支出部分将要注册来处理这两个事件。尽管服务器调用结束了,但用户界面更新略有不同。那么费用如何知道是否在c:expensesItemUpdate包中创建或更新费用?通过正在发送的事件的名称。

了解这里的区别,以及如何将一个事件用于多种目的,是学习Lightning组件的灯泡时刻。如果你还没有那个时刻,那么当你看其余的代码的时候,你就会拥有它。

在开始处理事件之前,让我们总结一下发送它们的过程。

  1. 通过创建闪电事件定义自定义事件,给它一个名称和属性。
  2. 注册您的组件发送这些事件,通过选择一个自定义事件类型,并给这种类型的具体使用一个名称。
  3. 通过以下方式在控制器(或助手)代码中触发事件:
    1. 使用component.getEvent()来创建特定的事件实例。
    2. 用fire()发送事件。

如果你继续执行所有我们刚刚看过的代码,你可以测试一下。重新加载您的应用程序,并切换报销?复选框几次。如果你错过了一步,你会得到一个错误,你应该重新检查你的工作。如果你做的一切正确…嘿,等等,花费改变颜色显示其报销?状态,就像预期的一样!

这个行为在我们开始这个单位之前就已经存在了。这就是<lightning:input>组件具有value =“{!v.expense.Reimbursed__c}”集合的效果。切换开关时,费用的本地版本将更新。但是这个改变没有被发送到服务器。如果您查看Salesforce中的费用记录,或重新加载应用程序,则不会看到更改。

为什么不?我们只做了一半的工作来为我们的活动创建一个完整的电路。我们必须通过在另一端创建事件处理程序来完成电路布线。该事件处理程序将负责将更改发送到服务器,并使更新持久。

处理事件

使费用项组件发送一个事件需要三个步骤。启用费用组件来接收和处理这些事件需要三个并行的步骤。

  1. 定义一个自定义事件。我们已经做到了这一点,因为费用项目正在发送费用正在接收的相同的自定义事件。
  2. 注册组件来处理事件。这将事件映射到动作处理程序。
  3. 实际上在动作处理程序中处理事件。

由于我们已经完成了第一步,我们立即转到第二步,注册费用来接收和处理updateExpense事件。就像注册发送一个事件一样,注册来处理一个事件是一行标记,你应该在init处理程序之后添加到费用组件中。

     <aura:handler name="updateExpense" event="c:expensesItemUpdate"
        action="{!c.handleUpdateExpense}"/>

与init处理程序一样,它使用<aura:handler>标记,并具有为该事件设置控制器操作处理程序的action属性。就像当你在costItem中注册事件一样,在这里你设置了事件的名字和类型 – 尽管注意到这个类型使用了更明智的事件属性。

换句话说,在这里你没有看到太多。什么是新的,特定于处理自定义事件,是属性的组合,并知道如何在费用中的接收器“套接字”与费用项中的发件人“套接字”相匹配。

这完成了这项工作的接线部分。剩下的就是实际编写动作处理程序!

我们将从handleUpdateExpense动作处理程序开始。这里是代码,并确保把它放在clickCreate动作处理器下。

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

呵呵。那很有意思。除了表单验证检查和特定的帮助函数,我们正在委托工作,它看起来像这个动作处理程序是一样的handleCreateExpense。

现在让我们添加updateExpense帮助函数。正如我们对动作处理器所做的那样,请确保将该代码放在createExpense帮助器函数的正下方。

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

两件事你应该注意到的蝙蝠。首先,除了回调细节外,updateExpense帮助器方法与createExpense帮助器方法相同。这听起来像机会。

其次,关于这些回调细节。是什么赋予了?怎样才能做正确的事情呢?

想一想。之前,在测试发送事件(如果不是之前)时,我们看到了费用项组件的颜色是否改变以响应已报销的?复选框。记得解释吗?费用记录的本地副本已经更新!所以,至少现在,当服务器告诉我们它已经成功地更新它的版本时,我们不必做任何事情。

请注意,此代码仅处理服务器成功更新费用记录的情况。如果出现错误,我们肯定会有一些工作要做。说,会计将这笔费用标记为无法报销,因此无法将此字段设置为true。但是,正如他们所说,这是另一天的教训。

重构帮助函数

让我们回到我们看到的那个机会来分解一些常见的代码。除了回调之外,这两个辅助函数是相同的。所以,我们来创建一个新的,更广义的函数,将回调作为参数。

    saveExpense: function(component, expense, callback) {
        var action = component.get("c.saveExpense");
        action.setParams({
            "expense": expense
        });
        if (callback) {
            action.setCallback(this, callback);
        }
        $A.enqueueAction(action);
    },
回调参数是可选的。如果它在那里,我们会把它传递给行动。简单。现在我们可以将我们的事件特定的帮助函数减少到下面的代码。
    createExpense: function(component, expense) {
        this.saveExpense(component, expense, function(response){
            var state = response.getState();
            if (state === "SUCCESS") {
                var expenses = component.get("v.expenses");
                expenses.push(response.getReturnValue());
                component.set("v.expenses", expenses);
            }
        });
    },

    updateExpense: function(component, expense) {
        this.saveExpense(component, expense);
    },
createExpense只是稍微短一些,但它完全专注于响应返回时的操作(回调)。哇,updateExpense是一个单线!

重构添加费用表单

那个小小的重构练习是如此令人满意,使用事件是如此(我们很抱歉)让人激动,让我们再做一次,但是做得更大。更多的牛铃!

接下来的任务包括从费用组件中提取“添加费用”表单,并将其移至自己的新组件。提取表单标记很简单,只需简单的复制粘贴练习即可。但是还有什么动作呢?在我们开始移动碎片之前,让我们先思考一下什么是移动和停留。

在当前的设计中,表单的动作处理器clickCreate处理输入验证,将请求发送到服务器,并更新本地状态和用户界面元素。表单仍然需要一个动作处理程序,而且应该还可以处理表单验证。但是我们打算让其余的留下来,因为我们将我们的服务器请求逻辑合并到费用组件中。

所以有一点点(但只有一点点),在那里戏弄。那么我们的计划就是先移动表单标记,然后尽可能少地移动,以使其正常工作。我们将重构这两个组件以通过事件进行通信,而不是直接访问费用数组组件。

让我们开始吧!

在主费用组件中,选择两个<! – CREATE NEW EXPENSE – >注释之间的所有内容,包括开始和结束注释本身。把它剪切到你的剪贴板。 (是的,切,我们承诺)

创建一个新的Lightning组件,并将其命名为“expenseForm”。将复制的“添加费用”表单标记粘贴到<aura:component>标记之间的新组件中。

返回费用组件。将新的expenseForm组件添加到标记中。这部分费用应该是这样的。

    <!-- 新的费用表格 -->
    <lightning:layout >
        <lightning:layoutItem padding="around-small" size="6">
            <c:expenseForm/>
        </lightning:layoutItem>
    </lightning:layout>

此时,您可以重新加载您的应用程序以查看更改。应该没有明显的变化。但是,不出所料,创建费用按钮不再起作用。

让我们快速浏览其余的部分动作。

接下来,将费用组件中的newExpense属性移至expenseForm组件标记。这是用于表单域,所以它需要在表单组件。它移动不需要更改,所以只需从一个剪切并粘贴到另一个。

在expenseForm组件中,创建控制器和帮助器资源。

将clickCreate操作处理程序从费用控制器移至expenseForm控制器。按钮位于表单组件中,因此按钮的操作处理程序也需要在那里。相信与否,这也不需要改变。 (你可能在这里开始感受一个主题。)

现在我们需要做一些实际的改变。但是这些会很熟悉,因为我们只是添加事件发送,这是我们之前做的费用项目。您会记得,expenseItem还会发送一个由费用组件处理的费用负载的事件。

在expenseForm助手中,创建createExpense函数。

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

这看起来非常像在expenseItem中的clickReimbursed操作处理程序。

如果组件要发送事件,则需要注册该事件。将以下内容添加到expenseForm组件标记中,位于newExpense属性下方。

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

在这一点上,我们已经完成了所有的工作来实现expenseForm组件。您应该可以重新加载应用程序,并且表单现在“有效”,因为没有错误,您输入无效数据时应该会看到相应的表单消息。如果您使用的是Salesforce Lightning Inspector,则甚至可以看到正在触发expenseItemUpdate事件。剩下的就是处理它。

在我们处理事件之前,请注意这个重构是多么容易。大部分的代码没有改变。在前面的步骤中总共有六行新的代码和标记。今天做这个工作是不熟悉的,但是要做几次,而且你意识到你只是在移动一些代码。

好的,让我们来完成这个。 expenseForm触发createExpense事件,但我们还需要费用组件来捕获它。首先我们注册createExpense事件处理程序,并将其连接到handleCreateExpense操作处理程序。再次,这是一个单一的标记线。将此行添加到updateExpense事件处理程序的正上方或下方。

    <aura:handler name="createExpense" event="c:expensesItemUpdate"
        action="{!c.handleCreateExpense}"/>
最后,在最后一步,在费用控制器中创建handleCreateExpense操作处理程序。在handleUpdateExpense操作处理程序的上方或下方添加此代码。
    handleCreateExpense: function(component, event, helper) {
        var newExpense = event.getParam("expense");
        helper.createExpense(component, newExpense);
    },

是的,那很简单。所有这些工作都委托给createExpense帮助函数,并且不会移动或更改。我们的handleCreateExpense动作处理程序就在那里把正确的东西连接在一起。

于是,我们完成了如何使用事件松散地耦合组件。在一个组件中创建和激发事件,在另一个组件中捕获并处理事件。无线电路!

奖金课初级视觉改进

在我们进入夕阳之前,或更确切地说,挑战之前,这里是两个适度的视觉改善。

首先,你有没有注意到报销和相关日期时间标签在绿色背景上几乎是看不见的蓝色?

Blue labels on green fix

如果它也困扰着你,那么这里是如何解决它的。 (我们一直这样做直到我们将表单重构为自己的组件)。在费用项目组件中,通过单击STYLE按钮添加一个样式资源。用下面的代码替换默认的样式规则。

.THIS.slds-theme_success .slds-form-element__label {
    color: #ffffff;
}
.THIS.slds-theme_success .slds-text-title {
    color: #ffffff;
}
重新加载应用程序的甜美,甜美的眼睛应变救济。

最后,我们想通过添加一些容器组件来改进我们的应用程序的布局。最后一点也让您有机会在所有的变化之后看到完整的费用部分。在费用组件中,将费用列表标记替换为以下内容。

<lightning:layout>
    <lightning:layoutItem padding="around-small" size="6">
        <c:expensesList expenses="{!v.expenses}"/>
    </lightning:layoutItem>
    <lightning:layoutItem padding="around-small" size="6">
        Put something cool here
    </lightning:layoutItem>
</lightning:layout>
这些变化的影响是增加一些边距和填充,并使费用列表更窄。布局留下的空间把东西放在右边。在下一个单元中,我们会建议你可以自己做一些练习。

Lightning-组件(7)服务器端控制器

学习目标

完成本单元后,您将能够:

  • 创建可以从Lightning组件代码远程调用的Apex方法。
  • 从Lightning组件调用远程方法。
  • 使用回调函数异步处理服务器响应。
  • 伸展目标:解释“c。”,“c:”和“c。”之间的区别。

服务器端控制器的概念

到目前为止,我们所做的一切都是严格的客户端。我们还没有将开支节省回到Salesforce。创建一些费用,然后打重装,会发生什么?没错,所有的费用都消失了。呜呼,免费的钱!

除了刚才所说的会计,他们对这种事情都是有些冷淡的。而实际上,我们是不是想要报销那些从我们口袋里拿出来的费用呢?哎呀!我们需要将我们的数据保存到Salesforce,不再有任何延误!

开玩笑,现在是时候把服务器端控制器添加到我们的应用程序。我们一直把这个拿回来,而我们的基础知识倒了。现在你已经准备好了,让我们潜入!

进入一些图片,就是。让我们确保我们知道我们的目标,并且在我们上路之前,我们的油箱已经满了。

首先,让我们重温一下我们在这个模块中看到的第一个图表,一个非常高层次的Lightning组件应用程序的体系结构。

A very high level architecture of Lightning Components: client view and controller, server apex controller and database

到目前为止,我们所看到的一切都在这张照片的客户端上。 (注意我们通过在这里合并控制器和帮助器来简化。)虽然我们引用了一个自定义的对象类型,Expense__c,它是在服务器端定义的,但我们并没有直接触及服务器。

请记住我们如何将不同的元素连接起来以创建一个完整的电路?我们在最后一个单位建立的费用形式可能看起来像这样。

Client side of flow

该电路以创建按钮开始,该按钮连接到clickCreate操作处理程序(1)。当动作处理程序运行时,它会从表单字段(2)中获取值,然后向费用数组(3)添加新的开销。当数组通过设置更新时,触发费用列表(4)的自动重新显示,完成电路。很简单,对吧?

那么,当我们在服务器端访问时,图表会变得更加复杂。更多的箭头,更多的颜色,更多的数字! (暂时解释一切吧。)

Complete flow: client and server-side

而且,这个电路没有相同的平滑,同步的控制流程。服务器通话费用很高,可能需要一些时间。情况良好时为毫秒,网络拥塞时为长时间。 Lightning组件不希望应用程序在等待服务器响应时被锁定。

在等待时保持响应的解决方案是异步处理服务器响应。这意味着什么,当你点击Create Expense按钮时,你的客户端控制器触发一个服务器请求,然后继续处理。它不仅不等待服务器,它忘记了它提出的要求!

然后,当响应从服务器返回时,与请求打包在一起的代码(称为回调函数)将运行并处理响应,包括更新客户端数据和用户界面。

如果你是一个有经验的JavaScript程序员,异步执行和回调函数可能是你的面包和黄油。如果你之前没有和他们一起工作,这将是新的,也许是非常不同的。这也很酷。

从Salesforce查询数据

我们将从阅读Salesforce数据开始,在费用应用程序启动时加载现有费用列表。

注意

如果您尚未在Salesforce中创建一些真正的费用记录,现在将是一个好时机。否则,在执行后面的内容之后,您可能会花时间调试为什么没有加载,实际上什么也没加载。你这卑微的作者是为这一切而堕落的。的。时间。

第一步是创建您的Apex控制器。 Apex控制器包含您的Lightning组件可以调用的远程方法。在这种情况下,查询和接收来自Salesforce的费用数据。

我们来看一下代码的简化版本。在开发者控制台中,创建一个名为“ExpensesController”的新Apex类,并粘贴以下代码。

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];
    }
}
我们将在下一节深入讨论Apex控制器,但现在这是一个非常简单的Apex方法。它运行一个SOQL查询并返回结果。只有两个具体的事情,使这个方法可用于您的闪电组件代码。
  • 方法声明之前的@AuraEnabled注解。
    “Aura”是Lightning组件核心的开源框架的名称。您已经看到它在一些核心标签的命​​名空间中使用,如<aura:component>。现在你知道它来自哪里。
  • 静态关键字。所有@AuraEnabled控制器方法都必须是静态方法,可以是公共范围或全局范围。

如果这些要求提醒您使用Visualforce的JavaScript远程功能的远程方法,那不是巧合。要求是一样的,因为架构在关键点非常相似。

另一件值得注意的事情是该方法没有做任何特殊的事情来打包Lightning组件的数据。它只是直接返回SOQL查询结果。 Lightning组件框架处理大多数情况下涉及的所有编组/解组工作。太好了!

从Salesforce加载数据

下一步是将费用组件连接到服务器端的Apex控制器。这很容易,可能会让你头晕目眩。将费用组件的开头<aura:component>标记更改为指向Apex控制器,如下所示。

<aura:component controller="ExpensesController">
新的部分以粗体突出显示,是的,这真的很简单。

但是,指向Apex控制器并不实际加载任何数据,或者调用远程方法。就像组件和(客户端)控制器之间的自动连线一样,这个指向只是让这两个组件“彼此​​了解”。这个“知道”甚至采取了同样的形式,另一个价值提供者,我们稍后会看到。但是自动布线只有这么远。完成电路仍然是我们的工作。

在这种情况下,完成电路意味着以下。

  1. 当费用组件被加载时,
  2. 查询Salesforce的现有费用记录,和
  3. 将这些记录添加到费用组件属性。

我们将依次采取每一个。第一个项目,当费用组件首次被加载时触发行为,要求我们编写一个init处理程序。这只是一个连接到组件的init事件的动作处理程序的简称,当组件首次创建时会发生这种情况。

你需要的接线是一行标记。将以下内容添加到组件的属性定义之下的费用组件。

<aura:handler name="init" action="{!c.doInit}" value="{!this}"/>
<aura:handler>标签是你如何说组件可以处理特定的事件。在这种情况下,我们说我们将处理init事件,我们将使用控制器中的doInit操作处理程序来处理它。 (设置值=“{!this}”将其标记为“值事件”,这意味着这里太复杂了,只要知道应该将这个属性值对添加到init事件中。

调用服务器端控制器方法

一步下去,两步走。剩下的步骤都在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);
    },

在这里,所有新事物都让你感到迷惘,请注意,这只是另一个动作处理程序。它的格式相同,功能签名相同。我们在熟悉的领域。

也就是说,函数签名后的每一行代码都是新的。我们稍后会看看所有这些,但是这里是这个代码的大纲:

  1. 创建一个远程方法调用。
  2. 设置远程方法调用返回时应该发生的事情。
  3. 排队远程方法调用。

这听起来很简单,对吧?也许代码的结构或特性是新的,但是需要发生什么的基本要求再次被熟悉。

这听起来像我们真的很鼓舞人心?比如说,也许我们试图通过一个粗糙的补丁来指导你?那么,我们需要谈论一些艰难的事情。该问题出现在函数的第一行代码中。

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

这行代码创建我们的远程方法调用,或远程操作。首先,component.get()部分看起来很熟悉。我们已经做了很多次了。

除了…之前,我们得到的是“v.something”之前,v是视图的价值提供者。这里是“c”,是的,c是另一个价值提供者。我们已经在press =“{!c.clickCreate}”和action =“{!c.doInit}”这样的表达式中看到了一个c值提供者。

在视图中,这些表达式在组件标记中。在控制器中,c值提供者表示不同的东西。它代表远程Apex控制器。

“等一下。你是否告诉我,我们有客户端控制器,c默认命名空间,c服务器端控制器,都在Lightning组件中?“

那么,一句话,是的。深呼吸。

看,我们会诚实的对你。如果我们把这一切都做了,我们可能会做出一些不同的选择。虽然我们所做的选择不是事故,但三个“c”绝对是一个混乱的机会。我们也感到困惑!

但是正如他们所说的那样,就是这样。有备则无患。现在你知道了。

识别

上下文

含义

c.

组件标记

客户端控制器

c.

控制器代码

服务器端控制器

c:

标记

默认命名空间

好的,回到我们的代码。在我们偏离之前,我们正在看这条线。

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

在之前的代码中,component.get(“v.something”)返回给我们对视图(组件标记)中的子组件的引用,component.get(“c.whatever”)返回对可用操作的引用在控制器中。在这种情况下,它会向我们的Apex控制器返回一个远程方法调用。这是如何创建对@AuraEnabled方法的调用。

下一行“action.setCallback(…)”是远程方法调用返回时将运行的代码块。由于这种情况发生在“稍后”,所以我们暂且搁置一边。

实际运行的下一行是这一行。

        $A.enqueueAction(action);

我们之前简单地看过$ A,但没有讨论过。这是一个框架全局变量,提供了一些重要的功能和服务。 $ A.enqueueAction(action)将我们刚刚配置的服务器调用添加到Lightning Components框架请求队列中。它连同其他未决的服务器请求将在下一个请求周期中发送到服务器。

这听起来有些模糊。完整的细节非常有趣,对于Lightning组件的高级使用非常重要。但现在,这是你需要知道的$ 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()的参数。 在真正的动作处理程序代码中,我们称之为如此。
        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());
        }
    },
我们会再说一遍。异步执行和回调函数对JavaScript程序员来说是必须的,但是如果你来自另一个背景,那么可能不太熟悉。希望我们已经把它放在了这个位置,因为它是使用Lightning组件开发应用程序的基础。

处理服务器响应

现在我们已经得到了创建一个服务器请求的结构,让我们来看看我们的回调函数实际处理响应的细节。这里只是回调函数。

    function(response) {
        var state = response.getState();
        if (state === "SUCCESS") {
            component.set("v.expenses", response.getReturnValue());
        }
    }
回调函数采用单个参数,响应,这是一个不透明的对象,提供返回的数据(如果有的话)以及有关请求状态的各种细节。

在这个特定的回调函数中,我们执行以下操作。

  1. 获取响应的状态。
  2. 如果状态是成功的,也就是说,我们的要求按计划完成,那么:
  3. 将组件的费用属性设置为响应数据的值。

你可能有几个问题,比如:

  • 如果响应状态不是成功会发生什么?
  • 如果回应永远不会到来会发生什么? (打电话给我,可能的话。)
  • 我们如何才能将响应数据分配给我们的组件属性?

前两个答案不幸的是,我们不打算在这个模块中介绍这些可能性。他们当然是你需要知道的事情,并考虑在你的真实世界的应用程序,但我们只是没有空间。

最后一个问题在这里是最相关的,但也是最容易回答的。我们为费用属性定义了一个数据类型。

<aura:attribute name="expenses" type="Expense__c[]"/>
而我们的服务器端控制器动作有一个方法签名,它定义了它的返回数据类型。
public static List<Expense__c> getExpenses() { ... }

类型匹配,所以我们可以只分配一个到另一个。闪电组件处理所有的细节。您当然可以自己处理结果,并在应用程序中将其转换为其他数据。但是,如果你正确地设计你的服务器端操作,你不一定非要。

好吧,这是很多不同的方式来看十几行代码。这里的问题是:你有没有尝试过你的我们的应用程序的版本呢?因为我们已经完成了Salesforce部分的加载费用。重新加载应用程序,并查看您在Salesforce中输入的费用是否显示!

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方法saveExpense()。它需要一个Expense(Expense__c)对象并插入它。这使我们可以使用它来创建新的记录和更新现有的记录。

接下来,请注意,我们使用with sharing关键字创建了该类。这将自动将组织的共享规则应用于通过这些方法可用的记录。例如,用户通常只会看到自己的费用记录。 Salesforce会自动在幕后为您处理所有复杂的SOQL规则。

使用with共享关键字是编写服务器端控制器代码时需要采取的基本安全措施之一。但是,这是必要的措施,但还不够。你看到有关执行isAccessible()和isUpdatable()检查的意见吗?分享只会带你到目前为止。尤其是,您需要自己实现对象和字段级别的安全性(您经常会看到缩写为FLS)。

例如,下面是我们的getExpenses()方法的一个版本,该安全性最低限度地实现。

    @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];
    }

这是我们最初的单线的一个扩展,而且还是足够的。另外,描述通话费用很高。如果您的应用程序经常调用此方法,则应该找到一种方法来优化或缓存每个用户的访问权限检查。

和SLDS一样,我们根本没有空间来教授安全Apex编码的所有细节。与SLDS不同,承担您编写的代码的安全性不是可选的。如果您还没有阅读参考资料中的安全编码实践文章,请将其添加到您的队列中。

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

这是否像你所期望的那样复杂?多少行?我们希望不是!

事实上,这个动作处理器中只有一件新事物,而且很容易理解。我们来看看代码。

我们首先创建action,使用component.get(“c.saveExpense”)获取新的Apex控制器方法。很熟悉。

接下来,我们将数据有效载荷附加到该操作。这是新的。我们需要将新费用的数据发送到服务器。但看看它是多么容易!您只需使用action.setParams()并提供带有参数名称 – 参数值对的JSON样式的对象。一个技巧,重要的是您的参数名称必须与Apex方法声明中使用的参数名称匹配。

接下来我们设置请求的回调。同样,这是服务器返回响应时会发生的情况。如果您将此回调函数与我们的原始createExpense帮助函数进行比较,则实际上是相同的(减去恶心的黑客)。

就像在之前的版本中一样,我们得到()费用属性,将一个值push()到它上面,然后set()它。唯一真正的区别是,我们不是将我们本地版本的新开销push到数组中,而是推送服务器的响应!

为什么这个工作?因为服务器端方法插入(在这种情况下是新的)记录,其上印上一个ID,然后返回结果记录。服务器端和客户端数据类型再次匹配,所以我们不需要做任何额外的工作。

而且,就是这样。不需要滚石!

需要注意的事项

虽然我们已经介绍了将客户端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"/> 

<lightning:input>标记的必需属性设置为true。这些都说明了所需要的一个含义,即“设置该元素的用户界面以指示该字段是必需的”。换句话说,这只是表面化的。这里没有保护您的数据的质量。

在我们为同一领域编写的验证逻辑中说明了“必需”一词的另一个含义。

var validExpense = component.find('expenseform').reduce(function (validSoFar, inputCmp) {
    // 显示无效字段的错误消息
    inputCmp.showHelpMessageIfInvalid();
    return validSoFar && inputCmp.get('v.validity').valid;
}, true);

“必需的”这个词是无处可见的,但这就是验证逻辑强制执行的。您必须为费用名称字段设置一个值。

而且,就这一点而言,这太棒了。您的费用表格不会以空名称提交新费用。除非,你知道,有一个错误。或者,也许其他一些小部件使用相同的服务器端控制器,但并不仔细地进行表单验证。等等。所以,这对您的数据质量有一些保护,但并不完美。

您如何执行,我们的意思是执行一个数据完整性规则,在这个例子中是关于费用名称?你做服务器端。而不仅仅是服务器端的任何地方。您将规则放在字段定义中,或者将其编码到触发器中。或者,如果你是一个腰带式和吊带式的工程师,就像所有正确的思维工程师一样。

为了真正的数据完整性,当需要“需要”意味着需要的时候,尽可能在最低级别执行。

Lightning-组件(6)输入

学习目标

完成本单元后,您将能够:

  • 创建一个窗体来显示当前值并接受新的用户输入。
  • 从表单元素读取值。
  • 验证用户输入并显示无效输入的错误消息。
  • 将组件的控制器中的代码重构为其助手。

使用表单输入数据

就这个单元而言,我们完成了helloWhatever-style的组件。从这里开始,我们将创建和组装我们之前预览过的费用跟踪器迷你应用程序。我们将花费大部分的单位建设和理解的形式,让你创造一个新的开支。

费用应用程序容器

但是在开始之前,我们还需要做一些简单或者彻头彻尾的丑陋的组件。那么我们要做的第一件事就是引入Salesforce Lightning Design System或SLDS,并在我们的应用程序中“激活”它。我们这样做的方式让我们谈一谈关于应用程序容器。

注意

我们实际上不会在这个单元里讨论SLDS本身,或者在模块的其余部分的任何地方。我们将重点介绍将它添加到应用程序中,然后在我们的示例代码中,我们将使用SLDS类,但不会详细解释它们。有关SLDS的更多信息,请参阅参考资料。

今天,在Lightning Experience或Salesforce应用程序中运行SLDS时,SLDS将自动提供给您的组件。我们有时会在one.app容器中调用它。该内置版本与许多标准Lightning组件使用的版本相同。但是,SLDS在独立应用程序中默认情况下不可用,或者在“闪电输出”或“闪电组件”中使用您的组件用于Visualforce。这些是不同的应用程序容器,它们提供不同的服务和资源。我们希望以这种方式创建我们的费用应用程序,使其在所有这些情况下都能够正常工作。幸运的是,这并不是很难做到。

我们将这样做的方式是将SLDS添加到我们的线束应用程序。然后,在“真正的”费用应用程序(实际上是顶级组件及其所有子组件)中,我们可以使用SLDS工具和技术,而不用担心SLDS资源样式表,图标等等 – 来自。也就是说,我们的应用程序容器(线束应用程序)在其上下文中设置资源,以便在该容器内运行的任何应用程序都拥有所需的资源。

让我们把这些罗嗦的概念转换成一些代码。使用以下标记创建一个新的expensesApp.app闪电应用程序。

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

</aura:application>

这是怎么回事通过包含Lightning Experience和Salesforce应用程序提供的Lightning Design System样式,extends =“force:slds”属性将激活此应用程序中的SLDS。但请注意,这个线束应用程序只是一个包装,一个外壳。真正的应用程序是费用组件,我们还没有创建。 (这是<! – c:expenses / – >部分;它被注释掉了,因为我们不能保存我们的应用程序直到费用组件实际存在。)

通过包装器应用程序,我们的组件使用extends =“force:slds”机制从这个应用程序运行时访问SLDS。当他们在Lightning Experience或Salesforce应用程序中运行时,无需更改代码,他们就会使用该容器自动包含SLDS。

在这种情况下,这相当于同样的事情。但是,这个使用外部线束应用程序来设置上下文的概念,以便真正的应用程序不需要担心上下文的差异,并不仅限于样式资源。你可以用它来提供替换事件处理程序,例如…虽然这已经超越了我们自己。在我们尝试飞行之前,我们学习走路吧!

应用程序组件的费用

下一步是创建我们的费用应用程序的顶级组件。 (请记住,尽管我们将其称为“应用程序”,但它实际上只是另一个Lightning组件)。在开发者控制台中,创建一个名为“费用”的新Lightning组件,并使用以下代码替换默认标记。

<aura:component>

    <!-- 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 -->

    <!-- 新的费用表格 -->
    <lightning:layout>
        <lightning:layoutItem padding="around-small" size="6">

        <!-- [[ 费用表单在这里]] -->

        </lightning:layoutItem>
    </lightning:layout>
    <!-- / NEW EXPENSE FORM -->

</aura:component>
我们在这里创建的是使用由<lightning:layout>和<lightning:layout>组件提供的网格布局的页眉。 size =“6”会创建一个宽度为50%(或12的6)的<div>容器。您可能已经注意到,闪电名称空间中的组件与Lightning Experience和Salesforce应用程序中的组件类似。除按钮和布局之外,您还可以在此名称空间中找到许多其他有用的组件,这些组件可与SLDS样式开箱即用。

注意

注意<lightning:icon>标签?这个组件呈现你喜欢的SLDS图标。你不得不创建一个帮助组件来显示SLDS图标的日子已经一去不复返了。

现在,您可以在实际的.app中取消注释<c:expenses />标记,并打开现在只是一个空壳的预览。你应该看到像下面这样的东西。

Basic My Expenses form

现在还不是很多,但是看到SLDS造型已经有了效果,这真是令人兴奋。请记住,我们不会解释大部分SLDS标记,但我们将在标记中包含注释。你可以看到我们如何为应用程序创建标题,并开始获得这个想法。

新的费用表格

在我们开始表格之前,我们先来确认一下:我们要做的只是暂时的。记住所有的谈话谈话谈论分解你的应用程序分成更小的组件,然后从那里建立?我们现在还没有这样做 – 还没有 – 坦率地说,这有点作弊。

但是,这样做会使代码不能太复杂太快。我们这样做,所以我们可以一次集中一堂课。而且,对于你自己来说,这不是一个坏的方法:在一个组件内部构建,直到它变得太忙,然后重构并分解成更小的子组件。只要你记得重构!

好,</ preaching>。在费用组件中,将<! – [[费用表单转到此处]] – >注释替换为添加费用表单的以下代码。

    <!-- 创建新的费用 -->
    <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>
  
        <!-- 创建新的费用表格 -->
        <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="checkbox" aura:id="expenseform" label="Reimbursed?"  
                             name="expreimbursed"
                             checked="{!v.newExpense.Reimbursed__c}"/>
            <lightning:button label="Create Expense" 
                              class="slds-m-top--medium"
                              variant="brand"
                              onclick="{!c.clickCreate}"/>
        </form>
        <!-- / 创建新的费用表单-->
  
      </fieldset>
      <!-- / BOXED AREA -->

    </div>
    <!-- / 创建新的费用 -->
这看起来像很多代码要立即把握。不是。当你剥离SLDS标记和类时,这个表格归结为一系列输入字段和一个表单提交按钮。

这是由此产生的形式。

New Expense form

注意

<lightning:input>是输入字段的瑞士军刀,融入了SLDS造型的优点。每当你发现自己到达<ui:input>组件变种(如<ui:inputText>,<ui:inputNumber>等)时使用它。 ui命名空间中的组件不具有SLDS样式,并且被认为是遗留组件。

首先,请注意,我们正在使用特定的数据类型创建<lightning:input>组件的多个实例。也就是说,您不希望使用type =“date”和日期字段,等等。有一系列不同的类型,远远超出了指定的类型,最好将组件类型与数据类型相匹配。如果您不指定类型,则默认为文本。原因可能不是很明显,但它会是当你在手机上试用这个应用程序特定的组件可以提供最适合的形式因素的输入小部件。例如,日期选择器针对鼠标或指尖进行了优化,具体取决于您访问的位置。

接下来,请注意,每个输入组件都在其上设置了标签,并且标签文本自动显示在输入栏旁边。还有一些我们以前没见过的属性:required,placeholder,type,min和step。这些属性中的大多数与HTML对应类似。例如,min指定输入的最小值。如果你不能猜出这些是什么,你可以在Lightning Components开发者指南中找到它们。 (我们会回到那个欺骗性的要求。)

接下来,在每个标签上设置一个aura:id属性。那个有什么用途?它在添加的每个标签上设置一个(本地)唯一的ID,该ID是如何从表单字段中读取值的。在这个例子中,所有的字段共享相同的ID,这样我们就可以将它们作为一个数组来访问,用于字段验证。我们将很快考虑如何做到这一点。

 

Salesforce对象的属性(sObject)

但首先,我们需要看看值属性。每个标签都有一个值,设置为一个表达式。例如,{!v.newExpense.Amount__c}。从表达的格式,你应该能够推断出一些东西。

  • v表示这是视图值提供程序的属性。这意味着这是组件上的一个属性。 (我们还没有创建。)
  • 基于点符号,你可以知道newExpense是某种结构化的数据类型。也就是说,newExpense本身具有属性。还是……字段?
  • 从大多数属性名称末尾的“__c”中,您可以猜测这些映射回自定义字段,最有可能在费用自定义对象上。
  • 所以,newExpense可能是一个费用对象!

很酷,我们还没有讨论这个呢!这里是实际的属性定义,您应该在打开<aura:component>标签之后添加到组件顶部。

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

这里发生的事情其实很简单。您已经知道的名称属性。毫无疑问,这种类型是我们自定义对象的API名称。到现在为止还挺好。

默认属性不是新的,但其值的格式是。但是不要太难把握。这是一个sObject的JSON表示,指定对象的类型(同样是API名称),以及默认设置的每个字段的值。在这种情况下,我们基本上把所有东西都设置为一个空值的表示。

这就是大部分你需要了解的关于sObject的知识!从这里开始,Lightning组件框架将允许您使用JavaScript和标记来处理newExpense,就像Salesforce中的记录 – 即使我们尚未从Salesforce中加载它!

在一个Action Handler中处理表单提交

所以我们有一个表格。现在,如果您填写并点击按钮来创建新的费用,会发生什么?除非你已经提前创建了它,否则你将会得到关于缺少控制器操作的另一个错误。这是因为控制器和<lightning:button>上指定的操作处理程序都没有被创建。

在Developer Console中,单击费用组件的CONTROLLER按钮以创建控制器资源。然后用下面的代码替换默认的代码。

({
    clickCreate: function(component, event, helper) {
        var validExpense = component.find('expenseform').reduce(function (validSoFar, inputCmp) {
            // 显示无效字段的错误消息
            inputCmp.showHelpMessageIfInvalid();
            return validSoFar && inputCmp.get('v.validity').valid;
        }, true);
        // 如果我们通过错误检查,做一些真正的工作
        if(validExpense){
            // Create the new expense
            var newExpense = component.get("v.newExpense");
            console.log("Create expense: " + JSON.stringify(newExpense));
            helper.createExpense(component, newExpense);
        }
    }
})
好的,这是全新的,所以我们仔细看看。首先,我们注意到,这个动作处理函数基本上分为三个部分,或者是步骤:
  1. 设置
  2. 处理表单值
  3. 如果没有错误,请做一些事情

这种结构对您来说可能是熟悉的,因为这是在Web应用程序中处理用户输入的基本方法。让我们看看每个步骤,看看他们如何在闪电组件。

对于设置,我们所做的只是初始化错误检查的状态。这是一个简单的标志,这是一个有效的开支?每次调用clickCreate操作处理程序时,我们都会假设费用数据正常,然后在发现问题时使其无效。下面是validExpense标志的简要说明,初始值设置为true。

  • component.find(‘expenseform’) 获取需要验证的<lightning:input>字段数组的引用。如果ID是唯一的,则引用返回组件。在这种情况下,ID不是唯一的,引用返回一个组件数组。
  • JavaScript reduce()方法将数组减少为由validSoFar捕获的单个值,该值保持为真,直到找到无效字段,将validSoFar更改为false。无效字段可以是空的必填字段,具有低于指定最小数字的字段的字段等等。
  • inputCmp.get(‘v.validity’).valid返回数组当前输入字段的有效性。
  • inputCmp.showHelpMessageIfInvalid() 显示无效字段的错误消息。 <lightning:input>提供了默认的错误消息,可以通过messageWhenRangeUnderflow等属性进行自定义,您可以在费用表单中看到该消息。

让我们进入一些有趣的细节。回到helloMessageInteractive,我们没有使用find()来找出被点击的按钮的标签文本。这是因为我们不需要。我们可以通过使用event.getSource()将其从事件参数中拉出来直接获得对该按钮的引用。你并不总是有这种奢侈;事实上,从用户输入中获得的所有内容都只来自事件,这是非常罕见的。

所以,当你的控制器需要一种方法来获取子组件时,首先在标记中设置该组件上的aura:id,然后使用component.find(theId)在运行时获取组件的引用。

注意

component.find()只允许你访问直接的子组件。在组件层次结构中漫游并读取或更改内容不是一种神奇的方式。请记住,组件应该是独立的,或者与…进行交流,那么我们就可以做到这一点。

使用<lightning:input>进行验证将利用底层HTML输入元素的处理能力来处理表单值,所以在大多数情况下您不需要这样做。需要验证一个电话号码?使用type =“tel”并使用正则表达式定义模式属性。需要验证一个百分比值?在formatter =“percent”中使用type =“number”。需要验证别的东西吗?让<lightning:input>为你做重担。

当验证失败时,事情会再次变得有趣。当用户输入无效的输入时,我们希望发生两件事情:

  1. 不要试图创造费用。
  2. 显示有用的错误消息。

首先,当inputCmp.get(’v.validity’)。valid引用的字段有效性计算结果为false时,我们将validExpense标志设置为false。第二,我们利用内置的验证错误或为错误提供自定义消息。在费用表单中,如果该字段为空,并且您尝试提交表单,则所需的名称字段将显示“完成此字段”。但是,您可以通过指定messageWhenValueMissing =“您忘了我吗?”来提供您自己的自定义消息。

相反,如果该字段通过验证,则validExpense标志的计算结果为true,并且不显示任何错误。

就这一点而言,我们正在处理表单提交的第三步:实际上是在创造费用!正如您所看到的,为了做好准备,我们从component属性中获取完整的newExpense对象:component.get(“v.newExpense”)。这给我们一个单一的变量,我们可以用它来创建一个新的费用记录。

但是,在我们做到这一点之前,您需要考虑一个问题:为什么我们不从NewExpense中提取表单值呢?在动作处理程序的开始处获取一次结构化变量,然后访问它的属性,而不是一系列find()。get()调用?

原因很简单:因为我们需要对各个字段的引用来调用showHelpMessageIfInvalid()。验证原始表单数据也是一个很好的做法。您的验证逻辑不知道在newExpense对象内可能发生什么类型的处理。

创建新的费用

记得早些时候我们说过把费用表单放在主要的组件上有点作弊了吗?那么,下一节没有得到“一点”限定符。我们在这里做的只是避免真正创造记录的复杂性。而现在我们正在避免它,因为那是整个下一个单位。所以,让我们用简单的东西来包装这个,但是这仍然给我们一些重要的概念。

首先,我们创建一个“存储”新开支的地方。我们将简单地创建一个本地支出数组来保存它们。在费用组件标记的顶部,在newExpense属性之前,添加一个新的费用属性,该属性将包含一组费用对象。

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

我们所要做的就是更新费用数组。事实证明,这很容易(以我们形式的作弊者的形式),并且说明了另一个重要的概念。

在我们的控制器中,我们隐藏了在这个函数调用后面实际创建新开销的工作:helper.createExpense(component,newExpense)。在软件开发中,“隐藏”这个词是抽象的。而我们正在使用一种叫做助手的方式来抽离我们的作弊行为。

我们之前简要地讨论过帮助者,而我们不打算介绍这个模块中帮助者的高级细节。现在,让我们说三件关于帮手的事情:

  • 组件的帮助程序是将代码在几个不同的操作处理程序之间共享的合适位置。
  • 组件的帮助程序是放置复杂的处理细节的好地方,所以您的操作处理程序的逻辑保持清晰和精简。
  • 助手功能可以有任何功能签名。也就是说,它们不受控制器中动作处理程序的限制。 (这是为什么呢?因为你直接从你的代码中调用helper函数,相反,框架通过框架运行时调用动作处理函数。)这是一个约定和推荐的做法,总是提供组件作为帮助函数的第一个参数。

好吧,让我们继续吧。在开发人员控制台中,单击费用组件的HELPER按钮以创建关联的助手资源,然后将示例代码替换为以下内容。

({
    createExpense: function(component, expense) {
        var theExpenses = component.get("v.expenses");
 
        //将费用复制到一个新的对象这是一个令人讨厌的,暂时的黑客
        var newExpense = JSON.parse(JSON.stringify(expense));
 
        theExpenses.push(newExpense);
        component.set("v.expenses", theExpenses);
    }
})
目前,忽略令人厌恶的黑客部分。其他三行代码说明了我们以前见过的一个常见模式,您将一遍又一遍地使用它:get-process-set。首先,我们从费用属性中获得一系列费用。然后我们添加新的费用“记录”。然后我们用更改的数组更新(设置)费用属性。

参考不是收集

这里有什么新东西是第一次,我们正在更新一个集合,一个数组。如果你是一个有经验的程序员,你可能想知道:“为什么我需要set()在这里?

也就是说,component.get(“v.expenses”)获取存储在组件属性中的数组的引用。 component.set(“v.expenses”,theExpenses)只是将组件属性设置为相同的引用。当然,在这之间,数组的内容已被添加到,但容器是相同的:数组的引用实际上并没有改变!那么,为什么更新呢?

如果您无法理解这意味着什么,请在关键语句之前和之后添加两条日志语句,并将这些Expenses的内容转储到控制台。

console.log("创建之前的开支: " + JSON.stringify(theExpenses));
theExpenses.push(newExpense);
component.set("v.expenses", theExpenses);
console.log("创建后的费用: " + JSON.stringify(theExpenses));

重新加载并运行应用程序,添加至少两个费用,并查看费用的结构。现在注释掉component.set()行,并再次执行。

什么…? component.set()根本不影响Expenses!但!但!但?它究竟做了什么?!?

你问这个问题绝对正确。答案是:魔法!

什么component.set()在这里没有更新费用属性的值。它会触发费用属性已更改的通知。

结果是,在您的应用程序中的任何位置您已经引用了表达式中的费用属性,该表达式的值将更新,并且该更新会在所有使用费用属性的地方级联。而且他们都根据新的内容更新为放弃。这一切都发生在幕后,由Lightning Components框架处理,作为使用{!v.expenses}时发生的自动布线的一部分。总之,魔法。

总而言之,如果这是纯JavaScript,则不需要component.set()。为了触发内置在Lightning组件中的基础效应,您可以这样做。如果你编写了一些控制器或者帮助代码,测试一下,没有任何反应,确保你已经完成了所需的component.set()。

“恶心的黑客”围绕着类似的问题与参考。要查看该问题,请更改该行以除去两个JSON调用,然后测试该应用程序。你会很快看到问题是什么。我们将在下一个单元中删除它,所以不会进一步解释。

显示费用列表

所以,对于所有讨论“神奇地”更新任何使用{!v.expenses}的东西,猜猜看是什么。没有别的使用它,但。我们来解决这个问题。

在开发人员控制台中,创建一个名为expenseItem的新Lightning组件,并使用以下代替默认标记。如果您已经创建了费用项目,只需更新标记。您已经看到了之前访问费用记录中的字段的表达式。该版本包含SLDS标记,使其更加时尚。另外,使用{!v.expense.Reimbursed__c? ‘slds-theme-success’:”}当费用已经报销时,表达式在容器上设置绿色。

<aura:component>
    <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: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>
接下来,用下面的代码创建一个客户端控制器expenseItemController.js。在这里,我们将稍后由服务器返回的日期转换为JavaScript Date对象,以便通过<lightning:formattedDateTime>和<lightning:relativeDateTime>正确显示。此转换在组件初始化期间处理,由<aura:handler>标签捕获。这是处理初始化事件的有用方法,稍后我们将在从Salesforce加载数据时再次使用它。
({
    doInit : function(component, event, helper) {
        var mydate = component.get("v.expense.Date__c");
        if(mydate){
            component.set("v.formatdate", new Date(mydate));
        }
    },
})
在开发者控制台中,创建一个名为expensesList的新Lightning组件,并使用以下代码替换默认标记。
<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>

这里没有太多新东西。这是一个显示费用列表的组件。它有一个属性费用,这是一个费用数组(Expense__c)对象。它使用<aura:iteration>为每个这些费用对象创建一个<c:expenseItem>。正如我们所指出的那样,总体效果是显示费用清单。到现在为止还挺好。

现在将费用清单组件添加到费用组件的末尾。在expenses.cmp中的结尾</ aura:component>标记之前添加它。

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

如果您重新加载应用程序,则会在窗体下方看到“费用”部分。 (视觉上不太对,但现在已经足够了。)

我们刚刚做了什么?我们添加了费用列表组件,并将主费用属性传递给它。那么现在费用列表的费用实例和费用组件所具有的费用实例是一样的。它们是对相同记录数组的引用,并且通过Lightning组件的魔法,当更新主要费用数组时,费用列表组件将“注意到”并重新列出其列表。给它一个旋转!

呼!这是一个很长的单位,它需要一个休息时间。请站起来走走几分钟。

然后回来,我们会告诉你如何为真正的节省新的开支。

Lightning-组件(5)操作

学习目标

完成本单元后,您将能够:

  • 创建一个客户端控制器来处理用户操作。
  • 从组件属性读取值。
  • 从组件中的用户界面控件读取值。
  • 用JavaScript编写控制器代码来改变用户界面。

处理与控制器的行动

到目前为止,我们只使用XML风格的标记。到目前为止,让组件的输出更改的唯一方法是更改​​该标记。到目前为止,我们的组件没有对用户输入做出反应。到目前为止,我们还没有编写任何JavaScript。

这个单位的所有变化。

为了开始,我们来看一个非常简单的组件,想象一下为了处理它的简单行为需要做些什么。

Message of the day: You look nice today button, Today is going to be a great day button

这是helloMessageInteractive,很难想象一个简单的组件“做些什么”。它是一些静态文本,一个(当前空白)消息和两个按钮。代码如下:

<aura:component>
 
    <aura:attribute name="message" type="String"/>
 
    <p>Message of the day: {!v.message}</p>
 
    <div>
        <lightning:button label="You look nice today."
            onclick="{!c.handleClick}"/>
 
        <lightning:button label="Today is going to be a great day!"
            onclick="{!c.handleClick}"/>
    </div>
 
</aura:component>

这应该看起来很熟悉。我们真正做的是将两个<lightning:button>组件添加到helloMessage。当你点击一个按钮,当天的消息被更新。

那么,还不完全。如果您已经输入了代码并自己尝试过,那么您已经注意到,当您单击其中一个按钮时,您会收到错误消息。

There is a problem

我们将首先承认,并不是每一个你会在Lightning Components中看到的错误信息都像你所希望的那样有帮助。但是这个是!它说没有名为“handleClick”的控制器操作。 “handleClick”从哪里来?它来自我们分配给两个<lightning:button>标签中每一个的onclick属性的表达式:

onclick="{!c.handleClick}"

鉴于这是一个按钮,你可能会猜测onclick属性是如何为点击按钮分配一个行为。但是我们分配了什么?表达式{!c.handleClick},可能有点神秘。

这其实很简单。就像前面的v.message表达式一样,c.handleClick是一个值提供者c,带有一个属性handleClick。 c是组件客户端控制器的值提供者,handleClick是在该控制器中定义的函数。所以,{!c.handleClick}是对组件控制器中的一个动作处理器的引用。

c.handleClick: c is a value provider for the component controller, with the property handleClick, a function defined in that controller

Uh, 什么是控制器?

哎呦!控制器基本上是一组代码,用于定义应用程序在“事情发生”时的行为,其中“事物”是指用户输入,定时器和其他事件,数据更新等等。如果你在任何数量的开发者网站上查找“模型 – 视图 – 控制器”,你会得到不同的定义。就我们的目的而言,对于Lightning组件,控制器是组件包中的一个资源,该组件包含该组件的操作处理程序。而动作处理程序只是具有特定功能签名的JavaScript函数。

超越基础

我们在这个单元中谈了很多关于控制器的知识,而且我们知道这个组件本身就是一个视图。我们甚至提到了MVC或模型 – 视图 – 控制器的设计模式,这在Web应用程序框架中很常见。闪电组件建立在MVC模式上吗?

总之,没有。可以肯定的是有相似之处,但是说Lightning组件是View-Controller-Controller-Model或者View-Controller-Controller-Database可能更为正确。

为什么“控制器”在这个模式名称翻倍?因为在与Salesforce交互时,除了我们在本单元中使用的客户端控制器外,您的组件还将拥有服务器端控制器。这个双控制器设计是Lightning组件和MVC之间的主要区别。

“模型”和“数据库”有什么区别?在传统的MVC中,模型是底层数据存储(通常是关系数据库)和其他应用程序之间的程序化抽象(通常是一个类)。在Lightning组件中,没有Apex类直接位于@AuraEnabled控制器方法和DML操作之间。但是再一次,sObjects已经是您的Apex代码和底层存储层之间的抽象了。您可以添加计算字段,验证逻辑,甚至以触发器的形式添加完全的编程行为。那么,这是一个数据库还是一个模型?我们说po-TAY-tow,但是如果你想用po-TAH-tow去,那真是太酷了。

困惑?激动吗?我们将在后面的单元中对服务器端控制器的细节进行分类。

让我们更详细地看看helloMessageInteractive控制器,并解释更具体一些。

({
    handleClick: function(component, event, helper) {
        var btnClicked = event.getSource();         // the button
        var btnMessage = btnClicked.get("v.label"); // the button's label
        component.set("v.message", btnMessage);     // update our message
    }
})
控制器资源有一个有趣的格式。它们是包含名称 – 值对映射的JavaScript对象,其中名称是操作处理程序的名称,值是函数定义。


行动处理程序

名称 – 值对和特定函数签名的组合是一个动作处理程序。您将会听到或看到交替使用的术语“动作处理程序”,“控制器动作”和“控制器功能”,而且大部分都是正确的。他们几乎总是提到同样的事情。 (我们不会担心这个模块中的异常。)

不要太担心控制器资源的特殊格式。当您在开发者控制台中点击CONTROLLER按钮时,您将获得一个已添加示例操作处理程序的控制器资源。一个技巧是 – 如果你忘记了,你会得到语法错误 – 你需要在操作处理程序之间加逗号。这只是基本的JavaScript语法,我们稍后会看到具体细节。

实际的handleClick函数只有四行代码,但起初似乎很难理解。在高层次上,这很简单:点击按钮时,其操作处理程序被调用(1)。在动作处理程序中,控制器获取被单击的按钮,将标签文本从中拉出,然后将组件的消息属性设置为该文本(2)。并且当天的消息被更新(3)。你今天看起来不错!

In the action handler, the controller gets the clicked button text, then sets the component message attribute

很简单,对吧?好…

因为这是非常重要的,让我们逐行分解。

handleClick: function(component, event, helper) {

操作处理程序名称,后跟一个匿名函数声明。这里重要的是函数签名。虽然这不是技术上的要求,但您应该始终声明您的控制器功能采取这三个参数。现在我们将更多地讨论它们,但是现在这些参数代表:

  • component—组件。在这种情况下,它是helloMessageInteractive。
  • event—导致操作处理程序被调用的事件。
  • helper—组件的助手,另一个可重用函数的JavaScript资源。
    var btnClicked = event.getSource();         // the button
请记住,handleClick已连接到我们的<lightning:button>标记及其onclick属性。那么,事件就是有人点击按钮。在这个事件里面,它有一个源的概念,即产生事件的东西,也就是按钮本身。所以,调用event.getSource()让我们引用被点击的特定<lightning:button>。
    var btnMessage = btnClicked.get("v.label"); // the button's label

我们现在做什么,我们有一个参考按钮?我们在里面查看它的标签,它在组件标记中的<lightning:button>上设置。例如,<lightning:button label =“今天看起来不错。” …>。

让我们再想一想。我们没有在我们面前定义<lightning:button>,但标签只是另一个属性,非常类似于我们添加到helloMessageInteractive的消息属性。您可以在任何组件上调用get(),并以v.attributeName的格式提供您想要检索的属性的名称。结果是属性值。

请注意,就像在组件标记中一样,v代表视图,组件本身 – 但在这种情况下,它是<lightning:button>子组件,而不是helloMessageInteractive!想想这样。 btnClicked.get(“v.label”)在任何组件btnClicked的肩膀上点击并且说“嘿,给我v.label”。该组件认为“v就是我”,在自己的内部查找,并返回其标签属性的值。

所以,现在我们有一个从按钮中检索的文本字符串,我们只剩下一步:将我们的消息属性更改为新的消息文本。毫不奇怪,就像get()从组件中读取值一样,set()写入一个值。

    component.set("v.message", btnMessage);     // update our message
但是,让我们注意一个重要的区别。我们在btnClicked上调用了get(),这是helloMessageInteractive中的<lightning:button>。我们在组件上调用set() – helloMessageInteractive组件本身。实际上,您将在每个创建的组件中重复这种模式:从子组件获取值,可能会进行一些处理,并在组件中设置值。


闪电组件视图 – 控制器编程模型

好的,检查一下时间。这是有道理的吗?当然?如果你这么想,确保你已经使用前面的代码创建了helloMessageInteractive组件。它是一个组件,复制/粘贴代码需要两分钟的时间,但能够使用它是理解处理操作的关键。

你在这里做的事情看起来很简单,因为它不是很多代码行。但是这些代码行说明了使用Lightning组件构建应用程序的一些基本概念。

您可以将组件连接到动作处理程序上,将它们连接起来。将helloMessageInteractive想象成一个简单的电路。有开关,还有灯泡。 (Lightning组件框架提供电源。)本身,一个开关可能会发出一个很好的咔哒声,但是直到你连接它,它不是功能。你可以有最时尚的爱迪生风格的灯泡,但直到你把它连接起来,它不会照亮任何东西。

Lightning组件也是如此。就在前面,我们说组件捆绑中的不同资源是相互“自动连接”的。这是真的:接线采取v和c值提供商的形式。它们会自动创建并在您的组件中可用,因此您的控制器可以引用组件,反之亦然。但是这个自动接线只发生在组件.cmp资源和控制器.js资源之间的高层次 – helloMessageInteractive中。这是下图中的绿色箭头。

helloMessageInteractive and its controller are auto-wired

将一个特定的<lightning:button>组件连接到一个特定的动作处理程序 – 也就是将产生事件的东西(比如按钮)连接到处理事件的东西,比如一个特定的控制器功能 – 这是你需要的连线自己做。其实,你只是做了你自己!这些是完成工作电路所需的红色箭头。

将{!c.handleClick}添加到<lightning:button>组件(1)的onclick属性,将其连接到特定的操作处理程序。调用component.set(“v.message”,newMessage)(2)将该操作处理程序的结果连接到组件的消息属性。它本身连接到{!v.message}表达式。

您可以进一步将onclick事件视为沿着您创建的电路流动的电子。如果你还没有创建一个完整的电路,事件不会发生,没有任何反应。当你开始编写自己的组件时,记住这一点。你有一个完整的电路?你确定?如果有疑问,有时候可以在白板或纸上画出全部草图,并确认每个连接。

你会把组件,事件和处理程序连接在一起,所以你经常会感觉像电工。 (或者,考虑到框架的名字,也许是本·富兰克林。)把事情联系在一起是Lightning组件的基本编程模型。

所以,我们再做一些。毕竟,练习是完美的。

函数链接,重新布线和简单调试

我们的handleClick的第一个版本是三行代码,因为我们将get-process-set模式中的每一步分解成单独的行。你可以使用一些叫做函数链的东西把它们折成较少的行。由于您可能会在其他Lightning组件代码中看到这一点,因此我们可以让它自己旋转。在handleClick之后,将以下附加代码添加到helloMessageInteractive控制器中。

    handleClick2: function(component, event, helper) {
        var newMessage = event.getSource().get("v.label");
        component.set("v.message", newMessage);
    },

    handleClick3: function(component, event, helper) {
        component.set("v.message", event.getSource().get("v.label"));
    }

Whoopsie!尝试保存时是否出现语法错误?当我们说你需要在你的动作处理程序之间添加逗号的时候,记得早些时候?这是这个问题。在handleClick的最后一个大括号(“}”)后面添加一个逗号,就像你在前面的代码片段的handleClick2的末尾可以看到的那样。

这些动作处理程序与handleClick完全相同,使用更少的代码行。他们通过跳过中间变量来做到这一点,通常通过直接将“链接”链接到下一个函数调用,通过将该调用添加到前一个函数调用的结尾,以句点分隔。 (在查看handleClick和handleClick2之间的差异时,这个概念是最清楚的。)

你喜欢哪种风格是个人品味的问题,也许你的组织的编码风格。你的卑微的作者喜欢handleClick2,分离得到和设置,但不打扰与按钮的变量,我们只需要它的标签文本。

当然,通过将onclick属性设置为{!c.handleClick2}或{!c.handleClick3},您无法验证新的动作处理程序是否正常工作,直到连线<lightning:button>才能使用其中的一个。把它看作是从一个灯泡断开电线,然后把它们连接到另一个灯泡。就这么简单!

在这一点上,你重新加载应用程序,点击一个重新布线的按钮,…呃,这是相同的设计,不是吗?我们甚至可以告诉哪个动作处理程序被调用?

有时候简单是最好的。让我们添加一些记录到一个动作处理函数:

    handleClick2: function(component, event, helper) {
        var newMessage = event.getSource().get("v.label");
        console.log("handleClick2: Message: " + newMessage);
        component.set("v.message", newMessage);
    },

现在,如果连接一个<lightning:button>来处理Click2,则只要您点击它,您就会在浏览器的JavaScript控制台中看到一条日志消息。

是的,有更复杂的调试工具,但打印到控制台的东西是一个悠久的调试技术。如果要输出某种类型的对象,请使用JSON.stringify(yourObject)将其包装,以获取更多有用的细节。当你快速浏览时,console.log()是你的朋友。

我们不会在这里介绍更复杂的调试工具和技术,但请参阅参考资料中的一些精彩的工具和说明。

好的,helloMessageInteractive很简单,并且有一个硬编码(和无情的积极)的态度。在下一个单元中,我们将处理更复杂的事情,并学习如何捕捉真实的用户输入。而且,由于现实世界中的人们并不总是如此积极,我们也将学习如何验证他们的输入。