UNPKG

@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
/**----------------------------------------------------------------------------------------- * 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