Fire datatable event based on cutom lookup record selection in salesforce LWC - salesforce-lightning

I have custom reusablelookup component, datatable component. If and only if I select some record in lookup, only corresponding results must be displayed in datatable. If no record is selected in lookup, datatable should display 0 records.(I am struck here "how to fire event from one to other or connecting these both components")
I have used reusable lookup component from here..https://www.sfdcpanther.com/custom-lookup-in-lightning-web-component/
Datatable JS
#wire(getProvider)
wireddata({ error, data }){
if (data)
{ var ObjData = JSON.parse(JSON.stringify(data));
ObjData.forEach(record => {
record.AccountName = record.ProviderId__r != undefined ? record.ProviderId__r.Name:'';
record.AccountPhone = record.ProviderId__r != undefined ? record.ProviderId__r.NPI__c: '';
record.AccountManager = record.ProviderId__r != undefined ? record.ProviderId__r.FedId__c: '';
record.AccountAddress1 = record.ProviderId__r != undefined ? record.ProviderId__r.LicenceId__c: '';
record.AccountAddress2 = record.ProviderId__r != undefined ? record.ProviderId__r.MedicareOscarNumber__c: '';
});
//alert('After====> '+ JSON.stringify(ObjData));
this.allRecords = ObjData;
this.showTable = true;
}
else if (error) {
this.error = error;
this.data = undefined;
}
}
Datatable HTML
<template if:true={showTable}>
<c-lwc-datatable-utility records={allRecords}
total-records={allRecords.length}
columns = {columns}
key-field="Id"
show-search-box="true"
onrowaction={handleRowAction}
onpaginatorchange={handlePaginatorChange}
table-height="200">
</c-lwc-datatable-utility>
</template>
Lookup JS
fields = ["Provider_Name_API__c","NPI__c","FedId__c"];
displayFields = 'Provider_Name_API__c, NPI__c, FedId__c'
handleLookup(event){
console.log( JSON.stringify ( event.detail) )
}
lookup HTML
<p class="slds-p-horizontal_small">
<c-search-component
obj-name="ProviderEntity__c"
icon-name="standard:contact"
label-name="Provider Search"
placeholder="Search"
fields={fields}
display-fields={displayFields}
onlookup={handleLookup}
onchange={handleChange} >
</c-search-component>
</p>

So you have two independent components: a DataTable component and a LookUp Component. Based on a value that will be selected in the LookUp component, the DataTable should react and display some rows.
Because the two components are independent, you have two ways of communicating both components:
Using the Lightning Message Service (more info here https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.use_message_channel). You should create a new channel to which your DataTable component would subscribe. The LookUp component should publish some event to the Channel when the value is selected and the DataTable component would receive it and react accordingly
The second way to achieve the communication would be to create a parent component that would contain both components (the DataTable one and the LookUp one). Then, the parent component would listen to a CustomEvent fired by the LookupElement (https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.events_create_dispatch) and then react accordingly (maybe changing some parameter on the table or even just changing the "records" attribute). As an example, I've created some components that display the idea. I have not used exactly your lookup to simplify but the idea for the communication would be the same. In my case, the "c-custom-picklist" is acting as your Lookup component and the "c-custom-text-area" is acting as your DataTable Component. c-parent-component communicates both components:
Parent Component HTML:
<template>
<c-custom-picklist oncolor={handleColorChange}></c-custom-picklist>
<c-custom-text-area selected-color={selectedColor}></c-custom-text-area>
</template>
Parent Component JS:
import { LightningElement } from "lwc";
export default class ParentComponent extends LightningElement {
selectedColor = "No Color Selected";
handleColorChange(event) {
this.selectedColor = event.detail;
}
}
Picklist Component HTML:
<template>
<lightning-combobox
label="Select a Color"
placeholder="Select a Color"
options={options}
onchange={handleChange}
></lightning-combobox>
</template>
Picklist component JS:
import { LightningElement } from "lwc";
export default class CustomPicklist extends LightningElement {
options = [
{ value: "Red", label: "Red" },
{ value: "Blue", label: "Blue" },
{ value: "Pink", label: "Pink" }
];
handleChange(event) {
let selectedColor = event.detail.value; //Selected Color
this.dispatchEvent(new CustomEvent("color", { detail: selectedColor }));
}
}
Text Area HTML:
<template>
<textarea>
The selected color is: {selectedColor}
</textarea
>
</template>
Text Area JS:
import { LightningElement, api } from "lwc";
export default class CustomTextArea extends LightningElement {
#api selectedColor;
}

Related

Dynamic tag name in Solid JSX

I would like to set JSX tag names dynamically in SolidJS. I come from React where it is fairly simple to do:
/* Working ReactJS Code: */
export default MyWrapper = ({ children, ..attributes }) => {
const Element = "div";
return (
<Element {...attributes}>
{children}
</Element>
)
}
but when I try to do the same thing in SolidJS, I get the following error:
/* Console output when trying to do the same in SolidJS: */
dev.js:530 Uncaught (in promise) TypeError: Comp is not a function
at dev.js:530:12
at untrack (dev.js:436:12)
at Object.fn (dev.js:526:37)
at runComputation (dev.js:706:22)
at updateComputation (dev.js:691:3)
at devComponent (dev.js:537:3)
at createComponent (dev.js:1236:10)
at get children [as children] (Input.jsx:38:5)
at _Hot$$Label (Input.jsx:7:24)
at #solid-refresh:10:42
I would like to know if I miss something here, or whether it is possible to achieve this in SolidJS in any other way.
Solid has a <Dynamic> helper component for that use.
import {Dynamic} from "solid-js/web";
<Dynamic component="div" {...attributes}>
{props.children}
</Dynamic>
Here is an alternative implementation covering simple cases like strings and nodes although you can extend it to cover any JSX element:
import { Component, JSXElement} from 'solid-js';
import { render, } from 'solid-js/web';
const Dynamic: Component<{ tag: string, children: string | Node }> = (props) => {
const el = document.createElement(props.tag);
createEffect(() => {
if(typeof props.children === 'string') {
el.innerText = String(props.children);
} else if (props.children instanceof Node){
el.appendChild(props.children);
} else {
throw Error('Not implemented');
}
});
return el;
};
const App = () => {
return (
<div>
<Dynamic tag="h2">This is an H2!</Dynamic>
<Dynamic tag="p">This is a paragraph!</Dynamic>
<Dynamic tag="div"><div>Some div element rendering another div</div></Dynamic>
</div>
)
}
render(App, document.body);
This works because Solid components are compiled into native DOM elements, however since we do not escape the output, it is dangerous to render any children directly, given that you have no control over the content.
This alternative comes handy when you need to render rich text from a content editable or a textarea, text that includes tags like em, strong etc. Just make sure you use innerHTML attribute instead of innerText.

ionic4 display Object type data in template

I am having a problem displaying data in the angular template with Ionic 4.
Here is part of the code:
here I am sending data to the template page
item: any;
constructor(public route: ActivatedRoute, items: Items) {
this.route.queryParams.subscribe(item => {
this.item = item;
});
}
here I am reading the data
<div class="item-detail" padding>
<h2>{{item.name}}</h2>
<p>{{item.about}}</p>
</div>
here is the data object value returned from the console
{
name: "Burt Bear", profilePic: "assets/img/speakers/bear.jpg", about: "Burt is a Bear."}
So the question is, how do I display these values on the HTML template with angular? This is giving me an error:
FindingItemDetailPage.html:8 ERROR TypeError: Cannot read property
'name' of undefined
Init item prop, do not let it be any. e.g
public item = {}; // or a given class/model it's up to you.
Into the constructor there is no guarantee that item is there so ensure this case and apply appropariate behavior. (again it's up to you)
e.g
constructor(public route: ActivatedRoute, items: Items) {
this.route.queryParams.subscribe(item => {
this.item = item || {} // class/model would be better;
});
}
Lastly into the template you could set *ngIf
e.g
<div class="item-detail" padding *ngIf="item.name">
to ensure that there is an item, item.name is an example, you could use a property which guarantee that there is an item.

How to use "template" and "rowTemplate" field in syncfusion React DataGrid?

I am setting up a Data Grid table from React syncfusion in my application.
My application is built with antDesign Pro template, DvaJS, UmiJS, and ReactJS.
I have made the basic syncfusion Data Grid that uses default fields to fetch the cell data and it works fine.
As soon as I add "template" field to ColumnDirective or "rowTemplate" to GridComponent, I get the error.
import React, { Component } from 'react';
import router from 'umi/router';
import { connect } from 'dva';
import { Input } from 'antd';
import moment from 'react-moment';
import { ColumnDirective, ColumnsDirective, Filter, Grid, GridComponent } from '#syncfusion/ej2-react-grids';
#connect(({loading, data})=> ({
data: data.data,
loading: loading.effects['data/fetchData']
}))
class dataComponent extends Component {
constructor(){
super(...arguments);
this.state = {
data: [],
}
this.columnTemplate = this.columnTemplate;
}
columnTemplate(props) {
return (
<div className="image"><p>text</p></div>
);
}
render() {
const { match, children, location, dispatch, data} = this.props;
return (
<GridComponent dataSource={data}>
<ColumnsDirective>
<ColumnDirective headerText='Heading' template={this.columnTemplate}/>
<ColumnDirective field='EmployeeID' headerText='Employee ID'/>
<ColumnDirective field='FirstName' headerText='Name'/>
</ColumnsDirective>
</GridComponent>
);
}
Expected output:
Heading Employee ID FirstName
Text 123 John
In actual, it doesn't render anything after adding template field to code.
In console, I get this error:
Uncaught TypeError: str.match is not a function
at evalExp (template.js:65)
at compile (template.js:52)
at Object.push../node_modules/#syncfusion/ej2-grids/node_modules/#syncfusion/ej2-base/src/template-engine.js.Engine.compile (template-engine.js:57)
at compile (template-engine.js:16)
at templateCompiler (util.js:145)
at new Column (column.js:131)
at prepareColumns (util.js:185)
at GridComponent.push../node_modules/#syncfusion/ej2-grids/src/grid/base/grid.js.Grid.render (grid.js:704)
at GridComponent.push../node_modules/#syncfusion/ej2-react-grids/src/grid/grid.component.js.GridComponent.render (grid.component.js:35)
at finishClassComponent (react-dom.development.js:14741)
at updateClassComponent (react-dom.development.js:14696)
at beginWork (react-dom.development.js:15644)
at performUnitOfWork (react-dom.development.js:19312)
at workLoop (react-dom.development.js:19352)
at HTMLUnknownElement.callCallback (react-dom.development.js:149)
at Object.invokeGuardedCallbackDev (react-dom.development.js:199)
at invokeGuardedCallback (react-dom.development.js:256)
at replayUnitOfWork (react-dom.development.js:18578)
at renderRoot (react-dom.development.js:19468)
at performWorkOnRoot (react-dom.development.js:20342)
at performWork (react-dom.development.js:20254)
at performSyncWork (react-dom.development.js:20228)
at requestWork (react-dom.development.js:20097)
at scheduleWork (react-dom.development.js:19911)
at Object.enqueueSetState (react-dom.development.js:11169)
at DynamicComponent../node_modules/react/cjs/react.development.js.Component.setState (react.development.js:335)
at dynamic.js:91
When I click on template.js:65
it show the following as error:
var isClass = str.match(/class="([^\"]+|)\s{2}/g);
Here's the link to code I am trying to follow:
Syncfusion template example
I'd highly appreciate your help!
Query #1: How to use the column template on Syncfusion EJ2 Grid in React platform.
You can customize the own element instead of field value in EJ2 Grid column. Please refer the below code example, sample and Help Documentation for more information.
export class ColumnTemplate extends SampleBase {
constructor() {
super(...arguments);
this.template = this.gridTemplate;
}
gridTemplate(props) {
return (
<div className="image"><p>text</p></div> //Here defined your element
);
}
render() {
return (<div className='control-pane'>
<div className='control-section'>
<GridComponent dataSource={employeeData} width='auto' height='359'>
<ColumnsDirective>
<ColumnDirective headerText='Heading' width='180' template={this.template} textAlign='Center'/>
<ColumnDirective field='EmployeeID' headerText='Employee ID' width='125' textAlign='Right'/>
<ColumnDirective field='FirstName' headerText='Name' width='120'/>
</ColumnsDirective>
</GridComponent>
</div>
</div>);
}
}
Sample link: https://stackblitz.com/edit/react-gdraob?file=index.js
Help Documentation: https://ej2.syncfusion.com/react/documentation/grid/columns/#column-template
Query #2: How to use the row template on Syncfusion EJ2 Grid in React platform.
We have provided the EJ2 Grid with row template feature support it is allow template for the row and we have rendered element instead of each Grid row. Please refer the below code example, sample and Help Documentation for more information.
export class RowTemplate extends SampleBase {
constructor() {
super(...arguments);
this.format = (value) => {
return instance.formatDate(value, { skeleton: 'yMd', type: 'date' });
};
this.template = this.gridTemplate;
}
gridTemplate(props) {
return (<tr className="templateRow">
<td className="details">
<table className="CardTable" cellPadding={3} cellSpacing={2}>
. . . .
</tbody>
</table>
</td>
</tr>);
}
render() {
return (<div className='control-pane'>
<div className='control-section'>
<GridComponent dataSource={employeeData} rowTemplate={this.template.bind(this)} width='auto' height='335'>
<ColumnsDirective>
<ColumnDirective headerText='Employee Details' width='300' textAlign='Left' />
</ColumnsDirective>
</GridComponent>
</div>
</div>);
}
}
Sample link: https://stackblitz.com/edit/react-ka9ixk?file=index.js
Please get back to us, if you need further assistance.
Regards,
Thavasianand S.

Populating a select form by iterating through an object

Attempting to populate a select form using an object I'm passing to my component as a prop. The object looks like this: {24: {14: 64.99, 20: 89.99, 26: 114.99}, 30: {14: 74.99, 20: 99.99, 26: 124.99} and I'm attempting to isolate the 24 and the 30 as values in my form. Here's the relevant code:
import React, { Component } from 'react';
import { Row, Input } from 'react-materialize';
class HeightPicker extends Component {
constructor(props){
super(props);
this.state = {
height: '',
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({height: e.target.value});
}
displayHeights(){
let prices = this.props.prices;
let height;
let heights = [];
console.log(prices);
for (height in prices) {
heights.push(height)
};
console.log(heights);
heights.forEach(function(h) {
return(<option value={h}>{h}</option>);
});
}
render() {
return(
<div>
<Row>
<Input l={12} value={this.state.height} onChange={this.handleChange} type='select' label="Height">
{this.displayHeights()}
</Input>
</Row>
</div>
)
};
};
export default HeightPicker;
As constructed above, it's returning a blank form. If I hardcode options into my render function it works, therefore I'm assuming my issue is arising through my displayHeights function. But I also was running into some issues earlier with React Materialize and the version of React I was running -- had to downgrade versions from 16.0.0 to 15.6.2 -- so I'm wondering if it's related to that. Any help would be appreciated. Thanks.
Use map instead of forEach in displayHeights method
forEach method does some operation on each element of array or collection, but does not return the modified element, map method returns the modified element after some operation
Your implemenetation has two issues
you are using forEach which does not return the modified elements
you did not return the array containing options, in your case , heights
The modified code block will be
return heights.map(function(h) {
return(<option value={h}>{h}</option>);
});
This should be your displayHeights function. Here is a WORKING DEMO
You need to return the array back in your function and also map is what you should be using.
displayHeights(){
let prices = this.props.prices;
let height;
let heights = [];
console.log(prices);
for (height in prices) {
heights.push(height)
};
console.log(heights);
return heights.map(function(h, i) {
return(<option key={i} value={h}>{h}</option>);
});
}
Using forEach
let heightsJSX = [];
heights.forEach(function(h, i) {
heightsJSX.push(<option key={i} {value={h}>{h}</option>));
});
return heights;
For efficiency's sake, why not just map over the keys. Here's a one-liner.
displayHeight() {
return Object.keys(this.props.prices).map(key => <option key={key} value={key}>{key}</option>);
}

Angular 4 Create Dynamic formArray inside array using reactive forms

Here, we are creating dynamically form array's inside array.
Below is the sample structure of expected result given below.
{
"optionsRadios": null,
"Package_Title": null,
"HotelData": [
{
"Htitle": "",
"HDescription": "",
"hotelStar": "",
"RoomData": [
{
"Hotel_Room_Type": ""
},
{
"Hotel_Room_Type": ""
}
]
}
]
}
I want to create HotelData Dynamically, within that HotelData array i want to create RoomData array fields also dynamically.
I created HotelData fields by the following codes:
export class AddPackageComponent implements OnInit {
ngOnInit() {
this.invoiceForm = this._formBuild.group({
Package_Title: [],
HotelData: this._formBuild.array([this.addRows()])
})
}
addRows() {
return this._formBuild.group({
Htitle: [''],
HDescription: [''],
hotelStar: ['']
});
}
addHotel() {
const control: FormArray = this.invoiceForm.get(`HotelData`) as FormArray;
control.push(this.addRows());
}
}
You are on the right track, we just need to add some more code...
addRows need the form array RoomData, and here we also initially push an empty form group of room. If you don't want that, modify it.
addRows() {
let group = this._formBuild.group({
...
RoomData: this._formBuild.array([])
});
// push formgroup to array initially
this.addRoom(group.controls.RoomData)
return group;
}
addRoom looks like this:
addRoom(hotel:any) {
let group = this._formBuild.group({
Hotel_Room_Type: ['']
})
hotel.push(group)
}
addRoom is also the method we are calling from template when we want to add a new room to a hotel. Remember to pass the current hotel as parameter from template.
As for adding a new hotel, your addHotel stays the way you have it now.
Then over to your template, the relevant part should look something like this:
<div formArrayName="HotelData">
<div *ngFor="let hotel of invoiceForm.get('HotelData').controls; let i = index" [formGroupName]="i" >
<!-- form controls here -->
<button (click)="addRoom(hotel.get('RoomData'))">Add Room</button>
<div formArrayName="RoomData">
<div *ngFor="let room of hotel.get('RoomData').controls; let j = index" [formGroupName]="j">
<!-- form controls here -->
</div>
</div>
</div>
</div>
Finally, here's a Demo: http://plnkr.co/edit/7tcLcnzALew3oKGenjwK?p=preview