There are multiple conditions related to whether a detail row needs to be displayed, whether a master-grid row is expanded, etc., for example related to the conditional display of the detail template:
To gain a better understanding of the inner logic and mechanics of the various Grid functionalities and rendering logic, you can obtain the whole Grid source code, and inspect it as described in the following section of our documentation:
import { Component, Input, SimpleChange, NgZone, Renderer2, ElementRef, OnInit, OnDestroy } from
'@angular/core'
;
import { GroupDescriptor } from
'@progress/kendo-data-query'
;
import { ColumnBase } from
'../columns/column-base'
;
import { DetailTemplateDirective } from
'./details/detail-template.directive'
;
import { DetailsService } from
'./details/details.service'
;
import { GroupsService } from
'../grouping/groups.service'
;
import { GroupItem, Item, GroupFooterItem } from
'../data/data.iterators'
;
import { ChangeNotificationService } from
'../data/change-notification.service'
;
import { isChanged, isPresent } from
'../utils'
;
import { NoRecordsTemplateDirective } from
'./no-records-template.directive'
;
import { EditService } from
'../editing/edit.service'
;
import { LocalizationService } from
'@progress/kendo-angular-l10n'
;
import { RowClassFn } from
'./common/row-class'
;
import { SelectableSettings } from
'../selection/selectable-settings'
;
import { columnsToRender, columnsSpan } from
"../columns/column-common"
;
import { closest, closestInScope, hasClasses, isFocusable, matchesClasses, matchesNodeName } from
'./common/dom-queries'
;
import { DomEventsService } from
'../common/dom-events.service'
;
import { SelectionService } from
"../selection/selection.service"
;
import { ColumnInfoService } from
"../common/column-info.service"
;
import { FilterableSettings, hasFilterRow } from
'../filtering/filterable'
;
import { CellKeydownEvent } from
'../navigation/cell-keydown-event'
;
import { Subscription } from
'rxjs/Subscription'
;
import { NavigationService } from
'../navigation/navigation.service'
;
import { Keys } from
'../common/keys'
;
const NON_DATA_CELL_CLASSES =
'k-hierarchy-cell k-detail-cell k-group-cell'
;
const NON_DATA_ROW_CLASSES =
'k-grouping-row k-group-footer k-detail-row k-grid-norecords'
;
const IGNORE_TARGET_CLASSSES =
'k-icon'
;
const IGNORE_CONTAINER_CLASSES =
'k-widget k-grid-ignore-click'
;
const columnCellIndex = (cell, cells) => {
let cellIndex = 0;
for
(let idx = 0; idx < cells.length; idx++) {
if
(cells[idx] === cell) {
return
cellIndex;
}
if
(!hasClasses(cells[idx],
'k-hierarchy-cell k-group-cell'
)) {
cellIndex++;
}
}
};
/**
* @hidden
*/
@Component({
selector:
'[kendoGridTableBody]'
,
// tslint:disable-line:component-selector
template: `
<ng-template [ngIf]=
"editService.hasNewItem"
>
<tr class=
"k-grid-add-row k-grid-edit-row"
kendoGridLogicalRow
[logicalRowIndex]=
"addRowLogicalIndex()"
[logicalSlaveRow]=
"lockedColumnsCount > 0"
[logicalCellsCount]=
"columns.length"
[logicalSlaveCellsCount]=
"unlockedColumnsCount"
>
<ng-template [ngIf]=
"!skipGroupDecoration"
>
<td [class.k-group-cell]=
"true"
*ngFor=
"let g of groups"
role=
"presentation"
></td>
</ng-template>
<td [class.k-hierarchy-cell]=
"true"
*ngIf=
"detailTemplate?.templateRef"
kendoGridLogicalCell
[logicalRowIndex]=
"addRowLogicalIndex()"
[logicalColIndex]=
"0"
aria-selected=
"false"
>
</td>
<td *ngFor=
"let column of columns; let columnIndex = index"
kendoGridCell
[rowIndex]=
"-1"
[columnIndex]=
"lockedColumnsCount + columnIndex"
[isNew]=
"true"
[column]=
"column"
[dataItem]=
"newDataItem"
[ngClass]=
"column.cssClass"
[ngStyle]=
"column.style"
[attr.colspan]=
"column.colspan"
kendoGridLogicalCell
[logicalRowIndex]=
"addRowLogicalIndex()"
[logicalColIndex]=
"logicalColIndex(columnIndex)"
[colSpan]=
"column.colspan"
role=
"gridcell"
>
</td>
</tr>
</ng-template>
<tr *ngIf=
"data?.length === 0 || data == null"
class=
"k-grid-norecords"
>
<td [attr.colspan]=
"colSpan"
>
<ng-template
[ngIf]=
"noRecordsTemplate?.templateRef"
[templateContext]="{
templateRef: noRecordsTemplate?.templateRef
}">
</ng-template>
<ng-container *ngIf=
"!noRecordsTemplate?.templateRef"
>
{{noRecordsText}}
</ng-container>
</td>
</tr>
<ng-template ngFor
[ngForOf]=
"data"
[ngForTrackBy]=
"trackByFn"
let-item
let-rowIndex=
"index"
>
<tr *ngIf=
"isGroup(item) && isParentGroupExpanded(item) && showGroupHeader(item)"
kendoGridGroupHeader
[columns]=
"columns"
[groups]=
"groups"
[item]=
"item"
[hasDetails]=
"detailTemplate?.templateRef"
[skipGroupDecoration]=
"skipGroupDecoration"
[rowIndex]=
"rowIndex + 1"
[totalColumnsCount]=
"totalColumnsCount"
kendoGridLogicalRow
[logicalRowIndex]=
"logicalRowIndex(rowIndex)"
[logicalSlaveRow]=
"lockedColumnsCount > 0"
[logicalCellsCount]=
"columns.length"
[logicalSlaveCellsCount]=
"unlockedColumnsCount"
>
</tr>
<tr
*ngIf=
"isDataItem(item) && isInExpandedGroup(item)"
kendoGridLogicalRow
[dataRowIndex]=
"item.index"
[dataItem]=
"item.data"
[logicalRowIndex]=
"logicalRowIndex(rowIndex)"
[logicalSlaveRow]=
"lockedColumnsCount > 0"
[logicalCellsCount]=
"columns.length"
[logicalSlaveCellsCount]=
"unlockedColumnsCount"
[ngClass]=
"rowClass({ dataItem: item.data, index: item.index })"
[class.k-alt]=
"isOdd(item)"
[class.k-master-row]=
"detailTemplate?.templateRef"
[class.k-grid-edit-row]=
"editService.hasEdited(item.index)"
[attr.data-kendo-grid-item-index]=
"item.index"
[class.k-state-selected]=
"isSelectable() && isRowSelected(item)"
>
<ng-template [ngIf]=
"!skipGroupDecoration"
>
<td [class.k-group-cell]=
"true"
*ngFor=
"let g of groups"
role=
"presentation"
></td>
</ng-template>
<td [class.k-hierarchy-cell]=
"true"
*ngIf=
"detailTemplate?.templateRef"
kendoGridLogicalCell
[logicalRowIndex]=
"logicalRowIndex(rowIndex)"
[logicalColIndex]=
"0"
[dataRowIndex]=
"item.index"
[dataItem]=
"item.data"
[detailExpandCell]=
"true"
aria-selected=
"false"
>
<a class=
"k-icon"
*ngIf=
"detailTemplate.showIf(item.data, item.index)"
[ngClass]=
"detailButtonStyles(item.index)"
href=
"#"
tabindex=
"-1"
(click)=
"toggleRow(item.index, item.data)"
></a>
</td>
<td
kendoGridCell
[rowIndex]=
"item.index"
[columnIndex]=
"lockedColumnsCount + columnIndex"
[column]=
"column"
[dataItem]=
"item.data"
kendoGridLogicalCell
[logicalRowIndex]=
"logicalRowIndex(rowIndex)"
[logicalColIndex]=
"logicalColIndex(columnIndex)"
[dataRowIndex]=
"item.index"
[dataItem]=
"item.data"
[colIndex]=
"columnIndex"
[colSpan]=
"column.colspan"
role=
"gridcell"
aria-selected=
"false"
[ngClass]=
"column.cssClass"
[class.k-grid-edit-cell]=
"editService.isEditedColumn(item.index, column)"
[ngStyle]=
"column.style"
[attr.colspan]=
"column.colspan"
*ngFor=
"let column of columns; let columnIndex = index"
>
</td>
</tr>
<tr *ngIf="isDataItem(item) && isInExpandedGroup(item) && detailTemplate?.templateRef &&
detailTemplate.showIf(item.data, item.index) && isExpanded(item.index)"
[class.k-detail-row]=
"true"
[class.k-alt]=
"isOdd(item)"
kendoGridLogicalRow
[dataRowIndex]=
"item.index"
[dataItem]=
"item.data"
[logicalRowIndex]=
"logicalRowIndex(rowIndex) + 1"
[logicalSlaveRow]=
"false"
[logicalCellsCount]=
"1"
>
<td [class.k-group-cell]=
"true"
*ngFor=
"let g of groups"
></td>
<td [class.k-hierarchy-cell]=
"true"
></td>
<td [class.k-detail-cell]=
"true"
[attr.colspan]=
"columnsSpan"
kendoGridLogicalCell
[logicalRowIndex]=
"logicalRowIndex(rowIndex) + 1"
[logicalColIndex]=
"0"
[dataRowIndex]=
"item.index"
[dataItem]=
"item.data"
[colIndex]=
"0"
[colSpan]=
"totalColumnsCount - 1"
role=
"gridcell"
aria-selected=
"false"
>
<ng-template
[templateContext]="{
templateRef: detailTemplate?.templateRef,
dataItem: item.data,
rowIndex: item.index,
$implicit: item.data
}">
</ng-template>
</td>
</tr>
<tr *ngIf="isFooter(item) && (isInExpandedGroup(item) || (showGroupFooters && isParentGroupExpanded(item)))
&& !item.data.hideFooter"
[class.k-group-footer]=
"true"
kendoGridLogicalRow
[logicalRowIndex]=
"logicalRowIndex(rowIndex)"
[logicalSlaveRow]=
"lockedColumnsCount > 0"
[logicalCellsCount]=
"columns.length"
[logicalSlaveCellsCount]=
"unlockedColumnsCount"
>
<ng-template [ngIf]=
"!skipGroupDecoration"
>
<td [class.k-group-cell]=
"true"
*ngFor=
"let g of groups"
></td>
</ng-template>
<td [class.k-hierarchy-cell]=
"true"
*ngIf=
"detailTemplate?.templateRef"
kendoGridLogicalCell
[logicalRowIndex]=
"logicalRowIndex(rowIndex)"
[logicalColIndex]=
"0"
aria-selected=
"false"
>
</td>
<td kendoGridLogicalCell
[logicalRowIndex]=
"logicalRowIndex(rowIndex)"
[logicalColIndex]=
"logicalColIndex(columnIndex)"
[attr.data-skip]=
"skipGroupDecoration"
*ngFor=
"let column of footerColumns; let columnIndex = index"
>
<ng-template
[templateContext]="{
templateRef: column.groupFooterTemplateRef,
group: item.data,
field: column.field,
column: column,
$implicit: item.data?.aggregates
}">
</ng-template>
</td>
</tr>
</ng-template>
`
})
export class TableBodyComponent implements OnInit, OnDestroy {
@Input() public columns: Array<ColumnBase> = [];
@Input() public groups: Array<GroupDescriptor> = [];
@Input() public detailTemplate: DetailTemplateDirective;
@Input() public noRecordsTemplate: NoRecordsTemplateDirective;
@Input() public data: Array<GroupItem | Item | GroupFooterItem>;
@Input() public skip: number = 0;
@Input() public selectable: SelectableSettings | boolean;
@Input() public filterable: FilterableSettings;
@Input() public noRecordsText: string =
this
.localization.get(
'noRecords'
);
@Input() public skipGroupDecoration: boolean =
false
;
@Input() public showGroupFooters: boolean =
false
;
@Input() public lockedColumnsCount: number = 0;
@Input() public totalColumnsCount: number = 0;
private clickSubscription: Function;
private cellKeydownSubscription: Subscription;
private clickTimeout: any;
@Input() public rowClass: RowClassFn = () =>
null
;
constructor(
public detailsService: DetailsService,
public groupsService: GroupsService,
private changeNotification: ChangeNotificationService,
public editService: EditService,
private localization: LocalizationService,
private ngZone: NgZone,
private renderer: Renderer2,
private element: ElementRef,
private domEvents: DomEventsService,
public selectionService: SelectionService,
private columnInfoService: ColumnInfoService,
private navigationService: NavigationService
) {
this
.cellKeydownSubscription =
this
.navigationService.cellKeydown.subscribe((args) =>
this
.cellKeydownHandler(args));
}
public get newDataItem(): any {
return
this
.editService.newDataItem;
}
// Number of unlocked columns in the next table, if any
public get unlockedColumnsCount(): number {
return
this
.totalColumnsCount -
this
.lockedColumnsCount -
this
.columns.length;
}
public toggleRow(index: number, dataItem: any): boolean {
this
.detailsService.toggleRow(index, dataItem);
return
false
;
}
public trackByFn = (index: number, item: any): any => {
if
(item.type ===
'data'
&&
this
.editService.hasEdited(item.index)) {
return
item.data;
}
return
index;
}
public isExpanded(index: number): boolean {
return
this
.detailsService.isExpanded(index);
}
public detailButtonStyles(index: number): any {
const expanded =
this
.isExpanded(index);
return
expanded ?
'k-minus'
:
'k-plus'
;
}
public isGroup(item: Item | GroupItem): boolean {
return
item.type ===
'group'
;
}
public isDataItem(item: Item | GroupItem): boolean {
return
!
this
.isGroup(item) && !
this
.isFooter(item);
}
public isFooter(item: Item | GroupItem | GroupFooterItem): boolean {
return
item.type ===
'footer'
;
}
public isInExpandedGroup(item: Item): boolean {
return
this
.groupsService.isInExpandedGroup(item.groupIndex,
false
);
}
public isParentGroupExpanded(item: any): boolean {
return
this
.groupsService.isInExpandedGroup(item.index || item.groupIndex);
}
public isOdd(item: any): boolean {
return
item.index % 2 !== 0;
}
public isSelectable(): boolean {
return
this
.selectable && (<SelectableSettings>
this
.selectable).enabled !==
false
;
}
public isRowSelected(item: any): boolean {
return
this
.selectionService.isSelected(item.index);
}
public ngOnChanges(changes: { [propertyName: string]: SimpleChange }): void {
if
(isChanged(
"columns"
, changes,
false
)) {
this
.changeNotification.notify();
}
}
public logicalRowIndex(rowIndex: number): number {
let pos =
this
.skip + rowIndex;
if
(
this
.hasDetailTemplate) {
pos *= 2;
}
const absoluteRowIndex = 1 + pos;
const addRowOffset =
this
.editService.hasNewItem ? 1 : 0;
const filterRowOffset = hasFilterRow(
this
.filterable) ? 1 : 0;
const headerRowCount =
this
.columnInfoService.totalLevels + filterRowOffset + addRowOffset;
return
absoluteRowIndex + headerRowCount;
}
public addRowLogicalIndex(): number {
return
this
.columnInfoService.totalLevels + 1;
}
public logicalColIndex(colIndex: number): number {
return
this
.lockedColumnsCount + colIndex + (
this
.hasDetailTemplate ? 1 : 0);
}
public ngOnInit(): void {
this
.ngZone.runOutsideAngular(() => {
const clickHandler =
this
.clickHandler.bind(
this
);
const mousedownSubscription =
this
.renderer.listen(
this
.element.nativeElement,
'mousedown'
, clickHandler);
const clickSubscription =
this
.renderer.listen(
this
.element.nativeElement,
'click'
, clickHandler);
const contextmenuSubscription =
this
.renderer.listen(
this
.element.nativeElement,
'contextmenu'
, clickHandler);
this
.clickSubscription = () => {
mousedownSubscription();
clickSubscription();
contextmenuSubscription();
};
});
let originalNoRecordText =
this
.localization.get(
'noRecords'
);
this
.localization.changes.subscribe(() => {
if
(
this
.noRecordsText === originalNoRecordText) {
this
.noRecordsText =
this
.localization.get(
'noRecords'
);
originalNoRecordText =
this
.noRecordsText;
}
});
}
public ngOnDestroy(): void {
if
(
this
.clickSubscription) {
this
.clickSubscription();
}
this
.cellKeydownSubscription.unsubscribe();
clearTimeout(
this
.clickTimeout);
}
public get columnsSpan(): number {
return
columnsSpan(
this
.columns);
}
public get colSpan(): number {
return
this
.columnsSpan +
this
.groups.length + (
this
.hasDetailTemplate ? 1 : 0);
}
public get footerColumns(): ColumnBase[] {
return
columnsToRender(
this
.columns);
}
public showGroupHeader(item: any): boolean {
return
!item.data.skipHeader;
}
private get hasDetailTemplate(): boolean {
return
isPresent(
this
.detailTemplate);
}
private clickHandler(eventArg: any): void {
const target = eventArg.target;
const cell = closest(target, matchesNodeName(
'td'
));
const row = closest(cell, matchesNodeName(
'tr'
));
const body = closest(row, matchesNodeName(
'tbody'
));
if
(cell && !hasClasses(cell, NON_DATA_CELL_CLASSES) &&
!hasClasses(row, NON_DATA_ROW_CLASSES) &&
body ===
this
.element.nativeElement) {
this
.editService.preventCellClose();
const focusable = target !== cell && isFocusable(target,
false
);
if
(!focusable && !matchesNodeName(
'label'
)(target) && !hasClasses(target, IGNORE_TARGET_CLASSSES) &&
!closestInScope(target, matchesClasses(IGNORE_CONTAINER_CLASSES), cell)) {
const args =
this
.cellClickArgs(cell, row, eventArg);
if
(eventArg.type ===
'mousedown'
) {
this
.domEvents.cellMousedown.emit(args);
}
else
{
if
(args.isEditedColumn || !
this
.editService.closeCell(eventArg)) {
if
(eventArg.type ===
'click'
) {
this
.clickTimeout = setTimeout(() => {
this
.emitCellClick(args);
}, 0);
}
else
{
this
.emitCellClick(args);
}
}
}
}
}
}
private emitCellClick(args: any): void {
this
.domEvents.cellClick.emit(Object.assign(args, {
isEdited: args.isEditedRow || args.isEditedColumn
}));
}
private cellKeydownHandler(args: CellKeydownEvent): void {
const body = closest(args.rowElement, matchesNodeName(
'tbody'
));
if
(body !==
this
.element.nativeElement) {
return
;
}
if
(args.keyCode === Keys.enter) {
const clickArgs =
this
.cellClickArgs(args.cellElement, args.rowElement, args.originalEvent);
this
.domEvents.cellClick.emit(clickArgs);
}
}
private cellClickArgs(cell: any, row: any, eventArg: any): any {
const index = columnCellIndex(cell, row.cells);
const column = (
this
.columns as any).toArray()[index];
const columnIndex =
this
.lockedColumnsCount + index;
let rowIndex = row.getAttribute(
'data-kendo-grid-item-index'
);
rowIndex = rowIndex ? parseInt(rowIndex, 10) : -1;
const dataItem = rowIndex === -1 ?
this
.editService.newDataItem : (
this
.data as any).at(rowIndex -
this
.skip);
const isEditedColumn =
this
.editService.isEditedColumn(rowIndex, column);
const isEditedRow =
this
.editService.isEdited(rowIndex);
return
{
column: column,
columnIndex: columnIndex,
dataItem: dataItem,
isEditedColumn: isEditedColumn,
isEditedRow: isEditedRow,
originalEvent: eventArg,
rowIndex: rowIndex,
type: eventArg.type
};
}
}
I am afraid the provided information is not enough for us to determine what exactly is triggering the described error, but if you send us an isolated runnable project (or a Stackblitz example, similar to our online demos) where the described error can be observed, we will investigate it further, and try to determine what is causing and suggest a solution. Thank you in advance.