@progress/kendo-angular-grid
Version:
Kendo UI Grid for Angular - high performance data grid with paging, filtering, virtualization, CRUD, and more.
1,292 lines • 178 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import { Component, ContentChildren, ElementRef, EventEmitter, HostBinding, Input, Output, Renderer2, QueryList, ViewChild, isDevMode, NgZone, ViewChildren, ChangeDetectorRef, ViewEncapsulation } from '@angular/core';
import { ZoneAwareEventEmitter } from './common/event-emitter';
import { FormControl, FormGroup } from '@angular/forms';
import { merge } from 'rxjs';
import { map, tap, take, filter, switchMap, takeUntil } from 'rxjs/operators';
import { validatePackage } from '@progress/kendo-licensing';
import { packageMetadata } from './package-metadata';
import { ColumnComponent, isColumnComponent } from './columns/column.component';
import { isSpanColumnComponent } from './columns/span-column.component';
import { isColumnGroupComponent, ColumnGroupComponent } from './columns/column-group.component';
import { DetailTemplateDirective } from './rendering/details/detail-template.directive';
import { normalize } from './common/pager-settings';
import { isArray, anyChanged, isChanged, isPresent, isUniversal, observe, isTruthy, createPromise, hasObservers, roundDown } from './utils';
import { BrowserSupportService } from './layout/browser-support.service';
import { DataResultIterator, DataCollection } from './data/data.collection';
import { SelectionService } from './selection/selection.service';
import { Selection } from "./selection/selection-default";
import { EditService } from './editing/edit.service';
import { DetailsService } from './rendering/details/details.service';
import { GroupsService } from './grouping/groups.service';
import { ColumnsContainer } from './columns/columns-container';
import { GroupInfoService } from './grouping/group-info.service';
import { ChangeNotificationService } from './data/change-notification.service';
import { NoRecordsTemplateDirective } from './rendering/no-records-template.directive';
import { ColumnBase } from './columns/column-base';
import { syncRowsHeight } from './layout/row-sync';
import { CELL_CONTEXT, EMPTY_CELL_CONTEXT } from './rendering/common/cell-context';
import { L10N_PREFIX, LocalizationService } from '@progress/kendo-angular-l10n';
import { FilterService } from './filtering/filter.service';
import { PDFService } from './pdf/pdf.service';
import { PDFExportEvent } from './pdf/pdf-export-event';
import { SuspendService } from './scrolling/suspend.service';
import { ResponsiveService } from "./layout/responsive.service";
import { ExcelService } from './excel/excel.service';
import { ColumnList } from './columns/column-list';
import { ToolbarTemplateDirective } from "./rendering/toolbar/toolbar-template.directive";
import { expandColumns, expandColumnsWithSpan, isValidFieldName } from "./columns/column-common";
import { ScrollSyncService } from "./scrolling/scroll-sync.service";
import { ResizeService } from "./layout/resize.service";
import { closest, matchesClasses, matchesNodeName } from './rendering/common/dom-queries';
import { LocalDataChangesService } from './editing/local-data-changes.service';
import { DomEventsService } from './common/dom-events.service';
import { ColumnResizingService } from "./column-resizing/column-resizing.service";
import { hasFilterRow } from './filtering/filterable';
import { SinglePopupService } from './common/single-popup.service';
import { DragAndDropService } from './dragdrop/drag-and-drop.service';
import { DragHintService } from './dragdrop/drag-hint.service';
import { DropCueService } from './dragdrop/drop-cue.service';
import { ColumnReorderService } from './dragdrop/column-reorder.service';
import { ColumnReorderEvent } from './dragdrop/column-reorder-event';
import { FocusRoot } from './navigation/focus-root';
import { NavigationService } from './navigation/navigation.service';
import { NavigationMetadata } from './navigation/navigation-metadata';
import { IdService } from './common/id.service';
import { ColumnInfoService } from "./common/column-info.service";
import { ScrollRequestService } from './scrolling/scroll-request.service';
import { SortService } from './common/sort.service';
import { ColumnMenuTemplateDirective } from './column-menu/column-menu-template.directive';
import { ColumnVisibilityChangeEvent } from './column-menu/column-visibility-change-event';
import { ColumnLockedChangeEvent } from './column-menu/column-locked-change-event';
import { GROUP_CELL_WIDTH } from './constants';
import { sortColumns } from './columns/column-common';
import { defaultTrackBy } from './common/default-track-by';
import { CellSelectionService } from './selection/cell-selection.service';
import { ColumnStickyChangeEvent } from './column-menu/column-sticky-change-event';
import { CellLoadingTemplateDirective } from './rendering/cell-loading.template.directive';
import { ContextService } from './common/provider.service';
import { LoadingTemplateDirective } from './rendering/loading-template.directive';
import { SizingOptionsService } from './layout/sizing-options.service';
import { DraggableDirective, WatermarkOverlayComponent, guid, shouldShowValidationUI } from '@progress/kendo-angular-common';
import { DragTargetContainerDirective, DropTargetContainerDirective } from '@progress/kendo-angular-utils';
import { RowReorderService } from './row-reordering/row-reorder.service';
import { StatusBarTemplateDirective } from './aggregates/status-bar-template.directive';
import { CellSelectionAggregateService } from './aggregates/selection-aggregate.service';
import { ClipboardService } from './common/clipboard.service';
import { ColumnConfigurationErrorMessages, GridConfigurationErrorMessages } from './common/error-messages';
import { StatusBarComponent } from './aggregates/status-bar.component';
import { LoadingComponent } from './rendering/common/loading.component';
import { TableBodyComponent } from './rendering/table-body.component';
import { FooterComponent } from './rendering/footer/footer.component';
import { GridMarqueeDirective } from './selection/marquee.directive';
import { ListComponent } from './rendering/list.component';
import { ResizableContainerDirective } from './layout/resizable.directive';
import { HeaderComponent } from './rendering/header/header.component';
import { ColGroupComponent } from './rendering/common/col-group.component';
import { GridTableDirective } from './rendering/grid-table.directive';
import { TableDirective } from './column-resizing/table.directive';
import { GroupPanelComponent } from './grouping/group-panel.component';
import { NgIf, NgTemplateOutlet } from '@angular/common';
import { ToolbarComponent as GridToolbarComponent } from './rendering/toolbar/toolbar.component';
import { LocalizedMessagesDirective } from './localization/localized-messages.directive';
import { IconWrapperComponent } from '@progress/kendo-angular-icons';
import { PagerTemplateDirective, PagerContextService, PagerNavigationService, KENDO_PAGER } from '@progress/kendo-angular-pager';
import { RowspanService } from './rendering/rowspan.service';
import * as i0 from "@angular/core";
import * as i1 from "./layout/browser-support.service";
import * as i2 from "./selection/selection.service";
import * as i3 from "./selection/cell-selection.service";
import * as i4 from "./grouping/group-info.service";
import * as i5 from "./grouping/groups.service";
import * as i6 from "./data/change-notification.service";
import * as i7 from "./rendering/details/details.service";
import * as i8 from "./editing/edit.service";
import * as i9 from "./filtering/filter.service";
import * as i10 from "./pdf/pdf.service";
import * as i11 from "./layout/responsive.service";
import * as i12 from "./excel/excel.service";
import * as i13 from "./scrolling/scroll-sync.service";
import * as i14 from "./common/dom-events.service";
import * as i15 from "./column-resizing/column-resizing.service";
import * as i16 from "./dragdrop/column-reorder.service";
import * as i17 from "./common/column-info.service";
import * as i18 from "./navigation/navigation.service";
import * as i19 from "./common/sort.service";
import * as i20 from "./scrolling/scroll-request.service";
import * as i21 from "@progress/kendo-angular-l10n";
import * as i22 from "./common/provider.service";
import * as i23 from "./layout/sizing-options.service";
import * as i24 from "./row-reordering/row-reorder.service";
import * as i25 from "@progress/kendo-angular-pager";
const createControl = (source) => (acc, key) => {
acc[key] = new FormControl(source[key]);
return acc;
};
const validateColumnsField = (columns) => expandColumns(columns.toArray())
.filter(isColumnComponent)
.filter(({ field }) => !isValidFieldName(field))
.forEach(({ field }) => console.warn(ColumnConfigurationErrorMessages.fieldName(field)));
const handleExpandCollapseGroupsService = (service, expandEmitter, collapseEmitter, map) => (service.changes.pipe(filter(({ group, emit }) => emit && isPresent(group)))
.subscribe((x) => x.expand ? expandEmitter.emit(map(x)) : collapseEmitter.emit(map(x))));
const handleExpandCollapseDetailsService = (service, expandEmitter, collapseEmitter, map) => (service.changes.pipe(filter(({ dataItem }) => isPresent(dataItem)))
.subscribe((x) => x.expand ? expandEmitter.emit(map(x)) : collapseEmitter.emit(map(x))));
const isInEditedCell = (element, gridElement) => closest(element, matchesClasses('k-grid-edit-cell')) &&
closest(element, matchesNodeName('kendo-grid')) === gridElement;
const NOTIFY_DELAY = 500;
/**
* Represents the Kendo UI for Angular Data Grid component.
*
* @example
* ```html
* <kendo-grid [data]="gridData"></kendo-grid>
* ```
*/
export class GridComponent {
supportService;
selectionService;
cellSelectionService;
wrapper;
groupInfoService;
groupsService;
changeNotification;
detailsService;
editService;
filterService;
pdfService;
responsiveService;
renderer;
excelService;
ngZone;
scrollSyncService;
domEvents;
columnResizingService;
changeDetectorRef;
columnReorderService;
columnInfoService;
navigationService;
sortService;
scrollRequestService;
localization;
ctx;
sizingService;
rowReorderService;
/**
* Sets the data of the Grid. If an array is provided, the Grid automatically gets the total count
* ([more information and example]({% slug binding_grid %})).
*/
set data(value) {
this._data = value;
if (this.selectable && this.selectableSettings?.enabled && this.isVirtual) {
this.blockArrowSelection = false;
}
if (this.notifyTimeout) {
clearTimeout(this.notifyTimeout);
this.notifyTimeout = null;
}
if (this.rowReorderable) {
this.ngZone.runOutsideAngular(() => {
this.notifyTimeout = setTimeout(() => {
this.notifyReorderContainers();
}, NOTIFY_DELAY);
});
}
}
get data() {
return this._data;
}
get hintText() {
return this.rowReorderService.getDefaultHintText(this.columnList, this.flatData);
}
/**
* @hidden
*/
get customHintTemplate() {
if (this.rowReorderable) {
const allColumns = this.columnList.toArray();
const rowReorderColumn = allColumns.find(column => column.isRowReorderColumn);
return rowReorderColumn.rowDragHintTemplateRef;
}
}
/**
* @hidden
*/
get hintContext() {
if (this.customHintTemplate) {
const draggedRow = this.rowReorderService?.getDraggedRow(this.flatData);
return {
$implicit: draggedRow?.dataItem,
rowIndex: draggedRow?.rowIndex
};
}
}
/**
* Defines the page size used by the Grid pager.
* Required by the [paging]({% slug paging_grid %}) functionality.
*/
pageSize;
/**
* Defines the height (in pixels) that is used when the `scrollable` option of the Grid is set.
* To set the height of the Grid, you can also use `style.height`. The `style.height`
* option supports units such as `px`, `%`, `em`, `rem`, and others.
*/
height;
/**
* Represent the actual height of each Grid row (`tr`) element in the DOM.
* Required by the [virtual scrolling functionality]({% slug scrollmmodes_grid %}).
* Set the `rowHeight` option to the exact pixels as the height of the `tr` element appears in the DOM.
*/
rowHeight;
/**
* Represent the actual height of each Grid detail row (`tr`) element in the DOM.
* Required by the [virtual scrolling functionality]({% slug scrollmmodes_grid %}).
* Set the `detailRowHeight` option to the exact pixels as the height of the detail Grid `tr` element appears in the DOM.
*/
detailRowHeight;
/**
* Defines the number of records to be skipped by the pager.
* Required by the [paging]({% slug paging_grid %}) functionality.
*/
get skip() {
return this._skip;
}
set skip(value) {
if (typeof value === 'number' && value >= 0) {
this._skip = this.rowReorderService.skip = value;
}
}
/**
* Defines the scroll mode used by the Grid.
*
* @default 'scrollable'
*/
scrollable = 'scrollable';
/**
* Enables the [single-row selection](slug:grid_row_selection) of the Grid.
*
* @default false
*/
selectable = false;
/**
* The descriptors by which the data will be sorted ([see example]({% slug sorting_grid %})).
*/
set sort(value) {
if (isArray(value)) {
this._sort = value;
}
}
get sort() {
return this._sort;
}
/**
* Specifies the sizing of various Grid building blocks (e.g. tables, buttons, inputs, dropdowns, etc.)
* @default 'medium'
*/
set size(size) {
this._size = size;
if (size === 'none') {
this.wrapper.nativeElement.classList.remove('k-grid-sm', 'k-grid-md');
}
this.sizingService.changes.next(this.size);
}
get size() {
return this._size;
}
/**
* A function that defines how to track changes for the data rows.
* By default, the Grid tracks changes by the index of the data item.
* Edited rows are tracked by reference.
* [See example](slug:track_changes_grid)
*/
trackBy = defaultTrackBy;
/**
* The descriptor by which the data will be filtered ([see examples]({% slug filtering_grid %})).
*/
filter;
/**
* The descriptors by which the data will be grouped ([see example]({% slug grouping_grid %})).
*/
set group(value) {
if (isArray(value)) {
this._group = value;
}
}
get group() {
return this._group;
}
/**
* If set to `true`, the grid will render only the columns in the current viewport.
* @default false
*/
virtualColumns = false;
/**
* @hidden
*/
get showStatusBar() {
return !!(this.selectable);
}
/**
* @hidden
*/
get showTopToolbar() {
return this.toolbarTemplate && ['top', 'both'].indexOf(this.toolbarTemplate.position) > -1;
}
/**
* @hidden
*/
get showBottomToolbar() {
return this.toolbarTemplate && ['bottom', 'both'].indexOf(this.toolbarTemplate.position) > -1;
}
/**
* @hidden
*/
get isLocked() {
return this.lockedLeafColumns.length > 0;
}
/**
* @hidden
*/
get showTopPager() {
const position = this.pageable.position;
return !this.isVirtual && this.pageable !== false && ['top', 'both'].indexOf(position) > -1;
}
/**
* @hidden
*/
get showBottomPager() {
const position = this.pageable.position;
return !this.isVirtual && this.pageable !== false && position !== 'top';
}
/**
* @hidden
*/
get hasPager() {
return this.showTopPager || this.showBottomPager;
}
/**
* @hidden
*/
get showGroupPanel() {
return this.groupable && this.groupable.enabled !== false;
}
/**
* @hidden
*/
get groupableEmptyText() {
return this.groupable.emptyText;
}
/**
* @hidden
*/
get marqueeSelection() {
return this.selectionService.enableMarquee || this.cellSelectionService.enableMarquee;
}
/**
* @hidden
*/
gridData = () => { return this.flatData; };
/**
* Enables the [filtering]({% slug filtering_grid %}) of the Grid columns that have their `field` option set.
* @default false
*/
filterable = false;
/**
* Enables the [sorting]({% slug sorting_grid %}) of the Grid columns that have their `field` option set.
* @default false
*/
sortable = false;
/**
* Configures the pager of the Grid ([see example](slug:paging_grid_settings)).
* @default false
*/
pageable = false;
get normalizedPageableSettings() {
return normalize(this.pageable);
}
/**
* If set to `true`, the user can group the Grid by dragging the column header cells ([see example]({% slug grouping_grid %})).
* @default false
*/
groupable = false;
/**
* Determines whether the Grid can be resized.
* @default false
*/
gridResizable = false;
/**
* Enables the [row reordering]({% slug reordering_rows_grid %}) of the Grid.
* @default false
*/
set rowReorderable(value) {
this._rowReorderable = value;
if (value) {
this.rowReorderSubscription = this.rowReorderService.rowReorder.subscribe(args => {
this.ngZone.run(() => {
this.rowReorder.emit(args);
});
});
}
else {
this.rowReorderSubscription?.unsubscribe();
}
}
get rowReorderable() {
return this._rowReorderable;
}
/**
* If set to `true`, the user can use dedicated shortcuts to interact with the Grid.
* By default, navigation is disabled and the Grid content is accessible in the normal tab sequence.
* To enable navigation through separate Grid sections only, provide a [`GridNavigableSection`]({% slug api_grid_gridnavigablesection %}) array.
*/
set navigable(value) {
if (typeof value === 'boolean') {
this._navigable = value ? ['table', 'pager', 'toolbar'] : [];
this.ctx.navigable = value;
return;
}
else {
this.ctx.navigable = value.includes('table');
}
this._navigable = value;
}
get navigable() {
return this._navigable;
}
/**
* @hidden
*
* An alias for `navigable` for users who migrate from Kendo UI for jQuery.
*/
set navigatable(value) {
this.navigable = value;
}
/**
* @hidden
*/
get navigatable() {
return this.navigable;
}
/**
* Indicates whether the Grid columns will be resized during initialization so that
* they fit their headers and row content.
* Columns with `autoSize` set to `false` are excluded.
* To dynamically update the column width to match the new content,
* refer to [this example]({% slug resizing_columns_grid %}).
* @default false
*/
autoSize = false;
/**
* Defines a function that is executed for every data row in the component
* ([see example](slug:styling_grid_rows)).
*/
set rowClass(fn) {
if (isDevMode() && typeof fn !== 'function') {
throw new Error(GridConfigurationErrorMessages.functionType('rowClass', fn));
}
this._rowClass = fn;
}
get rowClass() {
return this._rowClass;
}
/**
* Defines a function that is executed for every data row in the component,
* and determines whether the row will be sticky, i.e. always visible after scrolling.
*/
set rowSticky(fn) {
if (isDevMode() && isPresent(fn) && typeof fn !== 'function') {
throw new Error(GridConfigurationErrorMessages.functionType('rowSticky', fn));
}
if (isPresent(fn)) {
this._rowSticky = fn;
}
}
get rowSticky() {
return this._rowSticky;
}
/**
* Defines a Boolean function that is executed for each data row in the component
* ([see example]({% slug grid_selection_custom %}#toc-setting-the-selected-rows)).
* Determines whether the row will be selected.
*/
set rowSelected(fn) {
if (isDevMode() && typeof fn !== 'function') {
throw new Error(GridConfigurationErrorMessages.functionType('rowSelected', fn));
}
this._rowSelected = fn;
}
get rowSelected() {
return this._rowSelected;
}
/**
* Defines a Boolean function that is executed for each data row in the component.
* Determines whether the row will be selectable.
*/
set isRowSelectable(fn) {
if (isDevMode() && typeof fn !== 'function') {
throw new Error(GridConfigurationErrorMessages.functionType('isRowSelectable', fn));
}
this._isRowSelectable = fn;
}
get isRowSelectable() {
return this._isRowSelectable;
}
/**
* Defines a function that determines the selected state of a data cell.
* Returns an object with `selected` and `item` properties.
* The cell is marked as selected only if the `selected` property equals `true`.
*
* The function is executed for each data cell and may be called more than once
* as part of a change detection cycle. ([see example]({% slug grid_selection_custom %}#toc-setting-the-selected-cells))
*/
set cellSelected(fn) {
if (isDevMode() && typeof fn !== 'function') {
throw new Error(GridConfigurationErrorMessages.functionType('cellSelected', fn));
}
this._cellSelected = fn;
}
get cellSelected() {
return this._cellSelected;
}
/**
* Returns the currently focused cell (if any).
*/
get activeCell() {
return this.navigationService.activeCell;
}
/**
* Returns the currently focused row (if any).
*/
get activeRow() {
return this.navigationService.activeRow;
}
/**
* Returns the current Grid selection.
*
* @hidden
*/
get selection() {
return (this.selectable || this.selectionDirective) ?
this.defaultSelection ? this.defaultSelection.stateToArray() : this.selectionDirective.stateToArray() :
[];
}
/**
* If set to `true`, the user can resize columns by dragging the edges (resize handles) of their header cells
* ([see example]({% slug resizing_columns_grid %})).
*
* @default false
*/
resizable = false;
/**
* If set to `true`, the user can reorder columns by dragging their header cells
* ([see example]({% slug reordering_columns_grid %})).
*
* @default false
*/
reorderable = false;
/**
* Specifies if the loading indicator of the Grid will be displayed ([see example]({% slug binding_grid %})).
*
* @default false
*/
set loading(value) {
this._loading = value;
this.rowReorderable && this.notifyReorderContainers();
}
get loading() {
return this._loading;
}
/**
* Specifies if the column menu of the columns will be displayed ([see example]({% slug columnmenu_grid %})).
*
* @default false
*/
columnMenu = false;
/**
* Specifies if the header of the grid will be hidden. The header is visible by default.
* The header includes column headers and the [filter row](slug:filter_row).
* @default false
*/
hideHeader = false;
/**
* Fires when the Grid filter is modified through the UI.
* You have to handle the event yourself and filter the data.
*/
filterChange = new EventEmitter();
/**
* Fires when the page of the Grid is changed ([see example]({% slug paging_grid %})).
* You have to handle the event yourself and page the data.
*/
pageChange = new EventEmitter();
/**
* Fires when the grouping of the Grid is changed.
* You have to handle the event yourself and group the data ([see example]({% slug grouping_grid %})).
*/
groupChange;
/**
* Fires when the sorting of the Grid is changed ([see example]({% slug sorting_grid %})).
* You have to handle the event yourself and sort the data.
*/
sortChange = new EventEmitter();
/**
* Fires when the user selects a Grid row.
*/
selectionChange = new EventEmitter();
/**
* Fires when the user drops the dragged row and reordering is performed.
*/
rowReorder = new EventEmitter();
/**
* Fires when the data state of the Grid is changed.
*/
dataStateChange = new EventEmitter();
/**
* Fires when the user expands a group header.
*/
groupExpand = new EventEmitter();
/**
* Fires when the user collapses a group header.
*/
groupCollapse = new EventEmitter();
/**
* Fires when the user expands a master row.
*/
detailExpand = new EventEmitter();
/**
* Fires when the user collapses a master row.
*/
detailCollapse = new EventEmitter();
/**
* Fires when the user clicks the **Edit** command button to edit a row
* ([see example](slug:inline_editing_grid#editing-records-1)).
*/
edit = new EventEmitter();
/**
* Fires when the user clicks the **Cancel** command button to close a row
* ([see example]({% slug inline_editing_grid %}#toc-canceling-editing-1)).
*/
cancel = new EventEmitter();
/**
* Fires when the user clicks the **Save** command button to save changes in a row
* ([see example]({% slug inline_editing_grid %}#toc-saving-records-1)).
*/
save = new EventEmitter();
/**
* Fires when the user clicks the **Remove** command button to remove a row
* ([see example]({% slug inline_editing_grid %}#toc-removing-records-1)).
*/
remove = new EventEmitter();
/**
* Fires when the user clicks the **Add** command button to add a new row
* ([see example]({% slug inline_editing_grid %}#toc-adding-records-1)).
*/
add = new EventEmitter();
/**
* Fires when the user leaves an edited cell ([see example](slug:editing_incell_grid)).
*/
cellClose = new EventEmitter();
/**
* Fires when the user clicks a cell ([see example](slug:editing_incell_grid)).
*/
cellClick;
/**
* Fires when the user clicks the **Export to PDF** command button.
*/
pdfExport = new EventEmitter();
/**
* Fires when the user clicks the **Export to Excel** command button.
*/
excelExport = new EventEmitter();
/**
* Fires when the user completes the resizing of the column.
*/
columnResize;
/**
* Fires when the user completes the reordering of the column.
*/
columnReorder = new EventEmitter();
/**
* Fires when the user changes the visibility of the columns from the column menu or column chooser.
*/
columnVisibilityChange = new EventEmitter();
/**
* Fires when the user changes the locked state of the columns from the column menu or by reordering the columns.
*/
columnLockedChange = new EventEmitter();
/**
* Fires when the user changes the sticky state of the columns from the column menu.
*/
columnStickyChange = new EventEmitter();
/**
* Fires when the user scrolls to the last record on the page and enables endless scrolling
* ([see example]({% slug scrollmmodes_grid %}#toc-endless-scrolling)).
* You have to handle the event yourself and page the data.
*/
scrollBottom = new EventEmitter();
/**
* Fires when the grid content is scrolled.
* For performance reasons, the event is triggered outside the Angular zone. Enter the Angular zone if you make any changes that require change detection.
*/
contentScroll = new EventEmitter();
/**
* A query list of all declared columns.
*/
columns = new QueryList();
get dir() {
return this.direction;
}
hostClass = true;
get sizeSmallClass() {
return this.size === 'small';
}
get sizeMediumClass() {
return this.size === 'medium' || !this.size;
}
get lockedClasses() {
return this.lockedLeafColumns.length > 0;
}
get virtualClasses() {
return this.isVirtual;
}
get noScrollbarClass() {
return this.scrollbarWidth === 0;
}
get isResizable() {
return Boolean(this.gridResizable);
}
get minWidth() {
return this.gridResizable.minWidth;
}
get maxWidth() {
return this.gridResizable.maxWidth;
}
get minHeight() {
return this.gridResizable.minHeight;
}
get maxHeight() {
return this.gridResizable.maxHeight;
}
detailTemplateChildren;
get detailTemplate() {
if (this._customDetailTemplate) {
return this._customDetailTemplate;
}
return this.detailTemplateChildren ? this.detailTemplateChildren.first : undefined;
}
set detailTemplate(detailTemplate) {
this._customDetailTemplate = detailTemplate;
}
cellLoadingTemplateChildren;
get cellLoadingTemplate() {
if (this._cellLoadingTemplate) {
return this._customDetailTemplate;
}
return this.cellLoadingTemplateChildren ? this.cellLoadingTemplateChildren.first : undefined;
}
set cellLoadingTemplate(cellLoadingTemplate) {
this._cellLoadingTemplate = cellLoadingTemplate;
}
loadingTemplateChildren;
get loadingTemplate() {
if (this._loadingTemplate) {
return this._loadingTemplate;
}
return this.loadingTemplateChildren ? this.loadingTemplateChildren.first : undefined;
}
set loadingTemplate(loadingTemplate) {
this._loadingTemplate = loadingTemplate;
}
statusBarTemplateChildren;
get statusBarTemplate() {
if (this._statusBarTemplate) {
return this._statusBarTemplate;
}
return this.statusBarTemplateChildren ? this.statusBarTemplateChildren.first : undefined;
}
set statusBarTemplate(statusBarTemplate) {
this._statusBarTemplate = statusBarTemplate;
}
noRecordsTemplateChildren;
get noRecordsTemplate() {
if (this._customNoRecordsTemplate) {
return this._customNoRecordsTemplate;
}
return this.noRecordsTemplateChildren ? this.noRecordsTemplateChildren.first : undefined;
}
set noRecordsTemplate(customNoRecordsTemplate) {
this._customNoRecordsTemplate = customNoRecordsTemplate;
}
pagerTemplateChildren;
get pagerTemplate() {
if (this._customPagerTemplate) {
return this._customPagerTemplate;
}
return this.pagerTemplateChildren ? this.pagerTemplateChildren.first : undefined;
}
set pagerTemplate(customPagerTemplate) {
this._customPagerTemplate = customPagerTemplate;
}
toolbarTemplateChildren;
get toolbarTemplate() {
if (this._customToolbarTemplate) {
return this._customToolbarTemplate;
}
return this.toolbarTemplateChildren ? this.toolbarTemplateChildren.first : undefined;
}
set toolbarTemplate(customToolbarTemplate) {
this._customToolbarTemplate = customToolbarTemplate;
}
columnMenuTemplates;
lockedHeader;
header;
footer = new QueryList();
ariaRoot;
dragTargetContainer;
dropTargetContainer;
get scrollbarWidth() {
return this.supportService.scrollbarWidth;
}
get headerPadding() {
if (isUniversal()) {
return '';
}
const padding = Math.max(0, this.scrollbarWidth) + 'px';
const right = this.rtl ? 0 : padding;
const left = this.rtl ? padding : 0;
return `0 ${right} 0 ${left}`;
}
columnMenuOptions;
columnList;
selectionDirective = false;
ariaRootId = `k-${guid()}`;
/**
* @hidden
*/
showLicenseWatermark = false;
columnsContainer = new ColumnsContainer(() => this.columnList.filterHierarchy(column => {
if (!isUniversal()) {
column.matchesMedia = this.matchesMedia(column);
}
return column.isVisible;
}));
view = new DataCollection(() => new DataResultIterator(this.data, this.skip, this.hasGroupFooters));
get hasGroupFooters() {
return this.columnsContainer.hasGroupFooter;
}
get showFooter() {
return this.columnsContainer.hasFooter;
}
get showGroupFooters() {
return this.groupable && this.groupable.showFooter;
}
get ariaRowCount() {
return this.totalColumnLevels + 1 + this.view.total + (hasFilterRow(this.filterable) ? 1 : 0);
}
get ariaColCount() {
return this.columnsContainer.leafColumnsToRender.length;
}
get navigation() {
return this.navigationService;
}
shouldGenerateColumns = true;
direction;
notifyTimeout = null;
_sort = new Array();
_group = new Array();
_skip = 0;
_data = [];
cachedWindowWidth = 0;
defaultSelection;
_rowSelected = null;
_isRowSelectable = null;
_cellSelected = null;
_customDetailTemplate;
_cellLoadingTemplate;
_loadingTemplate;
_statusBarTemplate;
_customNoRecordsTemplate;
_customPagerTemplate;
_customToolbarTemplate;
_rowReorderable = false;
leafViewportColumns;
viewportColumns;
_navigable = [];
_size = 'medium';
_loading = false;
get isVirtual() {
return this.scrollable === 'virtual';
}
get isScrollable() {
return this.scrollable !== 'none';
}
get visibleColumns() {
return this.columnsContainer.allColumns;
}
get lockedColumns() {
return this.columnsContainer.lockedColumns;
}
get nonLockedColumns() {
return this.columnsContainer.nonLockedColumns;
}
get lockedLeafColumns() {
return this.columnsContainer.lockedLeafColumns;
}
get stickyColumns() {
return this.columns.filter(column => column.sticky);
}
get nonLockedLeafColumns() {
return this.columnsContainer.nonLockedLeafColumns;
}
get leafColumns() {
return this.columnsContainer.leafColumns;
}
get totalColumnLevels() {
return this.columnsContainer.totalLevels;
}
get headerColumns() {
if (this.virtualColumns && !this.pdfService.exporting) {
return this.viewportColumns;
}
return this.nonLockedColumns;
}
get headerLeafColumns() {
if (this.virtualColumns && !this.pdfService.exporting) {
return this.leafViewportColumns;
}
return this.nonLockedLeafColumns;
}
get lockedWidth() {
const groupCellsWidth = this.group.length * GROUP_CELL_WIDTH;
return expandColumns(this.lockedLeafColumns.toArray()).reduce((prev, curr) => prev + (curr.width || 0), groupCellsWidth);
}
get nonLockedWidth() {
if ((!this.rtl && this.lockedLeafColumns.length) || this.virtualColumns) {
return !this.virtualColumns ? this.columnsContainer.unlockedWidth :
this.leafViewportColumns.reduce((acc, column) => acc + (column.width || 0), 0);
}
return undefined;
}
get selectableSettings() {
if (this.selectionService) {
return this.selectionService.options;
}
return undefined;
}
get columnMenuTemplate() {
const template = this.columnMenuTemplates.first;
return template ? template.templateRef : null;
}
get totalCount() {
if (this.isVirtual || !isPresent(this.pageSize)) {
return this.view.total;
}
return this.pageSize;
}
/**
* @hidden
*/
getDefaultSelectors(type) {
return this.rowReorderService.defaultSelectors[type];
}
/**
* @hidden
*/
getHintSettings(type) {
return this.rowReorderService[type];
}
/**
* @hidden
*/
blockArrowSelection = false;
selectionSubscription;
stateChangeSubscription;
groupExpandCollapseSubscription;
editServiceSubscription;
detailsServiceSubscription;
filterSubscription;
sortSubscription;
columnsChangeSubscription;
pdfSubscription;
excelSubscription;
columnsContainerChangeSubscription;
cellClickSubscription;
footerChangeSubscription;
columnResizingSubscription;
columnReorderSubscription;
detachElementEventHandlers;
localizationSubscription;
columnVisibilityChangeSubscription;
columnLockedChangeSubscription;
columnStickyChangeSubscription;
focusElementSubscription;
columnRangeChangeSubscription;
rowReorderSubscription;
rtl = false;
_rowSticky;
constructor(supportService, selectionService, cellSelectionService, wrapper, groupInfoService, groupsService, changeNotification, detailsService, editService, filterService, pdfService, responsiveService, renderer, excelService, ngZone, scrollSyncService, domEvents, columnResizingService, changeDetectorRef, columnReorderService, columnInfoService, navigationService, sortService, scrollRequestService, localization, ctx, sizingService, rowReorderService) {
this.supportService = supportService;
this.selectionService = selectionService;
this.cellSelectionService = cellSelectionService;
this.wrapper = wrapper;
this.groupInfoService = groupInfoService;
this.groupsService = groupsService;
this.changeNotification = changeNotification;
this.detailsService = detailsService;
this.editService = editService;
this.filterService = filterService;
this.pdfService = pdfService;
this.responsiveService = responsiveService;
this.renderer = renderer;
this.excelService = excelService;
this.ngZone = ngZone;
this.scrollSyncService = scrollSyncService;
this.domEvents = domEvents;
this.columnResizingService = columnResizingService;
this.changeDetectorRef = changeDetectorRef;
this.columnReorderService = columnReorderService;
this.columnInfoService = columnInfoService;
this.navigationService = navigationService;
this.sortService = sortService;
this.scrollRequestService = scrollRequestService;
this.localization = localization;
this.ctx = ctx;
this.sizingService = sizingService;
this.rowReorderService = rowReorderService;
const isValid = validatePackage(packageMetadata);
this.showLicenseWatermark = shouldShowValidationUI(isValid);
this.ctx.grid = this;
this.groupChange = new ZoneAwareEventEmitter(this.ngZone);
this.cellClick = new ZoneAwareEventEmitter(this.ngZone);
this.columnResize = new ZoneAwareEventEmitter(this.ngZone);
this.localizationSubscription = this.localization.changes.subscribe(({ rtl }) => {
this.rtl = rtl;
this.direction = this.rtl ? 'rtl' : 'ltr';
});
this.groupInfoService.registerColumnsContainer(() => this.columnList);
this.columnInfoService.init(this.columnsContainer, () => this.columnList);
this.columnVisibilityChangeSubscription = this.columnInfoService.visibilityChange.subscribe((changed) => {
this.columnVisibilityChange.emit(new ColumnVisibilityChangeEvent(changed));
});
this.columnLockedChangeSubscription = this.columnInfoService.lockedChange.subscribe((changed) => {
this.columnLockedChange.emit(new ColumnLockedChangeEvent(changed));
});
this.columnStickyChangeSubscription = this.columnInfoService.stickyChange.subscribe((changed) => {
this.columnStickyChange.emit(new ColumnStickyChangeEvent(changed));
});
this.groupExpandCollapseSubscription = handleExpandCollapseGroupsService(groupsService, this.groupExpand, this.groupCollapse, ({ group, groupIndex, parentGroup }) => ({ group, groupIndex, parentGroup }));
this.detailsServiceSubscription = handleExpandCollapseDetailsService(detailsService, this.detailExpand, this.detailCollapse, args => args);
this.filterSubscription = this.filterService.changes.subscribe(x => {
this.filterChange.emit(x);
});
this.sortSubscription = this.sortService.changes.subscribe(x => {
this.sortChange.emit(x);
});
this.attachStateChangesEmitter();
this.attachEditHandlers();
this.attachDomEventHandlers();
this.pdfSubscription = this.pdfService.exportClick.subscribe(this.emitPDFExportEvent.bind(this));
this.excelSubscription = this.excelService.exportClick.subscribe(this.saveAsExcel.bind(this));
this.columnsContainerChange();
this.handleColumnResize();
this.columnList = new ColumnList(this.columns);
this.columnReorderSubscription = this.columnReorderService
.changes.subscribe(this.reorder.bind(this));
this.columnRangeChangeSubscription = this.columnInfoService.columnRangeChange.subscribe(this.onColumnRangeChange.bind(this));
}
/**
* Expands the specified master row ([see example]({% slug hierarchy_grid %})).
*
* This method is provided only for backwards-compatibility with legacy versions.
* These versions tracked the expanded state internally using the data row index.
*
* For new development, use the [kendoGridDetailsExpandBy directive]({% slug api_grid_expanddetailsdirective %})
* or provide an isDetailExpanded callback. See [Controlling the Expanded State]({% slug master_detail_expanded_state_grid %})
* for examples on how to control the expanded state.
*
* @param index - The data row index of the master row.
*/
expandRow(index) {
this.toggleDetailRowLegacy(index, true);
}
/**
* Collapses the specified master row ([see example]({% slug hierarchy_grid %})).
*
* This method is provided only for backwards-compatibility with legacy versions.
* These versions tracked the expanded state internally using the data row index.
*
* For new development, use the [kendoGridDetailsExpandBy directive]({% slug api_grid_expanddetailsdirective %})
* or provide an isDetailExpanded callback. See [Controlling the Expanded State]({% slug master_detail_expanded_state_grid %})
* for examples on how to control the expanded state.
*
* @param index - The data row index of the master row.
*/
collapseRow(index) {
this.toggleDetailRowLegacy(index, false);
}
/**
* Expands a group header item for the given index. For example,
* `0_1` expands the second inner group of the first master group.
*
* This method is provided only for backwards-compatibility with legacy versions.
* These versions tracked the expanded group state internally using the hierarchical group index.
*
* When a Grid is pageable, the indexes of the groups are offset by the current Grid [skip]({% slug api_grid_gridcomponent %}#toc-skip).
*
* @param {string} index - The underscore separated hierarchical index of the group.
*/
expandGroup(index) {
this.toggleGroupRowLegacy(index, true);
}
/**
* Collapses a group header item for the given index. For example,
* `0_1` collapses the second inner group of the first master group.
*
* This method is provided only for backwards-compatibility with legacy versions.
* These versions tracked the expanded group state internally using the hierarchical group index.
*
* When a Grid is pageable, the indexes of the groups are offset by the current Grid [skip]({% slug api_grid_gridcomponent %}#toc-skip).
*
* @param {string} index - The underscore separated hierarchical index of the group.
*/
collapseGroup(index) {
this.toggleGroupRowLegacy(index, false);
}
/**
* @hidden
*/
resetGroupsState() {
this.groupsService.reset();
}
/**
* @hidden
*/
onDataChange() {
this.autoGenerateColumns();
this.changeNotification.notify();
this.pdfService.dataChanged.emit();
if (isPresent(this.defaultSelection)) {
this.defaultSelection.reset();
}
this.initSelectionService();
this.updateNavigationMetadata();
}
ngOnChanges(changes) {
if (isChanged("data", changes)) {
this.onDataChange();
}
if (this.lockedLeafColumns.length && anyChanged(["pageSize", "skip", "sort", "group"], changes)) {
this.changeNotification.notify();
}
if (anyChanged(["pageSize", "scrollable", 'virtualColumns'], changes)) {
this.updateNavigationMetadata();
}
if (isChanged("virtualColumns", changes)) {
this.viewportColumns = this.leafViewportColumns = null;
}
if (isChanged("height", changes, false)) {
this.renderer.setStyle(this.wrapper.nativeElement, 'height', `${this.height}px`);
}
if (isChanged("filterable", changes) && this.lockedColumns.length) {
this.syncHeaderHeight(this.ngZone.onStable.asObservable().pipe(take(1)));
}
if (anyChanged(["columnMenu", "sortable", "filterable"], changes, false)) {
this.columnMenuOptions = this.columnMenu && Object.assign({
filter: Boolean(this.filterable),
sort: Boolean(this.sortable)
}, this.columnMenu);
}
if (isChanged("scrollable", changes) && this.isScrollable) {
this.ngZone.onStable.pipe(take(1)).subscribe(() => this.attachScrollSync());
}
if (isChanged("selectable", changes) && this.shouldResetSelection(changes['selectable'])) {
if (this.defaultSelection) {
this.defaultSelection.reset();
}
else if (this.selectionDirective) {
this.selectionDirective.reset();
}
}
if (isChanged('groupable', changes, true)) {
this.groupable = changes['groupable'].currentValue;
}
if (isChanged('navigable', changes, true)) {
if (this.navigationService.enabled) {
this.navigationService.setActiveSections(this.navigable);
}
else {
if (this.navigable.length) {
this.navigationService.init(this.navigationMetadata(), this.navigable);
}
}
}
}
ngAfterContentInit() {
this.shouldGenerateColumns = !this.columns.length;
this.autoGenerateColumns();
this.columnList = new ColumnList(this.columns);
this.columnsChangeSubscription = this.columns.changes.subscribe(() => this.verifySettings());
}
ngAfterViewInit() {
this.attachScrollSync();
this.attachElementEventHandlers();
this.updateNavigationMetadata();
this.applyAutoSize();
const toolbarComponentWrapper = this.wrapper?.nativeElement?.querySelector('kendo-toolbar');
if (toolbarComponentWrapper) {
this.renderer.addClass(toolbarComponentWrapper, 'k-grid-toolbar');
}
}
ngAfterContentChecked() {
this.columnsContainer.refresh();
this.verifySettings();
this.initSelectionService();
}
ngOnInit() {
if (this.navigable.length) {
this.navigationService.init(this.navigationMetadata(), this.navigable);
}
}
ngOnDestroy() {
if (this.selectionSubscription) {
this.selectionSubscription.unsubscribe();
}
if (this.stateChangeSubscription) {
this.stateChangeSubscription.unsubscribe();
}
if (this.groupExpandCollapseSubscription) {
this.groupExpandCollapseSubscription.unsubscribe();
}
if (this.detailsServiceSubscription) {
this.detailsServiceSubscription.unsubscribe();
}
if (this.editServiceSubscription) {
this.editServiceSubscription.unsubscribe();
}
if (this.pdfSubscription) {
this.pdfSubscription.unsubscribe();
}
if (this.filterSubscription) {
this.filterSubscription.unsubscribe();
}
if (this.sortSubscription) {
this.sortSubscription.unsubscribe();
}
if (this.columnsChangeSubscription) {
this.columnsChangeSubscription.unsubscribe();
}
if (this.excelSubscription) {
this.excelSubscription.unsubscribe();
}
if (this.columnsContainerChangeSubscription) {
this.columnsContainerChangeSubscription.unsubscribe();
}
if (this.scrollSyncService) {
this.scrollSyncService.destroy();
}
if (this.detachElementEventHandlers) {
this.detachElementEventHandlers();
}
if (this.defaultSelection) {
this.defaultSelection.des