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會非常容易。