@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.
263 lines (262 loc) • 10.3 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 { Injectable } from '@angular/core';
import { Subject, Subscription, merge } from "rxjs";
import { ViewCollection } from '../data/data.collection';
import { SelectionChangeEvent } from './selection-change-event';
import { hasClasses, isPresent, Keys } from '@progress/kendo-angular-common';
import * as i0 from "@angular/core";
/**
* @hidden
*/
export const defaultSelected = (_item) => false;
const noop = () => false;
/**
* @hidden
*/
export class SelectionService {
changes = new Subject();
selectAllCheckedStateChange = new Subject();
state = [];
set settings(value) {
if (typeof value === 'object') {
this._settings = value;
}
else {
this._settings = {
enabled: value
};
}
this.enabled = this._settings.enabled !== false && !this._settings.readonly;
if (this._settings.enabled !== false) {
if (this._settings.mode === 'cell') {
this.isCellSelected = this.cellSelected;
this.isRowSelected = noop;
}
else {
this.isCellSelected = noop;
this.isRowSelected = this.rowSelected;
}
}
else {
this.isCellSelected = noop;
this.isRowSelected = noop;
}
}
get settings() {
return this._settings;
}
get enableMarquee() {
const checkboxOnly = this.settings?.checkboxOnly;
if (!this.settings || checkboxOnly) {
return false;
}
const dragAndMultiple = typeof (this.settings) === 'object' &&
isPresent(this.settings) &&
this.settings.multiple &&
this.settings.enabled !== false &&
!this.settings.checkboxOnly &&
this.settings.drag !== false;
return this.enabled && dragAndMultiple;
}
get enableMultiple() {
return this.enabled && this.settings.multiple;
}
get rowSelection() {
return this.settings.mode !== 'cell';
}
isSelected = defaultSelected;
isRowSelected = noop;
isCellSelected = noop;
enabled = false;
dragging = false;
selectAllCheckedState = false;
view;
columnsContainer;
_settings = {};
selectionOrigin;
tables = [];
subscriptions = new Subscription();
init(treelist) {
this.view = treelist.view;
this.columnsContainer = treelist.columnsContainer;
this.subscriptions.add(merge(treelist.pageChange, treelist.dataStateChange).subscribe(() => {
this.selectionOrigin = null;
}));
this.subscriptions.add(treelist.domEvents.cellMousedown.subscribe((args) => {
if (this.enabled && this._settings.multiple && args.originalEvent.shiftKey) {
args.originalEvent.preventDefault();
}
}));
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
registerTable(table) {
this.tables.push(table);
}
unregisterTable(table) {
this.tables = this.tables.filter(t => t !== table);
}
click(args, toggle) {
if (this.dragging) {
this.dragging = false;
return;
}
const isCheckbox = args.originalEvent.target ? hasClasses(args.originalEvent.target, 'k-checkbox') : true;
if (this.rowSelection && this.settings.checkboxOnly && !isCheckbox) {
return;
}
const { dataItem, column, columnIndex, originalEvent } = args;
const ctrlKey = originalEvent.ctrlKey || originalEvent.metaKey;
if (((originalEvent.code === Keys.Enter || originalEvent.code === Keys.NumpadEnter) && !ctrlKey) || (originalEvent.button && originalEvent.button !== 0)) {
return;
}
const selected = this.isSelected(dataItem, column, columnIndex);
const toggleSelected = ctrlKey || toggle;
if (this._settings.multiple) {
if (originalEvent.shiftKey) {
const origin = this.selectionOrigin || {
columnIndex: 0,
column: this.leafColumns[0],
item: this.view.firstItem.data
};
this.selectRange({ item: dataItem, column, columnIndex }, origin);
}
else {
this.selectionOrigin = {
item: dataItem,
column,
columnIndex
};
const action = toggleSelected ? (selected ? 'remove' : 'add') : 'select';
this.changes.next(new SelectionChangeEvent(action, [{
dataItem,
column,
columnIndex
}]));
}
}
else if (!selected || toggleSelected) {
const action = selected && toggleSelected ? 'remove' : 'select';
this.changes.next(new SelectionChangeEvent(action, [{
dataItem,
column,
columnIndex
}]));
}
if (ctrlKey) {
originalEvent.preventDefault();
}
}
checkboxClick(args) {
if (this.dragging) {
this.dragging = false;
return;
}
if (args.column.checkChildren && args.viewItem.hasChildren && !args.originalEvent.shiftKey && !args.originalEvent.ctrlKey) {
const data = [args.dataItem];
const selected = Boolean(args.viewItem.selected);
ViewCollection.loadView({
fields: Object.assign({}, this.view.fieldAccessor(), {
data: data,
hasFooter: false,
pageable: false,
isVirtual: false
}),
loaded: this.view.loaded,
selectionState: this.view.selectionState
}).subscribe(view => {
if (!view) {
return;
}
const selectedItems = view.data.filter(item => Boolean(item.selected) === selected).map(item => ({
dataItem: item.data
}));
this.changes.next(new SelectionChangeEvent(selected ? 'remove' : 'add', selectedItems));
});
}
else {
this.click(args, true);
}
}
toggleAll(select) {
ViewCollection.loadView({
fields: Object.assign({}, this.view.fieldAccessor(), {
hasFooter: false,
pageable: false,
isVirtual: false
}),
loaded: this.view.loaded,
selectionState: this.view.selectionState
}).subscribe(view => {
if (!view) {
return;
}
const selectedItems = view.data.filter(item => Boolean(item.selected) !== select).map(item => ({
dataItem: item.data
}));
this.changes.next(new SelectionChangeEvent(select ? 'add' : 'remove', selectedItems));
});
}
selectRange(firstPoint, secondPoint) {
const rangeItems = this.rangeItems(firstPoint, secondPoint);
this.changes.next(new SelectionChangeEvent('select', rangeItems));
}
rangeItems(firstPoint, secondPoint) {
const firstIndex = this.view.findIndex(item => item.data === firstPoint.item || item === firstPoint.item);
const secondIndex = this.view.findIndex(item => item.data === secondPoint.item || item === secondPoint.item);
const startIndex = Math.min(firstIndex, secondIndex);
const endIndex = Math.max(firstIndex, secondIndex);
const rangeItems = this.view.data.slice(startIndex, endIndex + 1).filter(item => item.type === 'data');
if (this._settings.mode === 'cell') {
const leafColumns = this.leafColumns;
const startColumnIndex = Math.min(firstPoint.columnIndex, secondPoint.columnIndex);
const endColumnIndex = Math.max(firstPoint.columnIndex, secondPoint.columnIndex);
const selectedItems = [];
for (let idx = 0; idx < rangeItems.length; idx++) {
for (let columnIdx = startColumnIndex; columnIdx <= endColumnIndex; columnIdx++) {
selectedItems.push({
dataItem: rangeItems[idx].data,
column: leafColumns[columnIdx],
columnIndex: columnIdx
});
}
}
return selectedItems;
}
return rangeItems.map(item => ({
dataItem: item.data
}));
}
updateSelectedState() {
if (this.rowSelection) {
this.view.updateSelectedState();
}
}
targetArgs(target, skipFocusable) {
let result;
this.tables.some(t => {
result = t.targetArgs(target, skipFocusable);
return result;
});
return result;
}
rowSelected(dataItem) {
return this.isSelected(dataItem);
}
cellSelected(dataItem, column, columnIndex) {
return this.isSelected(dataItem, column, columnIndex);
}
// expose in the treelist?
get leafColumns() {
return this.columnsContainer.lockedLeafColumns.toArray().concat(this.columnsContainer.nonLockedLeafColumns.toArray());
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SelectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SelectionService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SelectionService, decorators: [{
type: Injectable
}] });