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 StandardSetController class can be used to create a list controller or extend the pre-built Visualforce list controller.
  • The StandardSetController constructor accepts either a List of sObjects or a query locator Database.QueryLocator.
  • The StandardSetController stores 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: StandardSetController can handle up to 10,000 records. If query result is more than 10,000 records, LimitException will be thrown if using query locator, while results will be truncated to 10,000 records if using list of sObjects in 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 returned false, 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 records
    • getRecords() - return list of sObjects in current page set
    • getSelected() - return list of objects that is selected
    • getListViewOptions() - specify what list view options are available to the user
    • save() - upsert record
    • setPageSize() - used to set the number of records to be appeared on the page
    • next() - move to next record page
    • previous() - 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 Controller and Standard Controller is that Standard Set Controller operates on list of records while Standard Controller operates on single record.

Custom Controller

  • A Custom Controller is an Apex Class that defines and implements the logic for a Visualforce page without using Standard Controller.
  • NOTE: custom controller uses the default, no-argument constructor.
  • In Visualforce page, the controller attribute of <apex:page> component is used to associate the custom controller with the page.
  • NOTE: Custom Controller runs 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 Extension is an Apex class that can be used to extend the functionality of a standard or custom controller.
  • Controller Extension can 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.StandardController or CustomerControllerName.
  • NOTE: multiple Controller Extensions can be defined for a single Visualforce page 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 webservice methods is only checked at the entry point. If access is allowed to the webservice, 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 Apex classes, 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 attribute can be used in certain Visualforce page to specify which action should be taken.

  • ReRender attribute can be used in some Visualforce component tag to rerender on certain event and it must be referencing the component id to be rerendered.

  • NOTE: reRender attribute cannot be used to update content or data, and it is not the same as rendered which 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.

  • Visualforce tags that can accept action attribute:

    • <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
  • Visualforce tags that can use with Javascript:

    • <apex:commandButton> - generate a button that can use different types of Javascript attributes such as onclick, onkeypress, onmouseover and etc.
    • <apex:commandLink> - generate a link that executes an action and can use different types of Javascript attributes as well
    • <apex:input> - also support different events such as onclick and onchange
    • <apex:actionFunction> - the 'name' attribute of the component can be used to specify Javascript function that, when invoked elsewhere in the page, causing the method specified by the 'action' attribute to execute.
    • <apex:actionSupport> - supports multiple attributes that support Javascript functionality, such as oncomplete and onsubmit.
  • NOTE: An action must be public, accept no argument, and return a PageReference.

Display Error Messages

  • ApexPages.Message class can be used to create an error message to display on Visualforce page, simply calling ApexPages.addMessage() and include <apex:pageMessages/> in Visualforce page.
  • 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" on Visualforce to be checked and lightningStylesheets must 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/> in Visualforce page:
<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.AuraHandledException from server-side controller.
  • NOTE: you can use console.error() instead of console.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!');
    }
}
  • Javascript can 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 Visualforce in the following methods:
    • Custom Components (most flexible to modify code)
    • Templates
    • Page Includes (least flexbile since it includes an entire Visualforce page)
    • Static Resources

Visualforce Components

  • Visualforce Components is 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 Apex class)
      • 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 the Visualforce page 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

  • Templates are used to create a page structure where portions of it can be overwritten by the calling page.
  • Templates are 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 Visualforce page 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 a Template:
<!-- 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>
  • Templates cannot 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.
  • Templates description can only be referenced in Setup.

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

  • Javascript and CSS files can be uploaded to Static Resource and include the code directly.
    • <apex:includeScript value="{!$Resource.JavascriptFileName}" />
    • <apex:stylesheet value="{!$Resource.CSSFileName}" />

Javascript and Visualforce

  • Javascript allows implementing client-side logic and making use of asynchronous calls for processing.
  • 4 main ways of using Javascript with Visualforce:
    • Client-side processing and validation (Javascript)
      • HTML elements generated by Visualforce can be passed to Javascript by using $Component global variable
      • The DOM (document object model) Id generated for a Visualforce component is used for reference.
      • The value of 'id' attribute must be specified to refer to a Visualforce component in Javascript, ex: {!$Component.myComponentId}
    • 3rd party Javascript libraries such as jQuery
      • <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')}" />
      • <script> can be included in a page to call Javascript functions.
    • Javascript Remoting
      • Javascript remoting is used to call methods in Apex Controller asynchronously from Javascript.
      • Create UI with complex, dynamic behavior that process input faster than traditional Visualforce way 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 Apex method and automatically maps between Apex and Javascript types.
      • Setup remoting requires the following:
        • A Javascript method to invoke Apex method via Visualforce.remoting.Manger.invokeAction()
        • A method in Apex controller annotated with @remoteAction which will be called by Javascript
        • A callback Javascript function that handles the response from Apex Controller
      • Javascript remoting invocation in Visualforce page looks like this:
        • [namespace.]controller.method([parameters...,] callbackFunction, [configuration]);
    • Remote Objects
      • Visualforce Remote Objects are proxy objects that enable basic DML operations (CRUD) on sObjects directly from Javascript without Apex code 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 CRUD operates 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 Visualforce page 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
        • Javascipt data access function:
          • SObjectModel is used to instantiate the desired object
          • Once instantiated, CRUD operations can be used:
            • create()
            • retrieve()
            • update()
            • upsert()
            • del()
  • Comparison between Javascript Remoting and Remote Objects:
Javascript RemotingRemote Objects
Each call to a @remoteAction is a single transactionEach CRUD operation in a single transaction
Requires Javascript and Apex code Doesn't require Apex code
Handles complex object relationships betterNo auto-traversal through object relationship
Supports complex server-side logicSupports minimal server-side logic

Lightning Components

  • Lightning Components are 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 functionTag
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 via force:hasRecordId interface:
<!-- 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 set recordId, mode, layoutType or fields attributes.
    • recordId - specific the record to load
    • mode - can be set to EDIT or VIEW which determines the behavior of notifications and what operations are available to perform with the record.
    • layoutType - specific the layout FULL or COMPACT to display the record which determines what fields are included.
    • fields - specify which fields in the record to query.
  • <force:recordData /> also supports a set of target attributes:
    • targetRecord - populated with loaded record
    • targetFields - populated with simplified view of loaded record
    • targetError - 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 Aura methods to modify records are availables using Javascript component controller:

    • saveRecord() - insert or update record loaded into component
    • deleteRecord() - delete loaded record
    • getNewRecord() - load new record template that performs an insert when saved
    • reloadRecord() - rerun the loading code to overwrite the current targetRecord with the current attribute values.
  • LDS is available inLightning Experience and Salesforce App only, it can't be used in Lightning Components for Visualforce, Lightning Out, or Community.

  • 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: LDS handles 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 Apex controller can be defined for server-side processing.

  • NOTE: if to expose Apex controller method to client-side Javascript, annotation @AuraEnabled can be specified on the controller method in Apex, it must be static and either global or public.

  • Design file from the bundle can be used to define which attributes of the component can be customized.

  • Lightning component bundle 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) - contain Javascript function 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 in Lightning 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 in Lightning App Builder's component pane.
  • Lightning component must implement one or more of the following interfaces to make it available in Lightning App Builder:

    • flexipage:availableForAllPageTypes - make the component available in any type of page, including record page and the app utility bar
    • flexipage:availableForRecordHome - make the component available for record page only
    • clients:availableForMailAppAppPage - make the component available for Mail App Lightning page in Lightning App Builder and in Lightning for Outlook or Lightning 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 framework is 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.

  • Events are used to communicate data between components, declared by <aura:event> tag with .evt as extension.

  • Actions can be called from Lightning Component, such as <lightning:button>

  • Each action function has 3 parameters:

    • cmp - the component of the controller
    • event - event being handled by the action
    • helper - optional helper containing functions that can be reused
  • Example of making asynchronous calls to the server via Javascript and 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 DOM events)
      • Event handling steps:
        1. Event is fired up
        2. Event is captured down and propagates down from application root to source component
        3. 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 handler
      • event.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:

    1. Instantiate app
    2. Create components
    3. Create component definition
    4. Create parent hierarchy
    5. Create component facets
    6. Create component dependencies (attributes, interfaces, controllers and actions)
  • Component rendering lifecycle:

    1. Fire Init event
    2. Call Render()
    3. Call AfterRender()
    4. Fire <aura:doneWaiting> event
    5. Fire render event
    6. Fire <aura:doneRendering> event
  • Custom validation can be added to Lightning 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'};
            }
        }
    }
})
  • $ContentAsset global value provider can be used to reference images, stylesheets, and javascript files in Lightning 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!

Post was published on , last updated on .

Like the content? Support the author by paypal.me!