Welcome to the second largest chapter of Salesforce Platform Dev II Exam Preparation: User Interface. This chapter consists of 20% of total score! Without further ado, let’s get into it!
NOTE: This post is written in July 2019 and content might be changed/updated overtime. The content is inspired by focusonforce.com.
Standard Set Controller
- An object of the
StandardSetControllerclass can be used to create a list controller or extend the pre-built Visualforce list controller. - The
StandardSetControllerconstructoraccepts either aListofsObjectsor a query locatorDatabase.QueryLocator. - The
StandardSetControllerstores data sets on the server to reduce page state and increase performance. - Support operations including pagination through lists of records and performing bulk updates on list of selected records.
// example of Standard Set Controller using query locator
public with sharing class AccountQueryLocator{
public ApexPages.StandardSetController con{
get{
if(con == null){
con = new ApexPages.StandardSetController(
Database.getQueryLocator(
[SELECT Id, Name FROM Account WHERE Name LIKE '%A' LIMIT 10]
)
);
// work the same
con = new ApexPages.StandardSetController(
[SELECT Id, Name FROM Account WHERE Name LIKE '%A' LIMIT 10]
);
}
return con;
}
set;
}
public List<Account> getAccounts(){
return (List<Account>) con.getRecords();
}
}<!-- example of Visualforce page that uses Standard Set Controller using query locator -->
<apex:page controller="AccountQueryLocator">
<apex:pageBlock title="Query Locator Example">
<apex:pageBlockTable value="{!accounts}" var="a">
<apex:column value="{!a.Id}" />
<apex:column value="{!a.Name}" />
</apex:pageBlockTable>
</apex:pageBlock>
</apex:page>- NOTE:
StandardSetControllercan handle up to 10,000 records. If query result is more than 10,000 records,LimitExceptionwill be thrown if using query locator, while results will be truncated to 10,000 records if using list ofsObjectsin the parameter. - NOTE: Salesforce has a pagination limit of 2,000 records per page.
- Some common methods in
StandardSetController:getCompleteResult()- used to determine if number of returned records is over limit. If returnedfalse, it means the controller won’t be able to process all the records.getRecord()- used to access the prototype object that is used to store list of field updates that should be applied to the selected recordsgetRecords()- return list ofsObjectsin current page setgetSelected()- return list of objects that is selectedgetListViewOptions()- specify what list view options are available to the usersave()- upsert recordsetPageSize()- used to set the number of records to be appeared on the pagenext()- move to next record pageprevious()- move to previous record page
- Sample pagination with controller to display list of records:
// Custom Controller for visualforce page that returns up to 10,000 records
public with sharing class LargeAccountSetController{
public String searchString { get; set;} // search string
public Integer num {get; set;} // num of records returned
public Integer size {get; set;} // size of each page set
public ApexPages.StandardSetController con {
get{
if(con == null){
con = new ApexPages.StandardSetController(
Database.getQueryLocator(
[SELECT Id, Name, Type FROM Account]
)
);
if(num < size){
size = num;
} else{
size = 20;
}
con.setPageSize(size);
}
return con;
}
set;
}
// return list of account records
public List<Account> getAccounts(){
List<Account> accList = new List<Account>();
for(Account acc: (List<Account>) con.getRecords()){
accList.add(acc);
}
return accList;
}
// search by given string
public void search(){
con = new ApexPages.StandardSetController(
Database.getQueryLocator(
[SELECT Id, Name, Type FROM ACCOUNT WHERE Name LIKE :searchString + '%']
)
);
num = con.getResultSize();
if(num < size){
size = num;
} else{
size = 20;
}
con.setPageSize(size);
}
// return whether there are more records after the current page set
public Boolean hasNext{
get{
return con.getHasNext();
}
set;
}
// return whether there are more records before the current page set
public Boolean hasPrevious{
get{
return con.getHasPrevious();
}
set;
}
// return page number of current page set
public Integer pageNumber{
get{
return con.getPageNumber();
}
set;
}
// return to first page
public void first(){
con.first();
}
// return to last page
public void last(){
con.last();
}
// return to next page
public void next(){
con.next();
}
// return to previous page
public void previous(){
con.previous();
}
}<!-- visualforce page to display a list up to 10,000 records, with search function and pagination -->
<apex:page controller="LargeAccountSetController">
<apex:form>
<apex:pageBlock title="Account Search">
<apex:pageMessages />
<apex:pageBlockButtons location="top">
<apex:inputText value="{!searchString}" label="Search..." />
<apex:commandButton value="Search" action="{!search}" />
</apex:pageBlockButtons>
<apex:pageBlockTable value="{!accounts}" var="a">
<apex:column headerValue="Id" value="{!a.Id}" />
<apex:column headerValue="Account Name" value="{!a.Name}" />
<apex:column headerValue="Type" value="{!a.Type}" />
</apex:pageBlockTable>
</apex:pageBlock>
<apex:outputText>
{!IF(num > 0, (pageNumber * size) + 1 - size, 0)} -
{!IF((pageNumber * size) > num, num, (pageNumber * size))} of
{!num} (Page {!pageNumber})
</apex:outputText>
<apex:panelGrid columns="4">
<apex:commandButton action="{!first}" value="First" />
<apex:commandButton action="{!previous}" value="Prev" rendered="{!hasPrevious}" />
<apex:commandButton action="{!next}" value="Next" rendered="{!hasNext}" />
<apex:commandButton action="{!last}" value="Last" />
</apex:panelGrid>
</apex:form>
</apex:page>- Example of using Wrapper Class to process records:
// Custom Controller for visualforce page to process selected records
public with sharing class AccountSelectionController{
List<AccountWrapper> acocunts {get; set;}
public ApexPages.StandardSetController con {
get{
if(con == null){
con = new ApexPages.StandardSetController(
Database.getQueryLocator(
[SELECT Id, Name, Type FROM Account]
)
);
con.setPageSize(20);
}
return con;
}
set;
}
// return list of account wrapper records
public List<AccountWrapper> getAccounts(){
accounts = new List<AccountWrapper>();
for(Account acc: (List<Account>) con.getRecords()){
accounts.add(acc);
}
return accounts;
}
public PageReference process(){
for(AccountWrapper aw : accounts){
if(aw.checked){
ApexPages.addMessage(
new ApexPages.message(
ApexPages.severity.INFO,
'Name: ' + aw.acc.Name + ', Type: ' + aw.acc.Type
)
);
}
}
return null;
}
// return whether there are more records after the current page set
public Boolean hasNext{
get{
return con.getHasNext();
}
set;
}
// return whether there are more records before the current page set
public Boolean hasPrevious{
get{
return con.getHasPrevious();
}
set;
}
// return page number of current page set
public Integer pageNumber{
get{
return con.getPageNumber();
}
set;
}
// return to first page
public void first(){
con.first();
}
// return to last page
public void last(){
con.last();
}
// return to next page
public void next(){
con.next();
}
// return to previous page
public void previous(){
con.previous();
}
// return PageReference of original page (home page)
public void cancel(){
con.cancel();
}
}// Account Wrapper class
public class AccountWrapper{
public Boolean checked {get; set;}
public Account acc { get; set;}
public AccountWrapper(Account acc){
this.acc = acc;
checked = false;
}
}<!-- visualforce page to display some information on selected account records after pressing Process button -->
<apex:page controller="AccountSelectionController">
<apex:form>
<apex:pageBlock title="Select and Process Accounts">
<apex:pageMessages />
<apex:pageBlockButtons location="top">
<apex:inputText value="Process" action="{!process}" />
<apex:commandButton value="Cancel" action="{!cancel}" />
</apex:pageBlockButtons>
<apex:pageBlockTable value="{!accounts}" var="a">
<apex:column width="10px">
<apex:inputCheckbox value="{!a.checked}" />
</apex:column>
<apex:column headerValue="Account Name" value="{!a.Name}" />
<apex:column headerValue="Type" value="{!a.Type}" />
</apex:pageBlockTable>
</apex:pageBlock>
<apex:outputText>
{!IF(num > 0, (pageNumber * size) + 1 - size, 0)} -
{!IF((pageNumber * size) > num, num, (pageNumber * size))} of
{!num} (Page {!pageNumber})
</apex:outputText>
<apex:panelGrid columns="4">
<apex:commandButton action="{!first}" value="First" />
<apex:commandButton action="{!previous}" value="Prev" rendered="{!hasPrevious}" />
<apex:commandButton action="{!next}" value="Next" rendered="{!hasNext}" />
<apex:commandButton action="{!last}" value="Last" />
</apex:panelGrid>
</apex:form>
</apex:page>- Difference between
Standard Set ControllerandStandard Controlleris thatStandard Set Controlleroperates on list of records whileStandard Controlleroperates on single record.
Custom Controller
- A
Custom Controlleris anApexClass that defines and implements the logic for aVisualforcepage without usingStandard Controller. - NOTE: custom controller uses the default, no-argument constructor.
- In
Visualforcepage, the controller attribute of<apex:page>component is used to associate the custom controller with the page. - NOTE:
Custom Controllerruns in System Mode, meaning the current user’s permission and FLS are not taken into account. - Example of
Custom Controller:
public class CustomController{
private final Account acc;
public CustomController(){
acc = [SELECT Id, Name, Type FROM Account WHERE Id =
:ApexPages.currentPage().getParameters().get('id')];
}
public Account getAccount(){
return acc;
}
public PageReference save(){
update acc;
return null;
}
}<apex:page controller="CustomController" tabStyle="Account">
<apex:form>
<apex:pageBlock title="Hello and welcome, {!$User.FirstName}!">
<apex:outputLabel value="Account Name: "/>{!acc.Name}<p/>
<apex:outputLabel value="Account Type: "/><apex:inputField value="{!acc.Type}" /><p/>
<apex:commandButton action="{!save}" value="Save" />
</apex:pageBlock>
</apex:form>
</apex:page>- Miscellaneous stuff regarding to Person and Business Account:
- If creating a new account and set the Name field, the record will be Business Account; if setting LastName field, it will be Person Account
- When an input field references the Name field, you must use IsPersonAccount in your query so that the system knows whether to process the field as a Person or Business Account.
- Create custom name formula that will render the name properly for both Person and Business Account in
Visualforce
Controller Extension
Controller Extensionis anApexclass that can be used to extend the functionality of a standard or custom controller.Controller Extensioncan be used to add new actions or override existing actions, such as edit, view, save or delete.- NOTE: controller extension uses logic from a standard controller and respects user permissions with single argument of
ApexPages.StandardControllerorCustomerControllerName. - NOTE: multiple
Controller Extensionscan be defined for a singleVisualforcepage through a comma-separated list (the first in the list has the highest precedence). - Example of Controller Extension that enables creation of an account and contact:
// Example of Controller Extension to create account and contact
public class ContactControllerExtension{
Contact con {get; set;}
public ContactControllerExtension (ApexPages.StandardController std){
con = (Contact) std.getRecord();
}
// the method overrides the default save action
public PageReference save(){
Account acc = new Account (
Name = con.FirstName + ' ' + con.LastName
);
insert acc;
con.AccountId = acc.Id;
insert con;
PageReference pr = New PageReference('/' + acc.Id);
return pr;
}
}<apex:page standardController="Contact" extensions="ContactControllerExtension">
<apex:form>
<apex:pageBlock title="Create an account and contact">
<apex:outputText value="Contact First Name" /><apex:inputField value="{!con.FirstName}" /> <p/>
<apex:outputText value="Contact Last Name" /><apex:inputField value="{!con.LastName}" /> <p/>
<apex:commandButton value="Save" action="{!save}" />
</apex:pageBlock>
</apex:form>
</apex:page>- Important notes about controllers and extensions:
- If a class includes a web service method the class must be defined as global.
- Web service is run in system mode, even though initial web service controlled by user profile.
- Access to
webservicemethods is only checked at the entry point. If access is allowed to thewebservice, all subsequent code will execute in system mode (which means user can modify fields that they are not allowed to, call method that are not allowed as well). - Getter and setter methods cannot be used with DML or future method.
- The order of method and variables being processed is not guaranteed.
- DML statements cannot be used in getter or in constructor method.
- Like most
Apexclasses, controllers and extensions run in system mode. - The with sharing keyword can be used to ensure security policies if needed and are generally declared as public.
Performing Actions and Partial Page Refresh
-
Action attributecan be used in certainVisualforcepage to specify which action should be taken. -
ReRender attributecan be used in someVisualforcecomponent tag to rerender on certain event and it must be referencing the component id to be rerendered. -
NOTE:
reRenderattribute cannot be used to update content or data, and it is not the same asrenderedwhich only accept single boolean argument whether a section of the page should be rendered at all. -
The referenced name must be the same as method name wrapping in
{! }notation. -
Visualforcetags that can acceptactionattribute:<apex:page action="{!loadActions}">- action is called on page load<apex:commandButton action="{!saveRecord}">- action is called when button is clicked<apex:commandLink action="{!openWebsite}">- action is called when link is clicked<apex:actionPoller action="{!incrementCounter}" interval="5" reRender="counterComponent">- action is called periodically without user input- In this case, the counter will be incremented in every 5 seconds and component and that component with id “counterComponent” will be rerendered after the event.
<apex:actionSupport action="{!incrementCounter}" event="onmouseover" reRender="counterComponent">- action is called when an event on another component is triggered (such as “onclick”, “onmouseover”, and etc.)- In this case, the counter will be incremented when mouse is hovered over the component and that component iwith id “counterComponent” will be rerendered after the event.
<apex:actionFunction action="{!showAlert}">- define a Javascript that calls an action
-
Visualforcetags that can use withJavascript:<apex:commandButton>- generate a button that can use different types ofJavascriptattributes such asonclick,onkeypress,onmouseoverand etc.<apex:commandLink>- generate a link that executes an action and can use different types ofJavascriptattributes as well<apex:input>- also support different events such asonclickandonchange<apex:actionFunction>- the ‘name’ attribute of the component can be used to specifyJavascriptfunction that, when invoked elsewhere in the page, causing the method specified by the ‘action’ attribute to execute.<apex:actionSupport>- supports multiple attributes that supportJavascriptfunctionality, such asoncompleteandonsubmit.
-
NOTE: An action must be public, accept no argument, and return a PageReference.
Display Error Messages
ApexPages.Messageclass can be used to create an error message to display onVisualforcepage, simply callingApexPages.addMessage()and include<apex:pageMessages/>inVisualforcepage.- Example how to use
ApexPages.addMessage():
public class SampleController{
public Account acc;
public SampleController(){
acc = [SELECT Id, Name FROM Account];
}
public Account getAccount(){
return acc;
}
public PageReference save(){
update account;
try{
Account a = new Account();
insert a;
} catch (Exception e){
ApexPages.Message errorMessage = new ApexPages.Message(
ApexPages.Severity.ERROR, 'An unexpected error occured: ' + e.getMessage()
);
ApexPages.addMessage(errorMessage);
}
return null;
}
}- You can also display
toast, but it requires “Available for Lightning Experience, Lightning Communities, and the mobile app” onVisualforceto be checked andlightningStylesheetsmust be set as true in<apex:page>. - Example of displaying toast in
Visualforce:
<apex:page lightningStylesheets="true">
<script>
function showToast(){
sforce.one.showToast(
{
"title" : "Success!",
"message" : "Success message goes here",
"type" : "success"
}
);
}
</script>
<apex:form>
<apex:page>
<apex:commandButton value="Show Toast" onclick="showToast();" />
</apex:page>
</apex:form>
</apex:page>- Examples of using
<apex:pageMessages/>inVisualforcepage:
<apex:page controller="SampleController">
<apex:form>
<apex:pageMessages />
<apex:pageBlock title="Hello {!$User.FirstName}">
Account Name: <apex:inputField value="{!acc.name}" /><p/>
<apex:commandButton action="{!save}" value="Save" /><p/>
</apex:pageBlock>
</apex:form>
</apex:page>- You can also display error message by throwing
System.AuraHandledExceptionfrom server-side controller. - NOTE: you can use
console.error()instead ofconsole.log()to find message using browser’s developer tools. - Example of using
AuraHandledException:
public class SampleController2{
static final String errorInput = 'test';
@AuraEnabled
public static String throwError(String accName){
if(accName.containsIgnoreCase(errorInput){
throw new AuraHandledException('Error Input!');
}
return('Account name ' + accName + ' is valid!');
}
}Javascriptcan be used to handle exception as well:
<script type="text/javascript">
function getRemoteAccount(){
var accName = document.getElementById('accSearch').value;
Visualforce.remoting.Manager.invokeAction(
'{!$RemoteAction.AccountRemoteController.getAccount}',
accName,
function(result, event){
if(event.status){
document.getElementById('accId').innerHTML = result.Id;
document.getElementById('accName').innerHTML = result.Name;
} else if(event.type === 'exception'){
document.getElementById('responseErrors').innerHTML = event.message + '<br/>n<pre>' + event.where + '</pre>';
} else {
document.getElementById('responseErrors').innerHTML = event.message;
}
}
);
}
</script>Visualforce Code Reuse
- Code can be reused in
Visualforcein the following methods:- Custom Components (most flexible to modify code)
- Templates
- Page Includes (least flexbile since it includes an entire
Visualforcepage) - Static Resources
Visualforce Components
Visualforce Componentsis referenced by API name and can be defined using<apex:component>top-level tag.- Custom attributes can be added to a component which can be used by the component or its controller.
- NOTE: custom attributes cannot be used in component’s constructor method.
<apex:attribute>tag can have the following attributes:- name (required) - the required identifier by which calling pages reference the custom attribute (name must be unique from all other attributes in the component definition)
- description - a text description of the attribute
- type (required) - the required data type of the attribute (can be primitive, sObject, list, map, or custom
Apexclass)- Primitives - String, Integer, Boolean
- sObjects - Account, Custom_Object__c
- List (1-dimension array notation) - String[], Contact[]
- Map - specify type = “map” will do
- Apex Class
- required - indicate whether this component must set a value for the attribute or not
- id - the identifier that allows the component to be referenced by other components in the page
- rendered (default to true) - indicate whether this component should be rendered in the page
- Example of custom components:
<apex:component>
<apex:attribute name="name" description="Name" type="String" required="false" />
<apex:attribute name="age" description="Age" type="String" required="true" />
<p>Name: {!name}</p>
<p>Age: {!age}</p>
</apex:component>- To use
Visualforce components, you can include<c:componentName var1="test" var2="hello world">or<customNamespace:componentName>if the component was created in different namespace from theVisualforcepage that’s using it - Custom components can affect the way markup is displayed on the final page based on the values provided for the component’s attributes.
- Custom component descriptions display in the same way as standard component descriptions in the application’s component reference dialog.
Templates
Templatesare used to create a page structure where portions of it can be overwritten by the calling page.Templatesare mostly useful for overall structure to a page to be maintained but need the content of each page to be different, such as using same page layout for different articles.- A skeleton template is created which allows subsequent
Visualforcepage to implement different content within the same structure. - Following key tags are used when implementing templates:
<apex:composition>- section of a page which contains one or more<apex:define>tags<apex:define>- used within<apex:composition>to define the content that needs to be generated in a corresponding<apex:insert>tag<apex:insert>- indicates to pages that import a template that a section needs a definition
- Example of using
<apex:insert>in aTemplate:
<!-- template API name is Testing -->
<apex:page>
<h1>Header</h1>
<!-- calling form must have a <apex:define> section named "testingList" -->
<apex:insert name="testingList" />
</apex:page><!-- using template -->
<apex:page>
<apex:composition template="Testing">
<apex:define name="testingList">
<ul>
<li>Test 1</li>
<li>Test 2</li>
<li>Test 3</li>
</ul>
</apex:define name>
</apex:composition>
</apex:page>Templatescannot affect the way markup is displayed on the final page, because it doesn’t have a way of passing information back to the template definition. They can only provide content to include in the<apex:insert>areas.Templatesdescription can only be referenced inSetup.
Page Includes
<apex:include>tag can be used to duplicate the entire content of another page.- NOTE: If the page that uses
<apex:include>tag and the page use the same controller, both page will use the same instance of the controller. - Example of using
<apex:include>:
<apex:page controller="TestController">
<apex:include pageName="TestTemplate" />
<apex:pageBlock>
<apex:pageBlockSection>
<h1>Hello, world!</h1>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:page>Static Resources
JavascriptandCSSfiles can be uploaded to Static Resource and include the code directly.<apex:includeScript value="{!$Resource.JavascriptFileName}" /><apex:stylesheet value="{!$Resource.CSSFileName}" />
Javascript and Visualforce
Javascriptallows implementing client-side logic and making use of asynchronous calls for processing.- 4 main ways of using
JavascriptwithVisualforce:- Client-side processing and validation (
Javascript)- HTML elements generated by
Visualforcecan be passed toJavascriptby using$Componentglobal variable - The DOM (document object model) Id generated for a
Visualforcecomponent is used for reference. - The value of ‘id’ attribute must be specified to refer to a
Visualforcecomponent inJavascript, ex:{!$Component.myComponentId}
- HTML elements generated by
- 3rd party
Javascriptlibraries such asjQuery<apex:includeScript>can be used to reference the 3rd party libraries or resources (either a single file or an archive).- Reference a single file:
<apex:includeScript value="{!$Resource.singleFile}" /> - Reference a file inside an archive:
<apex:includeScript value="{!URLFOR($Resource.fileArchive, '/sub1/sub2/test.js')}" />
- Reference a single file:
<script>can be included in a page to callJavascriptfunctions.
- Javascript Remoting
Javascript remotingis used to call methods inApex Controllerasynchronously fromJavascript.- Create UI with complex, dynamic behavior that process input faster than traditional
Visualforceway because it is stateless. - Performs more complex logic or to traverse object relationships
- Each method call is a single transaction.
- It supports parameters and return types in
Apexmethod and automatically maps betweenApexandJavascripttypes. - Setup remoting requires the following:
- A
Javascriptmethod to invokeApexmethod viaVisualforce.remoting.Manger.invokeAction() - A method in Apex controller annotated with
@remoteActionwhich will be called byJavascript - A callback
Javascriptfunction that handles the response fromApex Controller
- A
Javascriptremoting invocation inVisualforcepage looks like this:[namespace.]controller.method([parameters...,] callbackFunction, [configuration]);
- Remote Objects
Visualforce Remote Objectsare proxy objects that enable basic DML operations (CRUD) onsObjectsdirectly fromJavascriptwithoutApexcode while respecting FLS, sharing rules, and other data accessibility concerns and also enforcing data access restriction such as validation rules, triggers and etc.- NOTE: each
CRUDoperates as separate transaction, which might cause inconsistent data state as some transactions can fail while others succeed. - Steps to use
Remote Objects:- Access definitions on
Visualforcepage using following components:<apex:remoteObjectField>which defines fields within<apex:remoteObjectModel>that can be accessed<apex:remoteObjectField>which defines fields within<apex:remoteObjectModel>that can be accessed
Javasciptdata access function:SObjectModelis used to instantiate the desired object- Once instantiated,
CRUDoperations can be used:create()retrieve()update()upsert()del()
- Access definitions on
- Client-side processing and validation (
- Comparison between
Javascript RemotingandRemote Objects:
| Javascript Remoting | Remote Objects |
|---|---|
| Each call to a @remoteAction is a single transaction | Each CRUD operation in a single transaction |
| Requires Javascript and Apex code | Doesn’t require Apex code |
| Handles complex object relationships better | No auto-traversal through object relationship |
| Supports complex server-side logic | Supports minimal server-side logic |
Lightning Components
Lightning Componentsare self-contained, reusable units of an app which can be placed in a container app alongside other components. In fact, it is a collection of resources called a bundle which all work together to encapsulate the functionality of the component.Lightning Data Service (LDS)can be used if needed for record processing (CRUD), remember to include<force:recordData>tag in the component markup.
| Form function | Tag |
|---|---|
| Display, create, or edit records | lightning:recordForm/ |
| Display records only | lightning:recordViewForm/ (with lightning:outputField/) |
| Create or edit records only | lightning:recordEditForm/ (with lightning:inputField/) |
| Display, create, or edit, or delete records with granular customization | force:recordData/ |
- For most use cases,
<lightning:recordForm />provides a great starting point. It combines and simplifies the functionality of<lightning:recordViewForm />and<lightning:recordEditForm />. - Example of
<lightning:recordForm />with accessing record Id viaforce:hasRecordIdinterface:
<!-- example of recordForm -->
<aura:component implements="flexipage:availableForRecordHome, force:hasRecordId">
<aura:attribute name="recordId" type="String" />
<lightning:card title="Display, Create, or Edit Records">
<lightning:recordForm recordId="{!v.recordId}"
objectApiName="Account"
fields="Name" />
</lightning:card>
</aura:component>- More advanced use cases that require custom field layouts using CSS and custom rendering of record data, use
<lightning:recordViewForm />and<lightning:recordEditForm />. <lightning:messages />can be used to display any errors during record update.
<!-- example of recordViewForm and recordEditForm -->
<aura:component implements="flexipage:availableForRecordHome, force:hasRecordId">
<lightning:card title="Display, Create, or Edit Records">
<lightning:recordEditForm recordId="{!v.recordId}"
objectApiName="Account">
<lightning:messages />
<lightning:inputField fieldName="Name" />
<lightning:button class="slds-m-top_small" type="submit" label="Create new" />
</lightning:recordEditForm>
<lightning:recordViewForm recordId="{!v.recordId}" objectApiName="Account">
<lightning:messages />
<lightning:outputField fieldName="Name" />
</lightning:recordViewForm>
</lightning:card>
</aura:component><force:recordData>doesn’t include any UI elements, which makes it good way to communicate to server.- To load a record on client side, you can add
<force:recordData />tag to your component and setrecordId,mode,layoutTypeorfieldsattributes.recordId- specific the record to loadmode- can be set toEDITorVIEWwhich determines the behavior of notifications and what operations are available to perform with the record.layoutType- specific the layoutFULLorCOMPACTto display the record which determines what fields are included.fields- specify which fields in the record to query.
<force:recordData />also supports a set oftargetattributes:targetRecord- populated with loaded recordtargetFields- populated with simplified view of loaded recordtargetError- populated with any errors
<!-- aura:id is required to reference the component in your Javascript controller -->
<force:recordData aura:id="forceRecordCmp"
recordId="{!v.recordId}"
layoutType="{!v.layout}"
fields="{!v.fieldsToQuery}"
mode="VIEW"
targetRecord="{!v.record}"
targetFields="{!v.simpleRecord}"
targetError="{!v.error}"
/>- In this example,
<lightning:formattedtext />displays the Name field from the record loaded by<force:recordData />.
<aura:component>
<aura:attribute name="recordId" type="String" />
<aura:attribute name="record" type="Object" />
<aura:attribute name="simpleRecord" type="Object" />
<force:recordData recordId="{!v.recordId}"
targetRecord ="{!v.record}"
targetFields ="{!v.simpleRecord}"
fields="Id, Name" />
<div class="recordName">
<p class="slds-text-heading--medium">
<lightning:formattedtext title="Record Name" value="{!v.simpleRecord.Name}" /></p>
</div>
</aura:component>- Several
Auramethods to modify records are availables usingJavascriptcomponent controller:saveRecord()- insert or update record loaded into componentdeleteRecord()- delete loaded recordgetNewRecord()- load new record template that performs an insert when savedreloadRecord()- rerun the loading code to overwrite the currenttargetRecordwith the current attribute values.
LDSis available inLightning ExperienceandSalesforce Apponly, it can’t be used inLightning ComponentsforVisualforce,Lightning Out, orCommunity.- All components using a record share the same cached data, hence performance is improved and any changes made to such a record from the user interface will generate a refresh in affected components.
- NOTE:
LDShandles sharing rules and FLS automatically, it operates on one record at a time and it is only loaded once. - NOTE: My Domain needs to be configured and deployed in order to use
Lightning Component. - A controller or helper can be defined for client-side processing whereas
Apexcontroller can be defined for server-side processing. - NOTE: if to expose
Apexcontroller method to client-sideJavascript, annotation@AuraEnabledcan be specified on the controller method inApex, it must be static and either global or public. Designfile from the bundle can be used to define which attributes of the component can be customized.Lightning componentbundle resources included:- Component file (
TestCmp.cmp) - the only required resource in a component bundle that defines the markup for the component - Controller (
TestCmpController.js) - client-side controller contains methods for event handling - Helper (
TestCmpHelper.js) - containJavascriptfunction that are used by other scripts in the bundle - Stylesheet (
TestCmp.css) - custom stylesheet for the component - Design (
TestCmp.design) - used to define which attributes to be exposed, such as inLightning App Builder - Documentation (
TestCmp.auradoc) - formatted documentation to assist admins and devs - Renderer (
TestCmpRenderer.js) - used to override default rendering behavior - SVG file (
TestCmp.svg) - SVG (Scalable Vector Graphics) file is used to define a custom icon for the component that appears next to the component name inLightning App Builder’s component pane.
- Component file (
Lightning componentmust implement one or more of the following interfaces to make it available inLightning App Builder:flexipage:availableForAllPageTypes- make the component available in any type of page, including record page and the app utility barflexipage:availableForRecordHome- make the component available for record page onlyclients:availableForMailAppAppPage- make the component available forMail App Lightningpage inLightning App Builderand inLightning for OutlookorLightning for Gmail.
- Components attributes are typed fields set on a specific instance of a component which allow making components more dynamic.
<aura:attribute>tag can be used to add attribute to a component or an app, usually define at the beginning of component’s markup.- Example:
<aura:attribute name="greeting" type="String" default="Hello!" /> - Attributes can be referenced from within component’s markup using an expression syntax that uses the value provider ‘v’, example:
{!v.greeting} - Example of
Lightning Component:
<!-- TestCmp.cmp -->
<aura:component implements="flexipage:availableForAllPageTypes" controller="SampleController">
<aura:attribute name="greeting" type="String" default="Greeting!" />
<aura:attribute name="hello" type="String" default="Hello!" />
<p>{!v.greeting}</p>
<lightning:button label="Click me..." onclick="{!c.actionHandler}" />
</aura:component>// TestCmpController.js
({
actionHandler : function(component, event, helper){
var msg = component.get('v.hello');
component.set('v.greeting', msg);
}
})Lightning component frameworkis all about event-driven programming, where events are fired from client-side controller and handlers are written to respond to interface events as they occur.Eventsare used to communicate data between components, declared by<aura:event>tag with.evtas extension.- Actions can be called from
Lightning Component, such as<lightning:button> - Each action function has 3 parameters:
cmp- the component of the controllerevent- event being handled by the actionhelper- optional helper containing functions that can be reused
- Example of making asynchronous calls to the server via
Javascriptand receive callback from server:
// Apex controller
public class ApexController{
@AuraEnabled
public static List<Account> getAccounts(String filter){
List<Account> accList;
// some condition on retrieving accounts
return accList;
}
}// javascript controller
({
onGetAccounts: function(component, event, helper){
// load the method from Apex controller
var action = component.get('c.getAccounts');
// value to pass to Apex method
var filter = component.get('v.filter');
// set the Apex method parameter
action.setParams({
'filter' : filter
});
// receive callback from Apex controller
action.setCallback(this, function(response){
var state = response.getState();
if(state === 'SUCCESS'){
var accounts = response.getReturnValue();
components.set('v.accounts', accounts);
}
});
// enqueue the action
$A.enqueueAction(action);
}
})- Two types of events:
-
Component Events - an event that is fired from an instance of component
- Events can be handled by component that fired the event or component that receives the event and it can be stopped by any registered handler.
- Event is using Capture and Bubbling mechanism (like
DOMevents) - Event handling steps:
- Event is fired up
- Event is captured down and propagates down from application root to source component
- Event is bubbled up and propagates from source component to application root.
-
Application Events - an event that is fired from an instance of component, but following a traditional publisher/subscriber model.
- ==All components that provide a handler for the event are notified==.
event.stopPropagation()can be used to stop event by any registered handlerevent.preventDefault()can be used to cancel event by any registered handler (default behavior)- In default phase, event handlers are invoked in a non-deterministic way from root node through its subtree.
-
<!-- create component event -->
<aura:event type="COMPONENT">
<aura:attribute name="message" type="String" />
</aura:event>
...
<!-- register event -->
<aura:registerEvent name="testingEvent" type="c.TestEvent" />
...
<!-- fire the event -->
({
myAction : function(component, event, helper){
var compEvent = component.get('e.testingEvent');
// var compEvent = component.getEvent('testingEvent');
compEvent.setParam('message', 'hello world');
compEvent.fire();
}
})
...
<!-- handle the fired event from own or other component -->
<!-- handler name must be the same as registered event name -->
<aura:handler name="testingEvent" event="c:TestEvent" action="{!c.handleTestingEvent}" />
...
<!-- get event parameter -->
({
handleTestingEvent : function(component, event, helper){
var compEvent = event.getParam('message');
}
})
<!-- create application event -->
<aura:event type="APPLICATION">
<aura:attribute name="message" type="String" />
</aura:event>
...
<!-- register event -->
<aura:registerEvent name="testingEvent" type="c.TestEvent" />
...
<!-- fire the event -->
({
myAction : function(component, event, helper){
var appEvent = $A.get('e.c:TestEvent');
appEvent.setParams({
"message" : "An application event fired me. " +
"It all happened so fast. Now, I'm everywhere!"
});
appEvent.fire();
}
})
...
<!-- handle the fired event from own or other component -->
<!-- no need to specify handler name -->
<aura:handler event="c:TestEvent" action="{!c.handleTestingEvent}" />
...
<!-- get event parameter -->
({
handleTestingEvent : function(component, event, helper){
var compEvent = event.getParam('message');
}
})
- NOTE: for all, double quote (”) is more appreciated in
Lightning Component, though using single or double quote should be fine. - Component creation lifecycle:
- Instantiate app
- Create components
- Create component definition
- Create parent hierarchy
- Create component facets
- Create component dependencies (attributes, interfaces, controllers and actions)
- Component rendering lifecycle:
- Fire Init event
- Call
Render() - Call
AfterRender() - Fire
<aura:doneWaiting>event - Fire render event
- Fire
<aura:doneRendering>event
Custom validationcan be added toLightning Flow.- The validation function runs when user clicks ‘Next’ or when a component runs the navigate function. It must return two parameters: ‘isValid’ and ‘errorMessage’ if ‘isValid’ is false, the value of ‘errorMessage’ will be displayed.
<!-- validate support in Lightning Component -->
<aura:component implements="lightning:availableForFlowScreens" access="global">
<!-- call init handler when rendering components -->
<aura:handler name="init" value="{!this}" action="{!c.init}" />
<!-- validation attribute to store validation logic -->
<aura:attribute name="validate" type="Aura.Action" />
</aura:component>// validation function in component controller
({
init : function(cmp, event, helper){
cmp.set('v.validate', function(){
if(/* true condition */){
return { isValid : true};
} else {
return { isValid : false, errorMessage : 'hello world'};
}
}
}
})$ContentAssetglobal value provider can be used to reference images, stylesheets, and javascript files inLightning Component. It allows developer to load an asset by name instead of using file paths or url.- Example:
<img src="{! $ContentAsset.logo}" />
Well, this is such a long chapter too. The content will be updated as time goes by. See you!