How to connect second datasource to the same kendo-grid - kendo-ui-angular2

I want to use the second datasource [data] to replace the IDs of a column with the NAMEs from another table(from another datasource) which have a common ID field. I have tried with kendo-grid-span-column, but it is working only with the same datasource as the grid.
<form novalidate #myForm="ngForm">
<kendo-grid
[data]="gridData | async"
<kendo-grid-column field="Contract_id" title="Contract_id"></kendo-grid-column>
<kendo-grid-span-column>
<kendo-grid-column field="Employee_id" title="Employee_id">
</kendo-grid-column>
<ng-template kendoGridCellTemplate let-dataItem>
<h4>{{dataItem.NameFromAnotherDataSource}}</h4>
</ng-template>
</kendo-grid-span-column>
<kendo-grid-column field="Contract_Num" title="Contract_Num"></kendo-grid-column>
</kendo-grid>
Any ideas?
Thanks,
Martin

I found the way.
container.component.html
<kendo-grid-column field="Employee_id" title="Employee" width="200">
<ng-template kendoGridCellTemplate let-dataItem>
<h4>{{category(dataItem.Employee_id)?.Name}}</h4>
</ng-template>
<ng-template kendoGridFilterCellTemplate let-filter>
<my-dropdown-filter
[filter]="filter"
[data]="employees"
textField="Name"
valueField="Employee_id">
</my-dropdown-filter>
</ng-template>
container.component.ts
private employees;
constructor(
public dataServiceEmployees: EmployeesDashboardService,
) {
this.dataServiceEmployees.fetch('Employee',this.gridState).subscribe((x) => this.employees = x.data);
}
public category (id: number): any {
if(this.employees){
return this.employees.find(x => x.Employee_id === id);
}
}
And using a filter template:
custom.filter.ts
import { Component, Input } from '#angular/core';
import { CompositeFilterDescriptor } from '#progress/kendo-data-query';
import { FilterService, BaseFilterCellComponent } from '#progress/kendo-angular-grid';
#Component({
selector: 'my-dropdown-filter',
template: `
<kendo-dropdownlist
[data]="data"
(valueChange)="onChange($event)"
[defaultItem]="defaultItem"
[value]="selectedValue"
[valuePrimitive]="true"
[textField]="textField"
[valueField]="valueField">
</kendo-dropdownlist>
`
})
export class DropDownListFilterComponent extends BaseFilterCellComponent {
public get selectedValue(): any {
const filter = this.filterByField(this.valueField);
return filter ? filter.value : null;
}
#Input() public filter: CompositeFilterDescriptor;
#Input() public data: any[];
#Input() public textField: string;
#Input() public valueField: string;
public get defaultItem(): any {
return {
[this.textField]: "Избери...",
[this.valueField]: null
};
}
constructor(filterService: FilterService) {
super(filterService);
}
public onChange(value: any): void {
this.applyFilter(
value === null ? // value of the default item
this.removeFilter(this.valueField) : // remove the filter
this.updateFilter({ // add a filter for the field with the value
field: this.valueField,
operator: "eq",
value: value
})
); // update the root filter
}
}

Related

Display Data in Angular Material Table

I'm struggling with the Angular mat-table library. I got an app with a Node.js backend and an Angular frontend. The node app provides data from a MySQL database in JSON.
Now I want to display this data in a mat-table. I have logged the data in the console, which allows me to see that the data is actually retrieved but just not displayed.
However, the HTML table is empty:
This is my Angular component:
component.html
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8 demo-table">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef>ID</th>
<td mat-cell *matCellDef="let element">{{element.id}}</td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Name</th>
<td mat-cell *matCellDef="let element">{{element.name}}</td>
</ng-container>
<ng-container matColumnDef="pop">
<th mat-header-cell *matHeaderCellDef>Population</th>
<td mat-cell *matCellDef="let element">{{element.population}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr
mat-row
(click)="clickedRows.add(row)"
[class.demo-row-is-clicked]="clickedRows.has(row)"
*matRowDef="let row; columns: displayedColumns;"
></tr>
</table>
component.ts
import {Component, OnInit,ViewChild} from '#angular/core';
import { Player } from '../player';
import { PlayerService } from '../player.service';
import { MatTableDataSource } from '#angular/material/table';
import {MatPaginator} from '#angular/material/paginator';
import {MatSort, SortDirection} from '#angular/material/sort';
/**
* #title Binding event handlers and properties to the table rows.
*/
#Component({
selector: 'app-players',
styleUrls: ['players.component.css'],
templateUrl: 'players.component.html',
})
export class PlayersComponent implements OnInit {
displayedColumns: string[] = ['id', 'name', 'pop'];
dataSource = new MatTableDataSource<Player>();
#ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
#ViewChild(MatSort, { static: true }) sort!: MatSort;
constructor(private playerService:PlayerService) { }
ngOnInit(): void {
this.getPlayers();
}
getPlayers() {
this.playerService.getPlayers().subscribe(players => {
console.log(players);
this.dataSource.data = players;
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
});
}
clickedRows = new Set<Player>();
}
player.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable, of } from 'rxjs';
import { Player } from './player';
#Injectable({
providedIn: 'root'
})
export class PlayerService {
constructor(private http: HttpClient) { }
rootURL = '/api';
getPlayers(): Observable<Player[]> {
return this.http.get<Player[]>(this.rootURL+ '/players');
}
}
Any ideas on this?
EDIT:
Could it have something to do with how the array comes back from the API? In Node.js it is retrieved with sequelize and maybe it is the response?
// Get all Players
exports.findAll = (req, res) => {
Player.findAll().then((players) => {
// Send all players as response
res.status(200).json({
status: true,
data: players,
});
});
};
Issue
The data returned based on the screenshot and Node.js API is not Player array, but it is an object with status and data properties.
{
"status": 200,
"data": [...]
}
This line expected that HTTP GET to receive Player array which is conflict with your data.
this.http.get<Player[]>(this.rootURL+ '/players');
Hence, it returns Observable with an empty array and your <mat-table> will not show the data.
Solution
Transform the data to Player array with map rxjs operator.
import { map } from 'rxjs';
getPlayers(): Observable<Player[]> {
return this.http
.get(this.rootURL+ '/players')
.pipe(map((response: any) => response.data as Player[]));
}
Sample Solution on StackBlitz
Create a instance of MatTableDataSource and assign to data Source.
Try this option
this.dataSource = new MatTableDataSource(players);
this.dataSource.sort = this.sort;
Ref: Using HTTP GET request to fetch data in an Angular Material Table

SharePoint react TextField onChanged error

When 'gulp serve' my SharePoint extension solution, I am receiving this error
Type { label: string; required: true; name: string; defaultValue: string; onChanged: (text: string) => void; } is not assignable to type 'IntrinsicAttributes & ITextFieldProps & { children?: ReactNode; }'.
Property 'onChanged' does not exist on type 'IntrinsicAttributes & ITextFieldProps & { children?: ReactNode; }.ts(2322)
I am using:
#microsoft/generator-sharepoint#1.10.0
gulp#4.0.2
npm#6.14.4
yo#3.1.1
SendEMailDialogContent.tsx file code below:
import * as React from 'react';
import {
TextField,
PrimaryButton,
Button,
DialogFooter,
DialogContent,
Spinner,
SpinnerSize
} from 'office-ui-fabric-react';
import { ISendEMailDialogContentProps } from './ISendEMailDialogContentProps';
import { EMailProperties, EMailAttachment } from '../models';
import { ISendEMailDialogContentState } from './ISendEMailDialogContentState';
export class SendEMailDialogContent extends React.Component<ISendEMailDialogContentProps, ISendEMailDialogContentState> {
private _eMailProperties: EMailProperties;
constructor(props) {
super(props);
this.state = {
isLoading: false
};
this._eMailProperties = this.props.eMailProperties;
this._onChangedTo = this._onChangedTo.bind(this);
this._onChangedSubject = this._onChangedSubject.bind(this);
this._onChangedBody = this._onChangedBody.bind(this);
this._submit = this._submit.bind(this);
}
public render(): JSX.Element {
var getDialogContent = () => {
if (this.state.isLoading) {
return (
<Spinner size={SpinnerSize.large} label="loading..." ariaLive="assertive" />
);
}
else {
return (
<div>
<TextField label='To' required={true} name="To" defaultValue={this._eMailProperties.To} onChanged={this._onChangedTo} />
<TextField label='Subject' required={true} defaultValue={this._eMailProperties.Subject} onChanged={this._onChangedSubject} />
<TextField label='Body' required={true} multiline defaultValue={this._eMailProperties.Body} onChanged={this._onChangedBody} />
<DialogFooter>
<Button text='Cancel' title='Cancel' onClick={this.props.close} />
<PrimaryButton text='OK' title='OK' onClick={this._submit} />
</DialogFooter>
</div>);
}
};
// UI
return <DialogContent
title='Send E-Mail Details'
subText=''
onDismiss={this.props.close}
showCloseButton={true}
>
{getDialogContent()}
</DialogContent>;
}
private _onChangedSubject(text: string) {
this._eMailProperties.Subject = text;
}
private _onChangedTo(text: string) {
this._eMailProperties.To = text;
}
private _onChangedBody(text: string) {
this._eMailProperties.Body = text;
}
The error is related to the TextField - onChanged={this._onChangedxxxxx}
Depending on the version of office-ui-fabric-react being used, it's likely that this callback is now called onChange instead of onChanged.
In addition to being renamed, onChange expects the first argument to be a react event, so you'll need to change your onChanged* methods to expect one more argument (and they're free to ignore it).
Relevant documentation: https://developer.microsoft.com/en-us/fluentui#/controls/web/textfield#implementation

react-admin: Access <List/> records from bulkActions

The docs state that bulkActions don't get selected records of a List component, just the selected ids, but i need to check a specific field in each selected record from a button's click handler of the bulkActionsButtons.
Any ideas of how this could be achieved?
I used the aside prop that is passed to the List component. Your ref based solution did not work for me.
https://marmelab.com/react-admin/List.html#aside-component
Check the above link.
The component passed as the aside prop to List component receives selectedIds and the data as part of the props.
Just to expand on #Sudharsan-Ravikumar's answer, the ref solution didn't work in my situation either (react-admin 3.14.1, using classes instead of functions+hooks mostly). I used aside like this...
import React, {Fragment} from 'react';
import {List, Datagrid, TextField, useListContext} from 'react-admin';
import Button from '#material-ui/core/Button';
import AccountTreeIcon from '#material-ui/icons/AccountTree'
import dataProvider from './dataProvider';
export class MyList extends React.Component {
list = null
handleStartMyTask = async () => {
if (!this.list || !this.list.ids || this.list.ids.length===0) return;
// console.log(`LIST DATA:`, this.list.data)
try {
const result = await dataProvider.doMyRemoteTask(this.list.data)
console.log(result)
}
catch(err) {
console.log()
}
}
/**
* This was the only way i could figure out how to get the list details.
* That is, the props you get access to when you do useListContext().
* Refs didn't work.
*/
Aside = () => {
this.list = useListContext()
// We don't actually want an aside component for the list.
return null;
}
render = () => {
return <Fragment>
/* A little card outside the list with some useful buttons */
<div class="card">
<Button
variant="outlined" color="primary" size="medium"
onClick={this.handleStartMyTask}
>
<AccountTreeIcon/> Start my task now!
</Button>
</div>
<List {...this.props} aside={<this.Aside/>} >
<Datagrid>
<TitleField source="title" />
</Datagrid>
</List>
</Fragment>
}
}
Probably absolute heresy to hooks dorks but life is short!
Ok, this is what i did and it works. A combination of a render prop and a ref.
Please if anyone have a better idea, please let me now.
import React, {Component} from 'react';
import {
List,
Datagrid,
TextField,
Button
} from 'react-admin';
class MyBulkButtons extends Component {
handleClick = () => {
const {getSelectedRecords} = this.props;
const records = getSelectedRecords();
const selectedRecords = records.filter(i => i.title === 'Extra action!');
this.processExtraActions(selectedRecords);
};
processExtraActions(selectedRecords) {
//...
}
render() {
return (
<Button onClick={this.handleClick} label={"Check for extra actions"}/>
);
}
}
export class MyList extends Component {
constructor(props) {
super(props);
this.myDataGrid = React.createRef();
}
getSelectedRecords() {
const gridProps = this.myDataGrid.current.props;
return gridProps.selectedIds.map(id => gridProps.data[id]);
}
render() {
return (
<List {...this.props}
bulkActionButtons={<MyBulkButtons getSelectedRecords={this.getSelectedRecords.bind(this)}/>}>
<Datagrid ref={this.myDataGrid}>
<TextField source="id"/>
<TextField source="title"/>
</Datagrid>
</List>
);
}
}

Perform action in parent on child event click

So I'm trying to perform some action on the parent component of the child component when a click event is fired in the child component. Currently I have a dynamic loader which is able to load different child components. The problem I have is that the #Output() is being emitted but the parent component doesn't seem to have any knowledge when this event is fired. Is there something I am missing?
child2.component.ts
import {Component, Injector, Output, EventEmitter} from '#angular/core';
#Component({
selector: 'hello-world',
template: `
<div>Hello World {{showNum}}</div>
<li (click)="childButtonClicked(false)"> </li>
`,
})
export class HelloWorldComponent {
showNum = 0;
#Output() childEvent = new EventEmitter<boolean>();
constructor(private injector: Injector) {
this.showNum = this.injector.get('showNum');
console.log("HelloWorldComponent");
}
childButtonClicked(agreed: boolean) {
this.childEvent.emit(agreed);
console.log("clicked");
}
}
child1.component.ts
import {Component, Injector, Output, EventEmitter} from '#angular/core';
#Component({
selector: 'world-hello',
template: `
<div>World Hello {{showNum}}</div>
<li (click)="childButtonClicked(false)"> </li>
`,
})
export class WorldHelloComponent {
showNum = 0;
#Output() childEvent = new EventEmitter<boolean>();
constructor(private injector: Injector) {
this.showNum = this.injector.get('showNum');
console.log("WorldHelloComponent");
}
childButtonClicked(agreed: boolean) {
this.childEvent.emit(agreed);
console.log("clicked");
}
}
dynamic.componentloader.ts
import {Component, Input, ViewContainerRef,ComponentRef, ViewChild, ReflectiveInjector, ComponentFactoryResolver} from '#angular/core';
#Component({
selector: 'dynamic-component',// Reference to the components must be here in order to dynamically create them
template: `
<div #dynamicComponentContainer></div>
`,
})
export class DynamicComponent {
currentComponent:any = null;
#ViewChild('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainer: ViewContainerRef;
// component: Class for the component you want to create
// inputs: An object with key/value pairs mapped to input name/input value
#Input() set componentData(data: {component: any, inputs: any }) {
if (!data) {
return;
}
// Inputs need to be in the following format to be resolved properly
let inputProviders = Object.keys(data.inputs).map((inputName) => {return {provide: inputName, useValue: data.inputs[inputName]};});
let resolvedInputs = ReflectiveInjector.resolve(inputProviders);
// We create an injector out of the data we want to pass down and this components injector
let injector = ReflectiveInjector.fromResolvedProviders(resolvedInputs, this.dynamicComponentContainer.parentInjector);
// We create a factory out of the component we want to create
let factory = this.resolver.resolveComponentFactory(data.component);
// We create the component using the factory and the injector
let component = factory.create(injector);
// We insert the component into the dom container
this.dynamicComponentContainer.insert(component.hostView);
// We can destroy the old component is we like by calling destroy
if (this.currentComponent) {
this.currentComponent.destroy();
}
this.currentComponent = component;
}
constructor(private resolver: ComponentFactoryResolver) {
}
}
main.component.ts
import { Component, OnInit } from '#angular/core';
import { HelloWorldComponent } from '../../views/main/sidebar-views/comps/hello-world.component';
import { WorldHelloComponent } from '../../views/main/sidebar-views/comps/world-hello.component';
#Component({
selector: 'main-component',
template: require('./main.component.html')
})
export class MainComponent {
private pressed: boolean = false;
componentData:any = null;
constructor() { }
createHelloWorldComponent(){
this.componentData = {
component: HelloWorldComponent,
inputs: {
showNum: 9
}
};
}
createWorldHelloComponent(){
this.componentData = {
component: WorldHelloComponent,
inputs: {
showNum: 2
}
};
}
test(){
console.log("some click event");
}
};
main.component.html
<div>
<h2>Lets dynamically create some components!</h2>
<button (click)="createHelloWorldComponent()">Create Hello World</button>
<button (click)="createWorldHelloComponent()">Create World Hello</button>
<dynamic-component [componentData]="componentData" (childEvent)="test()"></dynamic-component>
</div>
Since you are passing a parameter to the EventEmitter, you need to change your event binding on your component selector in your template to this:
<dynamic-component [componentData]="componentData" (childEvent)="test($event)"></dynamic-component>
Also, don't forget to change function signature in your component to accept the parameter:
test(agreed: boolean){
console.log("some click event");
}
More info on official docs.

Angular2: how can component bind to class?

I can't find the obvious evidence that how a #Component() binds to a class.
How can it know component binds with class RedditArticle instead of class Article? After switching the position of the two class, it is messed up. Does that mean the class we need to bind should followed by the corresponding component?
import { bootstrap } from "angular2/platform/browser";
import { Component } from "angular2/core";
#Component({
selector: 'reddit-article',
template: `
<div class="row">
<div class="col-md-3"></div>
<div>Points: {{article.votes}}</div>
<div class="col-md-9"></div>
<!--<div class="row">-->
Title: {{article.title}}
Link: {{article.link}}
<button (click)="voteUp()">upvote</button>
<button (click)="voteDown()">downvote</button>
<!--</div>-->
</div>
`
})
class RedditArticle {
article: Article;
constructor() {
this.article = new Article('angular2', 'google.com', 0);
}
voteUp() {
this.article.votes++;
}
voteDown() {
this.article.votes--;
}
}
class Article {
title: string;
link: string;
votes: number;
constructor(title: string, link: string, votes: number) {
this.title = title;
this.link = link;
this.votes = votes;
}
}
The #Component() decorator applies directly to the class that follows the annotation.
This is the same for all annotations.
For example
constructor(#Inject('xxx') private val:string, private zone:NgZone) {}
Here #Inject() is bound to val

Resources