Lightning 组件基础知识

Lightning框架简介

Lightning框架是Salesforce提供的一套基于用户界面的开发框架,对于开发单页面应用(Single Page Application)有很大的帮助。它和Visualforce可以共存,但开发的方法并不相同。

Lightning有单独的前端架构,基于名叫aura的框架,主要包括:

  • 组件:由XML语言开发的用户界面,组件内部可以包含其他组件和标准的HTML元素,Lightning框架本身也提供了若干标准组件
  • 应用:Lightning应用是一种特殊的组件,它是整个程序的入口。一个Lightning应用包含若干Lightning组件

每个组件或应用还包含了:

  • 前端控制器:包含了JavaScript函数,用于和组件的元素互动
  • 辅助函数:可以看作是前端控制器的扩展,用于保存JavaScript辅助函数
  • CSS:保存了针对于某个组件的CSS

Lightning框架中通过前端控制器和后端进行数据通信,在前端控制器中提供了直接调用Apex代码(后端控制器)的功能。

Lightning组件

本文通过一个简单的“Hello World”例子介绍如何建立和编辑Lightning组件、应用,以及组件间的通信。

新建Lightning组件

在Developer Console中,点击“File”菜单,指向“New”,点击“Lightning Component”,输入名字“helloworld”,点击“Submit”按钮,即可新建一个Lightning组件。

新建Lightning组件

每个Lightning组件不光包含了组件本身,还包含了其他的文件。当新建“helloworld”组件后,在Developer Console的右侧有一个列表,其中包含了和组件相关的各种文件,比如控制器、辅助函数、页面样式、文档等。点击任意一项,即可新建相应的文件。

定义Lightning组件外观的文件名以“.cmp”结尾,这里就是“helloworld.cmp”。

Lightning组件相关列表

在编辑区域中,系统已经默认生成了一段代码:

<aura:component >
</aura:component>

每一个Lightning组件都是包含在“aura:component”标签中。

在“aura:component”标签中写入一段HTML代码:

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

当组件运行之后,在屏幕中就会输出文字。

新建Lightning应用

Lightning组件无法单独运行,它必须被包含在一个Lightning应用中。用户只有通过Lightning应用才能运行组件的功能。

Lightning应用可以看作是一种特殊的组件。在Developer Console中,点击“File”菜单,指向“New”,点击“Lightning Application”,输入名字“helloworld_APP”,点击“Submit”按钮,即可新建一个Lightning应用。

Lightning组件可以包含其他组件,Lightning应用可以包含组件,但是Lightning应用不能包含应用。

定义Lightning应用外观的文件名以“.app”结尾,这里就是“helloworld_APP.app”。

在新建的Lightning应用中,系统也生成了默认的代码:

<aura:application >
</aura:application>

每一个Lightning应用都是包含在“aura:application”标签中。

在应用中调用刚才建立的组件:

<aura:application >
    <c:helloworld />
</aura:application>

在窗口右侧的列表上方有“Preview”按钮,点击即可运行Lightning应用。

预览Lightning应用

运行Lightning应用,即可看到屏幕上显示了“Hello world!”的字样,说明运行成功了,组件的内容也显示在了应用中。

为组件增加CSS样式

在“helloworld”组件中,会显示默认的“Hello world!”文字。如果想修改页面中的显示,可以在Developer Console右侧列表中点击“STYLE”,系统会自动建立“helloworld.css”文件。开发者可以在此文件中修改CSS样式。

要注意的是,不同于普通的CSS文件,在整个文件中,每一个样式必须带有“.THIS”,它的作用是保证新建的样式只对当前的组件有效。

向“helloworld.css”文件中添加一段CSS代码:

p.THIS {
    font-size: 48px;
    color: blue;
}

保存后再次运行应用,可以看到显示的文字样式已经变化了。

Lightning组件的CSS更改效果

为组件添加属性

现在的“helloworld”组件只能显示一段静态文字。如果需要增加其他动态功能,比如自定义显示文字的内容,则必须要用到组件的“属性”。

每个组件可以包含若干“属性”。组件的属性可以看作是包含在组件内的变量,它们可以是任何类型。当组件载入后,组件的属性值会被初始化,组件的控制器也可以更改属性的值。组件的属性可以被绑定在组件内部的元素中,从而实现动态功能。

组件的属性要定义在“aura:attribute”标签中。在组件的元素中,如果想绑定某个属性,需要用“{!v.属性名}”的语法来实现。

比如在“helloworld”组件中增加一个“message”属性,并输出到“p”标签中:

<aura:component >
    <aura:attribute name="message" type="String" default="test user" />
    <p> Hello world! - {!v.message} </p>
</aura:component>

再次运行Lightning应用,可以看到显示的文字从“Hello world!”变为了“Hello world! - test user”。

由于属性和组件的元素相互绑定,如果在应用运行时更改属性“message”的值,那么显示的文字也会相应的发生变化。

数据提供者

在组件中,如果想绑定一个属性,需要用“{!v.属性名}”的语法。其中的“v”被称为数据提供者(Value Provider)。如果想在组件中显示稍微复杂的表达式而非单独的属性值,同样可以用“{! }”表达式。

比如在Lightning框架中提供了一个标准显示文字的组件“ui:outputText”,设置其“value”属性即可显示相应的文字。在“helloworld”中可以将代码变为:

<aura:component >
    <aura:attribute name="message" type="String" default="test user" />
    <p> <ui:outputText value="{! 'Hello world! - ' + v.message}" /> </p>
</aura:component>

运行应用后输出的文字和之前一样。在这段代码中,“{! }”表达式的里面不光只有属性“message”,还在其之前增加了固定的字符串。

属性的类型

属性可以是任何类型,除了基本的字符串、数字等,还可以是集合类型、sObject对象类型。

比如:

<!--使用标准sObject对象作为属性类型-->
<aura:attribute name="account" type="Account" />
<!--使用标准sObject对象作为属性类型,并初始化某些属性-->
<aura:attribute name="account" 
                type="Account" 
                default="{ 'Name': 'Salesforce',
                            'Type': 'Prospect'}"/>
<!--使用自定义sObject对象作为属性类型-->
<aura:attribute name="address" type="Address__c" />
<!--使用自定义sObject对象作为属性类型,并初始化某些属性-->
<aura:attribute name="address" 
                type="Address__c" 
                default="{ 'Name': 'ExampleAddress',
                            'Street_name__c': 'Example Street Name'}"/>
<!--使用列表作为属性类型-->
<aura:attribute name="contactList" type="List" />
<!--使用列表作为属性类型,并初始化列表-->
<aura:attribute name="textList" 
                type="List" 
                default="['text 1',
                            'text 2',
                            'text 3']" />

<!--在组件中使用sObject对象的字段-->
<aura:outputText value="{!v.account.Name}" />

循环读取集合类型属性的值

当一个属性是集合类型时,比如字符串的列表,在组件中可以使用标准组件“aura:iteration”遍历其每一个元素。

比如:

<aura:attribute name="textList" 
                type="List" 
                default="['text 1',
                            'text 2',
                            'text 3']" />

<!--在组件循环显示字符串-->
<aura:iteration items="{! v.textList }" var="singleText">
    <p> {! singleText } </p>
</aura:iteration>

在上面的代码中,使用了标准元素“aura:iteration”。我们将字符串列表属性“textList”绑定到循环列表属性“items”中,并定义“singleText”为每一个循环中列表中变量的名字,类似于“for(String singleText : textList)”。在循环组件的内部,使用“{! }”表达式显示每一个循环元素的内容,注意这里不需要使用“v”了。

为组件添加功能

假设在“helloworld”中,需要增加一个按钮,点击之后属性“message”要发生变化。

用标准组件“ui:button”可以添加按钮,要想实现点击按钮之后更改属性的功能,就必须使用前端控制器。

在Developer Console的右侧列表中,点击“CONTROLLER”,系统会自动建立一个前端控制器文件“helloworldController.js”。它是一个JavaScript文件,开发者可以在其中添加JS函数实现功能。

在Developer Console的右侧列表中,点击“HELPER”,系统会自动建立一个辅助函数文件“helloworldHelper.js”。它是一个JavaScript文件,开发者可以在其中添加JS函数,这些函数可以从控制器文件中调用。

另外要注意的是,在控制器文件中,如果定义了若干函数,它们之间不能互相调用。所以必须将某些公共的功能挪到辅助函数文件中,再使用“helper.函数名”来调用功能。

在控制器文件中增加一个“handleClick”函数,更改组件中“message”属性的值:

handleClick : function(component, event, helper) {
    component.set('v.message', 'Updated Message!');
}

在组件的外观中增加一个按钮,点击之后执行“handleClick”函数:

<ui:button label="Change text" press="{!c.handleClick}"/>

其中“label”是要在按钮上显示的文字,“press”是一个事件,当点击按钮后,调用“press”里定义的函数。

注意,这里使用了“{!c.函数名}”的方式来调用JS控制器中的函数,其中的“c”便是代表了“Controller”。

运行应用,当点击了按钮之后,屏幕上显示的文字便从“Hello world! - test user”变成了“Hello world! - Updated Message!”。

控制器函数详解

每一个控制器的函数都默认带有三个参数:

  • component:代表了当前的组件
  • event:代表了触发的事件
  • helper:代表了辅助函数的文件,如果建立了“HELPER”文件,并定义了某些函数,则使用“helper.函数名()”的语法即可调用“HELPER”文件中的函数

用“component.set('v.属性名', 要设置的值)”的方式可以直接设置组件中属性的值,这是最常用的一种设置方法。

同样的,也可以用“component.get('v.属性名')”来得到组件中属性的值。

比如:

exampleFunction : function(component, event, helper) {
    // 得到message属性的值
    var messageValue = component.get('v.message');

    // 设置message属性的值
    component.set('v.message', 'value to set');

    // 调用helper文件中的某函数
    var resultFromHelper = helper.exampleHelperFunction();
}

如果想得到触发某函数的组件元素的内容,则需要使用event参数。

比如在组件中有一个按钮,点击会触发控制器中的“handleClick()”函数。在“handleClick()”函数中,使用“event.getSource()”即可得到按钮元素。

组件中的设置:

<ui:button label="button text" press="{!c.handleClick}" />

控制器中:

handleClick : function(component, event, helper) {
    // 得到组件中的按钮元素
    var buttonClicked = event.getSource();

    // 得到组件中按钮元素的“label”属性
    var buttonValue = buttonClicked.get('v.label');
    // buttonValue的值是“button text”
}

使用这种方式,可以直接得到组件中元素的各种属性等。

组件和Apex通信

在“helloworld”组件中,如果想要通过点击按钮,从数据库中读取一个名叫“GenePoint”的Account对象,并将其名字和电话号码显示在页面中,则不光需要前端的功能,也需要和Apex类进行通信,从数据库中查询并得到数据。

要实现这个功能,需要完成以下几个方面:

  • 准备Apex类和函数,能查询并返回对象的内容
  • 在组件中定义属性,类型为sObject对象,并将组件中的某些元素绑定到该对象的字段中
  • 将组件与Apex类联系起来
  • 在组件中调用Apex函数,接收Apex函数的执行结果,并更新组件中的属性

准备Apex类和函数

如果要使一个Apex函数可以被Lightning组件调用,则必须满足两点:

  1. 该函数的定义包含“@AuraEnabled”注解
  2. 该函数是静态类型

现在建立相应的Apex类和函数:

public class LightningAccountController {
    @AuraEnabled
    public static Account getAccount(String name) {
        List<Account> accountList = [SELECT Id, Name, Phone 
                                     FROM Account
                                     WHERE Name LIKE :name
                                    ];
        if(accountList.size() > 0) {
            return accountList[0];
        } else {
            return null;
        }
    }
}

在组件中定义属性

在“helloworld”组件中,定义一个类型为Account的属性:

<aura:attribute name="account" type="Account" />

<ui:button label="Get Account" press="{!c.handleClick}" />
        
<p>
    <ui:outputText value="{!v.account.Name}" />
</p>

<p>
    <ui:outputText value="{!v.account.Phone}" />
</p>

将组件与Apex类联系起来

组件与Apex类联系的方式是在“aura:component”标签中设置“controller”属性为Apex类的名字:

<aura:component controller="LightningAccountController">

在组件中调用Apex函数,接收Apex函数的执行结果,并更新组件中的属性

在组件中调用Apex函数,需要通过控制器文件。

在“helloworldController.js”文件中修改“handleClick()”函数为:

handleClick : function(component, event, helper) {
    // 1. 声明Apex类中的函数名
    var action = component.get("c.getAccount");

    // 2. 设置Apex函数的参数
    // 通常参数的值可以从组件中得到,比如使用component.get('v.userInput')
    action.setParams({
        "name": 'GenePoint'
    });

    // 3. 设置Apex函数执行完成后的功能
    action.setCallback(this, function(response) {
        // 得到Apex的结果状态
        var state = response.getState();

        if (state === "SUCCESS") {
            // 得到Apex的结果,结果可以是基本类型,也可以是sObject或集合类型等
            var result = response.getReturnValue();

            component.set('v.account', result);
        } else {
            // 错误处理
            // Do nothing
        }
    });

    // 4. 开始调用Apex函数
    $A.enqueueAction(action);
}

代码解释:

  1. 调用Apex函数需要四步,当然,如果Apex函数中没有参数,则第二步可以省略。
  2. Apex函数的调用是异步执行的,所以在上面的代码中,当执行了Apex函数之后,如果还有其他的代码,其他的代码有可能比“action.setCallback()”函数里的代码先执行,所以不能用“action.setCallback()”里的变量去决定“$A.enqueueAction(action);”语句之后的代码。
  3. 在Apex函数执行结束后,需要检测结果的状态,并且使用“getReturnValue()”函数来得到返回的结果。返回的结果无需类型转换,可以直接赋值给组件中的属性。
  4. $A是系统提供的一个全局变量,包含了一些重要的功能和服务。

至此,运行应用的话,点击按钮“Get Account”,屏幕上会给出查询到的Account对象的结果。

Lightning应用运行结果

事件(Event)和句柄(Handler)

在以上的例子中,所有的组件外观和逻辑(除了Apex部分)都是在一个组件中。在开发的过程中,这样做或许比较方便,但是有一个缺点,就是前端控制器中的逻辑只能被这一个组件使用。

如果有一个公用的方法,每个组件都可以使用,那么该方法就会变得可重用,提高了代码的效率。

Lightning中的事件(Event)和句柄(Handler)就实现了这种功能。

事件和句柄有以下几个特性:

  1. 事件需要单独定义,独立于任何组件。
  2. 每个组件都可以注册事件,从而取得事件的使用权。
  3. 在组件中可以设置句柄,句柄中可以设定具体某个事件,从而声明此组件对于某个事件会进行处理。
  4. 在注册了事件的组件(A)使用事件时,系统会自动寻找包含该事件句柄的组件(B),从而自动调用B中的函数对事件进行处理。在这个过程中,组件A和B是相互独立的,并不需要知道对方具体的功能,而事件通过“广播”被自动进行了正确的处理。

事件自动含有“type”属性,可以有两种值,“APPLICATION”和“COMPONENT”,表明了该事件被应用还是组件使用。

还是以上面的“查找Account对象名字、电话并显示在屏幕上”的情况为例,重写组件,并通过事件和句柄将功能完成。

分为以下几个步骤:

  1. 新建事件
  2. 建立事件触发组件
  3. 建立事件处理组件
  4. 在Lightning应用中包含事件处理组件

新建事件

在Developer Console中通过“File”菜单的“New”子菜单新建“Lightning Event”,命名为“FindAccount”。新建完成后,可以看到出现了“FindAccount.evt”的文件。将其中的代码修改为:

<aura:event type="COMPONENT" description="Event template" >
    <aura:attribute name="accountName" type="String" />
</aura:event>

重要的是将默认的“type”属性值改为“COMPONENT”,让此事件对组件有效。

事件中包含了一个属性“accountName”,用于接收要查询的Account对象的名字。

建立事件触发组件

要执行一个事件,必须要有事件的触发组件和事件的处理组件。前者使用“aura:registerEvent”触发事件,后者使用“aura:handler”处理事件。二者也可以被定义在同一个组件中。

在Developer Console中新建组件,命名为“FindAccountEventRegister”。新建完成后,修改代码如下:

<aura:component >
    <aura:registerEvent name="findAccountEvent" type="c:FindAccount"/>

    <ui:button label="Get Account" press="{!c.handleClick}" />
</aura:component>

可以看到,组件中注册了事件,并且只有一个按钮,用来点击并触发事件。

在其控制器文件中写入“handleClick()”函数:

handleClick : function(component, event, helper) {
    var cmpEvent = component.getEvent('findAccountEvent');
    
    cmpEvent.setParams({
        "accountName": 'GenePoint'
    });
    
    cmpEvent.fire();
}

这段代码主要就是使用“component.getEvent()”函数得到组件中注册的事件,再给事件中定义的属性赋值,最后通过“fire()”函数触发事件。

建立事件处理组件

新建组件,命名为“FindAccountEventHandler”。新建完成后,修改代码如下:

<aura:component controller="LightningAccountController">
    <aura:attribute name="account" type="Account" />
    
    <aura:handler name="findAccountEvent" event="c:FindAccount" action="{!c.handleEvent}"/>

    <c:FindAccountEventRegister />
    
    <p>
        <ui:outputText value="{!v.account.Name}" />
    </p>
    
    <p>
        <ui:outputText value="{!v.account.Phone}" />
    </p>
</aura:component>

代码解释:

  1. 组件中定义了一个类型为Account的属性,并在“p”标签中显示该属性的字段值。
  2. 组件连接了之前的例子中建立好的Apex类,从而可以调用其中的函数从数据库查找Account对象。
  3. 组件中使用“aura:handler”定义了事件的处理方式,并包含了“FindAccountEventRegister”组件。这里有个地方很重要:“aura:handler”的“name”属性值和“FindAccountEventRegister”组件中“aura:registerEvent”的“name”属性值是一样的(findAccountEvent),这就保证了在事件触发时,事件与两个组件之间都有关联。
  4. 组件中的“aura:handler”里定义了“action”属性,其作用是当接收到事件触发的消息时,调用控制器中相应的函数来处理事件。

在控制器文件中加入如下代码:

handleEvent : function(component, event, helper) {
    var accountName = event.getParam('accountName');

    // 调用Apex类的函数来查询Account对象并在组件中显示结果
    var action = component.get("c.getAccount");
    action.setParams({
        "name": accountName
    });
    action.setCallback(this, function(response) {
        var state = response.getState();

        if (state === "SUCCESS") {
            var result = response.getReturnValue();

            component.set('v.account', result);
        } else {
            // Do nothing
        }
    });

    $A.enqueueAction(action);
}

这里的重点是通过“event.getParam()”函数来得到事件中属性的值。通过这个函数,在事件触发组件(FindAccountEventRegister)中设置的事件的属性值就被传递到了事件处理组件(FindAccountEventHandler),并进行下一步的处理。

在Lightning应用中包含事件处理组件

在Lightning应用中包含事件处理组件(FindAccountEventHandler)。运行该应用,点击按钮,即可看到查询的Account对象的结果和信息。至此,事件和句柄的基本功能就完成了。

从这个例子中可以看出,事件可以将逻辑和输入分离,使得每个组件包含的功能尽可能少,增加重用性,提高开发效率。

小结

通过上面的例子,我们主要阐述了Lightning组件的基本实现方法,并通过事件和句柄来实现了组件之间的通信。

Lightning框架的前端部分主要基于aura框架,如果对其他前端框架(Vue,React)已经有了了解,上手Lightning会非常容易。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,475评论 25 707
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,865评论 6 13
  • 不知道从什么时候开始微信群开始发红包赌钱了,最开始的认知就是三五个好友抢最少的发红包,慢慢的变成一人做庄一群人猜...
    裙摆阅读 130评论 0 0
  • 炒股只做自己熟悉的板块,熟悉的个股。不要吃着碗里,还看着锅里。什么都想抓,结果什么都没有抓到。最终追高买入,套在高...
    飞过无痕阅读 561评论 0 0