@progress/kendo-angular-treelist
Version:
Kendo UI TreeList for Angular - Display hierarchical data in an Angular tree grid view that supports sorting, filtering, paging, and much more.
448 lines (445 loc) • 21.9 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, Input, TemplateRef, HostBinding, ChangeDetectorRef, Optional, isDevMode, ElementRef, ViewChild, Renderer2 } from '@angular/core';
import { NgClass, NgTemplateOutlet, NgIf } from '@angular/common';
import { moreVerticalIcon } from '@progress/kendo-svg-icons';
import { IconWrapperComponent } from '@progress/kendo-angular-icons';
import { SinglePopupService } from '../common/single-popup.service';
import { ColumnMenuService } from './column-menu.service';
import { filtersByField } from '../filtering/base-filter-cell.component';
import { hasFilter, hasSort, hasLock, hasColumnChooser, hasAutoSizeColumn, hasAutoSizeAllColumns } from './utils';
import { ColumnMenuFilterComponent } from './column-menu-filter.component';
import { ColumnMenuChooserComponent } from './column-menu-chooser.component';
import { ColumnMenuLockComponent } from './column-menu-lock.component';
import { ColumnMenuSortComponent } from './column-menu-sort.component';
import { ColumnMenuContainerComponent } from './column-menu-container.component';
import { ColumnMenuAutoSizeAllColumnsComponent } from './column-menu-autosize-all.component';
import { ColumnMenuAutoSizeColumnComponent } from './column-menu-autosize.component';
import { ColumnMenuItemDirective } from './column-menu-item.directive';
import { NavigationService } from '../navigation/navigation.service';
import { IdService } from '../common/id.service';
import { ContextService } from '../common/provider.service';
import { ColumnMenuErrorMessages } from '../common/error-messages';
import { Subscription } from 'rxjs';
import { replaceMessagePlaceholder } from '../utils';
import { MenuTabbingService } from '../filtering/menu/menu-tabbing.service';
import * as i0 from "@angular/core";
import * as i1 from "../navigation/navigation.service";
import * as i2 from "../common/single-popup.service";
import * as i3 from "../common/provider.service";
import * as i4 from "./column-menu.service";
import * as i5 from "../common/id.service";
const POPUP_CLASSES = 'k-grid-columnmenu-popup k-column-menu';
let id = 0;
const getId = (gridId) => `${gridId}-column-menu-${id++}`;
/**
* Represents the [column menu]({% slug columnmenu_treelist %}) component of the TreeList.
* Use this component to display and manage column menu options for a TreeList column.
*
* @example
* ```html
* <kendo-treelist ...>
* <kendo-treelist-column field="ProductName">
* <ng-template kendoTreeListColumnMenuTemplate let-service="service" let-column="column">
* <kendo-treelist-column-menu [column]="column"></kendo-treelist-column-menu>
* </ng-template>
* </kendo-treelist-column>
* </kendo-treelist>
* ```
*/
export class ColumnMenuComponent {
navigationService;
popupService;
ctx;
service;
cdr;
renderer;
idService;
anchor;
template;
defaultTemplate;
/**
* @hidden
*/
standalone = true;
/**
* The TreeList column instance to control with the menu.
*/
column;
/**
* The settings for the Column Menu.
*/
settings = {};
/**
* The descriptors by which the data is sorted.
* Typically bound to the same value as [TreeListComponent.sort]({% slug api_treelist_treelistcomponent %}#toc-sort).
*/
sort;
/**
* The descriptor by which the data is filtered.
* Typically bound to the same value as [TreeListComponent.filter]({% slug api_treelist_treelistcomponent %}#toc-filter).
*/
filter;
/**
* @hidden
*/
sortable = true;
/**
* @hidden
*/
columnMenuTemplate;
/**
* @hidden
*/
tabIndex = '-1';
moreVerticalIcon = moreVerticalIcon;
/**
* @hidden
*/
expandedFilter = false;
/**
* @hidden
*/
expandedColumns = false;
popupRef;
closeSubscription;
popupSubs = new Subscription();
constructor(navigationService, popupService, ctx, service, cdr, renderer, idService) {
this.navigationService = navigationService;
this.popupService = popupService;
this.ctx = ctx;
this.service = service;
this.cdr = cdr;
this.renderer = renderer;
this.idService = idService;
this.closeSubscription = service.closeMenu.subscribe(this.close.bind(this));
}
ngAfterViewInit() {
if (this.ctx.treelist.virtualColumns && isDevMode()) {
if (this.settings.autoSizeAllColumns) {
this.settings.autoSizeAllColumns = false;
console.warn(ColumnMenuErrorMessages.autoSizeAllColumns);
}
if (this.settings.autoSizeColumn) {
this.settings.autoSizeColumn = false;
console.warn(ColumnMenuErrorMessages.autoSizeColumn);
}
}
}
ngOnChanges() {
this.service.column = this.column;
this.service.sort = this.sort;
this.service.filter = this.filter;
this.service.sortable = this.sortable;
}
ngOnDestroy() {
this.close();
this.closeSubscription.unsubscribe();
this.popupSubs?.unsubscribe();
this.closeSubscription = this.popupSubs = null;
}
/**
* @hidden
*/
get isActive() {
return (this.hasFilter && filtersByField(this.filter, this.column.field).length > 0) ||
(!this.sortable && this.hasSort && this.sort.find(descriptor => descriptor.field === this.column.field));
}
/**
* @hidden
*/
get hasFilter() {
return hasFilter(this.settings, this.column);
}
/**
* @hidden
*/
get hasSort() {
return hasSort(this.settings, this.column);
}
/**
* @hidden
*/
get hasColumnChooser() {
return hasColumnChooser(this.settings);
}
/**
* @hidden
*/
get hasLock() {
return hasLock(this.settings, this.column);
}
/**
* @hidden
*/
get hasAutoSizeColumn() {
return hasAutoSizeColumn(this.settings);
}
/**
* @hidden
*/
get hasAutoSizeAllColumns() {
return hasAutoSizeAllColumns(this.settings);
}
/**
* @hidden
*/
get isNavigable() {
return this.navigationService.enabled;
}
/**
* @hidden
*/
toggle(e, anchor, template) {
if (e) {
e.preventDefault();
e instanceof KeyboardEvent && e.stopImmediatePropagation();
}
this.expandedFilter = this.getExpandedState(this.settings.filter);
this.expandedColumns = this.getExpandedState(this.settings.columnChooser);
this.popupRef = this.popupService.open(anchor, template, this.popupRef, POPUP_CLASSES);
// Needed as changes to 'popupRef' and 'popupId' are not reflected
// automatically when the Popup is closed by clicking outside the anchor
const ariaRoot = this.isNavigable ? anchor.closest('.k-table-th') : anchor;
if (this.popupRef) {
this.popupSubs?.unsubscribe();
this.popupSubs = null;
this.popupSubs = this.popupRef.popup.instance.anchorViewportLeave.subscribe(() => {
this.popupSubs?.unsubscribe();
this.popupSubs = null;
this.close(true);
this.updateAria(ariaRoot);
});
this.popupSubs.add(() => this.popupRef.popup.instance.close.subscribe(() => {
this.popupSubs?.unsubscribe();
this.popupSubs = this.popupRef = null;
this.updateAria(ariaRoot);
}));
const popupAriaElement = this.popupRef.popupElement.querySelector('.k-grid-columnmenu-popup');
if (popupAriaElement) {
const popupId = getId(this.idService?.gridId());
this.renderer.setAttribute(popupAriaElement, 'id', popupId);
this.renderer.setAttribute(popupAriaElement, 'role', 'dialog');
this.renderer.setAttribute(popupAriaElement, 'aria-label', this.columnMenuTitle);
ariaRoot && this.renderer.setAttribute(ariaRoot, 'aria-controls', popupId);
ariaRoot && this.renderer.setAttribute(ariaRoot, 'aria-expanded', 'true');
}
}
else {
this.focusRoot();
}
}
/**
* @hidden
*/
close(triggerFocus = false) {
this.popupService.destroy();
this.popupRef = null;
this.cdr.markForCheck();
if (!triggerFocus) {
return;
}
this.focusRoot();
}
/**
* @hidden
*/
get columnMenuTitle() {
const localizationMsg = this.ctx.localization.get('columnMenu') || '';
const columnName = this.column.title || this.column.field;
return replaceMessagePlaceholder(localizationMsg, 'columnName', columnName);
}
getExpandedState(menuItemSettings) {
return typeof (menuItemSettings) === 'object' ? menuItemSettings.expanded : false;
}
updateAria(ariaRoot) {
ariaRoot && this.renderer.removeAttribute(ariaRoot, 'aria-controls');
ariaRoot && this.renderer.setAttribute(ariaRoot, 'aria-expanded', 'false');
}
focusRoot() {
this.isNavigable ? this.navigationService.focusCell(0, this.column.leafIndex) : this.anchor.nativeElement.focus();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ColumnMenuComponent, deps: [{ token: i1.NavigationService }, { token: i2.SinglePopupService }, { token: i3.ContextService }, { token: i4.ColumnMenuService }, { token: i0.ChangeDetectorRef }, { token: i0.Renderer2 }, { token: i5.IdService, optional: true }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ColumnMenuComponent, isStandalone: true, selector: "kendo-treelist-column-menu", inputs: { standalone: "standalone", column: "column", settings: "settings", sort: "sort", filter: "filter", sortable: "sortable", columnMenuTemplate: "columnMenuTemplate", tabIndex: "tabIndex" }, host: { properties: { "class.k-grid-column-menu-standalone": "this.standalone" } }, providers: [
ColumnMenuService,
MenuTabbingService
], viewQueries: [{ propertyName: "anchor", first: true, predicate: ["anchor"], descendants: true, static: true }, { propertyName: "template", first: true, predicate: ["template"], descendants: true, read: TemplateRef, static: true }, { propertyName: "defaultTemplate", first: true, predicate: ["defaultTemplate"], descendants: true, read: TemplateRef, static: true }], usesOnChanges: true, ngImport: i0, template: `
<a #anchor
class="k-grid-column-menu k-grid-header-menu"
[ngClass]="{ 'k-active': isActive }"
(click)="toggle($event, anchor, template)"
(keydown.enter)="$event.stopImmediatePropagation()"
href="#"
[tabindex]="tabIndex"
[attr.title]="columnMenuTitle"
[attr.aria-expanded]="isNavigable ? undefined : false"
[attr.aria-haspopup]="isNavigable ? undefined : 'dialog'">
<kendo-icon-wrapper
name="more-vertical"
[svgIcon]="moreVerticalIcon"></kendo-icon-wrapper>
</a>
<ng-template #template>
<kendo-treelist-columnmenu-container
(keydown.escape)="close(true)"
(keydown.enter)="$event.stopImmediatePropagation()">
<ng-container
[ngTemplateOutlet]="column.columnMenuTemplateRef || columnMenuTemplate || defaultTemplate"
[ngTemplateOutletContext]="{ service: service, column: column }">
</ng-container>
</kendo-treelist-columnmenu-container>
</ng-template>
<ng-template #defaultTemplate>
<kendo-treelist-columnmenu-container
(keydown.escape)="close(true)"
(keydown.enter)="$event.stopImmediatePropagation()">
<kendo-treelist-columnmenu-sort #sortItem [kendoTreeListColumnMenuItem]="sortItem" *ngIf="hasSort" [service]="service">
</kendo-treelist-columnmenu-sort>
<kendo-treelist-columnmenu-lock #lockItem *ngIf="hasLock" [kendoTreeListColumnMenuItem]="lockItem" [service]="service">
</kendo-treelist-columnmenu-lock>
<span [style.borderColor]="'rgba(0, 0, 0, 0.08)'" *ngIf="hasColumnChooser || hasAutoSizeColumn || hasAutoSizeAllColumns" class="k-separator"></span>
<kendo-treelist-columnmenu-chooser
#chooserItem
*ngIf="hasColumnChooser"
[kendoTreeListColumnMenuItem]="chooserItem"
[service]="service"
[expanded]="expandedColumns">
</kendo-treelist-columnmenu-chooser>
<kendo-treelist-columnmenu-autosize-column
#autoSizeColumnItem
*ngIf="hasAutoSizeColumn"
[service]="service"
[kendoTreeListColumnMenuItem]="autoSizeColumnItem"
[column]="column"
>
</kendo-treelist-columnmenu-autosize-column>
<kendo-treelist-columnmenu-autosize-all-columns
#autoSizeAllColumnsItem
*ngIf="hasAutoSizeAllColumns"
[service]="service"
[kendoTreeListColumnMenuItem]="autoSizeAllColumnsItem"
>
</kendo-treelist-columnmenu-autosize-all-columns>
<span [style.borderColor]="'rgba(0, 0, 0, 0.08)'" *ngIf="hasColumnChooser || hasAutoSizeColumn || hasAutoSizeAllColumns" class="k-separator"></span>
<kendo-treelist-columnmenu-filter
#filterItem
*ngIf="hasFilter"
[kendoTreeListColumnMenuItem]="filterItem"
[service]="service"
[expanded]="expandedFilter">
</kendo-treelist-columnmenu-filter>
</kendo-treelist-columnmenu-container>
</ng-template>
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ColumnMenuSortComponent, selector: "kendo-treelist-columnmenu-sort" }, { kind: "component", type: ColumnMenuLockComponent, selector: "kendo-treelist-columnmenu-lock" }, { kind: "component", type: ColumnMenuChooserComponent, selector: "kendo-treelist-columnmenu-chooser", inputs: ["expanded", "isLast"], outputs: ["expand", "collapse"] }, { kind: "component", type: ColumnMenuFilterComponent, selector: "kendo-treelist-columnmenu-filter", inputs: ["expanded", "isLast"], outputs: ["expand", "collapse"] }, { kind: "component", type: ColumnMenuContainerComponent, selector: "kendo-treelist-columnmenu-container" }, { kind: "component", type: ColumnMenuAutoSizeAllColumnsComponent, selector: "kendo-treelist-columnmenu-autosize-all-columns" }, { kind: "component", type: ColumnMenuAutoSizeColumnComponent, selector: "kendo-treelist-columnmenu-autosize-column", inputs: ["column"] }, { kind: "directive", type: ColumnMenuItemDirective, selector: "[kendoTreeListColumnMenuItem]", inputs: ["kendoTreeListColumnMenuItem"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ColumnMenuComponent, decorators: [{
type: Component,
args: [{
providers: [
ColumnMenuService,
MenuTabbingService
],
selector: 'kendo-treelist-column-menu',
template: `
<a #anchor
class="k-grid-column-menu k-grid-header-menu"
[ngClass]="{ 'k-active': isActive }"
(click)="toggle($event, anchor, template)"
(keydown.enter)="$event.stopImmediatePropagation()"
href="#"
[tabindex]="tabIndex"
[attr.title]="columnMenuTitle"
[attr.aria-expanded]="isNavigable ? undefined : false"
[attr.aria-haspopup]="isNavigable ? undefined : 'dialog'">
<kendo-icon-wrapper
name="more-vertical"
[svgIcon]="moreVerticalIcon"></kendo-icon-wrapper>
</a>
<ng-template #template>
<kendo-treelist-columnmenu-container
(keydown.escape)="close(true)"
(keydown.enter)="$event.stopImmediatePropagation()">
<ng-container
[ngTemplateOutlet]="column.columnMenuTemplateRef || columnMenuTemplate || defaultTemplate"
[ngTemplateOutletContext]="{ service: service, column: column }">
</ng-container>
</kendo-treelist-columnmenu-container>
</ng-template>
<ng-template #defaultTemplate>
<kendo-treelist-columnmenu-container
(keydown.escape)="close(true)"
(keydown.enter)="$event.stopImmediatePropagation()">
<kendo-treelist-columnmenu-sort #sortItem [kendoTreeListColumnMenuItem]="sortItem" *ngIf="hasSort" [service]="service">
</kendo-treelist-columnmenu-sort>
<kendo-treelist-columnmenu-lock #lockItem *ngIf="hasLock" [kendoTreeListColumnMenuItem]="lockItem" [service]="service">
</kendo-treelist-columnmenu-lock>
<span [style.borderColor]="'rgba(0, 0, 0, 0.08)'" *ngIf="hasColumnChooser || hasAutoSizeColumn || hasAutoSizeAllColumns" class="k-separator"></span>
<kendo-treelist-columnmenu-chooser
#chooserItem
*ngIf="hasColumnChooser"
[kendoTreeListColumnMenuItem]="chooserItem"
[service]="service"
[expanded]="expandedColumns">
</kendo-treelist-columnmenu-chooser>
<kendo-treelist-columnmenu-autosize-column
#autoSizeColumnItem
*ngIf="hasAutoSizeColumn"
[service]="service"
[kendoTreeListColumnMenuItem]="autoSizeColumnItem"
[column]="column"
>
</kendo-treelist-columnmenu-autosize-column>
<kendo-treelist-columnmenu-autosize-all-columns
#autoSizeAllColumnsItem
*ngIf="hasAutoSizeAllColumns"
[service]="service"
[kendoTreeListColumnMenuItem]="autoSizeAllColumnsItem"
>
</kendo-treelist-columnmenu-autosize-all-columns>
<span [style.borderColor]="'rgba(0, 0, 0, 0.08)'" *ngIf="hasColumnChooser || hasAutoSizeColumn || hasAutoSizeAllColumns" class="k-separator"></span>
<kendo-treelist-columnmenu-filter
#filterItem
*ngIf="hasFilter"
[kendoTreeListColumnMenuItem]="filterItem"
[service]="service"
[expanded]="expandedFilter">
</kendo-treelist-columnmenu-filter>
</kendo-treelist-columnmenu-container>
</ng-template>
`,
standalone: true,
imports: [NgClass, IconWrapperComponent, NgTemplateOutlet, NgIf, ColumnMenuSortComponent, ColumnMenuLockComponent, ColumnMenuChooserComponent,
ColumnMenuFilterComponent, ColumnMenuContainerComponent, ColumnMenuAutoSizeAllColumnsComponent, ColumnMenuAutoSizeColumnComponent, ColumnMenuItemDirective]
}]
}], ctorParameters: function () { return [{ type: i1.NavigationService }, { type: i2.SinglePopupService }, { type: i3.ContextService }, { type: i4.ColumnMenuService }, { type: i0.ChangeDetectorRef }, { type: i0.Renderer2 }, { type: i5.IdService, decorators: [{
type: Optional
}] }]; }, propDecorators: { anchor: [{
type: ViewChild,
args: ['anchor', { static: true }]
}], template: [{
type: ViewChild,
args: ['template', { static: true, read: TemplateRef }]
}], defaultTemplate: [{
type: ViewChild,
args: ['defaultTemplate', { static: true, read: TemplateRef }]
}], standalone: [{
type: HostBinding,
args: ['class.k-grid-column-menu-standalone']
}, {
type: Input
}], column: [{
type: Input
}], settings: [{
type: Input
}], sort: [{
type: Input
}], filter: [{
type: Input
}], sortable: [{
type: Input
}], columnMenuTemplate: [{
type: Input
}], tabIndex: [{
type: Input
}] } });