To see a demonstration of the master-detail grid, please visit the master-detail grid demo. To enable in-cell editing, you must add the following events to both the master and detail grid components:
- cellClick -> Display input when a cell is clicked
- cellClose -> Save data when leaving a cell
//Master component
@Component({
providers: [CategoriesService],
selector: 'my-app',
template: `
<kendo-grid
[data]="view | async"
[loading]="view.loading"
[pageSize]="pageSize"
[skip]="skip"
[sortable]="true"
[sort]="sort"
[pageable]="true"
[height]="550"
[navigable]="true"
(dataStateChange)="dataStateChange($event)"
(cellClick)="cellClickHandler($event)"
(cellClose)="cellCloseHandler($event)"
>
<kendo-grid-column field="CategoryID" width="100"></kendo-grid-column>
<kendo-grid-column field="CategoryName" width="200" title="Category Name"></kendo-grid-column>
<kendo-grid-column field="Description" [sortable]="false">
</kendo-grid-column>
<div *kendoGridDetailTemplate="let dataItem">
<category-details [category]="dataItem"></category-details>
</div>
</kendo-grid>
`
})
export class AppComponent implements OnInit, AfterViewInit {
public view: Observable<GridDataResult>;
public sort: Array<SortDescriptor> = [];
public pageSize = 10;
public skip = 0;
@ViewChild(GridComponent) grid: GridComponent;
constructor(private formBuilder: FormBuilder, private service: CategoriesService) { }
public ngOnInit(): void {
// Bind directly to the service as it is a Subject
this.view = this.service;
// Fetch the data with the initial state
this.loadData();
}
public dataStateChange({ skip, take, sort }: DataStateChangeEvent): void {
// Save the current state of the Grid component
this.skip = skip;
this.pageSize = take;
this.sort = sort;
// Reload the data with the new state
this.loadData();
}
public ngAfterViewInit(): void {
// Expand the first row initially
this.grid.expandRow(0);
}
private loadData(): void {
this.service.query({ skip: this.skip, take: this.pageSize, sort: this.sort });
}
public cellClickHandler({ sender, rowIndex, columnIndex, dataItem, isEdited }) {
if (!isEdited) {
console.log(sender);
sender.editCell(rowIndex, columnIndex, this.createFormGroup(dataItem));
}
}
public cellCloseHandler(args: any) {
const { formGroup, dataItem } = args;
if (!formGroup.valid) {
// prevent closing the edited cell if there are invalid values.
args.preventDefault();
} else if (formGroup.dirty) {
console.log("save data")
}
}
public createFormGroup(dataItem: any): FormGroup {
return this.formBuilder.group({
'CategoryID': dataItem.CategoryID,
'CategoryName': [dataItem.CategoryName, Validators.required],
'Description': [dataItem.Description, Validators.required],
});
}
}
//Detail component
@Component({
selector: 'category-details',
providers: [ProductsService],
template: `
<kendo-grid
[data]="view | async"
[loading]="view.loading"
[pageSize]="5"
[skip]="skip"
[pageable]="true"
[scrollable]="'none'"
(pageChange)="pageChange($event)"
[navigable]="true"
kendoGridFocusable
(cellClick)="cellClickDetailsHandler($event)"
(cellClose)="cellCloseDetailsHandler($event)"
>
<kendo-grid-column field="ProductID" title="Product ID" width="120">
</kendo-grid-column>
<kendo-grid-column field="ProductName" title="Product Name">
</kendo-grid-column>
<kendo-grid-column field="UnitPrice" title="Unit Price" format="{0:c}">
</kendo-grid-column>
</kendo-grid>
`
})
export class CategoryDetailComponent implements OnInit {
/**
* The category for which details are displayed
*/
@Input() public category: Object;
public view: Observable<GridDataResult>;
public skip = 0;
constructor(private formBuilder: FormBuilder,private service: ProductsService) { }
public ngOnInit(): void {
this.view = this.service;
/*load products for the given category*/
this.service.queryForCategory(this.category, { skip: this.skip, take: 5 });
}
public pageChange({ skip, take }: PageChangeEvent): void {
this.skip = skip;
this.service.queryForCategory(this.category, { skip, take });
}
public cellClickDetailsHandler({ sender, rowIndex, columnIndex, dataItem, isEdited }) {
if (!isEdited) {
console.log(sender);
sender.editCell(rowIndex, columnIndex, this.createFormGroupDetail(dataItem));
}
}
public cellCloseDetailsHandler(args: any) {
const { formGroup, dataItem } = args;
if (!formGroup.valid) {
// prevent closing the edited cell if there are invalid values.
args.preventDefault();
} else if (formGroup.dirty) {
console.log("save data")
}
}
public createFormGroupDetail(dataItem: any): FormGroup {
return this.formBuilder.group({
'ProductID': dataItem.ProductID,
'ProductName': [dataItem.ProductName, Validators.required],
'UnitPrice': dataItem.UnitPrice,
'UnitsInStock': [dataItem.UnitsInStock, Validators.compose([Validators.required, Validators.pattern('^[0-9]{1,3}')])],
'Discontinued': dataItem.Discontinued
});
}
}