Thursday, April 18, 2019

Custom Lookup/Autocomplete with Event Bubbling and Wire Method to Function

Hello Guys, hope you are doing great!

This post explains the basic use case of custom Lookup/Autocomplete with Lightning web components. This component uses the Event bubbling and with Wire method to function.

Here is the sample, how it will look. This generic lookup can be used with any object. Just pass on the SObject name and some parameters, it will be available to use. 





Example:
     <c-lookup-search label="Custom Lookup - Account" selectedsobject="Account" 
       recordlimit="10" onlookupselect={handlelookupselectaccount}></c-lookup-search>  

Lets see the details component wise.

The following component will hold a lookup option element. Click on the option will fire a bubbling event. This event will be caught by parent and grandparent components.

The event will pass the selected record in the details.



lookupItem.html

 <template>  
   <template if:true={record}>  
     <lightning-layout vertical-align="center">  
       <div style="cursor:pointer" key={record.Id} data-contact-id={record.Id} onclick={handleSelect}>  
         <lightning-layout-item padding="around-small">  
           {record.Name}  
         </lightning-layout-item>  
       </div>  
     </lightning-layout>  
   </template>  
   <template if:false={record}>  
     <p>No contact data available.</p>  
   </template>  
 </template>  


lookupItem.js

 import { LightningElement, api } from 'lwc';  
 export default class LookupItem extends LightningElement {  
   @api record;  
   // This method handles the selection of lookup value  
   handleSelect(event) {  
     // Event will be triggerred and bubbled to parent and grandparent.  
     // Check the parameters passed.  
     const selectEvent = new CustomEvent('lookupselect', {  
       detail: this.record,  
       bubbles: true,  
       composed: true  
     });  
     // Fire the custom event  
     this.dispatchEvent(selectEvent);  
   }  
 }  


The following component will hold an input text to search the lookup. It will do the server side Apex call to get the records. Apex call is generic so it can accommodate any SObject. Records will be then iterated over lookup-item component which we created in the earlier step.

Note: Currently this component searches the text on Name field of the SObject. But we can change/make it generic.

lookupSearch.html

 <template>  
     <div class="slds-m-around_medium selectoptions">  
         <lightning-input class="slds-m-bottom_small" label="Search" value={selectedName} minlength="1" isloading=true onkeyup={handleKeyChange}>  
         </lightning-input>  
       <template if:true={showoptions}>  
           <template if:true={records}>  
             <template for:each={records} for:item="record">  
               <template if:true={record}>  
                 <c-lookup-item key={record.Id} record={record} onlookupselect={handlelookupselect}>  
                 </c-lookup-item>  
               </template>  
             </template>  
           </template>  
         <template if:true={noRecordsFlag}>  
           <p>'{searchString}' in {label}s</p>  
         </template>  
       </template>  
     </div>  
 </template>  


lookupSearch.js


 import { LightningElement, track, api, wire} from 'lwc';  
 import getLookupSerachRecords from '@salesforce/apex/ManageRecordsController.getLookupSerachRecords';  
 export default class LookupSearch extends LightningElement {  
   // Tracked properties  
   @track records;  
   @track noRecordsFlag = false;  
   @track showoptions = true;  
   @track searchString = '';  
   @track selectedName;  
   // API properties  
   @api selectedsobject;  
   @api recordlimit;  
   @api label;  
   // Wire method to function, which accepts the Search String, Dynamic SObject, Record Limit, Search Field  
   @wire(getLookupSerachRecords, { searchString: '$searchString' , selectedSObject : '$selectedsobject', recordLimit : '$recordlimit'})  
   wiredContacts({ error, data }) {  
     this.noRecordsFlag = 0;  
     if (data) {  
       this.records = data;  
       this.error = undefined;  
       this.noRecordsFlag = this.records.length === 0 ? true : false;  
     } else if (error) {  
       this.error = error;  
       this.records = undefined;  
     }  
   }  
   // handle event called lookupselect  
   handlelookupselect(event){  
     this.selectedName = event.detail.Name;  
     this.showoptions = false;  
   }  
   // key change on the text field  
   handleKeyChange(event) {  
     this.showoptions = true;  
     this.searchString = event.target.value;  
   }  
 }  


lookupSearch.css

 lightning-input,  
 c-lookup-item {  
   position: relative;  
   border: solid 1px #ecebea;  
   border-radius: 4px;  
   display: block;  
   padding: 2px;  
 }  
 c-lookup-item {  
   margin: 1px 0;  
 }  
 lightning-input:before,  
 c-lookup-item:before,  
 selectoptions:before {  
   color: #dddbda;  
   position: absolute;  
   top: -9px;  
   left: 4px;  
   background-color: #ffffff;  
   padding: 0 4px;  
 }  
 selectoptions {  
   border: solid 1px #ecebea;  
 }  

We are done with our web component implementation, now its time to use it.

You can see that we have included the lookup-search component with the parameters. Parameters are pretty easy and straight forward. 

label                     : Label to be shown on the UI
selectedsobject     : SObject from which data to be fetched.
recordlimit            : Number of records to be fetched.

Please check .js file, handlers will give you the selected record. The selected lookup record can be used for your further operations.



lookupWrapperParent.html


 <template>  
   <lightning-card title="Custom Lookup - Account" icon-name="custom:custom57">  
     <c-lookup-search label="Custom Lookup - Account" selectedsobject="Account"   
       recordlimit="10"    
       onlookupselect={handlelookupselectaccount}></c-lookup-search>  
     <tempate if:true={selectedAccountRecord}>  
       <p>SELECTED RECORD ID : {selectedAccountRecord.Id}</p>  
       <p>SELECTED RECORD VALUE : {selectedAccountRecord.Name}</p>  
     </tempate>  
   </lightning-card>  
   <lightning-card title="Custom Lookup - Contact" icon-name="custom:custom57">  
     <c-lookup-search label="Contact" selectedsobject="Contact" recordlimit="10"  
        onlookupselect={handlelookupselectcontact}>  
     </c-lookup-search>  
     <tempate if:true={selectedContactRecord}>  
       <p>SELECTED RECORD ID : {selectedContactRecord.Id}</p>  
       <p>SELECTED RECORD VALUE : {selectedContactRecord.Name}</p>  
     </tempate>  
   </lightning-card>  
 </template>  


lookupWrapperParent.js

 import { LightningElement, track } from 'lwc';  
 export default class LookupWrapperParent extends LightningElement {  
   @track selectedAccountRecord;  
   @track selectedContactRecord;  
   // Event bubbles to grandparent and being handled here - Account  
   handlelookupselectaccount(event) {  
     this.selectedAccountRecord = event.detail;  
   }  
   // Event bubbles to grandparent and being handled here - Contact  
   handlelookupselectcontact(event) {  
     this.selectedContactRecord = event.detail;  
   }  
 }  


Apex class 

ManageRecordsController.cls

 public with sharing class ManageRecordsController {  
   @AuraEnabled(cacheable=true)  
   public static List<Account> getLookupSerachRecords(String searchString, String selectedSObject, Integer recordLimit) {  
     if(searchString != null && searchString != ''){  
       String query = 'select Name, Id from ' + selectedSObject;  
         query += ' where Name like \'%'+ searchString +'%\' ';  
       query+= ' limit ' + recordLimit;  
       return Database.query(query);  
     }  
     return null;  
   }  
 }  
It was a simple example which shows the custom lookup or Autocomplete with Event Bubbling and Wire Method to Function.


I hope you enjoyed the learning, please write me back the suggestions, comments or any issues. Let's meet in our next post with more learnings and fun. :)

THANK YOU!

Wednesday, April 10, 2019

renderedCallback() function/action is getting called multiple times : How to handle?


Hello Guys,


If you have worked on Lightning Web Components and used the renderedCallback() method, you might have seen that it gets called multiple times with one action/operation.

Before going into the issue, first, let's try to understand what exactly
renderedCallback() does. This hook flows from child to parent. When a component rerenders, all the expressions used in the template are reevaluated.

Means all the template variables will be refreshed to get the latest value. Due to mutations, a component is generally rendered multiple times during its lifespan in an application. If you want this action to be called only once and not many times, ummmmm???

Soooo how to handle this?

Do you remember the Apex Trigger and recursion handling? We use the static variable to stop the recursion.

Similar to that, we are going to do it here.

1) Create one private property in your class. In our example its 
isRenderCallbackActionExecuted


import { LightningElement, track, api } from 'lwc';
export default class RenderCallBackExample extends LightningElement {

isRenderCallbackActionExecuted = false;


2) In your renderedCallback method add the following lines. What will these couple of lines do?
It will check if the action has been already executed, by checking if the flag is set. If it's true it will skip the execution and our function will be called only once.

renderedCallback() {
        if (this.isRenderCallbackActionExecuted)) {
            return;
        }

        this.isRenderCallbackActionExecuted = true;
        // Method action implementation.
}

Now, you might ask, this is the solution for executing it only once, what if I want this to be called once, on a particular action such as button click or anything. It's simple. Just reset the isRenderCallbackActionExecuted flag to false again. So next time method will execute once again.

Example: Following code is the snippet for Search box text change.


handleKeyChange(event) {

        if (this.searchKey !== event.target.value) {
            this.isRenderCallbackActionExecuted = false;
            this.searchKey = event.target.value;
        }

}


I hope you enjoyed the learning, please write me back the suggestions, comments or any issues. Let's meet in our next blog with more learnings and fun. :)


THANK YOU!

Tuesday, April 9, 2019

Salesforce : Lightning Web Components : Pagination with Search and List View - Step by Step Implementation


Hello Guys,
As we have seen Salesforce recently published a very very important development feature/platform called 'Lightning Web Components'. If you are new to these, I have a sample Pagination implementation example which will try to cover some of the use cases in Lightning web components.



Let's begin with our journey to Pagination. :)

  • Create a component called paginatorBottom. This component will have the buttons for the pagination such as PreviousNextFirstLast. This component will fire events on the button actions which will be caught by the parent components.




paginatorBottom.html

 <template>  
   <lightning-layout>  
     <lightning-layout-item>  
       <lightning-button label="First" icon-name="utility:chevronleft" onclick={handleFirst}  
         disabled={showFirstButton}></lightning-button>  
     </lightning-layout-item>  
     <lightning-layout-item>  
       <lightning-button label="Previous" icon-name="utility:chevronleft" onclick={handlePrevious}  
         disabled={showFirstButton}></lightning-button>  
     </lightning-layout-item>  
     <lightning-layout-item flexibility="grow"></lightning-layout-item>  
     <lightning-layout-item>  
       <lightning-button label="Next" icon-name="utility:chevronright" icon-position="right" onclick={handleNext}  
         disabled={showLastButton}></lightning-button>  
     </lightning-layout-item>  
     <lightning-layout-item>  
       <lightning-button label="Last" icon-name="utility:chevronright" icon-position="right" onclick={handleLast}  
         disabled={showLastButton}></lightning-button>  
     </lightning-layout-item>  
   </lightning-layout>  
 </template>  

paginatorBottom.js

 import { LightningElement, api } from 'lwc';  
 export default class PaginatorBottom extends LightningElement {  
   // Api considered as a reactive public property.  
   @api totalrecords;  
   @api currentpage;  
   @api pagesize;  
   // Following are the private properties to a class.  
   lastpage = false;  
   firstpage = false;  
   // getter  
   get showFirstButton() {  
     if (this.currentpage === 1) {  
       return true;  
     }  
     return false;  
   }  
   // getter  
   get showLastButton() {  
     if (Math.ceil(this.totalrecords / this.pagesize) === this.currentpage) {  
       return true;  
     }  
     return false;  
   }  
   //Fire events based on the button actions  
   handlePrevious() {  
     this.dispatchEvent(new CustomEvent('previous'));  
   }  
   handleNext() {  
     this.dispatchEvent(new CustomEvent('next'));  
   }  
   handleFirst() {  
     this.dispatchEvent(new CustomEvent('first'));  
   }  
   handleLast() {  
     this.dispatchEvent(new CustomEvent('last'));  
   }  
 }  

  • Create a Lightning Web component called as a recordList. This component will fetch the records from the Apex class by calling the method. This component will give us information about the Total number of records, Total pages, Accounts data.
  • We can see that the recordList.js will have the chaining of the apex method calls(First record count method call then retrieve actual data method call). I intentionally added chaining to showcase how it works.
  • This component also has the Search functionality. It showcases the usage of standard renderedCallback function and how to avoid the recursive of infinite calls. Ex. isSearchChangeExecuted variable.


recordList.html

 <template>  
   <lightning-card title="Accounts List" icon-name="custom:custom63">  
     <div class="slds-m-around_medium">  
       <lightning-input type="search" onchange={handleKeyChange} class="slds-m-bottom_small" label="Search"  
         value={searchKey}></lightning-input>  
       <template if:true={accounts}>  
         <table  
           class="slds-table slds-table_bordered slds-table_striped slds-table_cell-buffer slds-table_fixed-layout">  
           <thead>  
             <tr class="slds-text-heading_label">  
               <th scope="col">  
                 <div class="slds-truncate" title="ID">ID</div>  
               </th>  
               <th scope="col">  
                 <div class="slds-truncate" title="Name">Name</div>  
               </th>  
             </tr>  
           </thead>  
           <tbody>  
             <!-- Use the Apex model and controller to fetch server side data -->  
             <template for:each={accounts} for:item="account">  
               <tr key={account.Id}>  
                 <th scope="row">  
                   <div class="slds-truncate" title={account.Id}>{account.Id}</div>  
                 </th>  
                 <td>  
                   <div class="slds-truncate" title={account.Name}>{account.Name}</div>  
                 </td>  
               </tr>  
             </template>  
           </tbody>  
         </table>  
       </template>  
     </div>  
     <p class="slds-m-vertical_medium content">Total records: <b>{totalrecords} </b> Page <b>{currentpage}</b> of  
       <b> {totalpages}</b></p>  
   </lightning-card>  
 </template>  

recordList.js

 import { LightningElement, track, api } from 'lwc';  
 import getAccountsList from '@salesforce/apex/ManageRecordsController.getAccountsList';  
 import getAccountsCount from '@salesforce/apex/ManageRecordsController.getAccountsCount';  
 export default class RecordList extends LightningElement {  
   @track accounts;  
   @track error;  
   @api currentpage;  
   @api pagesize;  
   @track searchKey;  
   totalpages;  
   localCurrentPage = null;  
   isSearchChangeExecuted = false;  
   // not yet implemented  
   pageSizeOptions =  
     [  
       { label: '5', value: 5 },  
       { label: '10', value: 10 },  
       { label: '25', value: 25 },  
       { label: '50', value: 50 },  
       { label: 'All', value: '' },  
     ];  
   handleKeyChange(event) {  
     if (this.searchKey !== event.target.value) {  
       this.isSearchChangeExecuted = false;  
       this.searchKey = event.target.value;  
       this.currentpage = 1;  
     }  
   }  
   renderedCallback() {  
     // This line added to avoid duplicate/multiple executions of this code.  
     if (this.isSearchChangeExecuted && (this.localCurrentPage === this.currentpage)) {  
       return;  
     }  
     this.isSearchChangeExecuted = true;  
     this.localCurrentPage = this.currentpage;  
     getAccountsCount({ searchString: this.searchKey })  
       .then(recordsCount => {  
         this.totalrecords = recordsCount;  
         if (recordsCount !== 0 && !isNaN(recordsCount)) {  
           this.totalpages = Math.ceil(recordsCount / this.pagesize);  
           getAccountsList({ pagenumber: this.currentpage, numberOfRecords: recordsCount, pageSize: this.pagesize, searchString: this.searchKey })  
             .then(accountList => {  
               this.accounts = accountList;  
               this.error = undefined;  
             })  
             .catch(error => {  
               this.error = error;  
               this.accounts = undefined;  
             });  
         } else {  
           this.accounts = [];  
           this.totalpages = 1;  
           this.totalrecords = 0;  
         }  
         const event = new CustomEvent('recordsload', {  
           detail: recordsCount  
         });  
         this.dispatchEvent(event);  
       })  
       .catch(error => {  
         this.error = error;  
         this.totalrecords = undefined;  
       });  
   }  
 }  
  • Now we will create a parent component which will include both these components.
  • This component will handle the events fired by buttons.

paginationParent.html

 <template>  
   <lightning-card>  
     <c-record-list currentpage={page} onrecordsload={handleRecordsLoad} pagesize={pagesize}></c-record-list>  
     <div class="slds-m-around_medium">  
       <c-paginator-bottom onprevious={handlePrevious} onnext={handleNext} onfirst={handleFirst}  
         onlast={handleLast} currentpage={page} totalrecords={totalrecords} pagesize={pagesize}>  
       </c-paginator-bottom>  
     </div>  
   </lightning-card>  
 </template>  

paginationParent.js


 import { LightningElement, track, api } from 'lwc';  
 const PAGE_SIZE = 5;  
 export default class PaginationParent extends LightningElement {  
   @api page = 1;  
   @api totalrecords;  
   @api _pagesize = PAGE_SIZE;  
   get pagesize() {  
     return this._pagesize;  
   }  
   set pagesize(value) {  
     this._pagesize = value;  
   }  
   handlePrevious() {  
     if (this.page > 1) {  
       this.page = this.page - 1;  
     }  
   }  
   handleNext() {  
     if (this.page < this.totalPages)  
       this.page = this.page + 1;  
   }  
   handleFirst() {  
     this.page = 1;  
   }  
   handleLast() {  
     this.page = this.totalPages;  
   }  
   handleRecordsLoad(event) {  
     this.totalrecords = event.detail;  
     this.totalPages = Math.ceil(this.totalrecords / this.pagesize);  
   }  
   handlePageChange(event) {  
     this.page = event.detail;  
   }  
 }  
Now finally create Apex class ManageRecordsController, which will give us the required information such as record count, account list.

Note: We have intentionally created separate methods for record count and account list, to showcase the chaining of the Apex method calls.


ManageRecordsController.cls

 public with sharing class ManageRecordsController {  
   @AuraEnabled(cacheable = true)  
   public static List<Account> getAccountsList(Integer pagenumber, Integer numberOfRecords, Integer pageSize, String searchString) {  
     String searchKey = '%' + searchString + '%';  
     String query = 'select id, Name from Account ';  
     if (searchString != null && searchString != '') {  
       query += ' where name like \'%' + searchString + '%\' ';  
     }  
     query += ' limit ' + pageSize + ' offset ' + (pageSize * (pagenumber - 1));  
     return Database.query(query);  
   }  
   @AuraEnabled(cacheable = true)  
   public static Integer getAccountsCount(String searchString) {  
     String query = 'select count() from Account ';  
     if (searchString != null && searchString != '') {  
       query += ' where name like \'%' + searchString + '%\' ';  
     }  
     return Database.countQuery(query);  
   }  
 }  
We are ready with our pagination Lightning Web Component implementation. You can include this component anywhere. 

Example

<template>
<c-pagination-bar></c-pagination-bar>
</template>


And don't forget to include following lines in the meta.xml file, in case you wanna expose this pagination on App/Record/Hope page.

 <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">  
 <apiVersion>45.0</apiVersion>  
 <isExposed>true</isExposed>  
 <targets>  
 <target>lightning__AppPage</target>  
 <target>lightning__RecordPage</target>  
 <target>lightning__HomePage</target>  
 </targets>  
 </LightningComponentBundle>  

I hope you enjoyed the learning, please write me back the suggestions, comments or any issues. Let's meet in our next blog with more learnings and fun. :)


THANK YOU.

Salesforce: Export to Excel with Lightning Web Component

Hello Guys, I hope you are doing well. In this post, we are going to see an implementation of " Export to Excel" in lightn...