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;
        }
    }
    
}

Thursday, November 5, 2020

Scroll to a specific element in an LWC form

We may want to let the user navigate to different sections of a page in LWC when s/he clicks a particular button.

Such an example can be a form where index is maintained and when a user clicks on a particular section, navigates him/her to the associated content.

This works both in Desktop as well as Mobile devices.

To achieve this, the user can add different components inside div elements as below:

<div class="scrollCls">
    <c-hello-world >
</c-hello-world >
</div>

When a button is clicked, it fires the below event to scroll down to a particular element on the form:

handleScroll(){
    this.template.querySelector('.scrollCls').scrollIntoView();
}

Thursday, October 1, 2020

Dynamic table in LWC

Sometimes we come across scenarios when we need dynamic table where we have dynamic number of columns as well as rows.

One such was to display the Profiles with different users assigned to it in an org in Lightning Web Component (LWC).


pivotTable.html

<template>
    <div if:true={isLoading} class="cstm-spinner"> 
        <lightning-spinner alternative-text="Loading...">
        </lightning-spinner>
    </div>
    <div if:false={isLoading}>
        <table 
            class="slds-table slds-table_cell-buffer slds-table_bordered 
                    slds-max-medium-table_stacked-horizontal">
                   <thead>
                     <th class="" scope="col">
                       <div class="slds-truncate">
                        User
                        <lightning-icon if:false={val} 
                                icon-name="utility:down" 
                                size="x-small">
                        </lightning-icon>
                        /Profile
                        <lightning-icon if:false={val} 
                                icon-name="utility:right" 
                                size="x-small">
                        </lightning-icon></div>
                      </th>
                     <template for:each={profiles} for:item='prof' >
                       <th class="" scope="col" key={prof}>
                        <div class="slds-truncate" title={prof}>
                            {prof}
                        </div>
                        </th>
                      </template>
                      </thead>
                      <tbody>
                        <template for:each={users} for:item='user' >
                          <tr key={user.name}>
                            <td key={user.name} >
                                {user.name}
                            </td>
                            <template for:each={user.chex} for:item='val' >
                              <td class="" key={val} 
                                style="text-align: center;">
                                <lightning-icon if:true={val} 
                                    icon-name="utility:check" 
                                    variant="success" 
                                    size="small"></lightning-icon>
                                <lightning-icon if:false={val} 
                                    icon-name="utility:close" 
                                    variant="error" 
                                    size="xx-small"></lightning-icon>
                               </td>
                            </template>
                           </tr>
                          </template>
                       </tbody>
                     </table>
                    </div>
</template>

pivotTable.js

import { LightningElement } from 'lwc';
import getUserProfiles from '@salesforce/apex/pivotTableController.
                                getUserWithProfiles';

export default class PivotTable extends LightningElement {

    result = [];
    profiles = [];
    users = [];
    isLoading = true;

    connectedCallback(){
        getUserProfiles()
        .then(result =>{
            this.profiles = result[0].profiles;
            result.forEach(element => {
                this.users.push({
                    'chex' : element.isUserForProf,
                    'name' : element.userName
                });
            });
            this.isLoading = false;
        })
        .catch(error =>{
            console.log("error");
            this.isLoading = false;
        });
    }
}

pivotTableController.cls

public without sharing class pivotTableController {
    @AuraEnabled(cacheable=true)
    public static List<userProfWrappergetUserWithProfiles(){
        List<Userusers = [SELECT IdProfileIdProfile.Name
                            firstNamelastName, Name 
                            FROM User];
        List<Profileprofs = [SELECT IdName
                                      (SELECT Id,firstNamelastName 
                                       FROM Users
                               FROM Profile 
                               ORDER BY Name];
        List<userProfWrapperwrapperResult = new List<userProfWrapper>();
        List<Stringprofiles = new List<String>();
        for(User user : users){
            userProfWrapper wrapper = new userProfWrapper();
            List<Booleanbools = new List<Boolean>();
            String userName = '';
            for(Profile profile : profs){
                if(!profile.users.isEmpty()){
                    if(!profiles.contains(profile.Name))
                        profiles.add(profile.Name);
                    if(user.profileId == profile.Id)    
                        bools.add(true);
                    else    
                        bools.add(false);
                    userName = user.name;
                }
            }
            wrapper.isUserForProf = bools;
            wrapper.profiles = profiles;
            wrapper.userName = userName;
            wrapperResult.add(wrapper);
        }
        return wrapperResult;
    }

    public class userProfWrapper{
        @AuraEnabled
        public List<Stringprofiles;
        @AuraEnabled
        public List<BooleanisUserForProf;
        @AuraEnabled
        public String userName;
    }
}