refreshApex not trigger when called from parent lwc - javascript

I have a button on the parent lwc. When called, it will call a method on the child lwc to perform data update via apex method. Once done, it calls refreshApex to refresh the data on the lightning datatable. RefreshApex will not trigger for some reason.
Now, if I use the child's save button to save the changes, refreshApex will trigger and data will update without any issues.
Appreciate your help!!!
enter image description here
PARENT COMPONENT
</div>
<footer class="slds-modal__footer">
<lightning-button label="Save" variant="brand" onclick={saveModal} class="slds-m-left_x-small"></lightning-button>
<lightning-button label="Cancel" variant="neutral" onclick={closeModal} class="slds-m-left_x-small"></lightning-button>
</footer>
PARENT JS
saveModal() {
//firing a child method
this.template.querySelector("c-reuse-license-key-table").testHandleAction();
}
CHILD COMPONENT
<template if:true={quotelinelist}>
<div class="slds-grid_align-end" style="height: 500px; overflow: auto;">
<lightning-datatable key-field="Id"
data={quotelinelist}
columns={columns}
onsave= {testHandleAction}
oncellchange= {handleRowAction}
draft-values={fldsItemValues}
hide-checkbox-column="true"> <!--suppress-bottom-bar="true"-->
</lightning-datatable>
</div>
</template>
CHILD JS
#wire(getQuoteLinesList,{quoteId: '$parentRecordId', actionType: '$actionTypeForWire'})
cons(result) {
console.log('check result 1 ', result)
this.wiredDataResult = result;
if(result.error) {
console.log('inside wire error')
//this.quotelineList = undefined;
}
else {
console.log('inside else error')
this.quotelinelist = result.data;
}
}
handleRowAction(event) {
lKeyDraftList.push(event.detail.draftValues);
}
#api async testHandleAction() {
let strOriginalKeyValues = JSON.stringify(qlInList);
let strlKeyDraftList = JSON.stringify(lKeyDraftList);
let strDraftValue = [];
lKeyDraftList.forEach(function(c){
console.log('check c', c);
c.forEach(function(c2){
console.log('check c2', c2);
strDraftValue.push(c2);
});
});
await updateQuoteLines({IdToObjectForDraftValues : JSON.stringify(strDraftValue)}) //, IdToObjectForOriginalValues: strOriginalKeyValues
.then(result => {
const toast = new ShowToastEvent({
title: 'Success',
message: 'Records Updated Successfully!!',
variant: 'success'
});
this.dispatchEvent(toast);
this.actionTypeForWire = 'update';
return refreshApex(this.wiredDataResult).then(() => {
console.log('after refresh apex')
this.fldsItemValues = [];
});
})
.catch(error => {
console.log('error');
const toast = new ShowToastEvent({
title: 'Error',
message: 'An Error Occurred!!' + error.message,
variant: 'error'
});
this.dispatchEvent(toast);
}).finally(() => {
console.log('inside final', this.fldsItemValues);
});
}
APEX Controller
#AuraEnabled(cacheable=true)
public static List<SBQQ__QuoteLine__c> getQuoteLinesList(Id quoteId, String actionType) {
List<SBQQ__QuoteLine__c> qLineList = new List<SBQQ__QuoteLine__c>();
if(quoteId != null){
qLineList = [SELECT Id,Name,SBQQ__ProductName__c,SBQQ__Quantity__c,Reused_License_Key__c
FROM SBQQ__QuoteLine__c
WHERE SBQQ__RequiredBy__c != NULL
AND SBQQ__Quote__c = :quoteId
ORDER BY SBQQ__ProductName__c];
}
Boolean callLPUtility = true;
for(SBQQ__QuoteLine__c ql : qLineList) {
if(ql.Reused_License_Key__c != null) {
callLPUtility = false;
break;
}
}
if(callLPUtility == true && actionType == 'begin') {
LicenseProductUtility.toMapLicenseKeyForRenewal(qLineList, getSubscriptionsList(quoteId));
}
return qLineList;
}
#AuraEnabled
public static void updateQuoteLines(String IdToObjectForDraftValues) { //String IdToObjectForOriginalValues
List<Object> items = (List<Object>) JSON.deserializeUntyped(IdToObjectForDraftValues);
List<SBQQ__QuoteLine__c> qlList = new List<SBQQ__QuoteLine__c>();
List<Map<String, Object>> theJsonMapList = new List<Map<String, Object>>();
for(Object itemObj : items) {
theJsonMapList.add((Map<String, Object>) itemObj);
}
for(Object o : theJsonMapList){
SBQQ__QuoteLine__c q = new SBQQ__QuoteLine__c();
q.Id = ((Map<String, Object>) o).get('Id').toString();
q.Reused_License_Key__c = ((Map<String, Object>) o).get('Reused_License_Key__c').toString();
qlList.add(q);
}
Savepoint sp = Database.setSavepoint();
if(!qlList.isEmpty()){
try {
update qlList;
} catch (Exception e) {
Database.rollback(sp);
System.debug('An exception occurred updateQuoteLines: ' + e.getMessage());
}
}
}

Related

angular listen for changes

NgOnChanges update table when changes is detected causes infinite service call , which I dont have any idea why. Maybe someone can help. Thanks.
I have parent component TableMultiSortComponent and I passed output EventEmitter which is the table data .
The event or the output from EventEmitter is passed to child component which I will use the pagesize , pageindex etc to query as you can see on the dataServiceEvent on the child component.
And then the output of the request is dealsListData which I pass back from child to parent as data to be populated to the table.
It should update table when there is changes that is why I put it on ngOnchanges which is the initTableMultiSort but right now it is updating the table infinitely , it should only update once if there is changes
#the table data that I am passing from parent component to child component , this.dataServiceEvent.emit(this.table); , this is what I emit cause I will be needing the pageindex, size etc.
#HTML CODE - Parent Component
<mat-card *ngIf="table !== undefined">
<mat-table mat-table [dataSource]="table.dataSource" matMultiSort (matSortChange)="table.onSortEvent()">
<ng-container *ngFor="let column of table.columns" [matColumnDef]="column.id">
<mat-header-cell class="table-multi-sort-header" *matHeaderCellDef [mat-multi-sort-header]="column.id">
<div>{{column.name}}</div>
<div class="sub-text">{{getColumnSubtitle(column.id)}}</div>
</mat-header-cell>
<mat-cell *matCellDef="let row">
<ng-container *ngIf="column.id !== 'action'; then col; else actionCol"></ng-container>
<ng-template #col>
<app-table-multi-sort-cell-default [cellData]="row" [id]="column.id" [subId]="getColumnSubId(column.id)"></app-table-multi-sort-cell-default>
</ng-template>
<ng-template #actionCol>
<app-table-multi-sort-cell-action [rowData]="row" [actions]="getActions(column.id)" (actionClickEvent)="clickTableAction($event,row)"></app-table-multi-sort-cell-action>
</ng-template>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="table.displayedColumns; sticky:true"></mat-header-row>
<mat-row *matRowDef="let item; columns: table.displayedColumns;"></mat-row>
</mat-table>
<mat-progress-bar *ngIf="isLoading" mode="indeterminate"></mat-progress-bar>
</mat-card>
#ts code- parent component
export class TableMultiSortComponent implements OnInit {
#Input() tableOptions:any;
#Input() tableData:any = [];
#Input() isClientSide:boolean = false;
#Input() isLoading: boolean = false;
#Output() tableActionsEvent = new EventEmitter<any>();
#Output() dataServiceEvent = new EventEmitter<any>() ;
#ViewChild(MatMultiSort, { static: false }) sort: MatMultiSort;
tableConfig: any = TABLE_MULTI_SORT_OPTIONS.DEFAULT;
table:TableData<any>;
displayedColumns: any;
constructor() { }
ngOnInit(): void {
this.initTableMultiSort();
}
initTableMultiSort(){
this.tableConfig = {
...this.tableConfig,
...this.tableOptions
}
this.table = new TableData<any>(this.tableConfig.columns,this.tableConfig.sortParams);
this.table.pageSize = this.tableConfig.pageSize;
this.table.pageIndex = this.tableConfig.pageIndex;
this.table.nextObservable.subscribe(() => { this.getData(); });
this.table.sortObservable.subscribe(() => { this.getData(); });
this.table.previousObservable.subscribe(() => { this.getData(); });
this.table.sizeObservable.subscribe(() => { this.getData(); });
setTimeout(()=>{
this.table.dataSource = new MatMultiSortTableDataSource(this.sort, this.isClientSide);
this.getData();
},0);
}
ngOnChanges(changes: SimpleChanges) {
if (changes.tableData && changes.tableData.currentValue){
console.log("changes" , changes)
this.initTableMultiSort()
}
}
getData(){
this.table.totalElements = 1;
this.table.pageIndex = 0;
this.table.pageSize = 10;
this.table.data = this.tableData;
if(this.dataServiceEvent) {
this.dataServiceEvent.emit(this.table);
}
}
#child component HTML code
<app-table-multi-sort (dataServiceEvent)="dataServiceEvent($event)" [tableOptions]="tableOptions" [tableData]="dealsListData" (tableActionsEvent)="tableActions($event)"></app-table-multi-sort>
#child component ts code
export class DealsTransactionComponent implements OnInit {
#Input() transactionId: any = 2;
#Input() transactionName: string = '-';
#ViewChild(TableMultiSortComponent, { static: true }) tableMultiSortComponent: TableMultiSortComponent;
private currentTableConfig: TableData<any>;
dealsListData: any;
tableOptions: any;
#Input() transaction: any;
isLoading: boolean;
private _destroyed$ = new Subject();
totalDeals : number;
accountId: any;
searchInput: string;
table: any;
constructor(
private dialog: MatDialog,
private dealService: DealService,
private notificationService: NotificationService,
private route: Router,
) {}
ngOnInit(): void {
const currentAccountDetails = localStorage.getItem('currAcct') as any;
if (currentAccountDetails) {
this.accountId = JSON.parse(currentAccountDetails).accountId;
}
this.tableOptions = {
columns:[
{id:'name',name:'Deal Name',subId:'type', subtitle:'Deal Type'},
{id:'annualRentProposed',name:'Annual Rent (Proposed)', subId: 'annualRentCurrent', subtitle:'Annual Rent (Proposed)'},
{id:'firmTermRemain',name:'Firm Term Remaining', subId: 'firmTermAdded', subtitle:'(Current)'},
{id:'maxTerm',name:'Max Available Term'},
{id:'cash',name:'Cash Contribution'},
{id:'action', name: 'Actions', actions:[
{icon:'file_copy', name:'Copy', class:'primary-color'},
{icon:'delete', name: 'Delete', class:'mat-error'},
{icon:'forward', name: 'For Approval', class:'primary-color'}
]}
]
}
}
dataServiceEvent(item) {
this.table = item;
if(this.table) {
this._pageEventMyList();
}
}
private _pageEventMyList() {
if (!this.shouldLoadData(this.currentTableConfig, this.table)) {
return;
}
this.currentTableConfig = this.table;
this.searchInput = '';
this.isLoading = true;
this.dealService
.getAllDeals(
this.accountId,
this.transaction.id,
this.table.pageIndex + 1,
this.table.pageSize,
this.searchInput,
this.table.sortParams,
this.table.sortDirs
)
.pipe(finalize(() => (this.isLoading = false)))
.subscribe({
error: (err) => this.notificationService.showError(err),
next: (res) => {
this.dealsListData = res.totalItemCount;
this.dealsListData = res.lastItemOnPage;
this.totalDeals = res.items.length;
this.dealsListData = res.items;
console.log("this.dealsListData" , this.dealsListData)
},
complete: noop,
});
}
private shouldLoadData(oldTableConfig: TableData<any>, tableConfig: TableData<any>): boolean {
if (!oldTableConfig) {
return true;
}
return oldTableConfig.pageIndex !== tableConfig.pageIndex
|| oldTableConfig.pageSize !== tableConfig.pageSize
|| oldTableConfig.sortParams !== tableConfig.sortParams
|| JSON.stringify(oldTableConfig.sortParams) !== JSON.stringify(tableConfig.sortParams)
|| JSON.stringify(oldTableConfig.sortDirs) !== JSON.stringify(tableConfig.sortDirs);
}
createDeal() {
this.route.navigateByUrl(`deals/detail/${this.transactionId}~create`, {
state: { data: this.transaction },
});
localStorage.removeItem('breadCrumbsPath');
const breadCrumbsPath = [
{
text: 'My transactions',
link: '/transactions',
},
{
text: this.transactionName,
link: `/transactions/overview/${this.transactionId}`,
},
];
localStorage.setItem('breadCrumbsPath', JSON.stringify(breadCrumbsPath));
}
copyDeal(id: number) {
const data = { id : id }
this.duplicateDeal(data)
}
deleteDeal(id: number) {
this.isLoading = true;
this.dealService.delete(id)
.pipe(finalize(() => { this.isLoading = false;}))
.subscribe({
next: (res) => {
this.dealsListData = this.dealsListData.filter((x) => x.id !== id);
this.notificationService.showSuccess('Deal been removed successfully.');
},
error: (err) => {
this.notificationService.showError('Something went wrong, Try again later.');
this.isLoading = false;
},
complete: () => {
this.isLoading = false;
},
});
}
duplicateDeal(data: any) {
this.isLoading = true;
this.dealService.duplicate(data)
.pipe( finalize(() => { this.isLoading = false;}))
.subscribe({
next: (res) => {
let copyData = {
...this.dealsListData.filter((x) => x.id === data.id),
};
copyData.name = copyData.name + '(copy)';
copyData.id = this.dealsListData[this.dealsListData.length - 1].id + 1;
this.notificationService.showSuccess('Deal was successfully duplicated.' );
},
error: (err) => {
this.notificationService.showError('Something went wrong, Try again later.');
this.isLoading = false;
},
complete: () => {
this.isLoading = false;
},
});
}
approveDeal(data: any) {
this.isLoading = true;
this.dealService
.approve(data)
.pipe(finalize(() => { this.isLoading = false; }))
.subscribe({
next: (res) => {
this.notificationService.showSuccess('Deal was approved');
},
error: (err) => {
this.notificationService.showError('Something went wrong, Try again later.');
this.isLoading = false;
},
complete: () => {
this.isLoading = false;
},
});
}
You are emitting this.table whenever you call getData in the TableMultiSortComponent which happens in ngOnInit for the first time.
This event emission (I suppose) triggers the actual data fetch in the parent component, in the tableActions method (could not see the implementation of this method in the code you included). This method is probably doing something similar to _pageEventMyList that gets data from the API and sets the dealsListData property on the TableMultiSortComponent ([tableData]="dealsListData").
This in turn, triggers your onChange in the TableMultiSortComponent and also passes your if check (the tableData is actually changed). From onChange, you call initTableMultiSort that reinitializes this.table and emits it with this.dataServiceEvent.emit(this.table);. This makes all go into a cycle.
I recommend to implement some extra checks to make sure you are not triggering data reload if the table configuration is the same.
In short, it looks like you use the table for providing you data about the pageIndex, pageSize, sortParams and sortDirs, so I suggest to keep information about the value of these properties when you try to load data again. Compare them, and if something is changed, then fetch data
private currentTableConfig: TableData<any>;
private _pageEventMyList() {
if (!shouldLoadData(currentTableConfig, this.table)) {
return;
}
this.currentTableConfig = this.table;
this.searchInput = '';
this.isLoading = true;
this.dealService
.getAllDeals(
this.accountId,
this.transaction.id,
this.table.pageIndex + 1,
this.table.pageSize,
this.searchInput,
this.table.sortParams,
this.table.sortDirs
)
.pipe(finalize(() => (this.isLoading = false)))
.subscribe({
error: (err) => this.notificationService.showError(err),
next: (res) => {
this.dealsListData = res.totalItemCount;
this.dealsListData = res.lastItemOnPage;
this.totalDeals = res.items.length;
this.dealsListData = res.items;
},
complete: noop,
});
}
and the new shouldLoadData method performs the checks:
private shouldLoadData(oldTableConfig: TableData<any>, tableConfig: TableData<any>): boolean {
if (!oldTableConfig) {
return true;
}
return oldTableConfig.pageIndex !== tableConfig.pageIndex
|| oldTableConfig.pageSize !== tableConfig.pageSize
|| JSON.stringify(oldTableConfig.sortParams) !== JSON.stringify(tableConfig.sortParams)
|| JSON.stringify(oldTableConfig.sortDirs) !== JSON.stringify(tableConfig.sortDirs);
}
Disclaimer for using JSON.stringify:
This works, but it is not optimal. If you have another third party library installed, like lodash, you can go for _.isEqual instead (or something similar, that will compare the array contents for you).

How to use behaviorSubject when removing item from array

I have angular 8 application.
And I have two components, like child - parent relationship. So I remove the item from the child, but then the item is still visible in the parent(list of items). Only after page refresh the item is gone from the list.
So I have this service:
export class ItemListService {
_updateItemChanged = new Subject<any>();
_removeItemChanged = new BehaviorSubject<any>([]);
constructor() {}
}
and this is item.ts - child:
openRemoveDialog() {
const dialogRef = this.dialog.open(ItemRemoveDialogComponent, {
width: '500px',
height: '500px',
data: {
dossierId: this.dossier.id,
item: this.item,
attachments: this.item.attachments
}
});
this.itemListService._removeItemChanged.next(this.item.title);
dialogRef.afterClosed().subscribe(result => {
if (result === true) {
this.router.navigate(['/dossier', this.dossier.id]);
}
});
}
and this is the view.ts(item list) - parent: so in this component the refresh has to be made
ngOnInit(): void {
this.show = !this.router.url.includes('/item/');
this.itemlistService._updateItemChanged.subscribe(data => {
const index = this.dossierItems.findIndex(a => a.id === data.id);
this.dossierItems[index] = data;
});
this.itemlistService._removeItemChanged.subscribe(data => {
// this.dossierItems.pop(); What I have to fill in here?
});
So what I have to change?
Thank you
and this is the remove function:
remove() {
this.dossierItemService.deleteDossierItem(this.data.dossierId, this.data.item.id)
.subscribe(() => {
this.dialogRef.close(true);
}, (error) => {
const processedErrors = this.errorProcessor.process(error);
this.globalErrors = processedErrors.getGlobalValidationErrors();
});
}
I have it now like this:
remove() {
this.dossierItemService.deleteDossierItem(this.data.dossierId, this.data.item.id)
.subscribe(() => {
this.dialogRef.close(true);
this.itemListService._removeItemChanged.next(true);
}, (error) => {
const processedErrors = this.errorProcessor.process(error);
this.globalErrors = processedErrors.getGlobalValidationErrors();
});
}
and in the view.ts, like ths:
ngOnInit(): void {
this.itemlistService._removeItemChanged.subscribe(update => update === true ? this.dossierItems : '');
}
but still the list will not be refreshed
You need to create a new reference to your array for Angular to update the screen like this
this.itemlistService._removeItemChanged.subscribe(data => {
// this.dossierItems.pop(); What I have to fill in here?
this.dossierItems = this.dossierItems.filter(e => e.title !== data);
});

Amazon Connect Outbound CCP Softphone Number Prefill

I have a pretty simple requirement to click on a phone number hyperlink and have my web-app open the AWS connect soft-phone dialer with the selected number, ready for the person to press the "call button"
I have enabled an AWS connect account and I am hosting a custom CCP site via an S3 bucket (as illustrated here)
My plan is to initiate a link to the CCP page and embed a URL Search Param
"?number=04125412,customTag=helloWorld"
I have used this code on the CCP Page
Also, within the index page, I add some code to receive the input params:
<script>
var urlParams = new URLSearchParams(window.location.search);
console.log(urlParams.get('number')); //the phone number for the dialer
console.log(urlParams.get('customTag')); // the call notes for the CTR custom Attributes
</script>
I Am struggling to understand how I can interact with A: the Dialer to pre-fill the number and B: to post custom attributes to the AWS contact record during the call.
Any help would be appreciated.
I set this up in my React application but you should be able to repurpose for your needs
import React from "react";
import {connect} from 'react-redux'
import Button from "components/CustomButtons/Button.jsx";
import {receiveCallAttr, initCall, callFlow} from 'store/apps/AppSettings/actions';
class AmazonConnect extends React.Component {
constructor(props) {
super(props);
this.state = {
active:false,
reloadAttempts:0,
activeCall:{},
cip:false,
agentQueueNumber:"xxxxxxxxxx",
recordingQueueNumber:"xxxxxxxxxx"
};
this.awsConnect = this.awsConnect.bind(this)
this.loginWindow = this.loginWindow.bind(this);
this.activeWindow = this.activeWindow.bind(this);
this.initCall = this.initCall.bind(this)
this.initContact = this.initContact.bind(this)
this.redirect = this.redirect.bind(this)
}
componentWillReceiveProps(newProps){
const {AppSettings, initCall, callFlow} = newProps
const {cip, active} = this.state
if( active && !cip){
this.setState({activeCall: AppSettings.call})
if(AppSettings.call.number){
console.log("init call")
this.initCall(AppSettings.call.number)
initCall({})
}
else{
console.log("Invalid Phone number")
}
if( AppSettings.flow !== "" ){
this.setState({activeFlow: AppSettings.flow})
this.initCallFlow(AppSettings.flow)
callFlow("")
}
}
}
initCallFlow = flow => new Promise((res, rej) => {
if(this.contact){
console.log(this.contact)
let endpoint;
switch(flow){
case "agentQueue":
endpoint = window.connect.Endpoint.byPhoneNumber(this.state.agentQueueNumber);
this.contact.addConnection(endpoint, {
success: function() {
this.contact.conferenceConnections({
success: function() {
console.log("confrence success")
res("successfullly init ssn flow")
},
failure: function() {
console.log("confrence failure")
res("successfullly init ssn flow")
}
});
},
failure: function() {
rej("failed to init ssn flow")
}
});
break
case "recordingQueue":
endpoint = window.connect.Endpoint.byPhoneNumber(this.state.recordingQueueNumber);
this.contact.addConnection(endpoint, {
success: function() {
res("successfullly init recording flow")
},
failure: function() {
rej("failed to init recording flow")
}
});
break
default:
res()
break
}
}
else{
rej("no contact available")
}
})
awsConnect = () => new Promise((res, rej) => {
window.connect.core.initCCP(document.getElementById("softPhone"), {
ccpUrl: process.env.REACT_APP_AWS_CONNECT_URL, /*REQUIRED*/
loginPopup: true, /*optional, default TRUE*/
softphone: { /*optional*/
disableRingtone: false, /*optional*/
allowFramedSoftphone: true
}
});
this.bus = window.connect.core.getEventBus();
this.bus.subscribe(window.connect.AgentEvents.INIT, (agent) => {
this.activeWindow()
});
this.bus.subscribe(window.connect.EventType.TERMINATED, () => {
console.log("TERMINATED")
this.setState({cip:false})
this.logout()
});
this.bus.subscribe(window.connect.EventType.AUTH_FAIL, () => {
console.log("AUTH_FAIL")
this.logout()
})
window.connect.agent(function(agent) {
const w = window.open('', window.connect.MasterTopics.LOGIN_POPUP);
if (w) {
w.close()
}
});
window.connect.contact((contact) => {
this.contact = contact
const {receiveCallAttr} = this.props
try{
var attr = contact.getAttributes()
attr.active = true
console.log(attr)
receiveCallAttr(attr)
this.redirect()
}
catch(err){
console.log(err)
}
contact.onEnded(() => {
console.log("call ended")
receiveCallAttr({active:false})
this.setState({cip:false})
this.contact = null
})
});
res()
})
initContact = () => {
this.setState({cip:false})
}
redirect = () => {
const {location, auth, history} = this.props
switch(auth.user.type){
case "Agent":
if(location.pathname !== "/agent/management"){
history.push({
pathname: '/agent/management',
search: '',
state: {}
})
}
break;
case "Service":
//handle redirect to service page
if(location.pathname !== "/service/dashboard"){
history.push({
pathname: "/service/dashboard",
search: '',
state: {}
})
}
break;
default:
break
}
}
initCall = (phone) => {
this.initContact()
window.connect.agent(function(agent) {
const endpoint = window.connect.Endpoint.byPhoneNumber(phone)
agent.connect(endpoint , {
queueARN : process.env.CONNECT_QUEUE_ARN,
success : function(){
console.log("Success call!!!!!!")
},
failure : function(){
console.log("Call failed!!!!!!!")
}
});
});
}
logout(){
this.setState({cip:false})
this.loginWindow()
this.agent = null
this.contact = null
window.connect.core.terminate();
window.connect.core.client = new window.connect.NullClient();
window.connect.core.masterClient = new window.connect.NullClient();
window.connect.core.eventBus = new window.connect.EventBus();
window.connect.core.initialized = false;
this.bus = false;
var myNode = document.getElementById("softPhone")
while (myNode.firstChild) {
myNode.removeChild(myNode.firstChild);
}
}
componentWillUnmount() {
console.log("terminating aws connect session")
this.logout()
}
loginWindow(){
this.setState({active:false})
}
activeWindow(){
this.setState({active:true})
}
render() {
const displaylogin = this.state.active? "none":"block";
const displayConnect = this.state.active? "block":"none";
return (
<div>
<Button color={"rose"} onClick={this.awsConnect} style={{display:displaylogin, width:320}}>Login to AWS Connect</Button>
<div id="softPhone" style={{height:465,width:320, display:displayConnect}}>
</div>
</div>
);
}
}
function mapStateToProps(state){
return state
}
export default connect(mapStateToProps, {receiveCallAttr, initCall, callFlow})(AmazonConnect);
The previous answer by Ethan Harris helped me to reach the solution, but to distill it to allow a link to dial a number. You find the ARN in the Amazon Connect UI here:
Using the ARN copied from the Connect UI, this function seems to work for automating dialing a number. This took way more effort to figure out than I ever expected.
function dial_number(phone) {
connect.agent(function (agent) {
agent.connect(connect.Endpoint.byPhoneNumber(phone),
{
queueARN: arn
});
});
}

Remove class for another user vue.js

I have chat message system.
I have code:
<template>
<li :class="className">
{{ message }}
</li>
</template>
<script>
export default {
props: [
'message',
'user',
'time',
'seen',
],
computed: {
className() {
return this.seen;
}
},
mounted() {
console.log('Component mounted.')
}
}
</script>
App.js:
data:{
message: '',
convId: 1,
chat: {
message: [],
user: [],
time: [],
seen: [],
},
typing: '',
},
....
watch: {
message() {
Echo.private('chat')
.whisper('typing', {
name: this.message
});
}
},
methods: {
send(){
if(this.message.length != 0 && this.message.length <= 4000) {
this.chat.message.push(this.message);
this.chat.user.push('you');
this.chat.time.push(this.getTime());
this.chat.seen.push('unread'). //set class unread message for user
axios.post('/sendMessage', {
message: this.message,
//lastName: 'Flintstone'
})
.then(response => {
console.log(response);
this.message = '';
})
.catch(error => {
console.log(error);
});
}
},
seenMessage() {
axios.post('/setMessagesSeen/' + this.convId) //this request mark messages in chat all readed for auhenticated user
.then( response => { this.chat.seen.push(''); //remove unread class })
.catch( response => { console.log(response) } )
},
getTime() {
let time = new Date();
return time.getHours() + ':' + time.getMinutes();
}
},
mounted() {
Echo.private('chat')
.listen('ChatEvent', (e) => {
this.chat.message.push(e.message);
this.chat.user.push(e.user);
this.chat.time.push(this.getTime());
this.chat.seen.push('unread'). //set class unread message for user
console.log(e);
})
.listenForWhisper('typing', (e) => {
if(e.name != '')
this.typing = 'typing..';
else
this.typing = null;
});
}
My chat.blade.php:
<message v-for="value,index in chat.message"
:key=value.index
:user=chat.user[index]
:message="chat.message[index]"
:time="chat.time[index]"
:seen="chat.seen[index]"
>
</message>
<div class="form-group">
<textarea maxlength="4000" cols="80" rows="3" class="message-input form-control" v-model='message' v-on:click="seenMessage"></textarea>
</div>
<div class="form-group">
<button type="button" class="btn btn-lg btn-primary" v-on:click="send">Send message</button>
</div>
My function seen:
public function setMessagesSeen(Conversation $conversation) {
$user = User::find(Auth::id());
$conversations = Chat::conversation($conversation->id);
//$dd = Chat::conversations($conversation)->for($user)->readAll();
dd(Chat::conversations($conversations)->for($user)->getMessages()->where('body', 'asdfsadfsd'));
//$message = Chat::messages($message)->for($user)->markRead();
broadcast(new HasSeenMessage($message));
return response('ok');
}
How I can send class "unread" to element div other user? I can paste class on current user, and I get color on element chat only for me, but how I can hide element for me and other user, when message is seen?
I want do read/unread function for users.
Example:
If user in real time send message I send class unread, when other user click on textarea, I remove class unread, and said user, that message is seen. How I can do it in real time add/remove class unread? My function is not working.
To do this you have to create an Event in your Laravel application that you will broadcast on a precise channel (you can for example give the name 'chat. {Conversation}. {User_id}') and with Laravel Echo you will listen this event!
I allowed myself to make some changes in your code -:)
I presume you have this class HasSeenEvent
<?php
namespace App\Events;
use App\Order;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class HasSeenEvent implements ShouldBroadcast
{
use SerializesModels;
public $message;
/**
* Create a new event instance.
*
* #param Message $message
* #return void
*/
public function __construct(Message $message)
{
$this->message = $message;
}
public function broadcastOn()
{
// I presume we can get conversation id like this : $this->message->conversation->id
return new PrivateChannel('chat.'.$this->message->conversation->id.'.'.$this->message->sender->id);
}
}
Then, in your routes/broadcast.php declare this route chat.{conversation}.{user_id}
In the function where you put the 'seen' to 1 you broadcast the event at the same time
broadcast(new HasSeenMessage($message))
Then you listen to this event in your vuejs code
components/Message.js
<template>
<li :class="className">
{{ message }}
</li>
</template>
<script>
export default {
props: [
'message',
'user',
'time',
'readed',
],
computed: {
className() {
return this.readed == 1 ? 'seen' : 'unread';
}
},
mounted() {
console.log('Component mounted.')
}
}
</script>
chat.blade.php
<message v-for="message,index in chat.messages"
:key="index"
:user="message.user"
:message="message.content"
:time="message.time"
:readed="message.readed"
>
</message>
<div class="form-group">
<button type="button" class="btn btn-lg btn-primary" v-on:click="send">Send message</button>
</div>
App.js
data: {
message: '',
convId: 1,
chat: {
messages: [],
/* message: [],
user: [],
time: [],
seen: [], */
},
typing: '',
},
....
watch: {
message() {
Echo.private('chat')
.whisper('typing', {
name: this.message
});
}
},
methods: {
send() {
if (this.message.length != 0 && this.message.length <= 4000) {
let data = {
content: this.message,
user: 'you',
time:this.getTime(),
readed: 0
}
this.chat.messages.push(data)
data = {}
axios.post('/sendMessage', {
message: this.message,
//lastName: 'Flintstone'
})
.then(response => {
console.log(response);
this.message = '';
})
.catch(error => {
console.log(error);
});
}
},
seenMessage() {
axios.post('/setMessagesSeen/' + this.convId) //this request mark messages in chat all readed for auhenticated user
.then(response => {
//This is not the best way to do that
this.chat.messages[this.messages.length -1 ].readed = 0
}).catch(response => {
console.log(response)
})
},
getTime() {
let time = new Date();
return time.getHours() + ':' + time.getMinutes();
}
},
mounted() {
Echo.private('chat')
.listen('ChatEvent', (e) => {
this.chat.messages.push({
content: e.message,
user: e.user,
time: this.getTime(),
readed: 0
})
console.log(e);
})
.listenForWhisper('typing', (e) => {
if (e.name != '')
this.typing = 'typing..';
else
this.typing = null;
});
// I presume to can access to user info
let that = this
Echo.private('chat.'+convId+'.'+user.id)
.listen('HasSeenMessage', (e) => {
let message = e.message
let lookingForMessage = that.chat.messages.find((m) => {
// I presume in your db messages table has field content and time
return m.content == message.content && m.time => message.time
})
try {
lookingForMessage.readed = 1
}catch (err){
// message not found
console.log(err)
}
})
}
Hope it helped you!

Posting to Popup Controller Before the Parent Page Controller

How do I force a popup page to post to its controller first before posting to the parent controller? The popup page is setting up some session variables that would be used in the parent page. When the user double click on the grid on the pop-up page, it goes directly to the parent controller instead of going to the child controller.
Here is the parent where the popup is being called
//Javascript to open the popup window
#using (Html.BeginForm("Student", "StudentPage", FormMethod.Get, new { onsubmit = "", id = "student" }))
{
//where the popup window is located
}
Here is the popup form:
#using (Html.BeginForm("Index", "StudentInformation", FormMethod.Post, new {id="StudentSearchForm"}))
{
#(Html
.Telerik()
.Grid((IEnumerable<OverrideStudent>)SessionWrapper.Student.OtherStudentSelected)
.Name("StudentData")
.DataKeys(Keys =>
{
Keys.Add(c => c.StudentID);
})
.DataBinding(databinding => databinding.Server())
.Columns(columns =>
{
columns.Bound(p => p.StudentId)
.Title("Student ID")
.Width(15)
.Sortable(true)
.Filterable(false);
columns.Bound(p => p.StudentDescription)
.Title("Description")
.Width(65)
.Sortable(true)
.Filterable(false);
columns.Command(command =>
{
command.Custom("AddStudent")
.Text("Select")
.DataRouteValues(routes =>
{
routes.Add(o => o.StudentID).RouteKey("StudentID");
routes.Add(o => o.StudentDescription).RouteKey("StudentDescription");
})
.Action("Student", "StudentInfo");
.HtmlAttributes(new { onclick = "PostData(this);StudentSelectClick(this)" });
}).Width(20);
}).ClientEvents(clients => clients
.OnComplete("OnComplete")
//.OnDataBinding("DataBinding")
//.OnDataBound("onRowDataBound")
.OnRowSelected("StudentDoubleClick")
)
.Sortable()
.Selectable()
.Filterable(filtering => filtering
.Enabled(true)
.Footer(true)
.HtmlAttributes(new { style = "padding-right: 0.0em;" }))
}
//This is the script that handles that double click:
function StudentDoubleClick(e) {
var fromCourse = "#SessionWrapper.Student.FromCoursePage";
var fromList = "#SessionWrapper.Student.FromListingPage";
if (fromCourse == "True") {
$('tr', this).live('dblclick', function () {
alert("Inside TR count = " + count);
count = count + 1;
DoSearchStudent(e);
});
}
if (fromList == "True") {
$('tr', this).live('dblclick', function () {
DoSearchStudent(e);
});
}
}
function DoSearchStudent(e) {
var row = e.row;
var StudentID = row.cells[0].innerHTML;
var StudentDescription = row.cells[1].innerHTML;
// alert(procCodeDesc);
var data = { "StudentID": StudentID, "StudentDescription": StudentDescription, "action": "Double Click" };
var url = '#Url.Action("Student", "StudentInfo")';
$.ajax({
url: url,
type: 'post',
dataType: 'text',
cache: false,
async: false,
data: data,
success: function (data) {
window.top.location.href = window.top.location.href;
},
error: function (error) {
alert("An error has occured and the window will not be closed.");
}
});
}
//This is the controller that I need to go to first
public class StudentInfoController : Controller
{
.......
public string Student(string StudentID, string StudentDescription, string action)
{
if (StudentDescription != null)
{
StudentDescription = HttpUtility.HtmlDecode(StudentDescription);
}
try
{
RedirectToAction("AddStudent", "StudentInfo", new { StudentID = StudentID, StudentDescription = StudentDescription, action = action });
}
catch (Exception e)
{
return "Error " + e.ToString();
}
return "Success";
}
}
After the double click, it goes directly to the controller below instead. AS a result, my variables are not being set resulting in null exception.
public class StudentPageController : Controller
{
.......
public string Student(string StudentID, string StudentDescription, Student Students)
{
...........
}
}
It was a timing issue. When the user close the popup window, the popup thread is not done executing. At the same time, another thread starts to run, and not all the session variables are set as of yet. Before closing the popup window, I added a 1 second delay.
setTimeout('StudentWindow.close()', 1000);

Categories