Lightning-应用程序(4)

创建一个“Hello World”闪电组件

现在,您已经学会了如何将Visualforce页面设计为Lightning Experience,您可能会想:“我将继续使用Visualforce来构建和自定义页面”。当然,你可以做到这一点,而且没有任何问题。

但是,由于您已经转向Lightning Experience,因此开始考虑使用Lightning组件构建和自定义页面可能会更好。为什么?因为Lightning组件是Lightning Experience的构建模块,并且它们是模块化的,所以它们可以在多个页面上使用,或者就像我们即将做的那样,同一个组件可以在页面上多次出现,但执行不同的任务!

但为了学习构建闪电组件,你必须从某个地方开始。而且每个开发者都会告诉你,那个地方叫做Hello World。老实说,这是你将要建造的最笨的组件,但它会让你有机会探索构建Lightning组件的基本概念。

到此步骤结束时,您将会:

  • 建立一个简单的闪电组件
  • 学习声明属性并使用数据绑定
  • 从Salesforce中检索数据并将其显示在Lightning组件中。

第1部分 – 创建一个Hello World组件

Lightning组件可以使用开发者控制台来构建,或者当您获得更高级的时候,您可能需要使用Force IDE,Sublime Text,MavensMate或Visual Studio Code。但是由于我们已经在开发者控制台中工作,所以让我们继续前进。

  1. 关闭开发者控制台中的所有打开的选项卡,然后选择 File > New > Lightning Component.
    你会注意到最终的对话框实际上是说新的闪电包,而不是组件。这实际上更准确,因为Lightning组件实际上是一组文件 – 实际上最多八个文件。我们稍后会查看个别文件。
  2. 命名组件: HelloWorld
    接下来我们将选择组件的使用位置。有五个选项,每个选项由对话框中的复选框表示。通过检查一个或多个选项,实际上就是将所谓的接口添加到组件。该接口为组件添加了功能,以便在不同的情况下使用它:

    • Lightning Tab 指定该组件可以用作Salesforce 1中的选项卡。
    • Lightning Page 允许将组件同时放置在主页和记录页面上。
    • Lightning Record Page, 顾名思义,允许组件在“对象记录”页面上使用。
    • Lightning Communities Page 允许组件被使用,是的,你猜对了,一个闪电社区页面。
    • Lightning Quick Action 使组件可以用作Lightning Experience中的快速操作。
  3. 选择 Lightning Record Page 然后单击 Submit.
    恭喜!你已经创建了一个从字面上什么都不做的闪电组件!不过,这也让我们有机会谈论一些事情。比如,为什么当我认为我们正在创建一个闪电组件时,会说<aura:component>? Aura实际上是一个开源的版本框架,我们在上面建立了Lightning框架。

    注意implements =属性。这是组件使用的接口列表。对于这个组件,你勾选了闪电记录页面的框。在代码中,我们使用接口flexipage来表示:availableForRecordHome。在“记录”页面上使用组件时,可能还需要知道正在查看哪个记录。这表示为force:hasRecordId,并且在选择Lightning Record Page时自动添加。

  4. <aura:component> ... </aura:component> 标签内输入Hello。
    如前所述,Lightning组件最多可以包含八个文件。您当前正在使用组件文件或.cmp文件。这是组件的实际HTML标记。其他可能的文件由开发者控制台右侧的按钮表示。

    • Controller—这是一个JavaScript文件,将用于向组件添加功能。
    • Helper— 这也是一个JavaScript文件,用于共享JavaScript功能。
    • Style—这是一个CSS文件,您可以在其中编写自己的CSS组件。
    • Documentation—该文件允许您为组件编写文档,这些文档将显示在组织的帮助页面中。
    • Design—开发者最重要的文件,因为这个文件允许你在App Builder中公开你的组件的参数。
    • SVG—这个可缩放矢量图形是App Builder组件列表中的组件的图标。
  5. 保存文件。

Screenshot of Hello World component showing its eight files.

第2部分 – 将组件添加到页面

构建Lightning组件时,有两种预览组件的方法。首先是将组件添加到闪电应用程序。这实际上是一个独立的应用程序,而不是闪电体验的应用程序。实际上,App Builder本身就是一个Lightning应用程序。另一种方法是简单地把组件放在一个页面上。

  1. 在你的组织中,打开一个财产记录页面,如果你还没有打开。
  2. 点击设置图标Setup icon,然后选择 Edit Page.
  3. 在自定义组件下,找到您的HelloWorld组件并将其拖到右侧列的顶部。
  4. 点击保存,然后点击返回返回到属性页面。

Screenshot of Hello World component in the Property page.

瞧!您已经创建了您的第一个Lightning组件,并将其添加到Lightning Experience中的页面中。但我们不得不承认,这是一个非常愚蠢的组成部分。所以让我们更有趣吧

第3部分 – 使用数据绑定

Salesforce Classic和Lightning Experience之间的区别之一在于,Lightning Experience中的页面全部在客户端呈现。这意味着来自Salesforce的组件和数据作为单独的包到达浏览器,并且必须由Lightning Framework连接在一起。

  1. 在开发者控制台中,将Hello文本替换为:
    <aura:attribute name="greeting" type="String" default="World" />
    
    Hello, {!v.greeting}!
    

    您刚添加了一个名为<aura:attribute>的组件。一个<aura:attribute>基本上是一个存储数据的地方,以便组件可以使用这些数据。每个<aura:attribute>都有一个名称,您将在JavaScript中使用该名称来检索数据。另外这些<aura:attributes>是强类型的。这个属性被声明为String类型,所以如果我们尝试给它赋一个数值,我们会得到一个错误。而且我们已经为该属性分配了一个默认值“World”。

    Lightning组件中的表达式与Visualforce中的表达式几乎相同。他们都使用{! }作为表示表示的符号。唯一的区别是,在Lightning组件中,表达式可能需要评估包中不同文件中的某些内容。因此,Lightning组件使用v。来表示应在视图中评估表达式,这是在组件标记或.cmp文件中表达的一种奇特方式。

    因此,在这个例子中,组件被要求查找位于同一个文件中的问候语的值。

  2. 保存组件并重新加载“属性记录”页面。
    Screenshot of Hello World component.
  3. 回到开发者控制台,在HelloWorld.cmp中,在<aura:attribute />标签下的新行中添加以下输入字段:
    <lightning:input name="Greeting" label="Greeting" value="{!v.greeting}"/>
    

    在Visualforce中,我们使用了由服务器变成HTML的声明标签。在Lightning组件中,我们也有创建一些元素的声明方式。这些被称为基本闪电组件。在这里,您添加了一个<lightning:input>,当组件在页面上呈现时,它将被转换成合适的SLDS标记。

    注意,输入的名字是“Greeting”,也就是<aura:attribute>的名字。这是一件令人难以置信的事情,但我们要让你这样做来说明一个观点。而这一点,“问候”和“问候”是不一样的。在闪电组件世界里,事情是区分大小写的。

    最后,请注意输入值为{!v.greeting}。换句话说,当组件在页面上呈现时,<aura:attribute>的默认值将被预填充到输入中。这被称为数据绑定。

  4. 保存文件并重新加载“属性记录详细信息”页面。
  5. 在输入字段中输入名称以查看数据绑定的实际操作。
    Screenshot of Hello World component showing data binding.
    当您输入输入内容时,无论您输入什么内容,都会立即指定为名为“greeting”的<aura:属性>的{!v.greeting}的值。当这个值改变时,Hello,{!v.greeting}中表达式的值也会改变。

第4部分 – 从Salesforce中提取数据

当然,输入一个输入并改变页面上的一些文本是很有趣的,但是在现实世界中可能不那么有用。大多数(如果不是所有的)Lightning组件都将显示和处理来自Salesforce的数据。他们的工作方式与您在Visualforce中的完全相同。也就是说,他们使用Apex类作为控制器。

对于我们的最后一招,让我们有闪电组件向你问好。换句话说,让我们让组件找出谁登录并显示他们的名字。

  1. 在开发人员控制台中,选择 File > New > Apex Class. 将控制器HelloWorld Controller命名并单击OK。
  2. 添加 with sharing 到类的定义。
    public with sharing class HelloWorldController {
    
        }
    

    带有共享with sharing注释告诉Apex类强制登录用户的字段访问。

  3. Add the following method inside the class:
    @AuraEnabled
    public static User getCurrentUser() {
        return [SELECT Id, FirstName, LastName FROM User WHERE Id = :UserInfo.getUserId() LIMIT 1];
    }
    
    第一个是@AuraEnabled,这个签名指示类允许从闪电组件调用这个方法,第二个是静态的在方法上签名。所有从闪电组件调用的方法必须有这个签名。
  4. 回到 HelloWorld 组件并将 controller="HelloWorldController" 添加到 <aura:component> 标记中。

    就像在Visualforce中一样,我们需要告诉组件将使用哪个Apex Class作为它的服务器端控制器。

  5. 在greeting属性声明之后添加一个init事件处理程序。
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    

    闪电组件使用..<aura:handler> 侦听和响应事件这个处理器的名字是初始化,这也是它当一个组件加载触发事件的名称,但它在网页上呈现前在这种情况下,它是一个名为doInit的函数,它可以在组件包的客户端JavaScript控制器文件中找到。

  6. 点击右侧边栏中的CONTROLLER为该组件创建客户端控制器文件。
  7. 将生成的代码替换为:
    ({
        doInit : function(component, event, helper) {
            var action = component.get("c.getCurrentUser");
            action.setCallback(this, function(response) {
                var user = response.getReturnValue();
                component.set("v.greeting", user.FirstName);
            })
            $A.enqueueAction(action);
        }
    })
    
    如果你做任何JavaScript编码,我们宣布了doInit功能可能看起来有点怪异的方式。这是因为这实际上是一个JSON文件。JSON文件是包含名称/值对的文件。这个名字是doInit和值(用冒号分隔)是函数本身。

    “组件”是将组件的标记传递给函数“查找”诸如<aura:attributes>之类的东西以及实际它的元素值component.get(“c.getCurrentUser”)告诉组件找出它的服务器端控制器,然后getCurrentUser只要记住.cmp文件中的c表示“客户端JavaScript控制器”,而c。客户端JavaScript控制器中的意思是“服务器 – Apex班“。

    所以我们还没有回答如何回复请求,所以我们定义了一个回叫。这个值又分配给<aura:attribute name =“greeting”>这个值在回调函数中,一个变量user,被定义并分配了实际的数据。在组件中。

    最后,$ A.enqueueAction(action);将行动添加到Lightning Framework为构建页面而做的事情队列中。

  8. 保存所有文件,然后刷新属性页面。

    就像魔术一样,你的组件现在知道你已经登录了,并且向你问好!
    使用来自登录用户的数据的Hello World组件的屏幕截图。
    Screenshot of Hello World component using data fromlogged in user.

Lightning-应用程序(3)

使用Visualforce页面作为闪电页面中的组件

在之前的步骤中,我们学习了如何设计一个Visualforce页面以适应Lightning Experience。一旦你意识到一个Visualforce的页面可以成为Lightning Experience的头等公民,它将开启所有新的可能性。实际上,Visualforce页面不仅可以用作Lightning Experience中的完整页面或选项卡,就像在上一步中一样,Visualforce页面也可以用作Lightning Experience页面中的组件。

在这一步你会:

  • 使用App Builder修改Lightning页面。
  • 在“Lightning Experience”页面中启用Visualforce页面作为组件。

第1部分 – 使Visualforce页面可用于闪电体验

  1. 在您的组织中,单击“属性”并选择任何属性以查看“属性记录”页面。
    这是Property对象的标准Record页面。但是,如果您向下滚动该属性的图片,则会看到一些奇怪的情况。它看起来像一个Visualforce页面…但在闪电页面!而这正是它是什么。
    Screenshot showing look and feel of related properties
  2. 点击设置图标Setup icon,然后选择Click  and select Edit Page.
  3. 单击以选择App Builder中页面顶部的“突出显示”面板(A)。
    Screenshot of the Lightning App Builder

    点击Hightlights面板激活App Builder(B)右侧栏中的几个选项。

  4. 在“分配页面布局Assign Page Layouts”部分中,单击“属性布局Property Layout”(C)。

    当然,你可以通过点击Setup图标,选择Setup,点击Object Manager选项卡,滚动到所需的Object,在我们的例子Property中,然后点击选择它,点击左侧的Page Layouts链接手列并点击所需的布局。这取决于你选择哪一个更容易。

  5. 在“页面布局编辑器”中向下滚动,并注意已添加名为“类似属性”的部分。该部分内部是一个Visualforce页面,也被称为 Similar Properties.
  6. 单击主页Home 选项卡并在快速查找框中输入Visualforce并选择Visualforce页面。
  7. 点击类似属性页面的编辑。
  8. 选择 Available for Salesforce mobile apps and Lightning Pages.
    这个神奇的小复选框告诉Lightning Experience,您希望将此Visualforce页面不仅用作页面或选项卡,而且还希望能够将其用作组件,就像您在App Builder中使用的其他标准和自定义组件一样并自定义页面。
  9. 单击保存并关闭浏览器中的设置选项卡。

第2部分 – 再次修改标记!

一旦启用了用于Salesforce 1和Lightning Experience的Visualforce页面,使用App Builder将其添加到页面中很容易。但首先,因为我们已经看到该页面看起来像Salesforce Classic,所以让我们像上一个练习一样更新Lightning Experience的标记。

  1. 在开发者控制台中,选择 File > Open Resource.
  2. 打开 Similar_Properties.vfp.
  3. 将lightningStylesheets =“true”添加到<apex:page>标记。
  4. 保存页面。
  5. 切换回Lightning App Builder,点击Back返回到Property Record页面。
  6. 向下滚动到“类似属性”VF页面,查看对嵌入式Visualforce页面的更改。

Screenshot showing revised look and feel of related properties

第3部分 – 使用Visualforce页面作为闪电组件

页面定制是Lightning Experience的主要功能之一,因此,现在让我们根据您的Visualforce专业知识,在Salesforce Classic中做一些难以或甚至不可能的事情。我们将在“属性”页面的右侧列的顶部显示选定属性的类似属性。

  1. 点击Setup icon 然后选择 Edit Page.
  2. 在标准组件列表中找到Visualforce组件。
  3. 将组件拖动到页面上,并将其放在右侧列的顶部。
  4. 在组件属性中,应该自动选择SimilarProperties作为Visualforce页面名称。
    如果您为Salesforce 1和Lightning页面启用了多个Visualforce页面(请记住复选框?),它们将显示在选择列表中,然后选择要显示的页面。
  5. Label 字段中, 输入Other Properties. 将高度设置为 200.
  6. 点击 Save.

    由于这是我们第一次修改标准的属性页面,我们需要激活更新的页面,以便我们的用户可以看到我们已经做了什么。

  7. 点击 Activate.
  8. 点击 App Default ,然后Assign as App Default.
  9. 选择 Dreamhouse Lightning 应用程序,然后单击Next ,然后Save.
  10. 点击 Back 返回属性页面Screenshot showing new Visualforce component on the Properties page

Lightning-应用程序(2)

为用户界面有条件地呈现Visualforce页面

在上一步中,我们让服务器从Visualforce更新标准样式表。虽然页面看起来不错,但只要做一点工作,就可以更接近Lightning Experience的外观。所以这个练习我们将会:

  • 确定用户正在使用的UX环境(Salesforce Classic或Lightning Experience)
  • 将条件呈现逻辑添加到页面以提供来自SLDS的其他样式
  • 使用SLDS中的其他类来调整页面元素

第1部分 – 将用户主题的检查添加到Apex控制器

当用户在Salesforce中请求页面时,系统可以识别用户是处于Salesforce Classic还是Lightning Experience。有了这些知识,我们可以包含或排除页面的区域,以确保用户的正确体验。

  1. 在开发人员控制台中,单击 File > Open > Classes 然后选择DreamhouseProspects Apex 类,然后单击 Open.
  2. 在public String sortOrder {set; get;} 行,添加下面的方法来检测用户的当前主题:
    public Boolean getIsClassic() {
        return (UserInfo.getUiThemeDisplayed() == 'Theme3');
    }
    
    如果用户处于Salesforce Classic中,此方法将返回true,如果用户处于Lightning Experience中,则返回false。
  3. Apex类现在应该是这样的:
    public with sharing class DreamhouseProspects {
        public String sortOrder {set; get;}
    
        public Boolean getIsClassic() {
            return (UserInfo.getUiThemeDisplayed() == 'Theme3');
        }
    
        public List<Lead> getLeads() {
            if (sortOrder == null) {
                sortOrder = 'LastName';
            }        
            return Database.query('SELECT Description,Email,FirstName,Id,LastName,Phone FROM Lead WHERE Company=\'Dreamhouse\' ORDER BY '+sortOrder);
        }
    
        public pageReference sortList() {
            getLeads();
            return null;
        }
    }
    
  4. 保存Apex类并关闭其在开发者控制台中的标签。

第2部分 – 根据用户的主题添加动态块

  1. 在开发者控制台中,通过在打开的<apex:page>标记之后添加一个新行,将指令添加到DreamHouseLeads.vfp页面:
    <apex:slds />
    

    这个简单的标签告诉页面包含Salesforce Lightning设计系统中的CSS,以便我们可以将SLDS中的样式应用于各个页面元素,或者使用SLDS中的标记。以前,您需要创建此CSS的自定义版本,并将其作为静态资源包含在您的组织中。但是这有一个缺点,就是真正的静态 – 所以如果SLDS团队对CSS进行了更改,那么您的静态资源就不会收到这些更改。采用这种新方法,您总能从SLDS接收最新,最好的CSS。

    请记住,lightningStylesheets =“true”不会将SLDS样式表添加到页面,而是将标准样式表从Visualforce更改为像SLDS一样的“外观”。

  2. rendered="{! !isClassic}" 添加到 <apex:slds /> 标签中:
    <apex:slds rendered="{! !isClassic}" />
    
    该页面现在将检查由我们创建的Apex方法返回的值,以检查用户的环境。换句话说,{!isClassic}将评估为true或false。在isClassic前加上感叹号是说“不”的程序化方式。如果用户处于经典模式,则表达式呈现=“{!!isClassic}”,将导致<apex:slds rendered =“false”>,但是<apex:slds rendered =“true”> 。

    因此,如果用户在Lightning中,则可以使用来自SLDS的样式和标记,而在Classic中,该页面仍然像以前一样。

第3部分 – 添加SLDS标题

页面的自动样式看起来相当不错,但页面标题与Lightning Experience中的其他页面不完全相同。请注意,它缺少标题旁边的标准图标,选择列表看起来像标准浏览器选择。此外,选择和排序按钮太靠近表格。如果没关系,那就巧妙地移动。为了这个项目的目的,让我们继续前进,使之与Lightning Experience完全匹配。

  1. 交换<apex:sectionHeader>和<apex:form>元素(在第4行和第5行左右)。

    为了有条件地传递页面的元素,我们需要能够用<apex:outputPanel>标签包装完整的元素。所以,我们刚刚在窗体中移动了节标题,以便我们可以用New按钮,选择列表和排序按钮将它们包装在一起。

  2. 使用<apex:outputPanel rendered =“{!isClassic}”> … </ apex:outputPanel>将整个代码从<apex:sectionHeader>包装到<apex:commandButton>(从第5-14行开始)。你的代码现在应该是这样的:
    <apex:page controller="DreamhouseProspects" lightningStylesheets="true">
        <apex:slds rendered="{! !isClassic}" />
        <apex:pageBlock >
            <apex:form >
                <apex:outputPanel rendered="{!isClassic}">
                    <apex:sectionHeader title="Leads" subtitle="Home"/>
                    <div style="text-align:center;">
                        <apex:commandButton action="{!URLFOR($Action.Lead.New)}" value="New"/>
                    </div>
                    <apex:outputLabel value="Sort: " for="sortList" />
                    <apex:selectList value="{! sortOrder}" size="1" id="sortList">
                        <apex:selectOption itemvalue="LastName" />
                        <apex:selectOption itemvalue="FirstName" />
                    </apex:selectList>
                    <apex:commandButton value="Sort Table" action="{!sortList}" reRender="leads_list"/>
                </apex:outputPanel>
                <apex:pageBlockTable value="{! leads }" var="ct" id="leads_list">
                    <apex:column headerValue="First Name">
                        <apex:outputLink value="/{! ct.Id}">{! ct.FirstName }</apex:outputLink>
                    </apex:column>
                    <apex:column value="{! ct.LastName }"/>
                    <apex:column value="{! ct.Email }"/>
                    <apex:column value="{! ct.Phone }"/>
                </apex:pageBlockTable>              
            </apex:form>
        </apex:pageBlock>
    </apex:page>
    
  3. 保存页面并重新加载到浏览器中。
    Leads list in Lightning Edition with no header

    您已经丢失了整个页面标题和按钮,因为现在只会在{!isClassic}解析为true时才呈现。所以,现在我们可以添加一个匹配Lightning Experience的页面标题。

    虽然我们完全可以创建自己的HTML标记和CSS,但Visualforce开发人员可以做的更多,我们的目标是确保页面看起来与Lightning Experience完全一样。这正是Salesforce UX团队创建SLDS的原因。我们可以简单地将标记从SLDS复制并粘贴到我们的页面中,并修改其占位符内容以显示我们所需的内容。

  4. 导航到SLDS站点。点击侧边栏中的组件,然后点击页面标题,熟悉标记和样式。

    您看到的每个组件的预览都是由您在预览下方看到的标记构建的。这是您可以简单地复制/粘贴,然后修改的标记。

    但是,请注意,对于每个组件(在本例中为页眉),页面的右侧都有变体和状态。这些是所选组件的不同版本。当您选择变体或状态时,示例标记会更改。所以请确保您正在复制所需的变体或状态。

    为了让你更容易,我们已经复制了Object Home变体,并对其进行了修改。

  5. 返回到开发者控制台,在最后一步添加的</ apex:outputPanel>(可能是第16行)之后的新行中添加以下代码:
    <apex:outputPanel rendered="{! !isClassic}">
        <div class="slds-page-header">
            <div class="slds-grid">
                <div class="slds-col slds-has-flexi-truncate" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
                    <div class="slds-media slds-no-space slds-grow">
                        <div class="slds-media__figure">
                            <svg class="slds-icon slds-icon-standard-user .slds-icon_small" aria-hidden="true">
                                <use xlink:href="{!URLFOR($Asset.SLDS, 'assets/icons/standard-sprite/svg/symbols.svg#lead')}"></use>
                            </svg>
                        </div>
                        <div class="slds-media__body">
                            <p class="slds-text-title_caps slds-line-height_reset">Lead</p>
                            <h1 class="slds-page-header__title slds-m-right_small slds-align-middle slds-truncate"
                                title="this should match the Record Title">Home</h1>
                        </div>
                    </div>
                    <div class="slds-grid slds-grid_vertical-align-end slds-m-vertical_small">
                        <div class="slds-size_1-of-6 ">
                            <apex:outputLabel value="Sort: " for="sortListLightning" styleClass="slds-form-element__label" />
                            <div class="slds-select_container">
                                <apex:selectList value="{! sortOrder}" size="1" id="sortListLightning" styleClass="slds-select">
                                    <apex:selectOption itemvalue="LastName" />
                                    <apex:selectOption itemvalue="FirstName" />
                                </apex:selectList>
                            </div>
                        </div>
                        <div class="slds-no-flex slds-m-left_x-large">
                            <apex:commandButton value="Sort" action="{!sortList}" reRender="leads_list" styleClass="slds-button slds-button_neutral"/>
                        </div>
                    </div>
                </div>
                <div class="slds-col slds-no-flex slds-grid slds-align-top">
                    <apex:commandButton action="{!URLFOR($Action.Lead.New)}" value="New" styleClass="slds-button slds-button_neutral"/>
                </div>
            </div>
        </div>
    </apex:outputPanel>
    

    当isClassic的值为false时,这个<apex:outputPanel>再次呈现,即用户在Lightning Experience中。

    请注意,页面标题正在使用来自SLDS的SVG格式的图标。即使你没有上传静态资源,因为你包含了<apex:slds>标签,页面知道在哪里可以找到图标。

    您还可以看到我们已经在<apex:outputLabel>和<apex:selectList>周围添加了一些<div>包装,以便它们的标记与SLDS中的标记相匹配,正确的级联将允许它们正确呈现。

  6. 保存文件并在Lightning Experience中重新加载页面以查看您的更改。
    Leads list in Lightning Edition with a new header
    现在,你不得不承认这很酷,对吗?通过对页面进行一些更改,我们现在可以在Lightning Experience中正确呈现标准Visualforce页面,同时在Salesforce Classic中保持其原始外观。

    以防万一你的页面倒下,并轰然倒地…这是代码应该看起来的样子:

    <apex:page controller="DreamhouseProspects" lightningStylesheets="true">
        <apex:slds rendered="{! !isClassic}"/>
        <apex:pageBlock >
            <apex:form >
                <apex:outputPanel rendered="{! isClassic}">
                    <apex:sectionHeader title="Leads" subtitle="Home"/>
                    <div style="text-align:center;">
                        <apex:commandButton action="{!URLFOR($Action.Lead.New)}" value="New"/>
                    </div>
                    <apex:outputLabel value="Sort: " for="sortList" />
                    <apex:selectList value="{! sortOrder}" size="1" id="sortList">
                        <apex:selectOption itemvalue="LastName" />
                        <apex:selectOption itemvalue="FirstName" />
                    </apex:selectList>
                    <apex:commandButton value="Sort Table" action="{!sortList}" reRender="leads_list"/>
                </apex:outputPanel>
                <apex:outputPanel rendered="{! !isClassic}">
                    <div class="slds-page-header">
                        <div class="slds-grid">
                            <div class="slds-col slds-has-flexi-truncate" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
                                <div class="slds-media slds-no-space slds-grow">
                                    <div class="slds-media__figure">
                                        <svg class="slds-icon slds-icon-standard-user .slds-icon_small" aria-hidden="true">
                                            <use xlink:href="{!URLFOR($Asset.SLDS, 'assets/icons/standard-sprite/svg/symbols.svg#lead')}"></use>
                                        </svg>
                                    </div>
                                    <div class="slds-media__body">
                                        <p class="slds-text-title_caps slds-line-height_reset">Lead</p>
                                        <h1 class="slds-page-header__title slds-m-right_small slds-align-middle slds-truncate"
                                            title="this should match the Record Title">Home</h1>
                                    </div>
                                </div>
                                <div class="slds-grid slds-grid_vertical-align-end slds-m-vertical_small">
                                    <div class="slds-size_1-of-6 ">
                                        <apex:outputLabel value="Sort: " for="sortListLightning" styleClass="slds-form-element__label" />
                                        <div class="slds-select_container">
                                            <apex:selectList value="{! sortOrder}" size="1" id="sortListLightning" styleClass="slds-select">
                                                <apex:selectOption itemvalue="LastName" />
                                                <apex:selectOption itemvalue="FirstName" />
                                            </apex:selectList>
                                        </div>
                                    </div>
                                    <div class="slds-no-flex slds-m-left_x-small">
                                        <apex:commandButton value="Sort" action="{!sortList}" reRender="leads_list" styleClass="slds-button slds-button_neutral"/>
                                    </div>
                                </div>
                            </div>
                            <div class="slds-col slds-no-flex slds-grid slds-align-top">
                                <apex:commandButton action="{!URLFOR($Action.Lead.New)}" value="New" styleClass="slds-button slds-button_neutral"/>
                            </div>
                        </div>
                    </div>
                </apex:outputPanel>
                <apex:pageBlockTable value="{! leads }" var="ct" id="leads_list">
                    <apex:column headerValue="First Name">
                        <apex:outputLink value="/{! ct.Id}">{! ct.FirstName }</apex:outputLink>
                    </apex:column>
                    <apex:column value="{! ct.LastName }"/>
                    <apex:column value="{! ct.Email }"/>
                    <apex:column value="{! ct.Phone }"/>
                </apex:pageBlockTable>              
            </apex:form>
        </apex:pageBlock>
    </apex:page>
    

Lightning-应用程序(1)

介绍

在这个项目中,我们将介绍如何使用Visualforce页面并将其设计为Lightning Experience。然后,我们最终用Lightning组件取代闪电体验。以下是我们将要处理的过程:

  1. 使用Salesforce Lightning Design System(SLDS)为Lightning Experience创建一个Visualforce页面。
  2. 将条件逻辑添加到Visualforce页面,以便在Salesforce Classic和Lightning Experience中正确显示。
  3. 使用App Builder添加一个Visualforce页面作为Record Home Page的一部分。
  4. 作为记录主页的一部分,将Visualforce页面替换为Lightning组件。
  5. 在Visualforce中使用Lightning组件。

听起来很有趣?让我们开始吧!

在Lightning Experience中使用Visualforce页面

没错,可以在Lightning Experience中使用Visualforce页面。有一些需要注意的事项,例如Visualforce页面中的一些JavaScript可能需要更新。例如,用于导航的链接或按钮必须更新为Lightning Experience中的导航工作方式。有关更多信息,请查看此Trailhead模块,Lightning替代JavaScript按钮。但总的来说,Visualforce在闪电体验中“正常工作”。

第1部分 – 创建Trailhead游乐场,安装软件包和导入数据

我们需要做的第一件事是创建一个新的Trailhead游乐场。然后,我们可以安装一个包含我们在这个项目中工作的应用程序和代码的包。

  1. 滚动到此页面的底部。
  2. 点击启动旁边的箭头,然后选择 Create a Trailhead Playground. 不要跳过这一步!这个项目你需要使用一个清新干净的Trailhead游乐场
  3. 一旦你的游乐场创建(这需要一分钟!),按 Launch.
  4. 在您的游乐场的URL中,将lightning.force.com中的所有内容替换为: /packaging/installPackage.apexp?p0=04t6A000000SG0F. 您的网址应类似于以下内容:https://playful-raccoon-21646-dev-ed.lightning.force.com/packaging/installPackage.apexp?p0=04t6A000000SG0F. 点击 Return.
  5. 选择 Install for All Users 然后单击 Install.
  6. 安装完成后,点击完成,然后打开左上角的应用启动器应用启动器图标App Launcher Icon
  7. 选择 Dreamhouse Lightning 应用程序.
  8. 单击 Data Import 然后单击 Initialize Sample Data. 根据您的屏幕大小,数据导入选项卡可以在选项卡菜单中的更多。

第2部分 – 检查Visualforce页面

  1. 在DreamHouse Lightning应用程序中,单击Leads。注意,这个页面看起来像Salesforce Classic–因为它是!这是一个自定义的Visualforce页面,当DreamHouse应用程序仍然是一个经典的应用程序时,它被添加为选项卡。但是页面只能在Lightning Experience中运行。
  2. 现在启动开发者控制台。点击Gear图标Gear Icon for Settings进行设置,然后选择Developer Console。
  3. 单击 File > Open Resource, 然后找到名为DreamHouseLeads.vfp的Visualforce页面。请注意,这是标准的Visualforce标记,由页面块,节标题,表单,按钮,选择列表和表组成。正如您已经在您的组织中看到的那样,该页面只是简单地从数据库返回线索。但是,由于我们在Lightning Experience中,让我们将页面看起来像它实际上属于它的样式,看起来像闪电体验。为此,我们使用Salesforce Lightning设计系统。 Salesforce Lightning设计系统(简称SLDS)使您可以轻松构建符合新Salesforce Lightning Experience外观的应用程序,而无需将UI反向工程化为自定义CSS。实际上,使用Lightning Design System标记会导致具有Lightning Experience外观的页面,而不会写入任何CSS!

    Screenshot of the Leads list without Lightning Experience Styling.

第3部分 – 添加Lightning体验样式

  1. 在开发者控制台中,通过在<apex:page>标签中添加lightningStylesheets =“true”来为DreamHouseLeads页面添加一条指令:
    <apex:page controller="DreamhouseProspects" lightningStylesheets="true">
    
    这个简单的属性告诉服务器为Visualforce包含特殊版本的标准CSS样式表。标准样式表的这一特殊版本使用Salesforce Lightning设计系统的样式,与Lightning Experience的外观非常匹配。由于Visualforce的HTML标记与SLDS框架中的标记不同,因此无法实现完全匹配。但是它非常接近!
  2. 保存文件并重新加载组织中的页面。
    Screenshot of the Leads list without Lightning Experience Styling.就这样,你的Visualforce页面开始看起来像闪电!而在Classic中,页面仍然看起来与以往一样,因为采用Lightning Experience外观的Visualforce标准样式表仅在从Lightning Experience请求页面时应用。

Lightning-数据服务(3)

学习目标

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

  • 解释Lightning Data Service如何使用通知。
  • 使用recordUpdated来处理错误并记录更改。

记录更改

在最后一个单元中,我们介绍了Lightning Data Service如何处理CRUD。现在让我们来看看如何在记录更改时采取措施,以便您的组件可以响应记录加载,更改,更新或删除操作。

要在记录更改时采取措施,请处理recordUpdated事件。

<force:recordData aura:id="forceRecordDataCmp"
    recordId="{!v.recordId}" 
    layoutType="{!v.layout}"
    targetRecord="{!v.record}"
    targetFields="{!v.simpleRecord}"
    targetError="{!v.error}"
    recordUpdated="{!c.recordUpdated}" />
实现处理更改的动作处理程序。字段更改通过changedFields对象传入,该对象包含与新值关联的旧字段值。
({
    recordUpdated: function(component, event, helper) {
        var eventParams = event.getParams();
        if(eventParams.changeType === "CHANGED") {
            // 获取为此记录更改的字段
            var changedFields = eventParams.changedFields;
            console.log('Fields that are changed: ' + JSON.stringify(changedFields));
            // 记录被改变,所以刷新组件(或其他组件逻辑)
            var resultsToast = $A.get("e.force:showToast");
            resultsToast.setParams({
                "title": "Saved",
                "message": "The record was updated."
            });
            resultsToast.fire();
        } else if(eventParams.changeType === "LOADED") {
            console.log("Record is loaded successfully.");
        } else if(eventParams.changeType === "REMOVED") {
            var resultsToast = $A.get("e.force:showToast");
            resultsToast.setParams({
                "title": "Deleted",
                "message": "The record was deleted."
            });
            resultsToast.fire();
        } else if(eventParams.changeType === "ERROR") {
            console.log('Error: ' + component.get("v.error"));
        }
    }
})
当LDS检测到记录更改时,会通知使用更改记录的组件。如果您不处理更改,记录仍会更新,因此对targetRecord或targetFields属性的任何引用都会自动显示在您的组件中。对于每个强制:recordData组件引用更新的记录,LDS做两件事情。
  • LDS通过使用适当的changeType和changedFields值触发recordUpdated事件来通知所有其他的force的实例:recordData。
  • 它将每个force:recordData上的targetRecord和targetFields属性设置为新的记录值。如果targetRecord或targetFields被任何UI引用,则会自动触发重新渲染,以便UI显示最新的数据。

注意

如果force:recordData处于EDIT模式,则当记录更改时,targetRecord和targetFields不会自动更新。这是为了避免仍在进行的clobberin编辑,并防止未保存的更改出现在其他组件中。不用担心,可以通过处理recordUpdated事件并调用reloadRecord方法手动刷新记录。

当LDS检测到源自服务器的更改时,它使用相同的更改通知机制。 LDS在收到记录的新请求时检测到服务器上的更改。 LDS仅通知已注册并标记为isValid的组件。处理recordUpdated事件时,请检查changeType以确定要处理的更改类型。

错误处理

如果加载时发生错误,则将targetError属性设置为本地化的错误消息。如果强制属性:recordData无效,或者服务器不可访问且记录不在本地缓存中,则会发生错误。从那里,你决定如何显示错误。

如果记录在服务器上无法访问,那么recordUpdated事件触发changeType = REMOVED,并且没有错误设置为targetError,因为记录变得不可访问有时是预期的结果。由于记录或实体共享和可见性设置,或记录被删除,记录也可能变得不可访问。

把它放在一起

恭喜!现在您已经知道开始使用Lightning Data Service所需了解的一切。这是相对简单的,但它做了很多!在你脱颖而出并获得那个新奇的徽章之前,让我们把所有这些理论付诸实践。我们将放置一个页面,其中包含两个使用相同记录数据的组件,并正确响应记录更改。

该组件显示联系人的详细信息。请注意,它使用字段而不是layoutType,targetFields而不是targetRecord。请记住,可以包含fields或layoutType(或两者!),并且可以使用targetFields或targetRecord(或两者!)来检索记录数据。对于字段,您必须指定要查询的特定字段,而使用layoutType时,只需指定要使用的记录布局,即FULL或COMPACT。如果使用targetFields检索首选方法的数据,请在UI中使用v.targetFields.Name格式。如果您使用targetRecord,请使用v.targetRecord.fields.Name.value。

ldsShowContact.cmp

<aura:component implements="force:hasRecordId,flexipage:availableForRecordHome">

    <aura:attribute name="contactRecord" type="Object"/>
    <aura:attribute name="recordLoadError" type="String"/>
    
    <force:recordData aura:id="recordLoader"
        recordId="{!v.recordId}"
        fields="Name,Description,Phone,Industry"
        targetFields="{!v.contactRecord}"
        targetError="{!v.recordLoadError}"
    />

    <!-- 显示有关联系人详情的闪电卡 -->
    <div class="Contact Details"> 
        <lightning:card iconName="standard:contact" title="{!v.contactRecord.Name}" >
            <div class="slds-p-horizontal--small">
                <p class="slds-text-heading--small">
                    <lightning:formattedPhone title="Phone"  value="{!v.contactRecord.Phone}" /></p>
                <p class="slds-text-heading--small">
                    <lightning:formattedText title="Description" value="{!v.contactRecord.Description}" /></p>
                <p class="slds-text-heading--small">
                     <lightning:formattedText title="Industry" value="{!v.contactRecord.Industry}" /></p>
            </div>
        </lightning:card>
    </div>

</aura:component>
我们的下一个组件加载相同的联系人,但在编辑模式下,还有一个让用户编辑联系人的表单。它还处理recordUpdated事件,以便我们可以根据编辑的结果采取某些操作。
ldsEditContact.cmp

<aura:component implements="force:hasRecordId,flexipage:availableForRecordHome">

    <aura:attribute name="contactRecord" type="Object"/>
    <aura:attribute name="recordSaveError" type="String" default=""/>

    <!-- Load record in EDIT mode -->
    <force:recordData aura:id="recordLoader"
        recordId="{!v.recordId}"
        fields="Name,Description,Phone,Industry"
        targetFields="{!v.contactRecord}"
        targetError="{!v.recordSaveError}"
        mode="EDIT" 
        recordUpdated="{!c.handleRecordUpdated}" />

    <!-- Contact edit form -->
    <div class="Edit Contact">
        <lightning:card iconName="action:edit" title="Edit Contact">
            <div class="slds-p-horizontal--small">
                <lightning:input label="Contact Name" value="{!v.contactRecord.Name}"/>
                <lightning:input label="Contact Description" value="{!v.contactRecord.Description}"/>
                <lightning:input label="Contact Phone" value="{!v.contactRecord.Phone}"/>
                <br/>
                <lightning:button label="Save Contact" variant="brand" onclick="{!c.saveContact}" />
            </div>
        </lightning:card>
    </div>
    
    <!-- Display error message -->
    <aura:if isTrue="{!not(empty(v.recordSaveError))}">
        <div class="recordSaveError">
            {!v.recordSaveError}</div>
    </aura:if>

</aura:component>
最后,我们有我们的控制器,根据编辑的结果,我们的控制器会报告一个成功的编辑信息或向我们显示错误信息。
ldsEditContactController.js

({
    saveContact : function(cmp, event, helper) {
        var recordLoader = cmp.find("recordLoader");
        recordLoader.saveRecord($A.getCallback(function(saveResult) {
            if (saveResult.state === "ERROR") {
                var errMsg = "";
                // saveResult.error是一个错误数组,
                // 将所有错误收集到一个消息中
                for (var i = 0; i < saveResult.error.length; i++) {
                    errMsg += saveResult.error[i].message + "\n";
                }
                cmp.set("v.recordSaveError", errMsg);
            } else {
                cmp.set("v.recordSaveError", "");
            }
        }));
    },

    // 在更改记录时(通过任何组件),在这里控制组件行为
    handleRecordUpdated: function(component, event, helper) {
        var eventParams = event.getParams();
        if(eventParams.changeType === "CHANGED") {
            // 获取为此记录更改的字段
            var changedFields = eventParams.changedFields;
            console.log('Fields that are changed: ' + JSON.stringify(changedFields));
            // 记录被改变,所以刷新组件(或其他组件逻辑)
            var resultsToast = $A.get("e.force:showToast");
            resultsToast.setParams({
                "title": "Saved",
                "message": "The record was updated."
            });
            resultsToast.fire();
        } else if(eventParams.changeType === "LOADED") {
            // record is loaded in the cache
        } else if(eventParams.changeType === "REMOVED") {
            // record is deleted and removed from the cache
        } else if(eventParams.changeType === "ERROR") {
            console.log('Error: ' + component.get("v.error"));
        }
    }
})
你有它,你是一个闪电数据服务的主人。 现在出发,使用新发现的技能来改善各地开拓者的闪电组件的性能和一致性。

Lightning-数据服务(2)

学习目标

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

  • 使用闪电数据服务创建,读取,上传和删除记录。
  • 构建一个使用force的组件:recordData。
  • 解释Lightning Data Service如何缓存记录。

使用force … recordData标签

在最后一个单元中,我们介绍了Lightning Data Service提供的漂亮的性能升级和生活质量功能。现在让我们学习如何使用它们。

请记住,force:recordData本身不包含任何UI元素。 force:recordData标签只是用来与服务器通信并管理本地缓存的逻辑。为了让用户查看和修改由LDS获取的数据,您必须包含UI元素。 force:recordData标记使用UI API向您的UI组件提供数据。

加载记录

你做的第一件事是为你的UI组件创建一个记录,就是加载它。通过在指定recordId,mode和layoutType或fields属性的同时在组件中包含force:recordData来加载记录。

ldsDisplayRecord.cmp

<aura:component implements="flexipage:availableForRecordHome, force:hasRecordId"> <!--inherit recordId attribute-->

<aura:attribute name="record" type="Object" 
  description="The record object to be displayed"/>
<aura:attribute name="simpleRecord" type="Object" 
  description="A simplified view record object to be displayed"/>
<aura:attribute name="recordError" type="String" 
  description="An error message bound to force:recordData"/>

<force:recordData aura:id="record"
    layoutType="FULL"
    recordId="{!v.recordId}"
    targetError="{!v.recordError}"
    targetRecord="{!v.record}"
    targetFields ="{!v.simpleRecord}"
    mode="VIEW"/>
从这里,包含一些显示强制加载的数据的 force:recordData.
<!-- 显示有关记录详情的闪电卡 -->
<div class="Record Details"> 
    <lightning:card iconName="standard:account" title="{!v.simpleRecord.Name}" >
        <div class="slds-p-horizontal--small">
            <p class="slds-text-heading--small">
                <lightning:formattedText title="Billing City" value="{!v.simpleRecord.BillingCity}" /></p>
            <p class="slds-text-heading--small">
                <lightning:formattedText title="Billing State" value="{!v.simpleRecord.BillingState}" /></p>
        </div>
    </lightning:card>
</div>

<!-- 显示闪电数据服务错误,如果有的话 -->
<aura:if isTrue="{!not(empty(v.recordError))}">
    <div class="recordError">
        {!v.recordError}</div>
</aura:if>

</aura:component>

保存记录

LDS的神奇之处在于,在Lightning应用程序中有多个组件可以从相同的记录数据中提取。这些组件中的一部分只是显示记录数据,而其他组件则可以操纵数据本身。该组件加载记录以及一个简短的表单,用户可以在其中输入记录的新名称。

ldsSaveRecord.cmp

<aura:component implements="flexipage:availableForRecordHome, force:hasRecordId"> <!--inherit recordId attribute-->

<aura:attribute name="record" type="Object" />
<aura:attribute name="simpleRecord" type="Object" />
<aura:attribute name="recordError" type="String" />

<force:recordData aura:id="recordEditor"
    layoutType="FULL"
    recordId="{!v.recordId}"
    targetError="{!v.recordError}"
    targetRecord="{!v.record}"
    targetFields ="{!v.simpleRecord}"
    mode="EDIT" />

    <!-- 显示有关记录详情的闪电卡 -->
    <div class="Record Details"> 
        <lightning:card iconName="standard:account" title="{!v.simpleRecord.Name}" >
            <div class="slds-p-horizontal--small">
                <p class="slds-text-heading--small">
                    <lightning:formattedText title="Billing State" value="{!v.simpleRecord.BillingState}" /></p>
                <p class="slds-text-heading--small">
                     <lightning:formattedText title="Billing City" value="{!v.simpleRecord.BillingCity}" /></p>
            </div>
        </lightning:card>
    </div>
    <br/>

    <!-- 显示编辑表格 -->
    <div class="Record Details">
        <lightning:card iconName="action:edit" title="Edit Account">
            <div class="slds-p-horizontal--small">
                <lightning:input label="Account Name" value="{!v.simpleRecord.Name}"/>
                <br/>
                <lightning:button label="Save Account" variant="brand" onclick="{!c.handleSaveRecord}" />
            </div>
        </lightning:card>
    </div>

    <!-- 显示闪电数据服务错误,如果有的话 -->
    <aura:if isTrue="{!not(empty(v.recordError))}">
        <div class="recordError">
            {!v.recordError}</div>
    </aura:if>
</aura:component>
为了处理这个更新,创建一个调用saveRecord()方法的JavaScript控制器。 saveRecord()方法只有一个回调函数SaveRecordResult作为唯一的参数。 SaveRecordResult包括一个状态属性,告诉你保存是否成功,以及其他信息可以用来处理操作的结果。
LdsSaveRecordController.js

({
    handleSaveRecord: function(component, event, helper) {
        component.find("recordEditor").saveRecord($A.getCallback(function(saveResult) {
            if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {
                console.log("保存成功完成");
            } else if (saveResult.state === "INCOMPLETE") {
                console.log("用户离线,设备不支持草稿.");
            } else if (saveResult.state === "ERROR") {
                console.log('问题保存记录,错误:' + 
                           JSON.stringify(saveResult.error));
            } else {
                console.log('Unknown problem, state: ' + saveResult.state + ', error: ' + JSON.stringify(saveResult.error));
            }
        }));}
})
不错,对不对? LDS处理所有幕后的繁重工作,将请求发送到服务器并自动更新这两个记录。

好,现在我们来处理其余的CRUD。

Creating Records

要创建一个空记录,请保持recordId属性有效:recordData undefined。

ldsNewRecord.cmp

<aura:component implements="flexipage:availableForRecordHome, force:hasRecordId">

<aura:attribute name="newContact" type="Object"/>
<aura:attribute name="simpleNewContact" type="Object"/>
<aura:attribute name="newContactError" type="String"/>

<force:recordData aura:id="contactRecordCreator"
    layoutType="FULL"
    targetRecord="{!v.newContact}"
    targetFields ="{!v.simpleNewContact}"
    targetError="{!v.newContactError}"
    />

<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>

    <!-- 显示新的联系表格 -->
    <div class="Create Contact">
        <lightning:card iconName="action:new_contact" title="Create Contact">
            <div class="slds-p-horizontal--small">
                <lightning:input aura:id="contactField" label="First Name" value="{!v.simpleNewContact.FirstName}"/>
                <lightning:input aura:id="contactField" label="Last Name" value="{!v.simpleNewContact.LastName}"/>
                <lightning:input aura:id="contactField" label="Title" value="{!v.simpleNewContact.Title}"/>
                <br/>
                <lightning:button label="Save Contact" variant="brand" onclick="{!c.handleSaveContact}"/>
            </div>
        </lightning:card>
    </div>

    <!-- 显示闪电数据服务错误 -->
    <aura:if isTrue="{!not(empty(v.newContactError))}">
        <div class="recordError">
            {!v.recordError}</div>
    </aura:if>

</aura:component>
在组件控制器中,调用getNewRecord()方法。用户创建记录后,使用上面显示的saveRecord()方法进行保存。
ldsNewRecordController.js

({
    doInit: function(component, event, helper) {
        // 从模板准备一个新的记录
        component.find("contactRecordCreator").getNewRecord(
            "Contact", // sObject type (entityAPIName)
            null,      // recordTypeId
            false,     // skip cache?
            $A.getCallback(function() {
                var rec = component.get("v.newContact");
                var error = component.get("v.newContactError");
                if(error || (rec === null)) {
                    console.log("错误初始化记录模板:" + error);
                }
                else {
                    console.log("Record template initialized: " + rec.sobjectType);
                }
            })
        );
    },

    handleSaveContact: function(component, event, helper) {
        if(helper.validateContactForm(component)) {
            component.set("v.simpleNewContact.AccountId", component.get("v.recordId"));
            component.find("contactRecordCreator").saveRecord(function(saveResult) {
                if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {
                    // 记录已成功保存
                    var resultsToast = $A.get("e.force:showToast");
                    resultsToast.setParams({
                        "title": "Saved",
                        "message": "The record was saved."
                    });
                    resultsToast.fire();

                } else if (saveResult.state === "INCOMPLETE") {
                    // 处理不完整的状态
                    console.log("用户离线,设备不支持草稿");
                } else if (saveResult.state === "ERROR") {
                    // 处理错误状态
                    console.log('问题保存联系,错误: ' + 
                                 JSON.stringify(saveResult.error));
                } else {
                    console.log('未知问题,状态:' + saveResult.state +
                                ', error: ' + JSON.stringify(saveResult.error));
                }
            });
        }
    }
})
这个帮助器验证表单值。
ldsNewRecordHelper.js

({
    validateContactForm: function(component) {
        var validContact = true;

         // 如果必填字段为空,则显示错误消息
        var allValid = component.find('contactField').reduce(function (validFields, inputCmp) {
            inputCmp.showHelpMessageIfInvalid();
            return validFields && inputCmp.get('v.validity').valid;
        }, true);

        if (allValid) {
            // 验证我们有一个帐户附加到
            var account = component.get("v.newContact");
            if($A.util.isEmpty(account)) {
                validContact = false;
                console.log("Quick action context doesn't have a valid account.");
            }
        	return(validContact);
            
        }  
	}
       
})

删除记录

最后,要删除一条记录,至少指定字段属性设置为“Id”的recordId。

ldsDeleteRecord.cmp

<aura:component implements="flexipage:availableForRecordHome,force:hasRecordId">

<aura:attribute name="recordError" type="String" access="private"/>

<force:recordData aura:id="recordHandler"
    recordId="{!v.recordId}"
    fields="Id"
    targetError="{!v.recordError}"
    />

    <!-- 显示删除记录表格 -->
    <div class="Delete Record">
        <lightning:card iconName="action:delete" title="Delete Record">
            <div class="slds-p-horizontal--small">
                <lightning:button label="Delete Record" variant="destructive" onclick="{!c.handleDeleteRecord}"/>
            </div>
        </lightning:card>
    </div>

    <!-- 显示闪电数据服务错误,如果有的话 -->
    <aura:if isTrue="{!not(empty(v.recordError))}">
        <div class="recordError">
            {!v.recordError}</div>
    </aura:if>

</aura:component>
在组件的JavaScript控制器中,调用deleteRecord()方法。 LDS从缓存中删除记录并触发通知。 deleteRecord()方法使用与saveRecord()方法(deleteRecordResult)类似的回调函数,它告诉您操作是否成功。
ldsDeleteRecordController.js

({
    handleDeleteRecord: function(component, event, helper) {
    component.find("recordHandler").deleteRecord($A.getCallback(function(deleteResult) {
        if (deleteResult.state === "SUCCESS" || deleteResult.state === "DRAFT") {
            console.log("Record is deleted.");
            var resultsToast = $A.get("e.force:showToast");
            resultsToast.setParams({
                "title": "Deleted",
                "message": "The record was deleted."
            });
            resultsToast.fire();
        }
        else if (deleteResult.state === "INCOMPLETE") {
            console.log("用户离线,设备不支持草稿");
        }
        else if (deleteResult.state === "ERROR") {
            console.log('问题删除记录,错误:' +
                        JSON.stringify(deleteResult.error));
        }
        else {
            console.log('未知问题,状态:' + deleteResult.state +
                        ', error: ' + JSON.stringify(deleteResult.error));
        }
    }));
}})

异步记录保存

假设情况下的时间!因此,您使用的是Salesforce应用程序,由于连接问题,保存尝试无法到达服务器。也许你所乘坐的火车进入了一个隧道,你不小心在建筑物的那个角落里漫不经心地走过,或者那个小房间再次和细胞塔混乱了。无论如何,别担心,LDS会回来的。在发生连接问题时,Lightning Data Service会将您的更改存储在本地缓存中。这由SaveRecordResult对象中的DRAFT状态指示。记录的DRAFT状态在连接恢复时解决。通过LDS保存记录时,本地缓存不会更新,直到保存完成。在服务器上成功完成保存时,缓存将更新为服务器中记录的最新版本,并通知所有引用该记录的组件。保存后,您不必担心手动将记录重新加载到缓存中。 LDS为您处理所有的工作。

如果启用异步保存权限,或者满足以下所有条件,则设备脱机时发生的保存会导致DRAFT状态:

  • 客户端无法访问服务器。
  • 组织已启用脱机草稿。
  • 您拥有9.0或更高版本的Salesforce应用程序。

所有CRUD操作都尝试立即使用XMLHttpRequest解析到服务器。如果您的设备失去与服务器的连接,则Lightning Data Service可以从本地缓存中获取数据。 LDS是否从本地缓存或服务器提取数据取决于记录的年份,或者是否存在本地草案。如果记录足够新,或者存在本地草稿,则LDS将使用本地缓存。 LDS仅在需要时刷新记录,这意味着所有刷新都由组件触发。

创建一个Trailhead游乐场

好消息!您可以在免费的Trailhead Playground(TP)组织中练习使用LDS。什么是TP?这是一个为您定制的Salesforce开发版组织,可用于Trailhead。您可以启动一个TP,或创建一个新的TP,从任何动手挑战。现在就创建一个新的TP(使用现有的组织可能在检查挑战时产生问题)。滚动到此页面的底部。单击“启动”旁边的向下箭头,然后选择创建Trailhead Playground(需要登录)。如果您需要新TP的登录凭据,请按照本文中的说明进行操作。

Lightning-数据服务(1)

学习目标

注意,开拓者!

Salesforce有两个不同的桌面用户界面:Lightning Experience和Salesforce Classic。这个模块是为Lightning Experience设计的。

您可以在Trailhead的“Lightning Experience Basics”模块中了解如何在接口之间切换,启用Lightning Experience以及其他功能。

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

  • 描述什么是闪电数据服务。
  • 解释Lightning Data Service如何提高组件的性能。
  • 使用Lightning Data Service将记录加载到用户界面中。

什么是闪电数据服务?

我们建立了Lightning Data Service(LDS)作为Lightning的数据层。 LDS是Visualforce标准控制器的Lightning Components对应部件,可以访问页面上显示的数据。没有LDS,即使应用程序中的所有组件都从相同的记录数据中提取,应用程序中的每个组件都会独立调用服务器来对记录执行CRUD操作。每个服务器调用都会降低性能,使用户不必处理数据,而是用拇指旋转。这些独立的服务器调用也可能导致不一致,从而导致服务器调用刷新一个组件,使其他组件过期的情况。

闪电数据服务识别并消除涉及相同记录数据的请求,发送单个共享数据请求以更新所有相关组件。这不仅消除了组件间不一致的数据,而且还提供了一种方法来缓存数据以便在用户断开连接的情况下脱机工作,并在连接恢复后智能地同步数据。

Animation showing an application that uses Lightning Data Service to populate its components

这个gif显示了一个使用Lightning Data Service来编辑和更新组件的属性管理应用程序。状态更新后,销售状态进度栏会自动更新属性的详细信息。

Animation showing an application that uses Lightning Data Service to populate its components

这个gif显示了同一个页面中的第二个组件,它创建和显示父记录的约会。

简而言之,Lightning Data Service提供可重复使用的Aura组件:

  • 最小化XMLHttpRequests(XHR)
  • 取一次记录,减少网络传输,应用程序服务器负载和数据库服务器负载
  • 在客户端缓存记录数据,与组件元数据分开
  • 在组件间共享记录数据
  • 启用渐进式记录加载,缓存以及将更多字段和布局合并到缓存中
  • 启用主动式缓存填充
  • 通过在多个组件中仅使用一个记录数据实例来提高一致性
  • 记录数据更改时创建通知

如果您有Lightning应用程序创建,读取,更新或删除记录,则LDS是执行CRUD操作的最佳,最有效的方法。

LDS在这里简化你的生活。采用LDS意味着您不再需要编写自己的控制器代码。所有的数据访问代码都包含在LDS组件中,大大降低了应用程序和页面的复杂性。这种复杂性降低意味着您不必在性能和质量测试上花费太多时间。想想你可以节省的周期!

碰到force:recordData

那么你如何获得闪电数据服务的好处呢?简单!你所要做的就是在你的组件中包含force:recordData。好的,除此之外还有一点点,所以让我们来看一个基本的用例。

LDS最简单的用途之一是加载记录。要在客户端加载记录,必须将force:recordData标记添加到组件,并设置recordId,mode和layoutType或fields属性。

  • recordId 指定要加载的记录。记录不能没有recordId加载。
  • 模式可以设置为编辑或视图,这决定了通知的行为以及可以使用记录执行哪些操作。如果您使用force:recordData以任何方式更改记录,请将模式设置为EDIT。
  • layoutType 指定用于显示记录的布局(FULL或COMPACT)。
  • fields 指定要查询的记录中的哪些字段。必须提供字段或layoutType属性(或两者)

force:recordData标签还支持一组target *属性,这些属性是force:recordData填充自身。目标*属性可以用来允许从UI进行访问。

  • targetRecord 被装载的记录填充
  • targetFields 填充了加载记录的简化视图
  • targetError被填充任何错误
<force:recordData aura:id="forceRecordCmp" 
  <!-- aura:id is required to reference the component in your Javascript controller -->
    recordId="{!v.recordId}"
    layoutType="{!v.layout}"
    fields="{!v.fieldsToQuery}"
    mode="VIEW"
    targetRecord="{!v.record}"
    targetFields="{!v.simpleRecord}" 
    targetError="{!v.error}"
/>
force:recordData自身不包含任何UI元素;这只是逻辑和与服务器通信的一种方式。使用其他组件显示强制获取的数据:recordData。在这个例子中,lightning:formattedtext显示了由force:recordData加载的记录中的Name字段。
<aura:component>

    <aura:attribute name="recordId" type="String" />
    <aura:attribute name="record" type="Object" />
    <aura:attribute name="simpleRecord" type="Object" />

     <force:recordData recordId="{!v.recordId}"
          targetRecord ="{!v.record}"
          targetFields ="{!v.simpleRecord}"
          fields="Id, Name"/>

    <div class="recordName">
        <p class="slds-text-heading--medium">
            <lightning:formattedtext title="Record Name" value="{!v.simpleRecord.Name}"></p>
    </div>

</aura:component>
有几种灵气方法可以修改记录。以下简要介绍一下您可以在JavaScript组件控制器中使用的方法。
  • saveRecord()插入或更新加载到force:recordData组件的记录。
  • deleteRecord()删除加载的记录。
  • getNewRecord()加载保存时执行插入的新记录模板。
  • reloadRecord()重载加载代码,用当前的属性值覆盖当前的targetRecord。

我们将在下一个单元详细介绍如何使用这些Aura方法。

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

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

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

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

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