Categories not listed on Interaction Studio UI - salesforce

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

Related

Refresh Datatable in for:each loop: Lightning Web Components

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 = [];

Datatable-angular export data buttons only creating column headers not data

Export buttons are only exporting column headers not the data.
I am using datatables in angular5, with serverside processing.
Previously i was using clint-side processing with all data and it was working fine, later i moved for server-side processing with below code.
this.table = $('#my-data-table').DataTable({
serverSide: true,
filter: false,
sort: true,
orderCellsTop: true,
columnDefs: [{
targets: [-1, 0, 1, 2, 4, 10, 12],
orderable: false,
}],
ajax: (dataTablesParameters: any, callback) => {
this.draw += 1;
let info = $('#early-detection-data-table').DataTable().page.info();
if (info.length > 10) {
if (info.page > 0) {
this.offset = ((info.length) * (info.page));
} else {
this.offset = (((info.page + 1) * 10) - 10);
}
} else {
this.offset = (((info.page + 1) * 10) - 10);
}
this.countNumber = (this.offset + 1);
let limit = info.length;
this.patientService.getRecentTransmission(limit.toString(), this.offset.toString(), this.searchCriterian).subscribe(
(data: any) => {
this.earlyDetections = data;
let total: number;
$('.showbox').css('display', 'none');
//this.setVisiblility();
if (data[0] && data[0].recordsTotal > 0) {
total = data[0].recordsTotal;
} else {
total = 0;
}
callback({
recordsTotal: total,
recordsFiltered: total,
data: [],//JSON.stringify(data),
});
console.log(data)
if (data && data.length != 0) {
$('td.dataTables_empty').hide();
} else {
$('td.dataTables_empty').show();
}
});
}, loadingIndicator: true,
dom: 'lBfrtip',
// "order": [[ 7, "desc" ]],
buttons: {
buttons: [
{ extend: 'print', className: 'btn btn-primary btn-round' },
{ extend: 'excel', className: 'btn btn-primary btn-round' },
{ extend: 'pdf', className: 'btn btn-primary btn-round' }
]
},
});
Please help me and let me know if anything else required from my side.
Last week I had the same issue.
I spent almost 2 days to solve it, the solution that I come with was:
Go to the export buttons library and find where the data was built before exporting to file.
You will find there:
var = data;
data = {
body: [] //actual data of the table
footer: [] //footer of the table if exists
header: [] //header of the table
}
In my case here is the data object:
body: []
footer: null
header: (4) ["#", "Serial Number", "Filter Type", ""]
__proto__: Object
So as you can see the body is empty and that is why you get only headers are shown.
What I did is building the body from the response of the server call and store it on the local storage(so I can access it from everywhere).
Here is the code sample:
const TableData = [...resp.controllers];
this.utilService.getDataArrayForExport(TableData, 'Controllers');
getDataArrayForExport(TableData, TableType) {
const DataArray = [];
let formatedObject = {};
TableData.forEach(function (object, i) {
switch (TableType) {
case "Clients":
formatedObject = {
'email': object.email,
'name': object.name,
'jobDescription': object.jobDescription,
'company': object.company,
'countries': object.countries
};
break;
case "Controllers":
formatedObject = {
'controllerSN': object.controllerSN,
'filterType': object.filterType
};
break;
case "Flushes":
const pipeFlush = new DatePipe('en-US');
const timeFlush = pipeFlush.transform(object.time, 'short');
formatedObject = {
'controllerSN': object.controllerId,
'description': object.description,
'time': timeFlush,
'dpBefore': object.dpBefore,
'dpAfter': object.dpAfter
};
break;
case "Alerts":
const pipe = new DatePipe('en-US');
const time = pipe.transform(object.time, 'short');
formatedObject = {
'controllerSN': object.controllerId,
'description': object.description,
'time': time,
};
break;
case "Admins":
formatedObject = {
'email': object.email,
'jobDescription': object.jobDescription,
'company': object.company,
'countries': object.countries
};
break;
}
const array = [];
array.push((i + 1).toString());
$.each(formatedObject, function (key, value) {
array.push(value);
});
DataArray.push(array);
});
localStorage.setItem('export', JSON.stringify(DataArray));
console.dir(DataArray);
}
Now add a small logic on export if data.body is an empty array:
var data = dt.buttons.exportData( config.exportOptions );
var ddd = JSON.parse(localStorage.getItem("export"));
if(data.body.length === 0){
data.body = ddd;
}
Now the data object is populated with the data:
body: (10) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
footer: null
header: (4) ["#", "Serial Number", "Filter Type", ""]
__proto__: Object
So now all set, and you will get all the data export correctly.
I hope my post will help you.
If you have some questions you are welcome to ask :)

How to add dynamic property in angularjs object

I have object
$scope.postData = {
'pmu.messages.message': $scope.upd.message,
'pmu.received.id': $scope.upd.atomByReceivedBy.id,
};
and in scope there is $scope.ImageList which contains image path array
[
{ img: 'a.jpg', smallimg: 'b.jpg', smpath: 'c.jpg' },
{ img: 'a1.jpg', smallimg: 'b1.jpg', smpath: 'c1.jpg' },
];
I want to add these array value to $scope.postData field property value
as
$scope.postData = {
'pmu.messages.message': $scope.upd.message,
'pmu.received.id': $scope.upd.atomByReceivedBy.id,
'pmu.image[0].img':
'pmu.image[0].smallimg':
'pmu.image[0].smimg':
'pmu.image[1].img':
'pmu.image[1].smallimg':
'pmu.image[1].smimg':
}
How to achieve this?
Iterate $scope.ImageList and use Bracket Notation to create properties.
Here in the example, I have used just one property
$scope.ImageList.forEach(function(element, index){
$scope.postData['pmu.image[' + index +'].img'] = element.img;
});
You can iterate the array with Array.forEach, and then extract the key & value of each image using Object.entries.
$scope = {
upd: {
message: '',
atomByReceivedBy: {
id: ''
}
},
ImageList: [{
img: 'a.jpg',
smallimg: 'b.jpg',
smpath: 'c.jpg'
},
{
img: 'a1.jpg',
smallimg: 'b1.jpg',
smpath: 'c1.jpg'
},
]
}
$scope.postData = {
'pmu.messages.message': $scope.upd.message,
'pmu.received.id': $scope.upd.atomByReceivedBy.id,
};
$scope.ImageList.forEach((img, index) => {
Object.entries(img).forEach(([key, value]) => {
$scope.postData[`pmu.image[${index}].${key}`] = value;
});
});
console.log($scope.postData);

embed code twitter on Tinymce 4

I am adding a plugin which insert twitter embed code. the problem is that I can see the tweet on the editor but not in the source code and preview. And I can't save it. I saw in forum that I have to add 'http:' to '//platform.twitter.com/widgets.js' and put it before , unfortunately, it's not working. This is the code I put:
tinymce.PluginManager.add('twitter', function(editor, url) {
editor.on('init', function (args) {
editor_id = args.target.id;
});
editor.addButton('twitter', {
text: 'Twitter',
icon: false,
onclick: function () {
editor.windowManager.open({
title: 'Twitter Embed',
body: [
{ type: 'textbox',
size: 40,
height: '100px',
name: 'twitter',
label: 'twitter'
}
],
onsubmit: function(e) {
var embedCode = e.data.twitter;
var script = embedCode.match(/<script.*<\/script>/)[0];
var scriptSrc = script.match(/".*\.js/)[0].split("\"")[1];
console.log(script);
var sc = document.createElement("script");
sc.setAttribute("src", "https:"+scriptSrc);
sc.setAttribute("type", "text/javascript");
var iframe = document.getElementById(editor_id + "_ifr");
var iframeHead = iframe.contentWindow.document.getElementsByTagName('head')[0];
var iframeBody = iframe.contentWindow.document.getElementsByTagName('body')[0];
embedCode1 = embedCode.replace('//platform.twitter.com/widgets.js','https://platform.twitter.com/widgets.js');
iframeBody.appendChild(sc);
editor.insertContent(embedCode1);
iframeHead.appendChild(sc);
// setTimeout(function() {
// iframe.contentWindow.twttr.widgets.load();
// }, 1000)
}
});
}
});
});

Vue.js filtering on array

I am trying to filter an array using a computed property in vue.js. I would like to search on on multiple fields, name, state, tags etc.
My data:
events: [
{
id: 1,
name: 'Name of event',
url: '#',
datetime: '2017-05-10T00:00:00Z',
description: 'The full text of the event',
state: 'VIC',
tags: [
'ordinary',
'advanced'
]
},
{
id: 2,
name: 'Another event',
url: '#',
datetime: '2017-05-12T00:00:00Z',
description: 'The full text of the event',
state: 'VIC',
tags: [
'beginner'
]
},
{
id: 3,
name: 'Great event',
url: '#',
datetime: '2017-05-18T00:00:00Z',
description: 'The full text of the event',
state: 'NSW',
tags: [
'beginner'
]
}
]
},
The following function works as expected, however I cant work out how to have it search the items in 'tags' (commented out).
searchevents: function(){
let result = this.events
if (this.filterValue){
result = result.filter(event =>
event.name.toLowerCase().includes(this.filterValue.toLowerCase()) ||
event.state.toLowerCase().includes(this.filterValue.toLowerCase())
// event.tags.toLowerCase().values().includes(this.filterValue.toLowerCase())
)
}
return result
}
The following returns a blank array, this method works ok when i have done it in angular but not in vue.
searchevents2: function(){
var searchRegex = new RegExp(this.filterValue,'i')
this.events.filter(function(event){
return !self.filterValue || searchRegex.test(event.name) || searchRegex.test(event.state)
})
}
Ideally I would either like to be able to list array items to filter by or just filter by the entire array.
Appreciate any help, first post here so be gentle. I have a lot more experience with Python than Javascript so i may also use incorrect terminology at times.
You weren't too far off.
For your searchEvents filter, you just needed to add the tag filter. Here's how you might do that.
searchevents: function(){
let result = this.events
if (!this.filterValue)
return result
const filterValue = this.filterValue.toLowerCase()
const filter = event =>
event.name.toLowerCase().includes(filterValue) ||
event.state.toLowerCase().includes(filterValue) ||
event.tags.some(tag => tag.toLowerCase().includes(filterValue))
return result.filter(filter)
}
Array.some() is a standard array method that returns true if any element of the array passes your test.
searchevents2: function(){
const searchRegex = new RegExp(this.filterValue,'i')
return this.events.filter(event =>
!this.filterValue || searchRegex.test(event.name) || searchRegex.test(event.state))
}
With searchEvents2 you really only left an errant self in there. Either you needed to set self before you executed the filter, or, as I have done here, turned it into an arrow function.
Example.
const app = new Vue ({
el: '#app',
data: {
search: '',
userList: [
{
id: 1,
name: "Prem"
},
{
id: 1,
name: "Chandu"
},
{
id: 1,
name: "Shravya"
}
]
},
computed: {
filteredAndSorted(){
// function to compare names
function compare(a, b) {
if (a.name < b.name) return -1;
if (a.name > b.name) return 1;
return 0;
}
return this.userList.filter(user => {
return user.name.toLowerCase().includes(this.search.toLowerCase())
}).sort(compare)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<div class="search-wrapper">
<input type="text" v-model="search" placeholder="Search title.."/>
<label>Search Users:</label>
</div>
<ul>
<li v-for="user in filteredAndSorted">{{user.name}}</li>
</ul>
</div>

Resources