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组件的魔法,当更新主要费用数组时,费用列表组件将“注意到”并重新列出其列表。给它一个旋转!

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

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