Category Archives: Salesforce

Disable LookUp Input Field on Visualforce Page

Sample Code:

<apex:page standardController="Contact" id="pg">
    <apex:form id="fm">
        <apex:inputField value="{!Contact.AccountId}" id="ifAcc" />
    </apex:form>
    <script>
    var inpAcc = document.getElementById("pg:fm:ifAcc");
    inpAcc.setAttribute("readonly","true");   
    </script>  
</apex:page>

Output:

Inherited Lightning Component Attributes

A Child component that extends a Parent component inherits the attributes of the Parent component.

Let’s start with a simple example. Here in below example c:Parent has a description attribute with a value of “Hello!!”, and c:Child extends c:Parent by setting extends="c:Parent" in its aura:component tag.

Parent Component:

<!--Parent.cmp-->
<aura:component extensible="true">
    <aura:attribute name="description" type="String" default="Hello!!" />
    <p>Description: {!v.description}</p>
</aura:component>

Child Component:

<!--Child.cmp-->
<aura:component extends="c:Parent">
    <aura:set attribute="description" value="Hello World!" />
</aura:component>

Test App:

<!--Test.app-->
<aura:application extends="force:slds">
    <c:Parent />
    <c:Child />    
</aura:application>

Output:
Description: Hello!!
Description: Hello World!

Pagination In Lightning Component

Apex Class:

public class ContactAuraController {
    @AuraEnabled
    public static ContactDataTableWrapper getContactData(Integer pageNumber, Integer pageSize) {
        
        //Offset for SOQL
        Integer offset = (pageNumber - 1) * pageSize;
        
        //Total Records
        Integer totalRecords = [SELECT COUNT() FROM Contact];
        Integer recordEnd = pageSize * pageNumber;

        //Instance of Contact DataTable Wrapper Class
        ContactDataTableWrapper objDT =  new ContactDataTableWrapper();  
        objDT.pageSize = pageSize;
        objDT.pageNumber = pageNumber;
        objDT.recordStart = offset + 1;
        objDT.recordEnd = totalRecords >= recordEnd ? recordEnd : totalRecords;
        objDT.totalRecords = totalRecords;
        objDT.contactList = [SELECT Id, Name, Phone, Email FROM Contact ORDER BY Name LIMIT :pageSize OFFSET :offset];
        return objDT;
    }
    
    //Wrapper Class For Contact DataTable  
    public class ContactDataTableWrapper {
        @AuraEnabled
        public Integer pageSize {get;set;}
        @AuraEnabled
        public Integer pageNumber {get;set;}
        @AuraEnabled
        public Integer totalRecords {get;set;}
        @AuraEnabled
        public Integer recordStart {get;set;}
        @AuraEnabled
        public Integer recordEnd {get;set;}
        @AuraEnabled
        public List<Contact> contactList {get;set;}
    }
}

Pagination Component:

<!--Pagination.cmp-->
<aura:component controller="ContactAuraController">
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    
    <aura:attribute name="ContactList" type="Contact[]"/>
    <aura:attribute name="PageNumber" type="integer" default="1"/>
    <aura:attribute name="TotalPages" type="integer" default="0"/>
    <aura:attribute name="TotalRecords" type="integer" default="0"/>
    <aura:attribute name="RecordStart" type="integer" default="0"/>
    <aura:attribute name="RecordEnd" type="integer" default="0"/>
    
    <div class="slds-m-around_xx-large">
        <h1 class="slds-text-heading--medium">Contacts</h1>
        <br/>
        <div class="slds-float_right">
            <ui:inputSelect aura:id="pageSize" label="Display Records Per Page: " change="{!c.onSelectChange}">
                <ui:inputSelectOption text="10" label="10" value="true"/>
                <ui:inputSelectOption text="15" label="15"/>
                <ui:inputSelectOption text="20" label="20"/>
            </ui:inputSelect>
            <br/>
        </div>
        
        <table class="slds-table slds-table_bordered slds-table_cell-buffer">
            <thead>
                <tr class="slds-text-title_caps">
                    <th scope="col">
                        <strong><div class="slds-truncate" title="Name">Name</div></strong>
                    </th>
                    <th scope="col">
                        <strong><div class="slds-truncate" title="Phone">Phone</div></strong>
                    </th>
                    <th scope="col">
                        <strong><div class="slds-truncate" title="Email">Email</div></strong>
                    </th>
                </tr>
            </thead>
            <tbody>
                <aura:iteration items="{!v.ContactList}" var="con"> 
                    <tr>
                        <th scope="row" data-label="Name">
                            <div class="slds-truncate" title="{!con.Name}">{!con.Name}</div>
                        </th>
                        <th scope="row" data-label="Phone">
                            <div class="slds-truncate" title="{!con.Phone}">{!con.Phone}</div>
                        </th>
                        <th scope="row" data-label="Email">
                            <div class="slds-truncate" title="{!con.Email}">{!con.Email}</div>
                        </th>
                    </tr>
                </aura:iteration>	
            </tbody>
        </table>
        
        <div class="slds-clearfix">
            <div class="slds-page-header" role="banner">
                <div class="slds-float_right">            
                    <lightning:button disabled="{!v.PageNumber == 1}" variant="brand" aura:id="prevPage" label="Prev" onclick="{!c.handlePrev}" />            
                    <lightning:button disabled="{!v.PageNumber == v.TotalPages}" aura:id="nextPage" variant="brand" label="Next" onclick="{!c.handleNext}"/>
                </div>
                <p class="slds-page-header__title">{!v.RecordStart}-{!v.RecordEnd} of {!v.TotalRecords} | Page {!v.PageNumber} of {!v.TotalPages}</p>
            </div>
        </div>
    </div>
</aura:component>

Pagination JavaScript Controller:

({
    doInit: function(component, event, helper) {
        var pageNumber = component.get("v.PageNumber");  
        var pageSize = component.find("pageSize").get("v.value"); 
        helper.getContactList(component, pageNumber, pageSize);
    },
    
    handleNext: function(component, event, helper) {
        var pageNumber = component.get("v.PageNumber");  
        var pageSize = component.find("pageSize").get("v.value");
        pageNumber++;
        helper.getContactList(component, pageNumber, pageSize);
    },
    
    handlePrev: function(component, event, helper) {
        var pageNumber = component.get("v.PageNumber");  
        var pageSize = component.find("pageSize").get("v.value");
        pageNumber--;
        helper.getContactList(component, pageNumber, pageSize);
    },
    
    onSelectChange: function(component, event, helper) {
        var page = 1
        var pageSize = component.find("pageSize").get("v.value");
        helper.getContactList(component, page, pageSize);
    },
})

Pagination JavaScript Controller Helper:

({
    getContactList: function(component, pageNumber, pageSize) {
        var action = component.get("c.getContactData");
        action.setParams({
            "pageNumber": pageNumber,
            "pageSize": pageSize
        });
        action.setCallback(this, function(result) {
            var state = result.getState();
            if (component.isValid() && state === "SUCCESS"){
                var resultData = result.getReturnValue();
                component.set("v.ContactList", resultData.contactList);
                component.set("v.PageNumber", resultData.pageNumber);
                component.set("v.TotalRecords", resultData.totalRecords);
                component.set("v.RecordStart", resultData.recordStart);
                component.set("v.RecordEnd", resultData.recordEnd);
                component.set("v.TotalPages", Math.ceil(resultData.totalRecords / pageSize));
            }
        });
        $A.enqueueAction(action);
    }
})

Output:

Upload Multiple Files Using Lightning File Upload Component

In Winter ’18 Salesforce released a new lightning component lightning:fileUpload, which provides an easy and integrated way for users to upload multiple files. The file uploader includes drag-and-drop functionality and filtering by file types.

File Upload Component Limits:

  • By default, you can upload up to 10 files simultaneously unless your Salesforce admin has changed that limit.
  • The org limit for the number of files simultaneously uploaded is a maximum of 25 files and a minimum of 3 files.
  • The maximum file size you can upload is 2 GB. In Communities, the file size limits and types allowed follow the settings determined by community file moderation.

Considerations:

  • This component is not supported in Lightning Out or standalone apps, and displays as a disabled input.
  • The file uploader cannot be used to upload files with the following file extensions: .htm, .html, .htt, .htx, .mhtm, .mhtml, .shtm, .shtml, .acgi, .svg.

In this article I will show you how you can upload multiple files using lightning file upload component, without writing the apex code.

FileUpload Component:
Create a new Lightning Component FileUpload.cmp.

<!--FileUpload.cmp-->
<aura:component controller="FileUploadController" implements="force:appHostable, flexipage:availableForAllPageTypes, flexipage:availableForRecordHome, force:hasRecordId, forceCommunity:availableForAllPageTypes, force:lightningQuickAction" access="global">
    
    <lightning:fileUpload label="Upload File" multiple="true" accept=".pdf, .png" recordId="{!v.recordId}" aura:id="multifileUpload" onuploadfinished="{!c.handleUploadFinished}" />
</aura:component>

FileUpload JavaScript Controller:
Now create below JavaScript FileUploadController.js controller for above FileUpload.cmp component.

({    
    handleUploadFinished: function (cmp, event) {
        //Get the list of uploaded files
        var uploadedFiles = event.getParam("files");
        //Show success message – with no of files uploaded
        var toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams({
            "title": "Success!",
            "type" : "success",
            "message": uploadedFiles.length+" files has been uploaded successfully!"
        });
        toastEvent.fire();
        
        $A.get('e.force:refreshView').fire();
        
        //Close the action panel
        var dismissActionPanel = $A.get("e.force:closeQuickAction");
        dismissActionPanel.fire();
    }
})

Steps to test the functionality:
Go to Setup || Object Manager || Select Object(For example Account) || Buttons, Links, and Actions || New Action || Create Quick Action

Now add the created Quick Action into Object Page Layout.
Go to Setup || Object Manager || Select Object(For example Account) || Page Layouts || Select Your Layouts (For example Account Layout) || Select Mobile & lightning Actions || Add the Quick Action into Salesforce Mobile & Lightning Experience Actions section.

Now, Open an account record click Upload File action from right upload files and try to upload file.

Salesforce Generic sObject Lightning Lookup Component

In Salesforce Lightning we don’t have any lookup field component like Salesforce classic. So, here is a generic lookup Lightning Component, which can be used for any sObject lookup present in our org without changing any major code.

sObjectLookupController:
Create below apex controller to use it in sObjectLookup.cmp component.

/**
* @Author	:	Biswajeet Samal
* @Date		:	9 Dec 2017
* @Desc		:	Controller for sObject Lookup Lightning Component
* */
public with sharing class sObjectLookupController {
    @AuraEnabled
    public static List<sObject> searchRecord(String objectAPIName, String fieldAPIName, 
                                             List<String> moreFields, String searchText,
                                             Integer recordLimit)
    {
        
        List<sObject> objectList =  new List<sObject>();
        
        searchText='\'%' + String.escapeSingleQuotes(searchText.trim()) + '%\'';
        
        String soqlQuery = 'SELECT Id, Name';
        if(!moreFields.isEmpty()){
            soqlQuery = soqlQuery + ',' + String.join(moreFields, ',') ;
        }
        soqlQuery = soqlQuery + ' FROM ' + objectAPIName + ' WHERE ' +
            + fieldAPIName +' LIKE '+ searchText + ' LIMIT '+ recordLimit;
        objectList = Database.query(soqlQuery);
        return objectList;
    }
}

sObjectLookupSelectEvent.evt:
Create below Lightning Event, which is used to store and fill the input field with selected record Id and Name. Fired from sObjectLookupItem.cmp component, handled at Lookup component.

<!--sObjectLookupSelectEvent.evt-->
<aura:event type="COMPONENT" description="sObjectLookupSelectEvent">
    <aura:attribute name="recordId" type="String" required="true" description="Used to send selected record Id"/>
    <aura:attribute name="recordName" type="String" required="true" description="Used to send selected record Name" />
</aura:event>

sObjectLookup.cmp:
Create below Lightning Component, which will be used for sObject lookup.

<!--sObjectLookup.cmp-->
<aura:component controller="sObjectLookupController" description="Lightning component for lookup fields">
    
    <!--Declare Attributes-->
    <aura:attribute name="objectAPIName" type="String" required="true"
                    description="Object API name used for searching records"/>
    
    <aura:attribute name="fieldAPIName" type="String" required="true"
                    description="API Name of field to be searched"/>
    
    <aura:attribute name="lookupIcon" type="String" default="standard:contact"
                    description="Icon for lookup records"/>
    
    <aura:attribute name="placeholder" type="String" default="Search..."
                    description="Placeholder text for input search filed"/>
    
    <aura:attribute name="fieldLabel" type="String" required="true"
                    description="input search field Label"/>
    
    <aura:attribute name="selectedRecordId" type="String"
                    description="Used to store the selected record id.
                                 While calling this component from other component,
                                 set this attribute to the lookup field API name"/>
    
    <aura:attribute name="selectedRecordName" type="String"
                    description="This is used to show the selected record Name in search input"/>
    
    <aura:attribute name="subHeadingFieldsAPI" type="String[]"
                    description="Field API for the fields to be shown under the record Name.
                                 Must be comma separated. Example: Email,Phone"/>
    
    <aura:attribute name="matchingRecords" type="Object[]" access="private"
                    description="List of records returned from server side call"/>
    
    <aura:attribute name="recordLimit" type="Integer" access="public" default="5" 
                    description="Total number of record to be returned"/>
    
    <!--Declare Handlers-->
    <aura:handler name="lookupSelect" event="c:sObjectLookupSelectEvent" action="{!c.handleLookupSelectEvent}"
                  description="Event handler to get the selected record Id and Name from LookupItem component"/>
    
    <!--Component-->
    <div class="slds-form-element__control">
        <div class="slds-combobox_container slds-has-inline-listbox">
            <div aura:id="divLookup"
                 class="slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-combobox-lookup"
                 aria-expanded="false" aria-haspopup="listbox" role="combobox">
                
                <div class="slds-combobox__form-element">
                    <lightning:input type="search"
                                     aura:id="searchinput"
                                     label="{!v.fieldLabel}"
                                     name="{!v.fieldLabel}"
                                     value="{!v.selectedRecordName}"
                                     onchange="{!c.handleSearchRecords}"
                                     isLoading="false"
                                     placeholder="{!v.placeholder}"
                                     onfocus="{!c.handleSearchRecords}"
                                     onblur="{!c.hideList}"/>
                </div>
                <div id="listbox-unique-id" role="listbox">
                    <ul class="slds-listbox slds-listbox_vertical slds-dropdown slds-dropdown_fluid" role="presentation">
                        <aura:iteration var="rec" items="{!v.matchingRecords}">
                            <c:sObjectLookupItem record="{!rec}" subHeadingFieldsAPI="{!v.subHeadingFieldsAPI}" iconCategoryName="{!v.lookupIcon}"/>
                        </aura:iteration>
                    </ul>
                </div>
            </div>
        </div>
    </div>
</aura:component>

sObjectLookupController.js:

({
    handleSearchRecords : function (component, event, helper) {
        var searchText = component.find("searchinput").get("v.value");
        if(searchText){
            helper.searchRecord(component,searchText);
        }else{
            helper.searchRecord(component, '');
        }
    },
    
    handleLookupSelectEvent : function (component, event, helper) {
        var selectedRecordId = event.getParam("recordId");
        var selectedrecordName = event.getParam("recordName");
        component.set("v.selectedRecordId", selectedRecordId);
        component.set("v.selectedRecordName", selectedrecordName);
        helper.toggleLookupList(component, false, 'slds-combobox-lookup', 'slds-is-open');
    },
    
    hideList :function (component,event,helper) {
        window.setTimeout(
            $A.getCallback(function() {
                if (component.isValid()) {
                    helper.toggleLookupList(component, false, 'slds-combobox-lookup','slds-is-open');
                }
            }), 200
        );
    }
})

sObjectLookupHelper.js:

({
    toggleLookupList : function (component, ariaexpanded, classadd, classremove) {
        component.find("divLookup").set("v.aria-expanded", true);
        $A.util.addClass(component.find("divLookup"), classadd);
        $A.util.removeClass(component.find("divLookup"), classremove);
    },
    
    searchRecord : function (component, searchText) {
        component.find("searchinput").set("v.isLoading", true);        
        var action = component.get("c.searchRecord");
        action.setParams({
            "objectAPIName": component.get("v.objectAPIName"),
            "fieldAPIName":component.get("v.fieldAPIName"),
            "moreFields":component.get("v.subHeadingFieldsAPI"),
            "searchText":searchText,
            "recordLimit":component.get("v.recordLimit")
        });
        
        action.setCallback(this, function(response) {
            var state = response.getState();
            if(component.isValid() && state === "SUCCESS") {
                if(response.getReturnValue()){
                    component.set("v.matchingRecords", response.getReturnValue());
                    if(response.getReturnValue().length > 0){
                        this.toggleLookupList(component, true, 'slds-is-open', 'slds-combobox-lookup');
                    }
                    component.find("searchinput").set("v.isLoading", false);
                }
            }
        });
        $A.enqueueAction(action);
    }
})

sObjectLookupItem.cmp:
Create below lightning component, which will be used for creating list elements for records in sObjectLookup.cmp component.

<!--sObjectLookupItem.cmp-->
<aura:component description="Component used for creating list elements for records">
    
    <!--Declare Attributes-->
    <aura:attribute name="record" type="Object" required="true"
                    description="Holds the single record instance"/>
    
    <aura:attribute name="subHeadingFieldsAPI" type="String[]"
                    description="Holds the field API names to show as meta entity in list"/>
    
    <aura:attribute name="subHeadingFieldValues" type="String"
                    description="Used to construct the meta entity value. Works as subheading in record option"/>
    
    <aura:attribute name="iconCategoryName" type="String"
                    description="Lightning icon category and icon name to show with each record element"/>
    
    <!--Declare Events-->
    <aura:registerEvent name="lookupSelect" type="c:sObjectLookupSelectEvent"
                        description="Event used to send the selected record Id and Name to Lookup component"/>
    
    <!--Declare Handlers-->
    <aura:handler name="init" value="{!this}" action="{!c.getValues}"
                  description="standard init event to prepare the sub heading mete entity value"/>
    
    <!--Component-->
    <li role="presentation" class="slds-listbox__item" onclick="{!c.handleSelect}">
        <span class="slds-media slds-listbox__option slds-listbox__option_entity slds-listbox__option_has-meta"
              role="option">
            <!--Lightning Icon-->
            <span class="slds-media__figure">
                <lightning:icon iconName="{!v.iconCategoryName}" size="small" alternativeText="{!v.record.Name}"/>
            </span>
            <!--Option-->
            <span class="slds-media__body">
                <span class="slds-listbox__option-text slds-listbox__option-text_entity">
                    {!v.record.Name}
                </span>
                <!--Option sub heading-->
                <span class="slds-listbox__option-meta slds-listbox__option-meta_entity">
                    {!v.subHeadingFieldValues}
                </span>
            </span>
        </span>
    </li>
</aura:component>

sObjectLookupItemController.js:

({
    getValues : function (component) {
        var record = component.get("v.record");
        var subheading = '';
        for(var i=0; i<component.get("v.subHeadingFieldsAPI").length; i++ ){
            if(record[component.get("v.subHeadingFieldsAPI")[i]]){
                subheading = subheading + record[component.get("v.subHeadingFieldsAPI")[i]] + ' - ';
            }
        }
        subheading = subheading.substring(0,subheading.lastIndexOf('-'));
        component.set("v.subHeadingFieldValues", subheading);
    },
    
    handleSelect : function (component,event) {
        var chooseEvent = component.getEvent("lookupSelect");
        chooseEvent.setParams({
            "recordId" : component.get("v.record").Id,
            "recordName":component.get("v.record").Name
        });
        chooseEvent.fire();
    }
})

Usage:

<!--sObjectLookupApp.app-->
<aura:application extends="force:slds">
    <c:sObjectLookup fieldLabel = "Contact" objectAPIName = "Contact"
                     fieldAPIName = "Name" subHeadingFieldsAPI = "Email,Phone"
                     lookupIcon = "standard:contact" placeholder = "Search Contact"/>
</aura:application>

Output: