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

Lightning-组件(4)属性

学习目标

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

  • 在组件上定义属性,并将属性值传递给嵌套组件。
  • 理解组件定义和组件实例之间的区别,并创建组件的多个实例。
  • 创建基本表达式来显示更改和计算的值。
  • 为动态输出创建条件表达式。

组件属性

到目前为止,虽然我们已经创建了几个组件,并且在构建应用程序方面学到了一些(高层次的),但是我们编写的代码并没有做比HTML更简单的工作。也就是说,我们创建的两个组件输出相同的静态文本,不管我们做什么。你可以把他们放在同一个屏幕上,他们总是会说同样的事情。

无聊。

要改变这一点,我们需要学习两件事情。首先,我们需要学习如何使组件在创建时接受输入。也就是说,我们需要在组件上设置值。我们使用属性来做这件事。

(我们需要学习的第二件事是如何实际使用这些值来改变组件的行为和输出,在我们弄清楚属性之后,我们会这样做)。

组件上的属性就像对象中的实例变量一样。这是一种保存更改值的方法,也是一种命名这些值占位符的方法。例如,假设我们想编写一个打印自定义消息的helloMessage组件。我们可以想象为这个组件添加一个消息属性来定制它的输出。然后,我们可以在将组件添加到我们的应用程序时设置该消息,如下所示。

<aura:component>
	  
    <c:helloMessage message="You look nice today."/>
	    
</aura:component>

您需要将其添加到您的组织中,因为我们将继续使用它。但是,如果你现在做,你会得到一个错误。这是为什么?因为helloMessage组件还不存在。闪电组件在您编写代码时验证您的代码。如果你试图保存它知道是无效的代码,例如引用一个不存在的组件,你会得到一个错误。所以,让我们先弄清楚如何创建helloMessage。
您可以在创建组件时设置组件的属性,就像我们在前面的示例中所做的那样。您还可以在组件生命周期的过程中更改它们,以响应用户采取的操作或其他地方发生的事件等等。你当然可以用许多不同的方式读取和使用属性值。当我们到达表达式时,我们会看看那些。

现在,我们来看看如何定义组件的属性。使用<aura:attribute>标记定义属性,该标记需要名称和类型属性的值,并接受这些可选属性:default,description,required。

哇,这是在句子中使用“属性”的很多不同的方法!在这里很容易混淆,因为我们有三个不同的名称相似的概念。让我们具体。

  1. 组件属性是可以存储值的地方。在前面的例子中,helloMessage组件有一个名为message的组件属性。大多数时候我们正在讨论组件属性。
  2. 您可以使用<aura:attribute>标签定义组件属性。我们稍后会看到一个例子。我们来调用这些属性定义。
  3. 使用它时,<aura:attribute>标签本身就具有属性! 😖也就是说,使用<aura:attribute>标记定义组件属性时,可以在<aura:attribute>上设置属性,以指定要定义的组件属性的“形状”。 😡等等,让我们再试一次:通过设置属性定义的属性来添加一个组件属性定义。 component组件属性的属性定义是否具有属性? 😴
    这就是为什么作家😱。我们试着用一些代码来解决这个术语问题。 😄

下面是我们的helloMessage组件的开始:

<aura:component>

    <aura:attribute name="message" type="String"/>

    <p>Hello! [ message goes here, soon ]</p>

</aura:component>
helloMessage组件具有一个组件属性,该属性通过设置属性的名称和类型来定义。该属性的名称是消息,一旦我们了解表达式,这就是你如何引用它。它仍然只输出静态文本和HTML,但我们正在接近有用的东西。

👍 ?

我们在这里使用的另一个属性是type,我们已经设置了它,因为它在属性定义中是必需的。它说消息属性包含一个字符串,这是有道理的。我们将详细讨论属性数据类型和属性定义的其他部分,但是首先让我们学习表达式,然后让helloMessage实际上做一些事情。

表达式

不要再让我们迷失,而是让我们按照预期使用helloMessage。

<aura:component>

    <aura:attribute name="message" type="String"/>

    <p>Hello! {!v.message}</p>

</aura:component>
难道是这样的还是什么?

我们使用表达式{!v.message}输出消息的内容。也就是说,这个表达式引用了消息属性。表达式被评估,并解析为当前存储在消息中的文本字符串。这就是表达式输出到组件主体的内容。

嗯…这是什么“表达”?

表达式基本上是一个公式或计算,您将其放在表达式分隔符(“{!”和“}”)中。所以,表达式如下所示:

{!<expression>}

表达式的正式定义有点令人生畏,但让我们看一下,然后解压它:表达式是可以解析为单个值的任何一组字面值,变量,子表达式或运算符。

是的,基本上是一个公式,就像你在计算领域,过滤标准或Visualforce中写的一样。公式或表达式可以包含各种各样的东西。字面值应该是显而易见的;他们是像数字42,或字符串“你好”的东西。变量就像消息属性一样。运算符就像+, – 等等,子表达式基本上意味着你可以使用括号把事物分组在一起。

让我们试试这个,让我们的表情稍微复杂一些。

<aura:component>

    <aura:attribute name="message" type="String"/>

    <p>{!'Hello! ' + v.message}</p>

</aura:component>
我们所做的就是将“Hello”部分从表达式外部的静态文本移动到表达式中的文本文本。请注意,我们使用“+”运算符将两个字符串连接在一起。这可能看起来像一个很小的差异,但移动表达式中的问候语文本可以使用标签,而不是文本文本,这使得更新(和翻译)组件更容易。例如:
{!$Label.c.Greeting + v.message}

你注意到我们对表达式的正式定义有什么遗漏吗? JavaScript函数调用。 Lightning Components标记中的表达式中不能使用JavaScript。

在我们继续之前,关于表情的最后一件事。您可以将它们传递给另一个组件来设置该组件上的值。这是一个将自定义值传递给helloMessage组件的新组件。将值传递给其他组件会覆盖该组件上的值。

<aura:component>
    <aura:attribute name="customMessage" type="String"/>
    <p> <c:helloMessage message="{!v.customMessage}"/> </p>
</aura:component>

.

价值提供者

实际上,我们需要谈谈表情的另一个方面。在前面的例子中,我们用v.message引用了helloMessage组件的消息属性。什么是“v”部分?

v是所谓的价值提供者。价值提供者是分组,封装和访问相关数据的一种方式。价值提供者是一个复杂的话题,所以现在把v想象成一个可供你使用的自动变量。在我们的组件中,v是视图的值提供者,它是helloMessage组件本身。

v给你一个“钩子”来访问组件的消息属性,这就是你如何访问组件的所有属性。

值提供者中的值作为命名属性被访问。要使用值,请使用点(句点)分隔值提供程序和属性名称。例如,v.message,正如我们所见。

当组件的属性是对象或其他结构化数据(即不是原始值)时,使用相同的点符号访问该属性上的值。例如,{!v.account.Id}访问帐户记录的Id字段。对于深度嵌套的对象和属性,继续添加点来遍历结构并访问嵌套的值。

属性数据类型

访问结构化数据是讨论属性的一个很好的部分,特别是关于非基本属性类型。消息是一个字符串,但是有许多不同的属性类型。

  • 原始数据类型,如布尔值,日期,日期时间,十进制,双精度,整数,长或字符串。任何编程语言通常的使用的。
  • 标准和自定义Salesforce对象,例如Account或MyCustomObject__c。
  • 集合,例如List,Map和Set。
  • 自定义Apex类。
  • 特定于框架的类型,如Aura.Component或Aura.Component []。这些比我们在这个模块中会更先进,但是你应该知道它们存在。

这是一个精简的费用项目组件,我们将在稍后填写。它演示了如何为自定义对象定义属性,以及如何访问记录中的字段。

<aura:component>

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

    <p>Amount:
        <lightning:formattedNumber value="{!v.expense.Amount__c}" style="currency"/>
    </p>
    <p>
        Client: {!v.expense.Client__c}
    </p>
    <p>
        <lightning:input type="toggle"                            
                         label="Reimbursed?"                           
                         name="reimbursed"                         
                         checked="{!v.expense.Reimbursed__c}" />
     </p> 
    <!-- Other markup here -->

</aura:component>
这个组件有一个属性,开销,这是我们在本模块开始时创建的自定义对象。该组件的目的是通过使用{!v.expense.fieldName}表达式引用Expense__c记录中的字段来显示费用的详细信息。我们使用类型=“toggle”的<lightning:input>组件,这是一个切换形式的复选框,以便我们稍后可以更新UI中的值。

其他方面的属性定义

当涉及到您在<aura:attribute>标签上设置的属性时,以下是您需要了解的其余部分。

  • 默认属性定义了默认的属性值。它在引用属性时使用,但尚未设置该属性的值。
  • 必需的属性定义属性是否是必需的。默认值是false。
  • description属性定义了属性及其用法的简要摘要。

为具有复杂数据类型的属性设置默认值可能有些棘手。不过,我们稍后会看到一个例子,所以现在我们只是把你的头抬起来。

乐趣与属性和表达式

为了说明更多关于属性和表达式的概念,让我们用下面的标记创建一个非常愚蠢的组件helloPlayground。

<aura:component>

    <aura:attribute name="messages" type="List"
        default="['You look nice today.',
            'Great weather we\'re having.',
            'How are you?']"/>


    <h1>Hello Playground</h1>

    <p>Silly fun with attributes and expressions.</p>


    <h2>List Items</h2>

    <p><c:helloMessage message="{!v.messages[0]}"/></p>
    <p><c:helloMessage message="{!v.messages[1]}"/></p>
    <p><c:helloMessage message="{!v.messages[2]}"/></p>


    <h2>List Iteration</h2>

    <aura:iteration items="{!v.messages}" var="msg">
        <p><c:helloMessage message="{!msg}"/></p>
    </aura:iteration>


    <h2>Conditional Expressions and Global Value Providers</h2>

    <aura:if isTrue="{!$Browser.isIPhone}">
        <p><c:helloMessage message="{!v.messages[0]}"/></p>
    <aura:set attribute="else">
        <p><c:helloMessage message="{!v.messages[1]}"/></p>
        </aura:set>
    </aura:if>

</aura:component>

现在将helloPlayground组件添加到你的线束应用程序,并看看它是如何运行的!

这里有很多新东西。我们现在不会对它们进行深入的了解,但是你会再次看到所有这些。

首先,helloPlayground有一个属性消息,这是一个复杂的数据类型List。它有一个默认值的列表,这是一个由逗号分隔的三个单引号字符串组成的数组。而且,在“列表项”部分中,您可以看到如何使用索引访问每个字符串。

如果有人用两条消息创建一个<c:helloPlayground>,会发生什么?访问第三个项目将失败,虽然它不会导致崩溃,但可能会有更复杂的组件。

因此,在List Iteration部分中,您可以看到更好的方法来处理列表中的所有项目。 <aura:iteration>组件在其items属性中的每个项目重复其身体一次,所以列表收缩或增长,因为我们有更少或更多的消息。

在“条件表达式”和“全球价值提供商”部分,您可以看到在两种不同可能的输出之间进行选择的方法。这种格式有点尴尬,因为这是标记而不是JavaScript,但是<aura:if>组件允许您例如只在用户具有对该对象的编辑权限时才将编辑按钮添加到页面。

最后,有些不那么明显。在面向对象编程中,类和类的实例是有区别的。组件有一个相似的概念。当您创建.cmp资源时,您正在提供该组件的定义(类)。当你把一个组件标签放到一个.cmp文件中时,你正在创建一个对该组件(的实例)的引用。

我们可以添加具有不同属性的相同组件的多个实例,这并不奇怪。在前面的例子中,当你使用消息的默认值时,你最终会得到8个对<c:helloMessage>组件实例的引用。如果你通过一个更长的名单,你可以结束(多)更多。所有来自我们的一个小组件!

对此,朋友,我们认为你已经准备好了一些真正的工作。

Lightning-组件(3)创建组件

学习目标

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

  • 在开发者控制台中创建和编辑Lightning组件包资源。
  • 创建一个“线束”应用程序,用于测试开发中的组件。
  • 执行编辑和重新加载循环以预览开发中的组件。
  • 列出构成Lightning组件包的不同资源。

创建和编辑闪电组件

好!时间写一些代码! #最后!

编写Lightning组件代码的第一步是,呃,设置编写代码。幸运的是,这非常简单。在您的组织机构中,打开您的名字下的开发者控制台或快速访问菜单(设置齿轮图标Setup gear icon)。

闪电体验 Salesforce 经典
Opening the developer console
Classic mode: Opening the developer console

繁荣,你已经准备好写Lightning组件代码!

Boom: you can start writing component code

在开发者控制台中创建Lightning组件

所以,我们来写点东西吧。选择 File | New | Lightning Component 创建一个新的闪电组件。在New Lightning Bundle面板中,输入helloWorld作为组件名称,然后单击Submit。

New Lightning Bundle panel

这将创建一个新的helloWorld组件包,其中包含两个打开的选项卡。关闭helloWorld选项卡,并保持helloWorld.cmp选项卡打开。

helloWorld.cmp包含Lightning组件的开始和结束标签,<aura:component>。在它们之间,添加以下标记,并保存:

<p>Hello Lightning!</p>
你的组件标记应该如下所示。
Hello Lightning markup

呜呼,你的第一个闪电组件!现在…我们如何看待它的样子?

简单的回答是,这是棘手的。你不能直接运行你的组件,看看它是如何工作的。相反,您的组件需要在容器应用程序中运行,我们将其称为容器。容器的例子是Lightning Experience或Salesforce应用程序,或者是使用Lightning App Builder构建的应用程序 – 基本上,您在前一个单元结束时看到的任何内容。您将组件添加到其中一个容器,然后在该容器中访问它。

稍后我们将更多地讨论容器,以及其他Lightning组件模块。现在,让我们来简单介绍一下。

选择 File | New | Lightning Application 以创建一个新的闪电应用程序。在New Lightning Bundle面板中,输入“harnessApp”作为应用程序名称,然后单击Submit。

这将创建一个新的harnessApp包,其中包含两个打开的标签。关闭harnessApp选项卡,并保持harnessApp.app选项卡打开。

harnessApp.app包含Lightning应用程序的开始和结束标记,<aura:application>。在它们之间,添加以下标记,并保存:

<c:helloWorld/>

这将我们之前创建的helloWorld组件添加到harnessApp应用程序。

在我们解释这个看似简单的应用之前,请在开发者控制台的harnessApp.app和helloWorld.cmp标签之间来回点击。除了标记之外,你注意到哪些不同?

What is different?

有一个:预览按钮。应用程序有一个,组件不。现在点击它,另一个浏览器窗口应该打开并显示你的应用程序。

Preview the app.

现在我们正在用…做饭,好吧,这只是“你好世界”。但是这里有一些有趣的事情要注意,尽管标记是微不足道的。

我们从开发者控制台开始。如果您已经使用它来编写Visualforce或Apex,那么您肯定会注意到任何Lightning软件包编辑窗口右侧的调色板中出现的额外控件。每个带有“创建”标签的不同按钮都代表您可以添加到软件包的不同资源。我们将在下一节讨论资源和捆绑。现在,只要知道开发者控制台为您提供了一个简单的方法来创建和切换它们。

事实上,开发人员控制台具有许多与Lightning组件配合使用的功能。还有 File | Open | Lightning Resources, 可以让你一次打开一堆闪电资源。有用!

开发者控制台是编写Lightning代码的好地方,我们将在这个模块的其余部分使用它。但是,由于Lightning资源可通过Force.com Tooling API访问,因此还有其他方法可以创建和编辑它们。 Salesforce DX为开发Lightning组件的各个方面提供了强大的支持。 Force.com IDE是一个很好的独立客户端,并且有很好的第三方工具,如Sublime Lightning和VS Code。不要以为只限于开发者控制台!

在我们转向代码之前最后一件事。我们“预览”的URL实际上是我们的应用程序永久的家(一旦它提供给我们的用户)。 URL的格式如下:https:// <yourDomain> .lightning.force.com / <yourNamespace> / <yourAppName> .app。

<yourAppName>表示应用程序包的名称,在这种情况下为harnessApp。在你的Trailhead DE组织中,你不应该有一个配置好的名字空间,所以你应该在这个URL部分看到一个“c”。 “c”表示默认的命名空间…它会在以后回来困扰我们。 URL格式的其余部分应该是不言自明的。

好的,在代码上!

什么是组件?

你好的世界并不是经常触发存在的问题,但我们在这里。让我们来讨论一下我们的helloWorld示例中的组件是什么。实际上,一个组件是一个捆绑包,它包含一个用标记编写的定义资源,并且可以包含额外的可选资源,如控制器,样式表等等。资源有点像文件,但存储在Salesforce中而不是文件系统上。

我们的helloWorld.cmp组件定义资源很容易理解。

<aura:component>
    <p>Hello Lightning!</p>
</aura:component>

有<aura:component>标签的开始和结束,以及一些静态的HTML。这很难变得更简单,你可能会想它是一个“页面”,不要。我们会稍微回顾一下。

我们已经提到了组件包,但他们究竟是什么?一个捆绑就像一个文件夹。它将单个组件的相关资源分组。捆绑中的资源通过每种资源类型的命名方案自动连接在一起。自动布线只是意味着组件定义可以引用其控制器,帮助器等,这些资源可以引用组件定义。他们彼此(大部分)自动挂钩。

让我们看看这是如何工作的。使用helloWorld.cmp激活,单击右侧组件面板中的STYLE按钮。这会为添加到helloWorld包的样式资源打开一个新选项卡。它开始于一个单一的空选择器.THIS。要查看这是如何工作的,请在样式表中添加一个简单的样式,使其如下所示。

.THIS {
}

p.THIS {
    font-size: 24px;
}

然后重新加载harnessApp.app的预览窗口。 Voilà,更大的文字!但是,.THIS是如何工作的?这是自动布线的魔力!在运行时.THIS被替换为您的组件命名的样式范围字符串。它将样式规则限制为仅限于此组件,以便您可以创建特定于组件的样式,而不用担心这些样式可能会如何影响其他组件。

所以现在我们的helloWorld包有两个资源,组件定义helloWorld.cmp和样式表helloWorld.css。你可以把它想象成一个文件夹或者一个大纲:

  • helloWorld — 组件包
    • helloWorld.cmp — 组件的定义
    • helloWorld.css — 组件的样式

如您在开发者控制台中看到的那样,您可以添加到组件包中的其他许多资源类型。继续并单击CONTROLLER和HELPER项目,将这些资源添加到包中。现在你的包看起来像这样,你可以开始看到命名系统。

  • helloWorld — 组件包
    • helloWorld.cmp — 组件的定义
    • helloWorldController.js — 组件的控制器或主JavaScript文件
    • helloWorldHelper.js — 组件的助手或辅助JavaScript文件
    • helloWorld.css —组件的样式

在这个模块中,我们只使用这四种资源类型。当我们真正开始为他们编写代码时,我们会更多地讨论控制器和辅助资源。现在,你可以保留默认的实现。毕竟,这只是你好世界!

什么是应用程序?

现在我们知道什么是组件,实际上很容易解释一个应用程序是什么 – 一个应用程序只是一种特殊的组件!对于本模块而言,您可以将应用程序视为与组件不同,而只有两种有意义的方式:

  • 一个应用程序使用<aura:application>标记而不是<aura:component>标记。
  • 开发者控制台中只有一个应用具有“预览”按钮。

而已!

什么是应用程序?

就像听起来那么简单,关于如何使用应用程序和组件,有一些实用的细节。主要内容如下。

  • 编写标记时,可以将组件添加到应用程序,但不能将应用程序添加到其他应用程序或应用程序添加到组件。
  • 一个应用程序有一个独立的URL,您可以在测试时访问,也可以发布给您的用户。我们经常将这些独立的应用程序称为“my.app”。
  • 您无法将应用程序添加到Lightning Experience或Salesforce应用程序 – 只能添加组件。在最后一个单位之后,这听起来很奇怪。如果不是应用程序,到底应该将什么添加到应用程序启动器?您添加到应用启动器的是一个Salesforce应用程序,该应用程序包装了一个Lightning组件,这个组件在<aura:component>中定义。 Lightning组件应用程序(即<aura:application>中定义的某些内容)不能用于创建Salesforce应用程序。有点奇怪,但它是。

那么,什么是适合的应用程序?你为什么要用一个?我们之前回答了这个问题。您使用容器发布使用Lightning组件构建的功能。 Lightning组件应用程序是Lightning组件的一种容器。

再一次实用,这通常意味着你在顶级组件中构建了所有的“应用程序”功能。然后,最后,将一个组件粘贴到一个容器中 – 也许是一个Lightning组件应用程序,也许是Salesforce应用程序,也许还有其他的东西。如果使用my.app,那么容器可以为您的主要组件设置服务,否则就是在那里托管组件。

让我们再看看我们创建的应用程序。这里再一次是harnessApp.app定义资源:

<aura:application>
	  
    <c:helloWorld/>
	    
</aura:application>

无论我们决定添加多少功能,我们将添加到我们的helloWorld“应用程序”,它都将进入helloWorld组件。它可以嵌入一个Quip风格的编辑器来修改hello消息,但是我们的harnessApp.app定义仍然非常简单。

从这里开始,我们将假设您正在使用实际的Lightning Application捆绑包作为您创建的组件的容器或线束。随意继续使用harnessApp.app!但是,当我们谈论创建应用程序时,我们的确意味着在一个组件包中构建功能,而不是应用程序包,因为这就是在现实世界中构建“应用程序”的方式。

包含组件的组件,包含…组件!

harnessApp.app定义也很有趣,因为不是静态的HTML,而是我们的helloWorld组件。我们说harnessApp包含helloWorld组件。让我们深入一点,让helloWorld更复杂一点。

在开发者控制台中,创建一个名为helloHeading的新Lightning组件。对于它的标记,粘贴在下面的代码。

<aura:component>
    <h1>W E L C O M E</h1>
</aura:component>
现在,请回到helloWorld.cmp,并在“Hello Lightning”行上面添加<c:helloHeading />。您的helloWorld组件定义现在应该如下所示:
<aura:component>
	  
    <c:helloHeading/>
	    
    <p>Hello Lightning!</p>
	    
</aura:component>
重新加载应用程序以查看更改。你的组件结构,包含什么,现在看起来像这样:
  • harnessApp.app
    • helloWorld.cmp
      • helloHeading.cmp
      • (static HTML)

我们说helloHeading是helloWorld的子组件,或者helloHeading嵌套在helloWorld中,或者…。有许多不同的方式可以说helloWorld包含helloHeading。更重要的是,您可以将其他组件中的组件嵌套到几乎任何您关心的级别。在遇到Lightning组件的局限之前,开始很难保持头脑清醒。

这个将组件放在一起的过程是构建Lightning组件应用程序的基础。您可以从简单的“细粒度”组件开始,或者构建简单的“细粒度”组件,其中每个组件都提供了一组自定义的功能。然后,将这些组件组装成具有更高级功能的新组件。然后你使用这些组件,并再次“升级”。

让我们用软件环境以外的我们熟悉的隐喻来谈论这个问题。想象一下房子。更好的是,让我们给你看一个。

House animation

当你看这个房子,你看到了什么?如果你不再把它想象成一个“房子”,而是作为一个房子的组成部分,你也会开始看到它所构成的棋子和图案。

在最大的规模,这个房子是由三个相似的结构组成。这三个组件具有相似但不相同的设计。每一个都可以进一步分解成更小的组件,比如窗户,窗户可以分解成单独的窗格。这些较小部件的排列或组成定义了三个较大结构之间的差异。

三个结构通过两个较小的狭窄结构/部件连接在一起,这些结构/部件本身可以分解成更小的可重复使用的模式。这些连接部件将三个独立的结构组合成一个更大的整体:房子。

就像我们在架构方面所做的一样,所以我们可以使用一个网络应用。稍后在这个模块中,您将获取细粒度的输入组件,并使用它们创建一个表单组件。然后,您将获取该表单组件并将其放入另一个组件,以构建应用程序级别的功能。

Expenses app composition

看起来并不像房子那么漂亮,但是构图过程背后的原理非常相似。 在组件和组成方面进行思考是您在整个模块中开发的一项基本技能,每当您使用Lightning组件构建应用程序。

在这里你已经做了一点,但是在我们真的能够构建能够做某事的组件之前,我们需要了解属性,类型,值和表达式。 在此之前,虽然,你的第一个代码挑战!

Lightning-组件(2)入门

学习目标

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

  • 描述什么是Lightning组件框架,以及它的用途。
  • 列出Lightning组件和其他Web应用程序框架之间的四个主要区别。
  • 列出至少五种不同的方式,您可以使用Lightning组件来自定义Salesforce。

Lightning组件入门

是!你仍然和我们在一起!我们非常高兴地欢迎您来到Lightning Component派对,这是一个派对。当我们说Lightning组件是我们多年来建立的最令人兴奋和最强大的应用程序开发技术时,这是一个大胆的说法。我们知道这是事实,因为我们使用它构建了Salesforce应用程序和Lightning Experience。我们认为,一旦了解Lightning组件,您就会像使用它一样兴奋。

什么是闪电组件框架?

Lightning组件是为移动和桌面设备开发Web应用程序的UI框架。这是一个现代的框架,用于为Force.com应用程序构建具有动态响应用户界面的单页面应用程序。它在客户端使用JavaScript,在服务器端使用Apex。

这是很多流行语。让我们看看我们是否不能再次尝试,用正常的人使用的话。

Again, looking at a very high level architecture

“闪电组件是一个开发Web应用程序的框架。”这似乎是可以理解的。应用程序框架是代码和服务的集合,使您可以更轻松地创建自己的自定义应用程序,而无需亲自编写所有代码。有很多不同的Web应用程序框架,比如Ruby on Rails,Grails,AngularJS,Django,CakePHP等等。我们甚至有我们自己的,Visualforce,客户知道和爱。闪电组件是新的,我们认为它是非常特殊的。我们将讨论为什么它更特别,希望在本单元的最后,你会同意!

“移动和桌面设备的网络应用程序。”再次,这似乎很容易掌握。但是…你注意到那里的订单吗? Lightning组件诞生于并用于构建移动应用程序的Salesforce平台。 Mobile已经成为Lightning组件的核心,它使开发移动和桌面设备的应用程序比许多其他框架更简单。

“这是一个用于构建单页应用程序的现代框架。”好的,现在我们有点嗡嗡了。 “现代”只是营销,对不对?什么是“单页面应用程序”?

我们不认为现代是“只是”营销。在“闪电体验开发”模块中,我们详细地讨论了Web应用程序如何从简单的,逐页面向的体验演变为具有与原生应用程序相同的行为和交互性的高响应性应用程序,在台式机上尤其是在移动设备上。为了实现这些交互式用户体验,现代Web应用程序被构建为从单个URL加载的紧密绑定的代码集合,然后在您使用它时连续运行。这些单页面应用程序的构建与本机应用程序非常相似,其中管道由一个框架处理。像Lightning组件这样的框架。

“Force.com应用程序的动态响应用户界面”只是将上述想法应用于您在Salesforce之上构建的应用程序。最后,“它在客户端使用JavaScript,在服务器端使用Apex”本身就是不言自明的,即使它遗漏了一些关于什么地方的具体细节。我们很快就会做到的

示例闪电组件

好吧,这是很多谈话谈话,而不是很多的代码。让我们来看看一个真正的Lightning组件,看看这些话题是什么。首先,这是屏幕上显示的组件的外观:

Lightning component: not much to look at, but a fair bit going on

它看起来可能不多,但是还有一点点。这是它的代码;这是我们稍后将详细介绍的一个组件。

<aura:component>

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

即使在您对Lightning组件了解之前,您仍然可以注意到有关此示例的一些信息。首先,它是XML标记,并将静态HTML标记与自定义Lightning组件标记混合,如引导样本的<aura:component>标记。如果您使用过Visualforce,则该标签的格式很熟悉:namespace:tagName。正如你以后会看到的,内置的组件可以来自各种不同的命名空间,比如aura :(如这里)或者force :, lightning :,或者ui :.

您可能已经注意到有像<lightning:input>和<lightning:formattedNumber>这样的组件。再次,这是Visualforce开发人员熟悉的模式。如果你不是其中之一,现在我们会说你使用输入组件来收集用户输入,其他组件显示只读值。以下是片段中突出显示的几个组件。

  • <lightning:card>围绕一组信息创建一个容器。
  • <lightning:formattedDateTime>显示格式化的日期和时间。
  • <lightning:relativeDateTime>显示当前时间和提供的时间之间的相对时间差。

注意

什么是名字(空间)?闪电命名空间提供了许多使用Salesforce Lightning Design System或SLDS的UI组件。我们建议您尽可能在闪电命名空间中使用组件。例如,使用<lightning:input>而不是<ui:inputText>,<ui:inputNumber>等等。大多数输入类型,如文本,数字,电子邮件,还有更多可供您使用。

我们将在后面的单元中找到其余的组件。现在,最后需要注意的是,使用静态HTML和许多以“slds”开头的CSS类名称。我们将使用SLDS来设计我们的组件,虽然我们不会在这个模块中详细解释SLDS,但是我们希望您看到它的实例。

好的,酷,Lightning组件标记是XML。但是,我们之前没有谈过关于JavaScript的东西吗?请注意切换开关上的onchange =“{!c.clickReimbursed}”属性,这是一个真正复杂的复选框,可左右滑动以表示已选中和未选中的值。这意味着“当这个值改变时,调用控制器的clickReimbursed函数。”让我们看看它附加的代码。

({
    clickReimbursed: function(component, event, helper) {
        var expense = component.get("v.expense");
        var updateEvent = component.getEvent("updateExpense");
        updateEvent.setParams({ "expense": expense });
        updateEvent.fire();
    }
})

这是用JavaScript编写的组件的客户端控制器。组件控制器中的clickReimbursed函数对应于组件标记中复选框的onchange =“{!c.clickReimbursed}”属性。

在Lightning组件中,一个组件是一组代码。它可以像“.cmp资源”中的前面的示例一样包含标记,还可以包含JavaScript代码,并包含在许多关联的资源中。相关资源是相互“自动连线”的,它们一起构成组件包。

我们将在下一个单元中详细介绍,但现在,您已经看到了两个最重要的Lightning组件代码类型。

什么关于Visualforce?

我们从客户那里得到的问题是:“我应该使用Lightning组件还是Visualforce?”简短的回答是:是的!

Visualforce和Lightning组件各有其优势。这些在Lightning Experience开发模块中进行了讨论,您可以在这里找到关于每个模块适当使用的长篇答案。在这里,让我们去中等。

首先知道这个:Visualforce不会消失。您的Visualforce代码将在很长一段时间内在Salesforce上运行。您无需转换现有的Visualforce应用程序,也无需停止使用Visualforce创建应用程序。

但是,至少在某些情况下,您可能会想要。例如,Visualforce是在手机上的移动应用成为事物之前创建的。虽然您可以使用Visualforce开发移动应用程序,但是没有任何内置组件是精通移动的。这意味着你写更多的代码。另一方面,Lightning组件专门针对移动设备进行了优化。

再次,我们在Lightning Experience模块的开发中介绍了很多具体内容。如果您仍然对Visualforce和Lightning组件有疑问,那么接下来就是一个好的选择。

关于AngularJS,React和其他JavaScript框架呢?

另一个经常出现的问题是:“Lightning组件如何与MyFavoriteFramework进行比较?”这个最喜欢的框架是另一个现代的JavaScript Web应用框架,比如AngularJS,React或者Ember。

这些都是很好的框架!很多人都认识他们,有很多资源可以学习。您可能会惊讶地发现,我们认为这些框架是构建Force.com应用程序的好方法!

我们建议使用Visualforce,使用我们所谓的容器页面,并将您选择的框架和应用程序代码打包到静态资源中。通过使用空的容器页面,Visualforce可以自由使用,并让您使用所选框架的全部功能。

尽管可以在Lightning组件中使用第三方JavaScript框架,但这有点麻烦。 Lightning组件没有空白页面的概念,并且对于如何执行数据访问以及一些相当具体的安全需求有一些特定的意见。

坦率地说,Lightning组件和大多数现代框架的功能重叠了很多。尽管样式或细节可能不同,但所提供的功能在概念上相似,足以使您有效地运行重复代码。这既不有效也不容易。

另一件需要考虑的事情是:像AngularJS这样的通用框架被设计成与他们在特定数据服务上运行的平台无关。另一方面,Lightning Components设计用于本地连接Salesforce和Force.com平台提供的服务。你认为哪种方法可以帮助你更快地构建应用程序?

注意

我们只是在这里讨论应用程序框架。如果您有一个最喜欢的JavaScript图表或映射库或其他特殊用途的工具包,则受到某些安全要求的限制,现代JavaScript库通常可以正常工作。

好吧,足够的话语单词!让我们快速浏览一下您可以部署Lightning组件应用程序的许多地方。然后让我们深入了解有趣的东西:代码。

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

您可以使用Lightning组件以多种不同的方式自定义您的Salesforce组织。但是,这不是全部!您可以使用Lightning组件创建托管在Salesforce上的独立应用程序。您甚至可以创建托管在其他平台上的应用程序,包括将这些应用程序从这些平台嵌入到应用程序中。

将应用添加到Lightning Experience应用启动器

您的Lightning组件应用程序和自定义选项卡位于应用程序启动器中,您可以通过单击标题中的“应用程序启动器App Launcher icon”图标进行操作。

Add component to the App Launcher

点击一个自定义的应用程序(1)来激活它。导航栏中显示的应用程序中的项目,包括您添加到应用程序的任何Lightning组件选项卡。请注意,您需要将组件添加到选项卡,以便可以在应用启动器中访问它们。所有项目(2)中均可找到不在应用程序中的闪电组件选项卡。

将应用添加到Lightning Experience和Salesforce App Navigation

如上例所述,您可以将Lightning组件选项卡添加到应用程序,并在应用程序的导航栏中显示为项目。

Add built-in components to the main navigation

为Lightning App Builder和社区生成器创建拖放组件

使用您自己的Lightning组件或从AppExchange安装的桌面和移动设备构建自定义用户界面。

Create Drag-and-Drop Components for Lightning App and Community Builder

将闪电组件添加到闪电页面

Lightning Page是一种自定义布局,可让您设计在Salesforce移动应用程序或Lightning Experience中使用的页面。您可以使用Lightning Page来创建应用程序主页,并将您最喜爱的Lightning组件(如我们将在此模块中创建的Expenses应用程序)添加到该主页中。

添加闪电组件到闪电体验记录页面

正如标题所示,您可以通过添加Lightning组件来自定义Lightning Experience记录页面。

Just as the title suggests, customize Lightning Experience Record Pages by adding a Lightning Component

启动闪电组件作为 Quick Action

使用Lightning组件创建动作,然后将动作添加到对象的页面布局,使其可以从记录页面即时访问。

Lightning componet quick action on record page

用闪电组件覆盖标准操作

用一个Lightning组件覆盖一个动作,这个动作与Visualforce页面重叠。

Override actions with Lightning components

创建独立的应用程序

独立应用程序包含使用Salesforce数据的组件,可以独立于标准Salesforce环境使用。

standalone apps are components that use Salesforce data and can be used independent of the Salesforce app

在Visualforce页面中运行Lightning组件应用程序

将Lightning组件添加到您的Visualforce页面,以结合使用这两种解决方案构建的功能。 使用Lightning组件实现新功能,然后将其用于现有的Visualforce页面。

使用闪电在其他平台上运行Lightning组件应用程序

闪电是一个延伸Lightning应用程序的功能。 它在任何远程Web容器中充当闪电组件的桥梁。 这意味着您可以在外部站点(例如Sharepoint或SAP)内使用Lightning组件,在使用Mobile SDK构建的混合应用程序中,甚至在Heroku平台上的其他位置使用Lightning组件。

Lightning Out on Heroku dialog

Lightning-组件(1)开始之前

学习目标

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

  • 确定你是否有能力完成这个模块。
  • 为Developer Edition组织配置我的域。
  • 创建一个必需的自定义对象

开始本模块之前

我们知道。你真是开始了。远离我们的是挫伤任何人对Trailhead的热情!但是在你完成这个模块之前,我们有一些你应该做的事情。我们建议你在开始下一个单位之前先做。这些步骤可能需要一些时间,而更改会渗透到您的组织中,而这些步骤甚至可能会让您离开另一个方向。所以,在你承诺完整的模块之前,首先需要完成这个简单的单元。

我们要做的第一件事就是在Developer Edition中启用“我的域”,或者您正在使用您的挑战。我的域名需要使用Lightning组件进行开发,并且可能需要一点时间才能在30秒到30分钟的任意位置激活。我们不愿吃掉你的Trailhead Time,让你坐下来等着。我们还将在您的组织中创建必要的自定义对象,费用对象。

然后,当我们等待我的域名时,我们可以聊一下你是否想要打开这个模块。没有人喜欢被排除,但真的,这个模块并不适合每个人。所以我们想花一点时间,谈谈这个模块的用途以及你需要完成的技能。

我们知道这听起来像是我们在激动你。我们讨厌和你一样。但我们要尊重你的时间。如果这不是适合你的模块,那么让我们来指点一下Trailhead的另一部分,所以你和我们在一起的时间是有趣而富有挑战性的,而不是令人沮丧的。

注意

如果您的DE组织已经启用了“我的域”,或者您使用了Trailhead Playground组织,请跳过此部分。您已经设置了“我的域名”。

使用我的域名向您的组织添加自定义域名

要使用Lightning组件,您的组织需要使用“我的域”配置自定义域。

那么什么是自定义域名,为什么你需要有一个使用Lightning组件?首先,一个自定义域名是一种拥有自己的Salesforce服务器的方法。您可以通过自己的自定义URL(而不是通用的Salesforce实例URL)使用Salesforce。也就是说,一旦您拥有一个自定义域名,您将使用https://yourDomain.my.salesforce.com/上的Salesforce,该域名仅供贵组织使用。让其他人继续使用和分享https://na30.salesforce.com/。您的自定义域将您置于您自己的私人互联网岛上。

建立一个自定义的域名除了给你一个很酷的URL之外还有很多好处。除此之外,自定义域允许您:

  • 使用唯一的网域网址突出显示您的商家身份
  • 品牌您的登录屏幕和自定义右帧内容
  • 阻止或重定向不使用新域名的页面请求
  • 同时在多个Salesforce组织中工作
  • 设置自定义登录策略以确定用户如何进行身份验证
  • 让用户使用登录页面上的社交帐户(如Google和Facebook)登录
  • 允许用户登录一次以访问外部服务

一个自定义的域也提高了你的组织的安全性,以太复杂的方式进入现在。在这里,我们来谈谈Lightning组件需要的原因。为了为应用程序提供世界级的安全性,我们要求Lightning组件的所有用户使用My Domain,就像我们为其他高级功能(如Salesforce Identity)所做的一样。如果您要使用Lightning组件,则必须在您的组织中启用“我的域”。

在您的组织中启用我的域

在我们开始创建Lightning组件之前,让我们使用Salesforce My Domain来设置一个子域。设置我的域名是一个要求吗?是的,如果您想在Lightning标签,Lightning页面或独立应用程序中使用Lightning组件。 Salesforce需要我的域作为安全措施来帮助防止恶意攻击,以防万一安全漏洞隐藏在第三方或自定义组件中。
如果您的DE组织已经启用了“我的域”,或者使用了Trailhead Playground组织,请跳过本节和下一节。您已经设置了“我的域名”。

如果你还没有一个子域名,很容易设置一个子域名。

每个Salesforce组织都使用像https://na30.salesforce.com这样的URL在salesforce.com域中设置。通过我的域名,您可以在salesforce.com域中定义自己的域名或子域名。您的新网址如下所示:https://yourDomain.my.salesforce.com。

使用“我的域”向导创建一个子域。

My Domain wizard

  1. 从设置中,在快速查找框中输入我的域名,然后选择我的域名。
  2. 在https://之后输入您的子域的名称,然后单击“检查可用性”。通常情况下,一个子域名是你的公司名称,但只要它是唯一的,你可以使用任何名称。如果这个名字已经被使用,请选择另一个。
  3. 点击注册域名。

Salesforce使用您的新子域更新其域注册表。完成之后,您会收到一封电子邮件,其中包含“您的开发人员版本域准备好进行测试”这个主题。只需几分钟。

重要

你是否抓住了最后一部分?可能需要几分钟时间才能使用您的域名。在获得激活邮件之前,您无法进入下一步。

将我的域名发布到您的组织

你有没有收到你的激活邮件?从电子邮件中,单击链接返回到“我的域”向导。它会将您带到第3步,在此步骤中,您可以在将子域推出到您的组织之前测试指向您的子域URL的链接。即使您没有用户将其部署到您的DE组织中,仍然必须部署“我的域”以使您的Lightning页面,Lightning App Builder中以及独立应用程序都可以使用您的自定义Lightning组件。
My Domain Wizard
  1. 点击激活电子邮件中的链接以登录到您的Salesforce子域。它会将您带到您的Salesforce组织。
    请注意,浏览器地址栏中的网址显示新的子域名。现在,你是唯一拥有这个URL的人。URL of subdomain
  2. 点击您的组织,确保链接指向您的新域名。您可能还没有在您的DE组织中创建链接,所以我们可以继续。 (在生产组织中创建域时,这个重要步骤很容易被忽略。)
  3. 在“我的域”页面上,单击“部署到用户”,然后单击“确定”。部署子域在整个组织中推出新的子域URL。现在,所有用户都可以在浏览器地址栏中看到子域名网址。
  4. 向导的第4步显示配置选项,我们现在可以忽略它。
恭喜,你已经建立了我的域名!在生产组织中设置“我的域”时,您还需要执行一些步骤。通过完成“用户验证”模块的“我的域”单元了解更多信息。现在,您已经对子域的组织进行了保护并将其品牌化,接下来让我们继续。

定义费用自定义对象

我们将在这个模块中使用的许多示例依赖于一个自定义的Expense对象。如果您将这些样本添加到您的DE组织,并自己进行实验,您将从这个模块中获得最大收益。对sObjects的引用在保存时进行验证,如果有任何对象未定义,则该组件是无效的。 Lightning组件不会让你保存一个它认为无效的组件。让我们先创建Expense对象,这样就不会遇到编译和保存依赖于它的代码的任何问题。
虽然我们假设您已经知道如何创建自定义对象,但下面是简要说明以及Expense对象及其字段的具体信息。
  1. 转到对象管理器。
    从页面顶部的安装程序中,单击对象管理器。
  2. 创建自定义对象。
    选择 Create | Custom Object.
  3. 定义费用对象。

    为对象的定义输入以下值。

    字段
    标签 Expense
    Plural 标签 Expenses
    从检查元音 checked
    API名称 Expense__c

    接受对象定义其余部分的默认值。

  4. 将自定义字段添加到费用对象。

    滚动到对象详情页面的字段和关系部分。对于以下每个字段,请单击“新建”并使用以下详细信息定义该字段。

    字段标签 API名称 字段类型
    Amount Amount__c Number(16,2)
    Client Client__c Text(50)
    Date Date__c Date
    Reimbursed Reimbursed__c Checkbox

你需要完成这个模块的技能

Salesforce的优点之一是您可以使用应用程序对其进行自定义。自定义对象和字段,公式,流程,报告,批准甚至用户界面本身 – 您可以在安装程序中完成所有这些工作,而无需编写一行代码,并让用户非常开心。

但Salesforce的某些功能需要代码,而Lightning组件就是其中之一。关于这一点,没有两种方法:要成功使用Lightning组件,您需要能够读取和写入代码。我们将在整个模块中查看大量代码,并且您必须自己写一些相应的代码才能通过这些挑战。

具体来说,我们认为:

  • 你应该舒适地阅读和编写JavaScript。尽管Lightning组件也使用HTML风格的标记,但除了“hello world”以外的任何操作都需要JavaScript。
    有大量的资源,免费的,否则,学习JavaScript。我们建议您以正确的方式开始使用JavaScript。但是,如果你的朋友或同事有一个建议,或一本书,他们可以借给你,去为它! 
  • 如果你知道Apex,那将会很棒。从Salesforce读取和写入数据通常使用Apex,这就是我们在这个模块中学到的东西。你可以通过这个模块的挑战,而不是一个Apex的专家,但是当你去写真实的应用程序,你会写很多Apex。
    Apex基础和数据库模块是开始使用Apex的好方法,并且会为您提供该模块所需的一切。

如果这不能描述你,我们不想把你拒之门外。 没有先前的技能,完成这个模块并不是不可能的。 但我们确实认为这将是令人沮丧的。 虽然你可能获得徽章 – 这绝对是很酷! – 你不会真的准备好使用Lightning组件来编写应用程序。

我们希望Trailhead变得有趣,我们希望它能够帮助人们更完整,更自信地使用Salesforce。 有经验的程序员会从这个模块中获得很多。 但是在没有必要的编程背景的情况下研究它可能不是您的Trailhead时间的最佳使用。

好。 就这样,让我们潜入并开始学习吧!