Monday, September 9, 2019

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 lightning web components.






Exporting the data in Excel is a very common requirement for Developers. We will see how this can be achieved with Lightning web components.

Following HTML code just do a couple of things.

  •  Show a link to "Download Excel"
  •  Show the data to be exported, in the lightning data table.
  •  Click on the Download Excel button would call the js method called exportToCSV.



exportToExcelDemo.html

 <template>  
   <a target="_self" class="slds-button" download="ExportToCSV.csv" href={hrefdata} onclick={exportToCSV}  
     style="margin-left: 80%;">Download Excel</a>  
   <lightning-datatable data={contactList} columns={contactColumns} key-field="id"  
     hide-checkbox-column="true">  
   </lightning-datatable>  
 </template>  
This js file contains the main logic to export the data.


  • Link click will call the exportToCSV method. This function creates a CSV data required for our excel file. 
  • CSV created based on the controller method getContacts() which passes the contact wrapper.

exportToExcelDemo.js

 import {  
   LightningElement,  
   track  
 } from "lwc";  
 import getContacts from "@salesforce/apex/ExportToExcelDemoController.getContacts";  
 const columns = [{  
     label: "Name",  
     fieldName: "contactName",  
     type: "text"  
   },  
   {  
     label: "Email",  
     fieldName: "contactEmail",  
     type: "text"  
   }  
 ];  
 export default class ExportToExcelDemo extends LightningElement {  
   @track hrefdata;  
   @track contactList;  
   @track contactColumns = columns;  
   connectedCallback() {  
     this.getContacts();  
   }  
   getContacts() {  
     getContacts()  
       .then(result => {  
         this.contactList = result;  
       })  
       .catch(error => {  
         this.error = error;  
         console.log(this.error);  
       });  
   }  
   exportToCSV() {  
     let columnHeader = ["Name", "Email"];  // This array holds the Column headers to be displayd
     let jsonKeys = ["contactName", "contactEmail"]; // This array holds the keys in the json data  
     var jsonRecordsData = this.contactList;  
     let csvIterativeData;  
     let csvSeperator  
     let newLineCharacter;  
     csvSeperator = ",";  
     newLineCharacter = "\n";  
     csvIterativeData = "";  
     csvIterativeData += columnHeader.join(csvSeperator);  
     csvIterativeData += newLineCharacter;  
     for (let i = 0; i < jsonRecordsData.length; i++) {  
       let counter = 0;  
       for (let iteratorObj in jsonKeys) {  
         let dataKey = jsonKeys[iteratorObj];  
         if (counter > 0) {  csvIterativeData += csvSeperator;  }  
         if (  jsonRecordsData[i][dataKey] !== null &&  
           jsonRecordsData[i][dataKey] !== undefined  
         ) {  csvIterativeData += '"' + jsonRecordsData[i][dataKey] + '"';  
         } else {  csvIterativeData += '""';  
         }  
         counter++;  
       }  
       csvIterativeData += newLineCharacter;  
     }  
     console.log("csvIterativeData", csvIterativeData);  
     this.hrefdata = "data:text/csv;charset=utf-8," + encodeURI(csvIterativeData);  
   }  
 }  

Finally, this simple controller method that returns you the contact wrapper containing the data.


ExportToExcelDemoController.cls 

public with sharing class ExportToExcelDemoController { 
   public class ContactWrapper{  
     @AuraEnabled  
     public string contactName;  
     @AuraEnabled  
     public string contactEmail;  
     public ContactWrapper(Contact contactObj){  
       this.contactName = contactObj.Name;  
       this.contactEmail = contactObj.Email;  
     }  
   }  
   @AuraEnabled  
   public static List<ContactWrapper> getContacts(){  
     List<ContactWrapper> contactWrapperList = new List<ContactWrapper>();  
     for(Contact cwObj : [select id, Name, Email from Contact limit 5]){  
       contactWrapperList.add(new ContactWrapper(cwObj));  
     }  
     return contactWrapperList;  
   }  
 }  


It's pretty easy, isn't it?

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. :)

Tuesday, August 20, 2019

Lightning Web Component - How to use the Lightning "Data Table" with "Input" elements

Hello Guys, hope you are doing well.

We might come across the situation where we need to show input fields along with lightning data table. So here we are going to see an example of how to achieve that in Lightning Web Component.

For simplicity here, I am taking an example of marking attendance for Students.


So Lets start the implementation.

First, consider adding one input field on your HTML page in LWC.
   <lightning-input type="date" onchange={datechangeevent} label="Select Date" value={selecteddate} required>  
   </lightning-input>  


Then add the lightning data table.
   <lightning-datatable data={students} columns={columns} key-field="Id" onrowselection={getSelectedName}>  
   </lightning-datatable>  

Add one button This button will be called to save the selected records in the data table along with the selected date in the date picker. showConfirmation action will just show the popup to confirm the action. Once the user hits the Yes on popup, addAttendance method will be called.

      <lightning-button label="Save" variant="brand" onclick={showConfirmation} class="slds-m-top_medium">  
      </lightning-button>  


Here are the details of the js side

This change event will capture the changed date in the date-picker into selecteddate variable.
   @track selecteddate;  
  datechangeevent(event) {  
   this.selecteddate = event.target.value;  
  }  


The following code will fetch the default data to be shown on the data table.
 @track students;  
 getStudents(data) {  
 this.selecteddate = null;  
 getContacts({  
 opportunityId: this.opportunityId  
 })  
 .then(result => {  
 this.students = result;  
 })  
 .catch(error => {  
 this.error = error;  
 });  
 }  

Following code will be used to call the controller method and pass on the selected date and records in the data table.
  addAttendance() {  
   this.popup = false;  
   fireEvent(this.pageRef, "loadingAction", true);  
   console.log("this.selecteddate==>" + this.selecteddate);  
   console.log("this.selectedContacts==>" + this.selectedContacts);  
   createNewAttendanceDetails({  
    attendanceDate: this.selecteddate,  
    opportunityId: this.opportunityId,  
    contactList: this.selectedContacts  
   })  
    .then(result => {  
     console.log(result);  
     this.dispatchEvent(  
      new ShowToastEvent({  
       title: "Success",  
       message: "Attendance Created Successfully.",  
       variant: "success"  
      })  
     );  
    })  
    .catch(error => {  
     this.error = error;  
    });  
  }  

Following Apex controller method will be accepting the parameters sent by java-script lwc.
   public static Boolean createNewAttendanceDetails(Date attendanceDate, String opportunityId, List<String> contactList){  
      //We have all the data available in this function, we can take an action based on our requirements  
     return true;  
   }  


So that's it, we are ready with our sample use case.

I hope you liked the post and it helped. Please let me know your suggestions and feedback. 

Thank you so much for visiting.

Sunday, June 23, 2019

LWC : Create/Edit Form with Custom Lookup and Navigation

Hello Guys, 

Hope you are doing well. In this post we are going to see the very basic and common requirement of Create a record with a form with input fields, custom lookup, and navigation with the help of Lightning Web components.

So Let's start.

In the end, our screen will look somewhat like below.


Create a Lightning web component with name createContactForm.
In this example, we are going to create a contact record with a custom lookup of Account. This component will have the input elements to create a contact record.  Here we are going to use standard lightning-record-edit-form. We just need to pass a few parameters such as Object name, fields, etc.

We can define what actions need to be performed before load form load(onload) or before we submit a form(onsubmit) or after the successful execution of a form(onsuccess)
Implementation of these methods is explained in createContactForm.js

createContactForm.html
 <template>  
   <div class="slds-m-around_large">  
     <div if:false={isLoaded} class="slds-is-relative">  
       <lightning-spinner alternative-text="Loading...">  
       </lightning-spinner>  
     </div>  
   </div>  
   <lightning-card title="Contact Information">  
     <template if:true={accountRecord}>  
       <h1>Selected Account : <b>{accountRecord.Name}</b></h1>  
     </template>  
     <lightning-record-edit-form id="contactForm" object-api-name="Contact" layout-type="compact"  
       horizontal-align="stretch" title="Contact Information" onsuccess={handleContactSuccess}  
       onsubmit={handleOnContactSubmit} onload={handleOnLoad}>  
       <lightning-messages>  
       </lightning-messages>  
       <lightning-layout horizontal-align="stretch">  
         <lightning-layout-item flexibility="auto" >  
           <lightning-input-field field-name='FirstName'></lightning-input-field>  
         </lightning-layout-item>  
         <lightning-layout-item flexibility="auto" >  
           <lightning-input-field field-name='LastName'></lightning-input-field>  
         </lightning-layout-item>  
       </lightning-layout>  
       <lightning-layout horizontal-align="stretch">  
         <lightning-layout-item flexibility="auto" >  
           <lightning-input-field field-name='Title'></lightning-input-field>  
         </lightning-layout-item>  
         <lightning-layout-item flexibility="auto" >  
           <lightning-input-field field-name='Department'></lightning-input-field>  
         </lightning-layout-item>  
       </lightning-layout>  
       <lightning-layout horizontal-align="stretch">  
         <lightning-layout-item flexibility="auto" >  
           <lightning-input-field field-name='Email' required></lightning-input-field>  
         </lightning-layout-item>  
         <lightning-layout-item flexibility="auto" >  
           <lightning-input-field field-name='Alternate_Email__c'></lightning-input-field>  
         </lightning-layout-item>  
       </lightning-layout>  
       <lightning-layout horizontal-align="stretch">  
         <lightning-layout-item flexibility="auto" >  
           <lightning-input-field field-name='Phone'></lightning-input-field>  
         </lightning-layout-item>  
         <lightning-layout-item flexibility="auto" >  
           <lightning-input-field field-name='Extension__c'></lightning-input-field>  
         </lightning-layout-item>  
       </lightning-layout>  
       <lightning-layout horizontal-align="stretch">  
         <lightning-layout-item flexibility="auto" >  
           <lightning-input-field field-name='OtherPhone'></lightning-input-field>  
         </lightning-layout-item>  
         <lightning-layout-item flexibility="auto" >  
         </lightning-layout-item>  
       </lightning-layout>  
       <lightning-layout horizontal-align="stretch">  
         <lightning-layout-item flexibility="auto" >  
         </lightning-layout-item>  
         <lightning-layout-item flexibility="auto" >  
         </lightning-layout-item>  
       </lightning-layout>  
       <lightning-layout horizontal-align="stretch">  
         <lightning-layout-item flexibility="auto" >  
           <lightning-button variant="brand" type="submit" name="save" label="Save"  
             style="padding-left: 0.75rem;">  
           </lightning-button>  
           <lightning-button label="Cancel" variant="brand" onclick={cancelContactScreen}  
             style="padding-left: 0.75rem;">  
           </lightning-button>  
         </lightning-layout-item>  
       </lightning-layout>  
     </lightning-record-edit-form>  
   </lightning-card>  
 </template>  

This is the JS file/ class handles the actions needed by createContactForm.html. These actions include the onload, onsubmit, onsuccess.
  • We are showing the loading icon on the screen until the form is loaded(handleOnLoad)
  • Before we submit a form, we need to assign Account record id(explained later) to a contact record. So we are doing this in handleOnContactSubmit function. If you observe we have prevented the event until we assign all the values and perform our validations. Once we assign the related values, we are going to submit the form
  • Once the form is submitted and server-side action takes place, handleContactSuccess gets called. Here we are showing the success message and navigating a screen to a create contact record.
  • We have a Cancel button which navigates us to the home screen.
  • If you observe we are throwing events for success or cancel action, so that recipient of the component will take further actions according to their needs.

createContactForm.js


import { LightningElement, api, track } from "lwc";
import { ShowToastEvent } from "lightning/platformShowToastEvent";

export default class CreateContactForm extends LightningElement {
  @api accountRecord;
  @api eventRecordId;
  @track isLoaded = false;

  handleOnLoad() {
    this.isLoaded = true;
  }

  handleContactSuccess(event) {
    console.log(event.detail.fields);
    this.dispatchEvent(
      new ShowToastEvent({
        title: "Success",
        message: "Contact created Successfully.",
        variant: "success"
      })
    );
    this.dispatchEvent(
      new CustomEvent("contactsuccess", {
        detail: event.detail.id,
        bubbles: true
      })
    );
  }

  handleOnContactSubmit(event) {
    event.preventDefault(); // stop the form from submitting
    const fields = event.detail.fields;
    if (this.accountRecord === undefined || this.accountRecord === null) {
      this.dispatchEvent(
        new ShowToastEvent({
          title: "Error",
          message: "Please select an Account",
          variant: "error"
        })
      );
      return;
    }

    fields.AccountId = this.accountRecord.Id;
    this.template.querySelector("lightning-record-edit-form").submit(fields);
  }

  cancelContactScreen() {
    this.dispatchEvent(
      new CustomEvent("contactcancel", {
        bubbles: true
      })
    );
  }
}


This is a wrapper component which uses the component we created earlier createContactForm.js. Also, we are going to use the custom Account lookup component, which we can be referred to in this post. You can customize this component/queries according to your need.

We are taking the Account record from the custom lookup component and passing it on to contact form component.

contactFormWithCustomLookup.html
 <template>  
   <lightning-card title={title} icon-name="standard:record">  
     <div class="slds-m-around_medium">  
       <lightning-layout horizontal-align="stretch">  
         <lightning-layout-item flexibility="auto" style="padding-left: 0.75rem;">  
           <c-lookup-search label="Custom Lookup - Account" selectedsobject="Account"
             onlookupselect={handlelookupselectaccount}></c-lookup-search>  
         </lightning-layout-item>  
         <lightning-layout-item flexibility="auto" style="padding-left: 0.75rem;margin-top: 37px;">  
           Please start typing Account name in a box.  
         </lightning-layout-item>  
       </lightning-layout>  
       <c-create-contact-form oncontactsuccess={contactCreateSuccess} account-record={selectedAccountRecord}  
         event-record-id={eventRecordId} oncontactcancel={contactCancel}></c-create-contact-form>  
     </div>  
   </lightning-card>  
 </template>  


This JS method handles the success/cancel action event thrown by contact form component.
On success, we are navigating to contact record and on cancel to the home page.

contactFormWithCustomLookup.js


import {
  LightningElement,
  track,
  api
} from "lwc";
import {
  NavigationMixin
} from "lightning/navigation";

export default class ContactFormWithCustomLookup extends NavigationMixin(
  LightningElement
) {
  @api title = "Create Contact";
  @track selectedAccountRecord;
  @track isLoaded = false;

  contactCreateSuccess(event) {
    this[NavigationMixin.Navigate]({
      type: "standard__recordPage",
      attributes: {
        recordId: event.detail,
        objectApiName: "Contact", // objectApiName is optional
        actionName: "view"
      }
    });
  }

  contactCancel() {
    this[NavigationMixin.Navigate]({
      type: "standard__objectPage",
      attributes: {
        objectApiName: "Account",
        actionName: "home"
      }
    });
  }

  handlelookupselectaccount(event) {
    this.selectedAccountRecord = event.detail;
  }
}

Just create a lightning page/app and include this component in it, you are ready with your implementation.

So that's all, wasn't is so simple????

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!

Sunday, June 9, 2019

How to get URL parameters in Lightning Web Components??

Hello Guys, hope you are doing great!

Did you have a requirement to get the URL parameters in Lightning web component and use them further??

Here is the simple answer. As LWC is purely based on Javascript, we can utilize the Javascript functions to do this activity. I was working with communities and wanted to pass on some URL parameters, here is the implementation.

This was my URL.

<Community URL>?eventid=00UJ000000AzjeuMAB



    var eventId = ((new URL(window.location.href)).searchParams.get("eventid"));

The above line will give me the Event Id retrieved from URL. Sooo simple.

I hope you liked the post and it helped. Let me know your suggestions and feedback. 

Thank you so much for visiting.

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...