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

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

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

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

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

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>组件实例的引用。如果你通过一个更长的名单,你可以结束(多)更多。所有来自我们的一个小组件!

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