Friday, June 4, 2021

Lookup LWC



It's always taken a challenge by the Salesforce developers whenever a lookup component is needed to be added inside a component.

I have built a custom, easy solution on LWC that can be used anywhere.

LookupCMP.html

<template>
    <div class="slds-m-bottom_small slds-m-top_x-large">
        <lightning-card>
            <div class="slds-align_absolute-center">
                <lightning-layout>
                    <lightning-layout-item class="slds-p-left_small">
                        <lightning-icon icon-name="utility:salesforce1">
                        </lightning-icon>
                    </lightning-layout-item>
                    <lightning-layout-item>
                        <p class="slds-p-horizontal_small">
                            <b>Lookup to Account</b>
                        </p>
                    </lightning-layout-item>
                </lightning-layout>
            </div>
        </lightning-card>
    </div>

    <div class="slds-align_absolute-center">
        <lightning-card>
            <lightning-layout>
                <lightning-layout-item class="slds-m-around_small">
                    <lightning-input value={nameVar}
                                        variant="label-hidden"
                                        type="text" 
                                        placeholder="Account Name"
                                        autocomplete="none"
                                        data-field="accountLookup"
                                        onchange={handleAccNameChange}>
                    </lightning-input>
                    <template for:each={accounts} for:item="account" 
                             if:true={isAccountFound}>
                        <div key={account.value} id={account.value}
                            onclick={handleAccountSelection}>
                            <h3 title={account.value}
                                class="slds-border_bottom">
                                    {account.label}
                            </h3>
                        </div>
                    </template>
                </lightning-layout-item> 
                
                <lightning-layout-item class="slds-m-around_small"
                                       if:true={isAccountSelected}>
                    <table>
                        <tbody>
                            <tr>
                                <td><b>Selected Account Name : &nbsp;&nbsp;</b></td>
                                <td>{accountName}</td>
                            </tr>
                            <tr>
                                <td><b>Selected Account Id : &nbsp;&nbsp;</b></td>
                                <td>{accountId}</td>
                            </tr>
                        </tbody>
                    </table>
                </lightning-layout-item>
            </lightning-layout>
        </lightning-card>
    </div>
</template>

LookupCMP.js

import { LightningElementapi } from 'lwc';
import getAccounts from '@salesforce/apex/LookupController.fetchAccounts';

export default class LookupCMP extends LightningElement {
    nameVar = '';
    accounts = [];
    isAccountFound = false;
    accountName = '';
    accountId = '';
    isAccountSelected = false;

    //Onchange handler when Account name is entered by a user
    handleAccNameChange(event) {
        this.isAccountSelected = false;
        this.nameVar = event.target.value;
        this.getAccountsWithName();
    }
    
    //Imperatiev Apex method call for displaying the list of Account with the value entered
    getAccountsWithName() {
        getAccounts( { searchString : this.nameVar } )
        .then(result => {
            this.accounts = [];
            if(result.length > 0) {
                result.forEach(elem => {
                    let accVar = {
                        label : elem.Name,
                        value : elem.Id
                    };
                    this.accounts.push(accVar);
                });
            }
            this.isAccountFound = true;
        })
        .catch(error => {
            this.isAccountFound = false;
        });
    }

    //Handler method when an Account is selected
    handleAccountSelection(event) {
        let accountId = event.target.title;
        this.accounts.forEach(elem => {
            if(elem.value == accountId) {
                this.nameVar = elem.label;
            }
        });
        this.isAccountFound = false;
        this.accountName = this.nameVar;
        this.accountId = accountId;
        this.isAccountSelected = true;
        //Fire a custom event to send the data to a parent component
    }
}

LookupController.cls

//Method for fetching Accounts per the searchString provided
@AuraEnabled
public static List<AccountfetchAccounts(String searchString) {
    List<Accountaccounts = new List<Account>();
    try{
        String keyStr = '';
        if(!String.isBlank(searchString)) {
            keyStr = searchString + '%';
        }
        accounts = [SELECT IdName
                    FROM Account
                    WHERE Name LIKE :keyStr
                    ORDER BY Name
                    LIMIT 10];
        return accounts;
    }catch(exception exc) {
        return accounts;
    }
}

Thursday, February 11, 2021

Counter LWC - Lightning Design System


There are many awesome component blueprints defined in SLDS which are not available in LWC library, one such is Counter, full article here.


I have created a generic custom LWC component, code below, to use it across orgs - 

customCounter.html

<template>
    <div class="slds-form-element">

        <div class="slds-form-element__control">
            <button class="slds-button slds-button_icon 
slds-button_icon-small slds-input__button_decrement" 
                    title="Decrement counter">
                <lightning-icon icon-name="utility:ban" 
                                alternative-text="" 
                                size="xx-small" 
                                title="" 
                                variant="error"
                                onclick={setDecrementCounter}>
                </lightning-icon>
              <span class="slds-assistive-text">Decrement counter</span>
            </button>
            <input type="number" id="default1" placeholder={qtyVal} 
class="slds-input slds-input_counter" />
            <button class="slds-button slds-button_icon 
slds-button_icon-small 
slds-input__button_increment" 
                    title="Increment counter">
                <lightning-icon icon-name="utility:new" 
                                alternative-text="" 
                                size="xx-small" 
                                title="" 
                                variant="success"
                                onclick={setIncrementCounter}>
                </lightning-icon>
              <span class="slds-assistive-text">Increment counter</span>
            </button>
          </div>

      </div>
</template>

customCounter.js

import { LightningElementapi } from 'lwc';

export default class CustomCounter extends LightningElement {
    
    qty = 0;
    qtyVal = '';

    setDecrementCounter(event) {
        if(this.qty == 0)   this.qty = 0;
        else    this.qty = this.qty - 1;
        let quantity = this.qty.toString();
        this.qtyVal = quantity + ' ' + this.qtyMeasure;
        const decrementEvent = new CustomEvent(
'decrement'
detail: this.qty }
);
        this.dispatchEvent(decrementEvent);
    }

    setIncrementCounter(event) {
        this.qty = this.qty + 1;
        let quantity = this.qty.toString();
        this.qtyVal = quantity + ' ' + this.qtyMeasure;
        const incrementEvent = new CustomEvent(
'increment'
detail: this.qty }
);
        this.dispatchEvent(incrementEvent);
    }

    @api 
    get qtyMeasure() {
        return this._qtyMeasure;
    }

    set qtyMeasure(value) {
        if (value) {
            this._qtyMeasure = value;
            let qty = this.qty.toString();
            this.qtyVal = qty + ' ' + this.qtyMeasure;
        }else{
            this.qtyVal = '';
        }
    }
}

Call "customCounter" from other LWCs - 

parent.html

<template>
    <lightning-card >
        <lightning-layout>
            <lightning-layout-item class="slds-p-left_small">
                <lightning-icon icon-name="utility:trailhead" 
size="large"></lightning-icon>
            </lightning-layout-item>
            <lightning-layout-item>
                <p class="slds-p-horizontal_small">
                    <b>SLDS COUNTER</b>
                </p>
            </lightning-layout-item>
        </lightning-layout>
        
        <lightning-layout multiple-rows class="slds-m-around_xx-large">
                <lightning-layout-item class="slds-size_1-of-4">
</lightning-layout-item>    
                <lightning-layout-item class="slds-size_1-of-4 slds-box">
                    <lightning-icon icon-name="utility:product_workspace" 
size="small"></lightning-icon>
                    &nbsp;&nbsp;&nbsp;&nbsp;Paracetamol
                </lightning-layout-item>
                <lightning-layout-item class="slds-size_1-of-4 slds-box">
                    <c-custom-counter 
                                qty-measure='Strips'
                                ondecrement={quantityCtrl}
                                onincrement={quantityCtrl}>
                    </c-custom-counter>
                </lightning-layout-item>
                <lightning-layout-item class="slds-size_1-of-4">
</lightning-layout-item>
                <lightning-layout-item class="slds-size_1-of-4">
</lightning-layout-item>  
  
                <lightning-layout-item class="slds-size_1-of-4 slds-box">
                    <lightning-icon icon-name="utility:product_workspace" 
size="small"></lightning-icon>
                    &nbsp;&nbsp;&nbsp;&nbsp;Hand Sanitizer
                </lightning-layout-item>
                <lightning-layout-item class="slds-size_1-of-4 slds-box">
                    <c-custom-counter
                                qty-measure='Pieces'
                                ondecrement={quantityCtrl}
                                onincrement={quantityCtrl}>
                    </c-custom-counter>
                </lightning-layout-item>
                <lightning-layout-item class="slds-size_1-of-4">
</lightning-layout-item>

                <lightning-layout-item class="slds-size_1-of-4">
</lightning-layout-item>    
                <lightning-layout-item class="slds-size_1-of-4 slds-box">
                    <lightning-icon icon-name="utility:product_workspace" 
size="small"></lightning-icon>
                    &nbsp;&nbsp;&nbsp;&nbsp;Water Bottle
                </lightning-layout-item>
                <lightning-layout-item class="slds-size_1-of-4 slds-box">
                    <c-custom-counter 
                                qty-measure='Litres'
                                ondecrement={quantityCtrl}
                                onincrement={quantityCtrl}>
                    </c-custom-counter>
                </lightning-layout-item>
                <lightning-layout-item class="slds-size_1-of-4">
</lightning-layout-item>    

                <lightning-layout-item class="slds-size_1-of-4">
</lightning-layout-item>    
                <lightning-layout-item class="slds-size_1-of-4 slds-box">
                    <lightning-icon icon-name="utility:product_workspace" 
size="small"></lightning-icon>
                    &nbsp;&nbsp;&nbsp;&nbsp;Sugar
                </lightning-layout-item>
                <lightning-layout-item class="slds-size_1-of-4 slds-box">
                    <c-custom-counter 
                                qty-measure='Kilograms'
                                ondecrement={quantityCtrl}
                                onincrement={quantityCtrl}>
                    </c-custom-counter>
                </lightning-layout-item>
                <lightning-layout-item class="slds-size_1-of-4">
</lightning-layout-item>    
        </lightning-layout>

   </lightning-card>
        
</template>

parent.js

import { LightningElement } from 'lwc';

export default class Parent extends LightningElement {
    qty = 0;
    
    quantityCtrl(event) {
        this.qty = event.detail;
    }

}

Monday, November 30, 2020

 Custom one-to-one messaging service b/w agents using LWC






Chatter is a best way for the users to collaborate with each other in Salesforce, but what if we want to give the user feel of the chat that is provided by whatsapp/messenger ...??

The below code developed in LWC to create one's own personalized chatterbox to engage and connect.

chatterLWC.html

<template>
    <lightning-card>
        <div if:true={isLoading}"> 
            <lightning-spinner alternative-text="Loading...">
</lightning-spinner>
        </div>
        <div if:false={isLoading}>
        <h1 style="color: rgb(110, 41, 41); text-align: center; 
font-size: 17px;">
            <b>Collaboration chat for <i>{loggedInUser}</i></b>
        </h1>
        <div>
            <template if:true={feeds.length} iterator:it={feed}>
                <div class="slds-p-left_xx-large" 
                     if:true={it.value.isSpeaker} 
                     key={it.value.id}>
                    <lightning-layout multiple-rows key={it.value.id}
                               class="chatter-comments 
                                      slds-m-vertical_xx-small">
                        <lightning-layout-item size="6" 
                             class="slds-p-left_small">
                            {it.value.speaker} said...
                        </lightning-layout-item>
                        <lightning-layout-item size="6" 
class="slds-size_6-of-12 slds-p-right_small" 
                               style="text-align: right;">
                            {it.value.dateCreated}
                        </lightning-layout-item>
                        <lightning-layout-item size="12" 
                            class="slds-p-left_small">
                            <b><i>{it.value.comment}</i></b>
                        </lightning-layout-item>
                        <br/>
                    </lightning-layout>
                </div>
                <div class="slds-p-right_xx-large" if:false={it.value.isSpeaker} 
                     key={it.value.id}>
                    <lightning-layout multiple-rows key={it.value.id}
                                  class="chatter-comments 
                                         slds-m-vertical_xx-small" 
                                  if:false={it.value.isSpeaker}>
                        <lightning-layout-item size="6" 
                            class="slds-p-left_small">
                            {it.value.speaker} said...
                        </lightning-layout-item>
                        <lightning-layout-item size="6" 
                            class="slds-size_6-of-12 slds-p-right_small" 
                            style="text-align: right;">
                            {it.value.dateCreated}
                        </lightning-layout-item>
                        <lightning-layout-item size="12" 
                            class="slds-p-left_small">
                            <b><i>{it.value.comment}</i></b>
                        </lightning-layout-item>
                        <br/>
                    </lightning-layout>
                </div>
            </template>
        </div>
            <form onsubmit={handleSendChat}>
                <lightning-layout>
                    <lightning-layout-item size="4"></lightning-layout-item>
                    <lightning-layout-item size="6">
                        <lightning-input label="Add a comment" 
                            value={newComment} data-field="comment" 
                            class="slds-m-vertical_x-small">
                        </lightning-input>
                    </lightning-layout-item> 
                    <lightning-layout-item size="2">
                        <div class="slds-p-top_x-large">
                            <template if:false={sending}>
                                <lightning-button label="Send" 
                                    class="slds-float_left" 
                                    onclick={handleSendChat}>
                                </lightning-button>
                            </template>
                            <template if:true={sending}>
                                <lightning-button disabled 
                                    label="Sending..." 
                                    class="slds-float_left">
                                </lightning-button>
                            </template>
                        </div>
                    </lightning-layout-item>
                </lightning-layout>
            </form>
        </div>
    </lightning-card>
</template>

chatterLWC.js

import { LightningElementwire } from 'lwc';
import getFeeds from '@salesforce/apex/chatterClass.getFeeds';
import insertChatterPosts from '@salesforce/apex/chatterClass.
                                insertChatterPosts';
import getCurrentUser from '@salesforce/apex/chatterClass.getCurrentUser'

export default class ChatterLWC extends LightningElement {
    feeds = [];
    sending = false;
    newComment = '';
    leadId = '';
    isLoading = true;
    loggedInUser = '';

    @wire(getCurrentUser)
    wiredCurrentUser({errordata}) {
        if (error) {
        } else if (data) {
            this.loggedInUser = data;
        }
    }

    connectedCallback(){
        let address = window.location.href;
        this.leadId = address.split('Lead/')[1].
                      split('/')[0];
        
        this.getFeeds(this);
    }

    getFeeds(self){
        this.isLoading = true;
        getFeeds({
            leadId : this.leadId
        })
        .then(result => {
            this.feeds = result;
            this.isLoading = false;
            setTimeout(function () {
                self.getFeeds(self);
            }, 20000);
        })
        .catch(error => {
            this.isLoading = false;
        });
    }

    handleSendChat() {
        this.isLoading = true;
        this.sending = true;

        this.newComment = this.template.querySelector
                            ("[data-field='comment']")
                            .value;

        if (!this.newComment || !this.newComment.trim()) 
        {
            this.sending = false;
            return;
        }
        
        insertChatterPosts({
            leadId: this.leadId,
            comment: this.newComment
        })
        .then(result => {
            this.feeds = result;
            this.sending = false;
            this.newComment = '';
            this.isLoading = false;         
        })
        .catch(error => {
            this.sending = false;
            this.isLoading = false;         
        });
    }
}

chatterLWC.css

.chatter-comments {
    background-color#eee;
    width95%;
}

chatterClass.cls

public without sharing class chatterClass {
    
    @AuraEnabled(cacheable=true)
    public static String getCurrentUser(){
        return [SELECT IdName 
                FROM User 
                WHERE Id =:UserInfo.getUserId()
                ].Name;
    }

    @AuraEnabled
    public static List<chatterPostWrappergetFeeds(Id leadId) {
        List<feedWrapperfeedWrappers = new List<feedWrapper>();
        List<FeedItemfeedItems = [SELECT IdParentIdBody
                                        createdById
                                        createdBy.NameCreatedDate 
                                    FROM FeedItem 
                                    WHERE parentId = :leadId 
                                    AND Type = 'TextPost' 
                                    ORDER BY CreatedDate 
                                    DESC ];
        if(!feedItems.isEmpty()){
            for(FeedItem itemfeedItems){
                String dateString = getMessageDate(item.createdDate);
                String speakerName = item.createdBy.Name;
                Boolean isSpeaker = false;
                Id userId = UserInfo.getuserId();
                if(userId == item.createdById){
                    isSpeaker = true;
                }
                feedWrappers.add(new feedWrapper(item.Id
                                                item.createdById
                                                speakerName
                                                dateString
                                                item.Body
                                                .stripHtmlTags(), 
                                                isSpeaker));
            }
        }
        return postWrappers;
    }

    @AuraEnabled
    public static List<feedWrapperinsertChatterPosts(
                Id leadId
                String comment) {
        try{
            FeedItem post = new FeedItem();
            post.ParentId = leadId;
            post.createdById = UserInfo.getuserId();
            post.Body = comment;
            post.type = 'TextPost';
            INSERT post;
            return getFeeds(leadId);   
        }catch(exception e){
            system.debug('Something went wrong '+e.getMessage());
            return null;
        }
    }

    private static String getMessageDate(DateTime dTime){
        Long dt1Long = dTime.getTime();
        Long dt2Long = DateTime.now().getTime();
        Long milliseconds = dt2Long - dt1Long;
        Long seconds = milliseconds / 1000;
        Long minutes = seconds / 60;
        Long hours = minutes / 60;
        Long days = hours / 24;

        if(seconds < 60){
            return String.valueOf(seconds + ' seconds ago');
        }
        if(minutes < 60){
            return String.valueOf(minutes + ' minutes ago');
        }
        if(hours < 24){
            return String.valueOf(hours + ' hours ago');
        }
        if(days < 4){
            return String.valueOf(days + ' days ago');
        }
        else{
            return String.valueOf(dTime.format('MMM-d-yyyy'));
        }
    }

    public class feedWrapper{

        @AuraEnabled
        public String Id;
        @AuraEnabled
        public String speaker;
        @AuraEnabled
        public String dateCreated;
        @AuraEnabled
        public String comment;
        @AuraEnabled
        public Boolean isSpeaker;

        public chatterPostWrapper(String id
                                String createdById
                                String speaker
                                String dateCreated
                                String comment
                                Boolean isSpeaker){
            this.Id = id;
            this.speaker = speaker;
            this.dateCreated = dateCreated;
            this.comment = comment;
            this.isSpeaker = isSpeaker;
        }
    }
    
}