【Salesforce】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元件不光包含了元件本身,還包含了其他的檔案。當新建“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會非常容易。