Refresh Datatable in for:each loop: Lightning Web Components - salesforce

I am having trouble refreshing a Datatable in my Lightning Web Component after updating a record. I am calling an onclick action on a button within the row, and imperatively calling an Apex method to update that record. I then call the refreshApex() to update the data being fed into the Datatable.
However, after the refreshApex(), the tables within the for:each are not being refreshed with new data.
The records are properly modified and reflect the changes properly when refreshing the entire page.
Note: The Task object is not supported in LWC, and I cannot use the updateRecord() method to update these records.
HTML:
<template>
<template if:true="{taskCompWrapperList}">
<!--<lightning-layout multiple-rows="false" pull-to-boundary="small">-->
<template for:each="{taskCompWrapperList}" for:item="taskTemplate">
<lightning-layout-item
key="{taskTemplate.taskSectionOrder}"
size="3"
class="slds-p-around_x-small"
>
<!-- Start bear tile -->
<lightning-card title="{taskTemplate.taskSectionTitle}">
<div class="slds-m-around_medium">
<template if:true="{taskTemplate.taskList}">
<lightning-datatable
key-field="Id"
data="{taskTemplate.taskList}"
onrowaction="{handleRowAction}"
columns="{columns}"
onsave="{handleSave}"
draft-values="{draftValues}"
>
</lightning-datatable>
</template>
<template if:true="{contact.error}">
<!-- handle Apex error -->
</template>
</div>
</lightning-card>
<!-- End bear tile -->
</lightning-layout-item>
</template>
<!--</lightning-layout>-->
</template>
</template>
Javascript:
import { LightningElement, api, wire ,track} from 'lwc';
import getTaskCompWrappers from '#salesforce/apex/ENT_Task_Utility.getTaskComponentWrapper';
import updateTask from '#salesforce/apex/ENT_Task_Utility.updateTask';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { updateRecord } from 'lightning/uiRecordApi';
import { refreshApex } from '#salesforce/apex';
const COLS = [
{
type: 'button',
label: 'Complete',
typeAttributes:
{
//iconName: 'action:preview',
label: 'Complete',
name: 'Complete',
title: 'Complete',
value: 'Complete',
variant: 'brand',
alternativeText: 'Complete'
}
},
{
type: 'button-icon',
label: 'Start',
typeAttributes:
{
iconName: 'action:approval',
//label: 'Complete',
name: 'Start',
title: 'Start',
value: 'Start',
variant: 'success',
alternativeText: 'Start',
}
},
{
type: "button",
typeAttributes:
{
label: 'View',
name: 'View',
title: 'View',
disabled: false,
value: 'view',
iconPosition: 'left'
}
},
{
type: "button",
typeAttributes:
{
label: 'Edit',
name: 'Edit',
title: 'Edit',
disabled: false,
value: 'edit',
iconPosition: 'left'
}
},
//{ label: 'Complete', fieldName: 'Task_Complete__c', editable: true },
{ label: 'Status', fieldName: 'Status', type: 'picklist', editable: true },
{ label: 'Completed', fieldName: 'Completed', type: 'boolean', editable: true },
{ label: 'Owner', fieldName: 'OwnerId', editable: true },
{ label: 'Subject', fieldName: 'Subject' },
{ label: 'Due Date', fieldName: 'ActivityDate', type: 'date' }
];
export default class ENT_Task_Utility_LWC extends LightningElement {
#api objApiName;
#api recordId;
#track testMessage = 'Test Failed :c';
#track error;
#track columns = COLS;
#track draftValues = [];
taskCompWrapperList;
#track error;
//#wire(getTasks, {recordId: '$recordId'}) taskList;`
#wire(getTaskCompWrappers, {recordId: '$recordId', objApiName: '$objApiName'})
taskCompWrapperListWire({ error, data }) {
if (data) {
this.taskCompWrapperList = data;
this.error = undefined;
} else if (error) {
this.error = error;
this.taskCompWrapperList = undefined;
}
}
updateTaskValues (taskId, taskStatus) {
// eslint-disable-next-line no-console
console.log('updateTaskValues hit');
for(var counter = 0; counter < this.taskCompWrapperList.length; counter++) {
// eslint-disable-next-line no-console
console.log('taskWrapper: ' + this.taskCompWrapperList[counter]);
for(var counter2 = 0; counter2 < this.taskCompWrapperList[counter].taskList.length; counter2++) {
// eslint-disable-next-line no-console
console.log('task: ' + this.taskCompWrapperList[counter].taskList[counter2]);
if(this.taskCompWrapperList[counter].taskList[counter2].Id == taskId)
{
this.dispatchEvent(
new ShowToastEvent({
title: 'Task Id Found!',
message: this.taskCompWrapperList[counter].taskList[counter2].Id,
variant: 'success'
})
);
this.taskCompWrapperList[counter].taskList[counter2].Status = taskStatus;
}
}
}
}
handleRowAction(event) {
//TODO
}
}
Apex methods:
#AuraEnabled(cacheable=true)
global static List<Task> getTasks(String recordId)
{
return [SELECT Id, Subject, OwnerId FROM Task WHERE WhatId = :recordId];
}
#AuraEnabled(cacheable=true)
global static List<ENT_Task_Comp_Wrapper> getTaskComponentWrapper(String recordId, String objApiName)
{
List<Task_Template__c> taskTemplateList = [SELECT Id, Task_Component_Section_Order__c, Task_Component_Section_Title__c, (SELECT Id FROM Task_Template_Items__r)
FROM Task_Template__c
WHERE Active__c = true AND sObject__c = :objApiName ORDER BY Task_Component_Section_Order__c ASC];
List<Task> taskList = [SELECT Id, Task_Template_Item__c, OwnerId, Owner.Name, Subject, Description, Status, ActivityDate, Task_Complete__c FROM TasK WHERE WhatId = :recordId];
List<ENT_Task_Comp_Wrapper> taskCompWrapperList = new List<ENT_Task_Comp_Wrapper>();
for(Task_Template__c taskTemplate : taskTemplateList)
{
ENT_Task_Comp_Wrapper taskCompWrapper = new ENT_Task_Comp_Wrapper();
taskCompWrapper.taskSectionTitle = taskTemplate.Task_Component_Section_Title__c;
taskCompWrapper.taskSectionOrder = (Integer)taskTemplate.Task_Component_Section_Order__c;
taskCompWrapper.taskList = new List<Task>();
for(Task currentTask : taskList)
{
for(Task_Template_Item__c taskTemplateItem : taskTemplate.Task_Template_Items__r)
{
if(taskTemplateItem.Id == currentTask.Task_Template_Item__c)
{
taskCompWrapper.taskList.add(currentTask);
}
}
}
taskCompWrapperList.add(taskCompWrapper);
}
System.debug(taskCompWrapperList);
return taskCompWrapperList;
}
#AuraEnabled
global static void updateTask(String taskId, String newStatus)
{
System.debug(taskId);
Task taskToUpdate = new Task(Id = taskId, Status = newStatus);
update taskToUpdate;
//update taskToUpdate;
}
#AuraEnabled
global static void updateTask(String taskId, String newStatus)
{
System.debug(taskId);
Task taskToUpdate = new Task(Id = taskId, Status = newStatus);
update taskToUpdate;
//update taskToUpdate;
}

In your JS code you have imported refreshApex
by using this line import { refreshApex } from '#salesforce/apex';
but you didn't assigned to any wire method. Hence data is not refreshed
Please refer this documentation.
To refresh a wired method, pass the argument the wired method receives (which is the wired value) to refreshApex(). In this sample code, the wired method is taskCompWrapperListWire. Hold on to the value provisioned by the wire service and pass it to refreshApex().
#wire(getTaskCompWrappers, {recordId: '$recordId', objApiName: '$objApiName'})
taskCompWrapperListWire({ error, data }) {
if (data) {
this.taskCompWrapperList = data;
this.error = undefined;
} else if (error) {
this.error = error;
this.taskCompWrapperList = undefined;
}
}
And then use refreshApex() as below:
refreshApex(this.taskCompWrapperListWire);
Update you code as below
updateTaskValues({
taskId: this.taskId,
taskStatus: this. taskStatus
})
.then(() => {
// your code logic
refreshApex(this.taskCompWrapperListWire);
})
.catch((error) => {
this.message = 'Error received: code' + error.errorCode + ', ' +
'message ' + error.body.message;
});

you probably need to wait for next release to have a correct way to handle such situation.
You are getting record through uiRecordApi and updating through Apex if I'm correct.
Then you would need to use getRecordNotifyChange() available in Winter 21 release.

Apart from the answer provided by Sudarshan, you should also define taskCompWrapperList as a reactive property to make it rerender when the property is updated.
#track taskCompWrapperList = [];

Related

In a Salesforce LWC app how can I add checkbox values to an object's field and show it in a data-table?

I have made my research, but couldn't find the answer.
I have a Lightning App in Salesforce, I used LWC Js and Apex.
In one part of the app the user can add a 'desk item' (by typing its name) and select from a checkbox 1-2 items to add them to the 'desk'.
I used Apex to transfer the value of the 'desk item' to an Object and I can show it in a list (in the app).
How can I add the checkbox value(s) to the submitDesk(){...} so it sends its value(s) along with the 'desk item' value?
I don' know where/how exactly to add and to get it back?
The JS Code
import { LightningElement, track } from 'lwc';
import createDesk from '#salesforce/apex/FlexOfficeController.createDesk';
import getDesks from '#salesforce/apex/FlexOfficeController.getDesks';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
export default class DeskList extends LightningElement {
// Desk
#track data_desks = [];
//table to show the desks's Id + Name, and the checkbox
columns = [
{ label: 'Id', fieldName: 'Id', type: 'text' },
{ label: 'Name', fieldName: 'Name', type: 'text' },
{ label: 'Accessories', fieldName: **the checkbox value**, type: 'text' }
];
// value to the picklist
connectedCallback(){
this.retreiveDesk();
}
retreiveDesk(){
getDesks({})
.then(d => {
this.data_desks = JSON.parse(d);
})
}
desk = {};
changeValue(event){
this.desk[event.target.name] = event.target.value
}
submitDesk(){
console.log(this.desk, this.value + 'Hi there');
createDesk({desk:JSON.stringify(this.desk)})
.then(data=> {
console.log(data + 'hello');
this.retreiveDesk();
// toaster
const evt = new ShowToastEvent({
title: "New desk",
message: `succefully created. Check out your reservation.`,
variant: "success"
})
this.dispatchEvent(evt);
})
}
// Checkbox
value = [];
get options() {
return [
{ label: 'Mouse', value: 'mouse' },
{ label: 'Screen', value: 'screen' },
];
}
// put the checkbox values into a string ('join')
get checkboxValues() {
console.log(this.value);
return this.value.join(',');
}
handleCheckboxChange(event) {
this.value = event.detail.value;
}
}
Apex Controller
public class FlexOfficeController {
#AuraEnabled
public static string createDesk(String desk){
try {
Desk__c d = (Desk__c)JSON.deserialize(desk, Desk__c.class);
insert d;
return d.id;
} catch (Exception e) {
throw new AuraHandledException(e.getMessage());
}
}
#AuraEnabled
public static string getDesks(){
try {
List<Desk__c> desks = new List<Desk__c> ();
desks = [SELECT Id, Name FROM Desk__c];
return JSON.serialize(desks);
} catch (Exception e) {
throw new AuraHandledException(e.getMessage());
}
}
}
HTML
<template>
<lightning-card>
<div class="slds-m-around_medium slds-theme_alert-texture">
<lightning-input name="Name" label="Name your desk" onchange={changeValue}></lightning-input>
<lightning-checkbox-group name="Accessories" label="Checkbox Group" options={options} value={value}
onchange={handleCheckboxChange}></lightning-checkbox-group>
<p>{checkboxValues}</p>
<lightning-button onclick={submitDesk} label="Submit"></lightning-button>
<lightning-datatable key-field="id" data={data_desks} columns={columns} hide-checkbox-column></lightning-datatable>
</div>
</lightning-card>
</template>

Categories not listed on Interaction Studio UI

I'm implementing on a website a tool from Sales Force that is called Interaction Studio.
We need to develop into a sitemap file, where those interaction written into the file will catch the information that we need.
Here we have an example of a sitemap
Basically, the function that collects the categories is a custom function that collects the information from the breadcrumb. The function is:
const getCategoriesIdAsArray = () => {
return Evergage.resolvers.fromSelectorAttributeMultiple(
"a.c-breadcrumb__link",
"href",
(link) => {
let catId = "";
if (link.length) {
link.shift();
link.map((url, index) => {
if (index < link.length - 1) {
catId += url.split("/").reverse()[0].split("-")[0] + "|";
} else {
catId += url.split("/").reverse()[0].split("-")[0];
}
});
return [catId];
} else {
return;
}
}
);
};
To collect the information, we need to write some code in the pageTypes array. This following code is what I have in my sitemap:
pageTypes: [
{
name: "product_detail_tecnico",
action: "product_detail_tecnico",
//isMatch: () => document.body.classList.contains("is-tech-product"),
isMatch: () => {
return Evergage.DisplayUtils.pageElementLoaded(
"body#productcore.is-tech-product",
"html"
).then(() => true);
},
locale: () => {
return buildLocale();
},
catalog: {
Product: {
_id: productId,
name: Evergage.resolvers.fromJsonLd("name"),
url: Evergage.resolvers.fromJsonLd("url"),
imageUrl: Evergage.resolvers.fromJsonLd("image"),
description: Evergage.cashDom("meta[name='description']")
.first()
.attr("content")
.substring(0, 250),
decoTecnico: () => {
return bodyClasses.includes("is-deco-product");
},
inventoryCount: 1,
idc: productId && productId.split("_")[1],
idp: getProductIdFromUrl(),
price: Evergage.resolvers.fromJsonLd("offers.0.price"),
precioAnterior: Evergage.util.getFloatValue(
Evergage.cashDom(".c-product-price__before .js-old-price")
.first()
.text()
.split(" ")[0]
.replace(",", ".")
),
video: Evergage.cashDom("iframe#player").attr("src"),
categories: () => {
return getCategoriesIdAsArray();
},
},
},
]
As we can see, the last attribute, categories returns the value from the custom function that is an array with one string due to the data that I must pass in.
The data that I get on the visual editor:
In the sitemap into the pageTypes I also have this code:
{
name: "Category",
action: "Viewed Category",
isMatch: () => {
return Evergage.DisplayUtils.pageElementLoaded(
"body.categorycore",
"html"
).then(() => true);
},
catalog: {
Category: {
_id: getCategoriesId(),
//parentId: categoryParentId,
name: categoryName,
department: () => isDepartment(),
url: Evergage.resolvers.fromHref(),
description: Evergage.cashDom("meta[name='description']")
.first()
.attr("content"),
},
},
listeners: [
Evergage.listener("click", "section.c-distributive-filters", () => {
Evergage.sendEvent({
action: "Filter Results",
});
}),
],
},
This code is required because to assign a category to a product, we must have that category recorded in the UI.
As we can see in the next picture, the categories are recorded.
The event stream is recording the following:
This is the code from item:
{
description:
"Tira LED 12V DC 30LED/m 5m IP20 Ancho 10mm es una opción muy extendida si se desea disponer de una luz decorativa que consuma muy poco. Su diseño flexi...",
idc: "122262",
inventoryCount: 1,
type: "Product",
url: "https://dev61.efectoled.com/es/comprar-tiras-led-monocolor/62294-tira-led-12v-dc-smd5050-30ledm-5m-ip20.html",
idp: "62294",
price: 7.95,
imageUrl:
"https://4438d3e301b6821c9a36cfd759bc58f8/442295-thickbox_default/tira-led-12v-dc-smd5050-30ledm-5m-ip20.jpg",
name: "Tira LED 12V DC 30LED/m 5m IP20 Ancho 10mm",
decoTecnico: false,
attributes: {
idp: { value: "62294" },
decoTecnico: { value: false },
idc: { value: "122262" },
},
id: "62294_122262",
categories: [{ _id: "10|63|24", type: "c" }],
}
But when I go into the details of a product, the categories are not.
I have read all the documentation that Interaction Studio provides, but I can not understand what happens. Because in the visual editor the data is recorded and in the event stream also is recorded.
Does anybody know why this is happening? Thanks in advance

when i push select2OptionData object in an array, select2 don't display any value

select2 in my angular project. I want to display a select of an array of certificate requested from an api. For each element if I create and push a select2OptionData Object in an array, but when i want to display it in the Html page it is selected but without any option. When i create an array of object like the gitHub demo everything's right.
Can you help me please.
Here is the ts
export class DashboardCvComponent implements OnInit, OnDestroy {
certificateForm: FormGroup;
certificates: Array<Certificate>;
certificatesTypes: Array<Select2OptionData>;
exampleData: Array<Select2OptionData>;
constructor(private formBuilder: FormBuilder, private certificatesService: CertificatesService,
private trainingsService: TrainingsService, private authService: AuthenticationService,
private datepipe: DateFormatPipe, private CvService: CVService, private fileUploaService: FileUploadService) {
this.certificatesTypes = [];
this.exampleData = [];
}
ngOnInit() {
// get certificate types
this.certificatesService.getAllCertificatesType('50').subscribe(
(response) => {
// console.log('-> CERTIFICATES TYPES LOADING successful : ', response);
/* this.certificatesTypes = response.data; */
response.data.forEach(element => {
const certif = {id: element.id.toString(), text: element.name};
this.certificatesTypes.push(certif);
});
this.exampleData = [{
id: 'basic1',
text: 'Basic 1'
},
{
id: 'basic2',
disabled: true,
text: 'Basic 2'
},
{
id: 'basic3',
text: 'Basic 3'
},
{
id: 'basic4',
text: 'Basic 4'
}]
console.log('les certif', this.exampleData, this.certificatesTypes);
},
(error) => {
console.log('-> CERTIFICATES TYPES LOADING failed : ', error);
},
() => {
}
);
and the html
<div class="dashboard--degrees">
<app-certificate *ngFor="let certificate of certificates" [certificate]=certificate [url]=url></app-certificate>
<button class="button button--bluegreen" type="button" data-modal="modal-certificate">
<svg class="icon icon-plus-o">
<use xlink:href="#icon-plus-o"></use>
</svg> <span i18n="##cvcomponent-addcertificate">J'ajoute un diplôme </span>
</button>
<select2 [data]="certificatesTypes"></select2>
<select2 [data]="exampleData"></select2>
</div>
Here the exampleData select display well but not the certificatesTypes
Don't use this.certificate.push inside for loop. it will not work. instead of you can use something like :
let arrCertificates = [];
response.data.forEach(element => {
const certif = {id: element.id.toString(), text: element.name};
arrCertificates.push(certif);
});
this.certificatesTypes = arrCertificates;

indexOf comparing two lists, not matching exact values

Alright, so i have a list of checkmark forms, and upon clicking them, i submit an event and put all checked boxes values into a list, i then compare that list with another list to changes the matching checked values from true to false (using index of).
when i click canada, my console prints the expected output (Canada is true, United States and Mexico are false),
but then i click Mexico, so now Canada and Mexico have been selected, and all my countries turn to false.
i'm wondering if this is a problem with the way i'm implenting index of, or is it the order of calling functions that is causing this? a checked box should return true.
component:
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl } from '#angular/forms';
#Component({
selector: 'app-geo-drop',
templateUrl: './geo-drop.component.html',
styleUrls: ['./geo-drop.component.css']
})
export class GeoDropComponent implements OnInit {
selectedItems: any [] = [];
places = [
{ code: 'US', allow: 'true', name: 'United States', continent: 'North America' },
{ code: 'CA', allow: 'true', name: 'Canada', continent: 'North America' },
{ code: 'MX', allow: 'false', name: 'Mexico', continent: 'North America' }
];
countriesForm: FormGroup;
constructor() { }
ngOnInit() {
// add checkbox for each country
this.countriesForm = new FormGroup({});
for (let i = 0; i < this.places.length; i++) {
this.countriesForm.addControl(
this.places[i].name, new FormControl(false)
)
}
}
allow(selectedPlaces: Array<any>, allPlaces: Array<any>) {
allPlaces.filter(function (place) {
if (place.name.indexOf(selectedPlaces) > -1) {
place.allow = true;
} else {
place.allow = false;
};
});
this.places.forEach(item => {
console.log(item.name, item.allow);
});
}
search(place, event) {
let index = this.selectedItems.indexOf(place.name);
if (event.target.checked) {
if (index === -1) {
this.selectedItems.push(place.name);
console.log(this.selectedItems);
}
} else {
if (index !== -1) {
this.selectedItems.splice(index, 1);
console.log(this.selectedItems);
}
}
this.allow(this.selectedItems, this.places);
}
}
html
<div class="geo-list">
<div class="content-box container">
<form [formGroup]="countriesForm">
<div class="place" *ngFor="let place of places">
<input
type="checkbox"
formControlName="{{place.name}}"
(change)="search(place, $event)"
>
{{ place.name }} | {{ place.code }} | {{ place.continent }}
</div>
</form>
</div>
</div>
The answer is in your own debug info; you're asking the index of "Canada, Mexico"
Since none of your countries are called "Canada, Mexico", they're false.
You need to loop through all selected boxes in your html to fix it.
I found a simple solution. though i don't believe it's the fastest, if a faster method is known, please post it. this method changes the value directly on every (change), and then binds the checkbox to the current value of allowed
comp.
export class GeoDropComponent implements OnInit {
places = [
{ code: 'US', allow: true, name: 'United States', continent: 'North America' },
{ code: 'CA', allow: true, name: 'Canada', continent: 'North America' },
{ code: 'MX', allow: false, name: 'Mexico', continent: 'North America' }
];
countriesForm: FormGroup;
constructor() { }
// use ng on destroy to send final copy?
ngOnInit() {
// add checkbox for each country
this.countriesForm = new FormGroup({});
for (let i = 0; i < this.places.length; i++) {
this.countriesForm.addControl(
this.places[i].name, new FormControl(false)
)
}
}
search(place, event) {
for (let i = 0; i < this.places.length; i++) {
if (this.places[i].name === place.name) {
this.places[i].allow === true ? this.places[i].allow = false : this.places[i].allow = true;
}
}
console.log(place.allow);
console.log(this.places);
}
}
html
<input
type="checkbox"
formControlName="{{place.name}}"
value="{{place.allow}}"
(change)="search(place, $event)"
[checked]="place.allow"
>

Cannot find method on button click event in ag-grid with Angular2

I want to call editAsset() method on button's click event in ag-grid.
ListComponent :
ngOnInit() {
this.route.data.forEach((data: { assets: Asset[]}) => {
this.gridOptions.columnDefs = this.createColumnDefs(data.assets[0]);
this.gridOptions.rowData = data.assets
});
}
private createColumnDefs(asset) {
let keyNames = Object.keys(asset);
let headers = []
keyNames.filter(key => key != '__v' && key != '_id').map(key => {
headers.push({
headerName: key,
field: key,
width: 100
})
});
headers.push({
headerName: 'update',
field: 'update',
cellRendererFramework: {
template: '<button (click) = "editAsset()"> Edit </button>'
},
width: 100
});
return headers;
}
editAsset(): void {
alert("here");
}
Both of these are defined in the same component.
But it can't find the method and showing the following error :
Here's the Snapshot of error report
Here's the grid

Resources