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

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.

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.

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

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;
}

CDK drag-n-drop auto scroll

I have an Angular 7 app, using CDK Drag-n-Drop to drag and drop rows in a very long list.
What should I do to allow the long list to auto scroll when the dragged item out of the current view?
Any sample code I can refer to?
I have faced the same issue, It happens anytime an outside element is scrollable. This is the open issue - https://github.com/angular/components/issues/16677. - I have slightly modified the solution mentioned in this link.
import { Directive, Input, ElementRef, AfterViewInit } from '#angular/core';
import { CdkDrag } from '#angular/cdk/drag-drop';
#Directive({
selector: '[cdkDrag],[actualContainer]',
})
export class CdkDropListActualContainerDirective {
#Input('actualContainer') actualContainer: string;
originalElement: ElementRef<HTMLElement>;
constructor(cdkDrag: CdkDrag) {
cdkDrag._dragRef.beforeStarted.subscribe( () => {
var cdkDropList = cdkDrag.dropContainer;
if (!this.originalElement) {
this.originalElement = cdkDropList.element;
}
if ( this.actualContainer ) {
const element = this.originalElement.nativeElement.closest(this.actualContainer) as HTMLElement;
cdkDropList._dropListRef.element = element;
cdkDropList.element = new ElementRef<HTMLElement>(element);
} else {
cdkDropList._dropListRef.element = cdkDropList.element.nativeElement;
cdkDropList.element = this.originalElement;
}
});
}
}
Template
<div mat-dialog-content class="column-list">
<div class="column-selector__list">
<div cdkDropList (cdkDropListDropped)="drop($event)">
<div
*ngFor="let column of data"
cdkDrag
actualContainer="div.column-list"
>
</div>
</div>
</div>
</div>
As mentioned here you just need to add cdkScrollable to your list container.

Draftail mention plugin for wagtail

I would like to customise the draftail editor in wagtail in order to have "mentions" functionality (I am using the draft js plugin)
Because my react skills are extremely poor and my knowledge of draftail/draftjs very limited, I am using https://github.com/vixdigital/wagtail-plugin-base as a starting point (without knowing much about what it does) and trying to muddle through the task
I have managed to get it so the mention functionality appears in wagtail. The problem is that it appears in a "sub" editor in the toolbar
enter image description here
How can I avoid creating a sub editor and have it work in the body of the existing editor? Below are the two main JS files in my "wagtail-plugin-base"
index.js
import AutoComplete from './AutoComplete/AutoComplete.js';
window.draftail.registerControl(AutoComplete)
AutoComplete.js
import React, { Component } from '../../node_modules/react';
import createMentionPlugin, { defaultSuggestionsFilter } from '../../node_modules/draft-js-mention-plugin';
import editorStyles from './editorStyles.css';
import { mentions } from './mentions'
import { DraftailEditor, BLOCK_TYPE, INLINE_STYLE, createEditorState } from "draftail"
const mentionPlugin = createMentionPlugin();
const AutoComplete = class SimpleMentionEditor extends Component {
state = {
editorState: createEditorState,
suggestions: mentions,
};
onChange = (editorState) => {
this.setState({
editorState,
});
};
onSearchChange = ({ value }) => {
this.setState({
suggestions: defaultSuggestionsFilter(value, mentions),
});
};
onAddMention = () => {
// get the mention object selected
}
focus = () => {
this.editor.focus();
};
render() {
const { MentionSuggestions } = mentionPlugin
return (
<div>
<DraftailEditor
editorState={this.state.editorState}
onChange={this.onChange}
plugins={[mentionPlugin]}
ref={(element) => { this.editor = element; }}
/>
<MentionSuggestions
onSearchChange={this.onSearchChange}
suggestions={this.state.suggestions}
onAddMention={this.onAddMention}
/>
</div>
);
}
}
export default AutoComplete;
Any pointers much appreciated!

Ag-Grid Link with link in the cell

I am building angular 4 app with ag-grid and I am having an issue with trying to figure out how to put a link in the cell. Can anybody help me with that issue?
Thanks
Please check this demo
cellRenderer: function(params) {
return ''+ params.value+''
}
In this demo, the cell value for the column 'city' is a hyperlink.
I struggled with this the other day and it was bit more complex than I first thought. I ended up with creating a renderer component to which I send in the link and that needed a bit on NgZone magic to work all the way. You can use it in your column definition like this:
cellRendererFramework: RouterLinkRendererComponent,
cellRendererParams: {
inRouterLink: '/yourlinkhere',
}
Component where inRouterLink is the link that you send in and params.value is the cell value. That means that you can route to your angular route that could look something like 'yourlink/:id'. You could also simplify this a bit if you don't want a more generic solution by not sending in the link and just hard coding the link in the template and not using the cellRendererParams.
import { Component, NgZone } from '#angular/core';
import { Router } from '#angular/router';
import { AgRendererComponent } from 'ag-grid-angular';
#Component({
template: '<a [routerLink]="[params.inRouterLink,params.value]" (click)="navigate(params.inRouterLink)">{{params.value}}</a>'
})
export class RouterLinkRendererComponent implements AgRendererComponent {
params: any;
constructor(
private ngZone: NgZone,
private router: Router) { }
agInit(params: any): void {
this.params = params;
}
refresh(params: any): boolean {
return false;
}
// This was needed to make the link work correctly
navigate(link) {
this.ngZone.run(() => {
this.router.navigate([link, this.params.value]);
});
}
}
And register it in
#NgModule({
imports: [
AgGridModule.withComponents([
RouterLinkRendererComponent,
])
],
})
UPDATE: I have written a blog post about this: https://medium.com/ag-grid/enhance-your-angular-grid-reports-with-formatted-values-and-links-34fa57ca2952
This is a bit dated, but it may help someone. The solution with typescript on Angular 5 is similar to what C.O.G has suggested.
In the component's typescript file, the column definition can contain a custom cell rendering function.
columnDefs = [
{headerName: 'Client', field: 'clientName' },
{headerName: 'Invoice Number', field: 'invoiceNumber',
cellRenderer: (invNum) =>
`<a href="/invoice/${invNum.value}" >${invNum.value}</a>` },
];
The lambda function is called while rendering the cell. The 'value' of the parameter that gets passed is what you can use to generate custom rendering.
Inspired by #Michael Karén
This is a improved version that is more flexible.
We can set what text to display in link
We can pass more than 2 routerLink parameters
Resolve routerLink according to data
Support target
Display text only if link is not applicable
And more if you wanted to add, just further edit this component
import { Component } from '#angular/core';
import { ICellRendererAngularComp } from 'ag-grid-angular';
export interface IRouterLinkRendererComponentOptions {
routerLinkParams?: any[];
linkDescription?: string;
textOnly?: string;
target?: string;
}
#Component({
template: `
<a *ngIf="params.textOnly == null; else textOnlyBlock"
[routerLink]="params.routerLinkParams"
[target]="params.target ? params.target : '_self'"
>
{{ params.linkDescription }}
</a>
<ng-template #textOnlyBlock>
{{ params.textOnly }}
</ng-template>
`
})
export class RouterLinkRendererComponent implements ICellRendererAngularComp {
params: IRouterLinkRendererComponentOptions;
agInit(params: any): void {
this.params = params.routerLinkRendererComponentOptions(params);
}
refresh(params: any): boolean {
return true;
}
}
So that we can dynamically resolve parameters and return text only if wanted in column definition by
{
...
cellRendererFramework: RouterLinkRendererComponent,
cellRendererParams: {
routerLinkRendererComponentOptions: (param): IRouterLinkRendererComponentOptions => {
if (param.data.dispatch_adjustment) {
return {
routerLinkParams: ['/adjustments', param.data.dispatch_adjustment.id, 'edit'],
linkDescription: '#' + param.data.dispatch_adjustment.id
};
} else {
return {
textOnly: '-'
};
}
}
},
...
},
Instead of using href in cellRenderer , it's better to use cellrenderer framework as route link works in it.
Another Disadvantage is if you use href then the entire angular application will reload again it changes the navigation state from imperative to popstate. The angular router works on the imperative state.
I had implemented something similar to Michael and Tom, with only [routerLink] and no (click) handler. But recently I started getting the dreaded warning:
Navigation triggered outside Angular zone, did you forget to call 'ngZone.run()'?
After experimenting for awhile I found this post and added the navigate click handler function, which made the application start working again, however I found that the 'Navigation triggered outside Angular zone' message was still appearing in the logs.
So while the (click)="navigate()" call triggers the navigation inside the ngZone,the [routerLink] call is still being made, which bothered me. I really didn't want two attempts to navigate to happen - in case anything changed with a future API update.
I decided to replace the anchor tag with a span pseudoLink.
.pseudoLink {
color: blue;
text-decoration: underline;
cursor: pointer;
}
#Component({
template: '<span class="pseudoLink" (click)="navigate()">{{mytitle}}</span>'
})
navigate() {
this.ngZone.run(
() => {
console.log("LinkRendererComponent: navigate: (", this.mylink, ")");
this.router.navigate([this.mylink]);
}
);
}
this.mylink is defined in the agInit() method based on parameters passed in via cellRendererParams.
This works well for my main purpose which is to make the cell look like a link. Only thing I lost was the URL path popup in the browser status bar.
Hope this might help someone else.
Using a cell renderer is the correct solution but missing from the top answer is stopping the click event from reaching AgGrid:
cellRenderer: ({value}) => {
const a = document.createElement('a');
a.innerText = a.href = value;
a.target = '_blank';
// Prevent click from reaching AgGrid
a.addEventListener('click', event => { event.stopPropagation() });
return a;
}
If the click bubbles up to AgGrid it will cause row selection changes, etc if those are enabled.
I created a generic component that is usable for any link cell, uses no workarounds, and logs no warnings.
Usage
columnDefs = [
{
colId: 'My Column',
cellRendererFramework: AgGridLinkCellComponent,
cellRendererParams: {
// `text` and `link` both accept either an string expression (same as `field`) or a function that gets ICellRendererParams
text: 'title',
link: (params: ICellRendererParams) => `/my-path/${_.get(params, 'data.id')}`
}
}
]
Register the component in your AppModule:
imports: [
AgGridModule.withComponents([
AgGridLinkCellComponent
])
]
The component itself:
import * as _ from 'lodash';
import {Component} from '#angular/core';
import {AgRendererComponent} from 'ag-grid-angular';
import {ICellRendererParams} from 'ag-grid-community';
#Component({
selector: 'app-ag-grid-link-cell-component',
template: '<a [routerLink]="link">{{ text }}</a>',
})
export class AgGridLinkCellComponent implements AgRendererComponent {
link: string;
text: string;
constructor() {
}
agInit(params: ICellRendererParams): void {
this.refresh(params);
}
refresh(params: ICellRendererParams): boolean {
const dataParams = params.colDef.cellRendererParams;
this.link = _.isFunction(dataParams.link) ? dataParams.link(params) : _.get(params.data, dataParams.link);
this.text = _.isFunction(dataParams.text) ? dataParams.link(params) : _.get(params.data, dataParams.text);
return false;
}
}
We had this problem, and its not straightforward.
We ended up solving it in a different way as we use AdapTable on top of ag-Grid.
So we created an AdapTable Action Column and in the RenderFunction provided the link. That worked best for us as we didnt always want the Link to appear so we could use the ShouldRender function to decide whether or not we wanted to display link for each row.