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组件不光包含了组件本身,还包含了其他的文件。当新建“helloworld”组件后,在Developer Console的右侧有一个列表,其中包含了和组件相关的各种文件,比如控制器、辅助函数、页面样式、文档等。点击任意一项,即可新建相应的文件。
定义Lightning组件外观的文件名以“.cmp”结尾,这里就是“helloworld.cmp”。
在编辑区域中,系统已经默认生成了一段代码:
<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应用,即可看到屏幕上显示了“Hello world!”的字样,说明运行成功了,组件的内容也显示在了应用中。
为组件增加CSS样式
在“helloworld”组件中,会显示默认的“Hello world!”文字。如果想修改页面中的显示,可以在Developer Console右侧列表中点击“STYLE”,系统会自动建立“helloworld.css”文件。开发者可以在此文件中修改CSS样式。
要注意的是,不同于普通的CSS文件,在整个文件中,每一个样式必须带有“.THIS”,它的作用是保证新建的样式只对当前的组件有效。
向“helloworld.css”文件中添加一段CSS代码:
p.THIS {
font-size: 48px;
color: blue;
}
保存后再次运行应用,可以看到显示的文字样式已经变化了。
为组件添加属性
现在的“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组件调用,则必须满足两点:
- 该函数的定义包含“@AuraEnabled”注解
- 该函数是静态类型
现在建立相应的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);
}
代码解释:
- 调用Apex函数需要四步,当然,如果Apex函数中没有参数,则第二步可以省略。
- Apex函数的调用是异步执行的,所以在上面的代码中,当执行了Apex函数之后,如果还有其他的代码,其他的代码有可能比“action.setCallback()”函数里的代码先执行,所以不能用“action.setCallback()”里的变量去决定“$A.enqueueAction(action);”语句之后的代码。
- 在Apex函数执行结束后,需要检测结果的状态,并且使用“getReturnValue()”函数来得到返回的结果。返回的结果无需类型转换,可以直接赋值给组件中的属性。
- $A是系统提供的一个全局变量,包含了一些重要的功能和服务。
至此,运行应用的话,点击按钮“Get Account”,屏幕上会给出查询到的Account对象的结果。
事件(Event)和句柄(Handler)
在以上的例子中,所有的组件外观和逻辑(除了Apex部分)都是在一个组件中。在开发的过程中,这样做或许比较方便,但是有一个缺点,就是前端控制器中的逻辑只能被这一个组件使用。
如果有一个公用的方法,每个组件都可以使用,那么该方法就会变得可重用,提高了代码的效率。
Lightning中的事件(Event)和句柄(Handler)就实现了这种功能。
事件和句柄有以下几个特性:
- 事件需要单独定义,独立于任何组件。
- 每个组件都可以注册事件,从而取得事件的使用权。
- 在组件中可以设置句柄,句柄中可以设定具体某个事件,从而声明此组件对于某个事件会进行处理。
- 在注册了事件的组件(A)使用事件时,系统会自动寻找包含该事件句柄的组件(B),从而自动调用B中的函数对事件进行处理。在这个过程中,组件A和B是相互独立的,并不需要知道对方具体的功能,而事件通过“广播”被自动进行了正确的处理。
事件自动含有“type”属性,可以有两种值,“APPLICATION”和“COMPONENT”,表明了该事件被应用还是组件使用。
还是以上面的“查找Account对象名字、电话并显示在屏幕上”的情况为例,重写组件,并通过事件和句柄将功能完成。
分为以下几个步骤:
- 新建事件
- 建立事件触发组件
- 建立事件处理组件
- 在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>
代码解释:
- 组件中定义了一个类型为Account的属性,并在“p”标签中显示该属性的字段值。
- 组件连接了之前的例子中建立好的Apex类,从而可以调用其中的函数从数据库查找Account对象。
- 组件中使用“aura:handler”定义了事件的处理方式,并包含了“FindAccountEventRegister”组件。这里有个地方很重要:“aura:handler”的“name”属性值和“FindAccountEventRegister”组件中“aura:registerEvent”的“name”属性值是一样的(findAccountEvent),这就保证了在事件触发时,事件与两个组件之间都有关联。
- 组件中的“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会非常容易。