Showing posts with label LWC. Show all posts
Showing posts with label LWC. Show all posts

Building an efficient LWC form with combination of input and input-field using uiRecordApi


In this post we will see, how to create New& Edit form in single lightning web component, which is having both lightning-input and lightning-input-field tags.

In this approach i am using below things to create and edit the account record using LWC

  • Using uiRecordApi to create and update the record
  • Using combination of input and input-field for fast implementation
  • Update the field value with onchange event. we can use data-field on all fields for getting the field values
  • Differentiate the page type whether it is new or edit based on recordId
  • By creating the aura component will override the standard edit and new button

dynamicForm.html


<template>
<lightning-card title="Account Form">
<lightning-record-edit-form record-id={recordId} onload={handleLoad} object-api-name="Account"
onsuccess={handleSucess}>

<lightning-input-field data-field="Name" field-name="Name" onchange={handleFieldChange}>
</lightning-input-field>

<lightning-input-field data-field="Website" field-name="Website" onchange={handleFieldChange}>
</lightning-input-field>

<lightning-input-field data-field="Industry" field-name="Industry" onchange={handleFieldChange}>
</lightning-input-field>

<lightning-input label="AccountNumber" data-field="AccountNumber" value={accountRecord.AccountNumber}
onchange={handleFieldChange}>
</lightning-input>

<lightning-input label="NumberOfEmployees" data-field="NumberOfEmployees"
value={accountRecord.NumberOfEmployees} onchange={handleFieldChange}>
</lightning-input>

<lightning-input label="Site" data-field="Site" value={accountRecord.Site} onchange={handleFieldChange}>
</lightning-input>

<lightning-textarea label="Description" data-field="Description" value={accountRecord.Description}
onchange={handleFieldChange} ></lightning-textarea>

<lightning-button class="slds-m-top_small" variant="brand" name="save" label="Save" onclick={handleSubmit}>
</lightning-button>

</lightning-record-edit-form>
</lightning-card>

</template>


dynamicForm.js

import { LightningElement, api, track } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';
import { updateRecord } from 'lightning/uiRecordApi';
import { createRecord } from 'lightning/uiRecordApi';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';

export default class DynamicForm extends NavigationMixin(LightningElement) {
@track accountRecord = {};
@api recordId;

//handle load method
handleLoad(event) {
//To get the value of form fields on edit form
if (this.recordId !== undefined) {
let fields = Object.values(event.detail.records)[0].fields;
const recordId = Object.keys(event.detail.records)[0];
this.accountRecord = {
Id: recordId,
...Object.keys(fields)
.filter((field) => !!this.template.querySelector(`[data-field=${field}]`))
.reduce((total, field) => {
total[field] = fields[field].value;
return total;
}, {})
};
}
}

//To add the input field value to list
handleFieldChange(e) {
this.accountRecord[e.currentTarget.dataset.field] = e.target.value;
}

//save method
handleSubmit() {
//To create reccord
if (this.recordId === undefined) {
let recordInput = { apiName: ACCOUNT_OBJECT.objectApiName, fields: this.accountRecord }
createRecord(recordInput)
.then(result => {
this.accRecord = {};
this.recordId = result.id;

// Show success messsage
this.dispatchEvent(new ShowToastEvent({
title: 'Success!!',
message: 'Account Created Successfully!!',
variant: 'success'
}));
this.handleSucess();
})
.catch(error => {
this.error = JSON.stringify(error);
});
}

//To update the record
if (this.recordId !== undefined) {
updateRecord({ fields: this.accountRecord })
.then(() => {
this.dispatchEvent(
new ShowToastEvent({
title: 'Success',
message: 'Account updated',
variant: 'success'
})
);
this.handleSucess();
})
.catch((error) => {
this.dispatchEvent(
new ShowToastEvent({
title: 'Error creating record',
message: error.body.message,
variant: 'error'
})
);
});
}
}

//Handle sucess method
handleSucess() {
this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: {
recordId: this.recordId,
objectApiName: 'Account',
actionName: 'view'
}
});
}

}


dynamicForm.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>48.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__RecordPage</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__RecordPage">
<objects>
<object>Account</object>
</objects>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>


accountButtonOverride.cmp

<aura:component implements="lightning:actionOverride,force:appHostable,flexipage:availableForAllPageTypes,
force:hasRecordId" access="global" >
<c:dynamicForm recordId="{!v.recordId}"></c:dynamicForm>
</aura:component>   



GitHub : 


Demo :



Display Contact and their related Cases in different component using Pub-Sub in LWC


In this post we will see how to communicate between components that are not in same DOM tree.By using a singleton library that follows the publish-subscribe pattern we can communicate.

Let see how we can communicate between two components using Pub-Sub for the below scenario

Component 1 : 
  • Display the contacts on selected account on account view page 
  • Adding row action (case list) in each contact
Component 2 :
  • Display the cases on selected contact when user click the row action (case list) in component 1.

Implementation:

Step 1:

First we need to copy the pubsub.js file from salesforce library and create the name LWC named "pubsub".


/**
* A basic pub-sub mechanism for sibling component communication
*
* TODO - adopt standard flexipage sibling communication mechanism when it's available.
*/
const events = {};
const samePageRef = (pageRef1, pageRef2) => {
const obj1 = pageRef1.attributes;
const obj2 = pageRef2.attributes;
return Object.keys(obj1)
.concat(Object.keys(obj2))
.every(key => {
return obj1[key] === obj2[key];
});
};
/**
* Registers a callback for an event
* @param {string} eventName - Name of the event to listen for.
* @param {function} callback - Function to invoke when said event is fired.
* @param {object} thisArg - The value to be passed as the this parameter to the callback function is bound.
*/
const registerListener = (eventName, callback, thisArg) => {
// Checking that the listener has a pageRef property. We rely on that property for filtering
purpose in fireEvent()
if (!thisArg.pageRef) {
throw new Error(
'pubsub listeners need a "@wire(CurrentPageReference) pageRef" property'
);
}
if (!events[eventName]) {
events[eventName] = [];
}
const duplicate = events[eventName].find(listener => {
return listener.callback === callback && listener.thisArg === thisArg;
});
if (!duplicate) {
events[eventName].push({ callback, thisArg });
}
};
/**
* Unregisters a callback for an event
* @param {string} eventName - Name of the event to unregister from.
* @param {function} callback - Function to unregister.
* @param {object} thisArg - The value to be passed as the this parameter to the callback function is bound.
*/
const unregisterListener = (eventName, callback, thisArg) => {
if (events[eventName]) {
events[eventName] = events[eventName].filter(
listener => listener.callback !== callback || listener.thisArg !== thisArg
);
}
};
/**
* Unregisters all event listeners bound to an object.
* @param {object} thisArg - All the callbacks bound to this object will be removed.
*/
const unregisterAllListeners = thisArg => {
Object.keys(events).forEach(eventName => {
events[eventName] = events[eventName].filter(
listener => listener.thisArg !== thisArg
);
});
};
/**
* Fires an event to listeners.
* @param {object} pageRef - Reference of the page that represents the event scope.
* @param {string} eventName - Name of the event to fire.
* @param {*} payload - Payload of the event to fire.
*/
const fireEvent = (pageRef, eventName, payload) => {
if (events[eventName]) {
const listeners = events[eventName];
listeners.forEach(listener => {
if (samePageRef(pageRef, listener.thisArg.pageRef)) {
try {
listener.callback.call(listener.thisArg, payload);
} catch (error) {
// fail silently
}
}
});
}
};
export {
registerListener,
unregisterListener,
unregisterAllListeners,
fireEvent
};


Component 1: 

contactsForm.html

<template>
<lightning-card class="slds-card slds-card_boundary related_list_card_border_top"
title="Contact view (Publisher Component)">

<!--- Contact Table-->
<div style="width: auto;">
<template if:true={emptyList}>
<lightning-datatable data={data} columns={columns} key-field="id" show-row-number-column
hide-checkbox-column="true" onrowaction={handleRowActions}></lightning-datatable>
</template>
<template if:false={emptyList}>
There is no contacts
</template>
</div> <br />
</lightning-card>
</template>

contactsForm.js

import { LightningElement, track, wire, api } from 'lwc';
//Import Apex methods
import getContatcs from '@salesforce/apex/ContactUtilities.getContacts';
import { CurrentPageReference } from 'lightning/navigation';
import { fireEvent } from 'c/pubsub';

// row actions
const actions = [
{ label: 'caseList', name: 'caseList' }
];

// datatable columns with row actions
const columns = [
{ label: 'First Name', fieldName: 'FirstName', type: "string", sortable: true },
{ label: 'Last Name', fieldName: 'LastName', type: "string", sortable: true },
{ label: 'Email', fieldName: 'Email', type: "email", sortable: true },
{
type: 'action',
typeAttributes: {
rowActions: actions,
menuAlignment: 'right'
}
}
];

export default class ContactPublisher extends LightningElement {

@wire(CurrentPageReference) pageRef;
// reactive variable
@api recordId;
@track data;
@track columns = columns;
@track record = [];
@track emptyList = false;

// retrieving the data using wire service
@wire(getContatcs, { sourceAccount: '$recordId' })
relations(result) {
this.refreshTable = result;
if (result.data) {
this.data = result.data;
this.emptyList = true;
}
}

//To handle the row actions
handleRowActions(event) {
let actionName = event.detail.action.name;
let row = event.detail.row;
// eslint-disable-next-line default-case
switch (actionName) {
case 'caseList':
this.caseListReturn(row);
break;
}
}

/* Method to fire the event */
caseListReturn(currentRow) {
fireEvent(this.pageRef, "currentRowId", currentRow.Id);
}

}


caseSubscriber.html

<template>
<lightning-card class="slds-card slds-card_boundary related_list_card_border_top"
title="Case View (Subscriber component )">
<!--- Case Table-->
<div style="width: auto;">
<template if:true={emptyList}>
<lightning-datatable data={data} columns={columns} key-field="id" show-row-number-column
hide-checkbox-column="true"></lightning-datatable>
</template>
<template if:false={emptyList}>
There is no case
</template>
</div> <br />
</lightning-card>
</template>


caseSubscriber.js

import { LightningElement, track, wire } from 'lwc';
//Import Apex methods
import getCases from '@salesforce/apex/ContactUtilities.getCases';
import { registerListener, unregisterAllListeners } from 'c/pubsub';
import { CurrentPageReference } from 'lightning/navigation';

// datatable columns with row actions
const columns = [
{ label: 'Origin', fieldName: 'Origin', type: "text", sortable: true },
{ label: 'Subject', fieldName: 'Subject', type: "text", sortable: true },
{ label: 'Description', fieldName: 'Description', type: "text", sortable: true }
];

export default class CaseSubscriber extends LightningElement {

@wire(CurrentPageReference) pageRef;
// reactive variable

@track data;
@track columns = columns;
@track record = [];
@track emptyList = false;
@track contactId = '';

connectedCallback() {
registerListener("currentRowId", this.getContact, this);
}

disconnectedCallback() {
unregisterAllListeners(this);
}

getContact(currentRecordId) {
this.contactId = currentRecordId;

}

@wire(getCases, { sourceContact: '$contactId' })
relations(result) {
this.refreshTable = result;
if (result.data) {
this.data = result.data;
this.emptyList = true;
}
}

}


Result :






How to play video files in LWC using content asset file


In this post we will see how to play a video using content asset file in LWC.

I have added one video to content asset file.In lwc by importing the contentAssetUrl , we can access the content asset file.

 










VideoPlayer.html

<template>
<video width="60%" height="50%" controls="controls">
<source src={waterfallVideoUrl} type="video/mp4" />
</video>
</template>


VideoPlayer.js

import { LightningElement } from 'lwc';
import waterFallContentAsset from '@salesforce/contentAssetUrl/waterfall';

export default class VideoPlayer extends LightningElement {

waterfallVideoUrl = waterFallContentAsset;

}

VideoPlayer.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>48.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
<target>lightning__Tab</target>
</targets>
</LightningComponentBundle>


GitHub :



Result :


How to use refreshApex to refresh the list in LWC


In this post , we will see the how to refresh the list in lwc using refreshApex concept.

List of topics covered in this post as below.
  • Display the contacts related to selected Account with sortable column & row actions
  • Adding new button on top of the account to create new contact
  • if user creating the new contact using new button ,the contact list will update automatically.
  • if user deleting the any contact using row actions,the contact list will update automatically.
In this logic am using using apex class to create ,delete & return the existing contacts.

To refresh the list , have added the import statement refreshApex in lwc js . if user creating/deleting the contact the cache data becomes stale. so after the creation/deletion method just call the refreshApex to query the server for updated data and refresh the cache.

Apex class :

public with sharing class ContactUtilities{

    //Method to return the list of contatcs based on selected account
    @AuraEnabled(Cacheable = true)
    public static List<Contact> getContacts(Id sourceAccount){
        return [SELECT Id, Name, Account.Name, LastName, FirstName, Email, Phone, MobilePhone
         From Contact where AccountId = :sourceAccount];
    }

    //Method to delete the selected contacts
    @AuraEnabled
    public static void deleteContacts(list<Id> deleteContactIds){
        list<Contact> listContactToDelete = new list<Contact>();
        for (Id idContact : deleteContactIds){
            listContactToDelete.add(new Contact(Id = idContact));
        }
        if (!listContactToDelete.isEmpty()){
            delete listContactToDelete;
        }
    }

    //Method to create contact
    @AuraEnabled
    public static void addContact(string firstName, string lastName, String email,id acntId){
        Contact con = new Contact();
        con.FirstName = firstName;
        con.LastName = lastName;
        con.Email = email;
con.AccountId = acntId;
        try{
            insert con;
        } catch (Exception ex){
            throw new AuraHandledException(ex.getMessage());
        }
    }
}



Lightning Web Component

conatctListViewWithRefreshApex.html

<template>
<lightning-card class="slds-card slds-card_boundary related_list_card_border_top" title="ContactListView">

<!-- New button -->
<div slot="actions">
<lightning-button variant="neutral" label="New" type="text" onclick={createNewRecord}
class="slds-m-right_small">
</lightning-button>
</div>

<!--- Contact Table-->
<div style="width: auto;">
<template if:true={emptyList}>
<lightning-datatable data={data} columns={columns} key-field="id" show-row-number-column
hide-checkbox-column="true" onrowaction={handleRowActions} sorted-by={sortBy}
sorted-direction={sortDirection} onsort={handleSortdata}></lightning-datatable>
</template>
<template if:false={emptyList}>
There is no contacts
</template>
</div> <br />

<!-- Spinner -->
<div if:true={showLoadingSpinner}>
<lightning-spinner alternative-text="Loading" size="large"></lightning-spinner>
</div>

<!-- Detail view modal -->
<template if:true={ShowModal}>
<section role="dialog" tabindex="-1" aria-labelledby="modal-heading-01" aria-modal="true"
aria-describedby="modal-content-id-1" class="slds-modal slds-fade-in-open">
<div class="slds-modal__container">

<!-- modal header -->
<header class="slds-modal__header">
<button class="slds-button slds-button_icon slds-modal__close slds-button_icon-inverse"
title="Close" onclick={closeModal}>
<lightning-icon icon-name="utility:close" alternative-text="close" variant="inverse"
size="small"></lightning-icon>
</button>
<h2 id="modal-heading-02" class="slds-text-heading_medium slds-hyphenate modalHeading"
if:true={editRecord}> Update Contact</h2>
<h2 id="modal-heading-0" class="slds-text-heading_medium slds-hyphenate modalHeading"
if:true={newRecord}>New Contact</h2>
</header>

<!-- showing record edit form -->
<div if:true={isEditForm} class="slds-theme_default">
<lightning-record-edit-form layout-type="Full" object-api-name="Contact" record-id={currentRecordId}>
<lightning-messages></lightning-messages>
<div if:true={error}>
{error}
</div>
<div class="slds-col slds-size_1-of-1">
<lightning-input-field field-name="FirstName" onchange={firstNameChange}>
</lightning-input-field>
</div>
<div class="slds-col slds-size_1-of-1">
<lightning-input-field field-name="LastName" onchange={lastNameChange}>
</lightning-input-field>
</div>
<div class="slds-col slds-size_1-of-1">
<lightning-input-field field-name="Email" onchange={emailChange}>
</lightning-input-field>
</div>

<br />
<ul class="slds-button-group-row slds-grid slds-grid_align-center">
<li class="slds-button-group-item">
<lightning-button class="slds-m-top_small" variant="brand" name="update"
label="save" onclick={handleSubmit}></lightning-button>
</li>
<li class="slds-button-group-item">
<lightning-button class="slds-m-top_small" variant="brand" type="text"
label="Cancel" onclick={closeModal}></lightning-button>
</li>
</ul>
</lightning-record-edit-form><br/>
<div></div>
</div>
</div>
</section>

<div class="slds-backdrop slds-backdrop_open"></div>
</template>
</lightning-card>
</template>

conatctListViewWithRefreshApex.js

import { LightningElement, track, wire, api } from 'lwc';
//Import Apex methods
import getContatcs from '@salesforce/apex/ContactUtilities.getContacts';
import deletecontact from '@salesforce/apex/ContactUtilities.deleteContacts';
import createContact from '@salesforce/apex/ContactUtilities.addContact';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { refreshApex } from '@salesforce/apex';
import { NavigationMixin } from 'lightning/navigation';

// row actions
const actions = [
{ label: 'Delete', name: 'delete' }
];

// datatable columns with row actions
const columns = [
{ label: 'First Name', fieldName: 'FirstName', type: "string", sortable: true },
{ label: 'Last Name', fieldName: 'LastName', type: "string", sortable: true },
{ label: 'Email', fieldName: 'Email', type: "email", sortable: true },
{
type: 'action',
typeAttributes: {
rowActions: actions,
menuAlignment: 'right'
}
}
];

export default class ConatctListViewWithRefreshApex extends NavigationMixin(LightningElement) {

// reactive variable
@api recordId;
@track data;
@track columns = columns;
@track record = [];
@track ShowModal = false;
@track currentRecordId;
@track isEditForm = false;
@track showLoadingSpinner = false;
@track newRecord = false;
@track editRecord = false;
@track viewRecord = false;
@track sortBy;
@track sortDirection;
@track emptyList = false;
@track error;
@track firstName;
@track lastName;
@track email;


// non-reactive variables
refreshTable;

// retrieving the data using wire service
@wire(getContatcs, { sourceAccount: '$recordId' })
relations(result) {
this.refreshTable = result;
if (result.data) {
this.data = result.data;
this.emptyList = true;
}
}

handleSortdata(event) {
// field name
this.sortBy = event.detail.fieldName;
// sort direction
this.sortDirection = event.detail.sortDirection;
// calling sortdata function to sort the data based on direction and selected field
this.sortData(event.detail.fieldName, event.detail.sortDirection);
}

sortData(fieldname, direction) {
// serialize the data before calling sort function
let parseData = JSON.parse(JSON.stringify(this.data));
// Return the value stored in the field
let keyValue = (a) => {
return a[fieldname];
};
// cheking reverse direction
let isReverse = direction === 'asc' ? 1 : -1;
// sorting data
parseData.sort((x, y) => {
x = keyValue(x) ? keyValue(x) : ''; // handling null values
y = keyValue(y) ? keyValue(y) : '';

// sorting values based on direction
return isReverse * ((x > y) - (y > x));
});

// set the sorted data to data table data
this.data = parseData;

}

//To handle the row actions
handleRowActions(event) {
let actionName = event.detail.action.name;
let row = event.detail.row;
// eslint-disable-next-line default-case
switch (actionName) {
case 'delete':
this.deleteRelations(row);
break;
}
}

//To create new record
createNewRecord() {
// open modal box
this.currentRecordId = '';
this.ShowModal = true;
this.isEditForm = true;
this.newRecord = true;
}

// handleing record edit form submit
handleSubmit() {
createContact({ firstName: this.firstName, lastName: this.lastName, email: this.email, acntId: this.recordId })
.then(result => {
this.handleSuccess();
})
.catch(error => {
this.error = error.body.message;
});
}

// refreshing the datatable after record edit form success
handleSuccess() {
// closing modal
this.ShowModal = false;
// showing success message
if (this.newRecord === true) {
this.dispatchEvent(new ShowToastEvent({
message: 'Contact Created sucessfully',
variant: 'success'
}));

}
if (this.newRecord === false) {
this.dispatchEvent(new ShowToastEvent({
message: 'Contact updated sucessfully',
variant: 'success'
}));
}
return refreshApex(this.refreshTable);
}

//To delete the selected relations
deleteRelations(currentRow) {
let currentRecord = [];
currentRecord.push(currentRow.Id);
this.showLoadingSpinner = true;

// calling apex class method to delete the selected contact
deletecontact({ deleteContactIds: currentRecord })
.then(result => {
this.showLoadingSpinner = false;

// showing success message
this.dispatchEvent(new ShowToastEvent({
message: 'Contatact Deleted sucessfully',
variant: 'success'
}));

// refreshing table data using refresh apex
return refreshApex(this.refreshTable);

})
.catch(error => {
this.dispatchEvent(new ShowToastEvent({
message: error.message,
variant: 'error'
}));
});
}


//Event to track the First Name field
firstNameChange(event) {
this.firstName = event.target.value;
this.error = '';
}

//Event to track the last Name field
lastNameChange(event) {
this.lastName = event.target.value;
this.error = '';
}


//Event to track the Email field
emailChange(event) {
this.email = event.target.value;
this.error = '';
}

// closing modal box
closeModal() {
this.ShowModal = false;
this.newRecord = false;
this.editRecord = false;
this.viewRecord = false;
this.error = '';
}

}

conatctListViewWithRefreshApex.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>48.0</apiVersion>
<isExposed>True</isExposed>
<targets>
<target>lightning__RecordPage</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__RecordPage">
<objects>
<object>Account</object>
</objects>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>

GitHub:

Demo :