import { switchMap, takeUntil } from 'rxjs/operators'; /* * @Author: 疯狂秀才(lucas huang) * @Date: 2018-12-18 18:44:13 * @LastEditors: 疯狂秀才(Lucas Huang) * @LastEditTime: 2019-11-29 08:41:39 * @Company: Inspur * @Version: v0.0.1 */ import { Component, OnInit, Input, TemplateRef, Directive, ContentChildren, QueryList, AfterContentInit, OnDestroy, HostListener, Output, EventEmitter, ElementRef, Injector, ViewChild, Optional, Self, AfterViewInit, NgZone, Renderer2, ViewEncapsulation, OnChanges, SimpleChanges, AfterViewChecked, ChangeDetectionStrategy, ChangeDetectorRef, DoCheck, HostBinding } from '@angular/core'; import { FarrisTemplateDirective, CommonUtils, IdService, FarrisComponentInstanceService } from '@farris/ui-common'; import { ResizeService } from '@farris/ui-common/events'; import { ColumnFormatService, ColumnFormatter } from '@farris/ui-common/column'; import { InputGroupComponent } from '@farris/ui-input-group'; import { LocaleService } from '@farris/ui-locale'; import { PerfectScrollbarComponent } from '@farris/ui-perfect-scrollbar'; import { Subscription, Observable, of, BehaviorSubject, Subject, timer } from 'rxjs'; import { map } from 'rxjs/operators'; import { cloneDeep, extend, remove } from 'lodash-es'; import ResizeObserver from 'resize-observer-polyfill'; import { TreeTableColumn } from './types/treetable-column'; import { VirtualizedLoaderService } from './virtualized-loader.service'; import { TreeNode, RowNode, SelectEventArgs } from './types/treenode'; import { TreetableService } from './treetable.service'; import { SearchHandle } from './search.handle'; import { TTHotkeys } from './hotkeys'; import { PaginationInstance } from '@farris/ui-pagination'; /** 单元格点事件参数 */ export interface CellClickParam { event: Event; col: TreeTableColumn; node: TreeNode; } /** 节点钩选事件 */ export interface NodeCheckedParam { originalEvent?: any; node: TreeNode | TreeNode[]; nodes?: any[]; instance: TreeTableComponent; } export interface NodeSelectedParam { originalEvent?: Event; node: TreeNode | TreeNode[]; type?: 'row' | 'checkbox'; index?: number; parents?: any[]; instance: TreeTableComponent; } export type IconFieldType = { leaf?: string, expand?: string, collapse?: string } | string; @Directive({ selector: 'tt-column, [tt-column]' }) export class TreeTableColumnDirective { @Input() field: string; @Input() title: string; @Input() width?: number; @Input() hAlign?: string; @Input() align?: string; @Input() formatter?: () => any | ColumnFormatter; } @Component({ selector: 'farris-treetable', templateUrl: 'treetable.component.html', styles: [` .farris-treetable { position: relative; } .resizable-column { background-clip: padding-box; position: relative; } .column-resizer { display: block; position: absolute!important; top: 0; right: 0; margin: 0; width: .5em; height: 100%; padding: 0; cursor: col-resize; border: 1px solid transparent; } .column-resizer-helper { width: 1px; position: absolute; z-index: 10; display: none; border-right: 1px dashed #1890ff; } .farris-treetable-disabled .farris-treetable-body .table tr{ opacity: 0.4; cursor: not-allowed; } .farris-treetable-disabled .table tr.farris-table-selectedrow{ opacity: 1; cursor: default; } .custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e"); background-color: #1890ff; } .unselectable-text { -webkit-user-select: none; -moz-user-select: none; -o-user-select: none; -ms-user-select: none; user-select: none } .search-highlight { background-color: #FFFF1F; } .search-highlight.active{ background-color: #FF901C; } `], exportAs: 'treeTable', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush }) export class TreeTableComponent implements OnInit, AfterContentInit, AfterViewInit, OnChanges, AfterViewChecked, OnDestroy { id = ''; /** 宽度 */ @Input() width = 500; /** 高度 */ @Input() height = 400; @Input() showFilterBar = false; /** 显示查询字段 */ @Input() showSearchField = true; @Input() headerHeight = 35; /** 表头折行 */ @Input() headerWrap = false; /** 行高 */ @Input() rowHeight = 29; /** * 列信息 */ @Input() columns = []; private _groupColumns = []; @Input() set groupColumns(cols) { this._groupColumns = cols || []; this.columns = this.ttService.checkColumnsForGroupHeader(cols.map(cs => [...cs]), this).columns; } get groupColumns() { return this._groupColumns; } /** 启用多表头 */ @Input() useGroupHeader = false; @Input() fitColumns = true; @Input() searchFields: { label: string, value: string }[]; @Input() allColumnsTitle = '所有列'; /** 显示连接线,默认为 false */ @Input() showLines = false; @Input() showLinesOnHover = false; /** * 是否启用多选。 默认为单选 */ @Input() multiSelect = false; @Input() singleSelect = true; /** 启用多选后,是否显示checkbox */ @Input() showCheckbox = false; /** * 主键字段 */ @Input() idField: string; /** 显示边框线 */ @Input() showBorder = true; @Input() showCheckAll = true; @Input() showHeader = true; /** 启用斑马线 */ @Input() striped = false; /** 启用鼠标划过效果 */ @Input() hovered = true; /** 禁用。 禁用时不允许选中行 */ @Input() disabled = false; /** 数据加载方式; 默认值 为 all * - all: 全部加载 * - async: 分层加载 */ @Input() loadDataType: 'all' | 'async' = 'all'; /** 固定表头 */ @Input() fixedHeader = false; /** 当多选时,选中当前行并钩选 */ @Input() checkOnSelect = false; /** 当多选时,钩选并选中当前行 */ @Input() selectOnCheck = false; /** * 启用多选时,此属性为true 时,仅当前行选中 */ @Input() onlySelectSelf = false; /** 启用列宽调整 */ @Input() resizableColumns = true; /** 设置为true时自适应父容的尺寸 */ @Input() fit = false; /** 启用级联选中功能 */ @Input() cascadeCheck = true; /** 向下级联选中 */ @Input() cascadeDown = true; /** 向上级联选中 */ @Input() cascadeUp = true; /** 空数据时,显示的提示文本 */ @Input() emptyMessage = '暂无数据'; /** 缩进间距,默认为 16px */ @Input() indentSpacing = 16; /** * 单击行选中后,在次点击不会被取消选中状态; */ @Input() keepSelect = true; /** 虚拟加载 */ @Input() virtualized = false; /** * 仅允许选择叶子节点 * - default: 应用服务器端传回的设置,如果服务器端没有设置,则与 no 保持一至; * - yes: 如果客户端设置为 `yes`, 服务器端的设置失效; * - no: 允许选择所有节点;服务器端设置失效 */ @Input() onlySelectLeaf: boolean | 'default' | 'yes' | 'no' = 'default'; /** 显示图标 */ @Input() showIcon = false; /** 叶子节点图标 */ @Input() leafIcon = 'file'; /** 节点展开图标 */ @Input() expandIcon = 'folder-open'; /** 节点折叠图标 */ @Input() collapseIcon = 'folder'; @Input() foldIcon = 'treetable-toggler'; @Input() unfoldIcon = 'extanded treetable-toggler'; /** 启用节点图标从数据中获取。 默认为 false */ @Input() useIconFromData = false; /** 当数据源中icon未设置时使用内置ICON. 默认为 true */ @Input() whenDataIconEmptyUseDefault = true; /** 从数据源字段中获取图标 */ @Input() iconField: IconFieldType = ''; /** 图标自定义样式 */ @Input() iconCls: string; /** 启用远端排序 */ @Input() remoteSort = true; /** 排序字段 */ @Input() sortName: string; /** 排序方式 asc | desc */ @Input() sortOrder: string; /** 允许多列排序 */ @Input() multiSort: boolean; @Input() beforeSortColumn: (field: string, order: string) => Observable; @Input() rowStyler: (treeNode: TreeNode, field?: string) => any; @Input() cellStyler: (treeNode: TreeNode, field: string) => any; /** 启用前端文本查找 */ @Input() enableFindText = false; /** 文本查询字段, 弃用 */ @Input() findField: string; // 树列表默认展开层级。 -1: 不展开; 0: 全部展开; >0: 展开到指定级数; @Input() expandLevel = -1; @Input() selectValue = ''; @Output() selectValueChange = new EventEmitter(); /** 启用双击展开节点 */ @Input() dblClickExpand = true; /** 选中节点ID数组 */ @Input() selectValues = []; @Output() selectValuesChange = new EventEmitter(); /** 是否禁止折行,默认不折行 */ @Input() nowrap = true; @Input() checkValues = []; @Output() checkValuesChange = new EventEmitter(); @Input() beforeNodeSelected: (e: any) => Observable; @Input() beforeNodeUnSelected: () => Observable; @Input() beforeNodeChecked: () => Observable; @Input() beforeNodeUnChecked: () => Observable; @Input() beforeCheckAll: (e?: any) => Observable; @Input() beforeUnCheckAll: (e?: any) => Observable; @Input() beforeClickNode: (e?: any) => Observable; /** 分页信息 */ @Input() pagination = false; /** 分页加载子节点数据 */ @Input() paginationForChildren = false; /** 分页条显示模式, default, simple */ @Input() pagerViewMode = 'default'; /** 启用服务器端分页 */ @Input() pagerOnServer = true; /** 每页记录数 */ @Input() pageList = [10, 20, 30, 50, 100]; /** 当前页码 */ @Input() pageIndex = 1; /** 每页记录数 */ @Input() pageSize = 20; /** 分页区高度 */ @Input() pagerHeight = 56; /** 显示每页记录数 */ @Input() showPageList = false; /** 显示分页信息 */ @Input() showPageInfo = true; /** 显示页码 */ @Input() showPageNumber = true; /** 显示页码最大数量 */ @Input() pagerLabelSize = 7; /** 显示页码输入框 */ @Input() showGotoInput = false; /** 总记录数 */ @Input() total = 0; @Input() enableContextMenu = false; @Input() contextMenuItems = []; @Input() beforeShowContextMenu: (e: { [key: string]: any }) => Observable<{ show: boolean, menus: Array}>; @Output() pageSizeChanged = new EventEmitter(); @Output() pageChanged = new EventEmitter(); /** 排序后 */ @Output() columnSorted = new EventEmitter(); /** 选中事件 */ @Output() nodeSelected: EventEmitter = new EventEmitter(); /** 取消选中事件 */ @Output() nodeUnSelect: EventEmitter = new EventEmitter(); /** 钩选 */ @Output() nodeChecked: EventEmitter = new EventEmitter(); /** 取消钩选 */ @Output() nodeUnChecked: EventEmitter = new EventEmitter(); /** 全选事件 */ @Output() checkAll: EventEmitter = new EventEmitter(); /** 取消全选 */ @Output() unCheckAll: EventEmitter = new EventEmitter(); /** 查询 */ @Output() search: EventEmitter = new EventEmitter(); @Output() dblClick: EventEmitter = new EventEmitter(); @Output() loadedData: EventEmitter = new EventEmitter(); @Output() expand: EventEmitter = new EventEmitter(); @Output() collapse: EventEmitter = new EventEmitter(); @Output() expandAllNodes: EventEmitter = new EventEmitter(); @Output() collapseAllNodes: EventEmitter = new EventEmitter(); @Output() appendNew: EventEmitter = new EventEmitter(); @Output() removed: EventEmitter = new EventEmitter(); @Output() updated: EventEmitter = new EventEmitter(); @Output() clearSearchValue = new EventEmitter(); @Output() cellClick = new EventEmitter(); @Output() childsPageChanged = new EventEmitter(); @Input() loadedTimer = 100; serializedValue: RowNode[]; _data: TreeNode[] = []; get data(): any[] { return this._data; } @Input() set data(val: any[]) { this._data = val; this.updateSerializedValue(); this.initNodeStatus(); // autowidth 时,表头宽度依据内容宽度作同步处理 this.setNewColumnsWidth(); this.loadDataSuccess(); } /** 根据内容自动宽度,默认 false; * 此属性开启后,自动列宽失效 */ @Input() autoWidth = false; @ContentChildren(FarrisTemplateDirective) templates: QueryList; @ContentChildren(TreeTableColumnDirective) columnsRef: QueryList; @ViewChild('headerBox') headerBox: ElementRef; @ViewChild('resizerHelper') resizerHelperVC: ElementRef; @ViewChild('headerTable') headerTable: ElementRef; @ViewChild('bodyTable') bodyTable: ElementRef; @ViewChild('inputGroup') inputGroup: InputGroupComponent; @ViewChild(PerfectScrollbarComponent) psRef?: PerfectScrollbarComponent; private headerCheckboxCmp: TreeTableHeaderCheckboxComponent; @ViewChild('allCheckbox') set allCheckbox(content: TreeTableHeaderCheckboxComponent) { this.headerCheckboxCmp = content; } @ViewChild('nodespagerbar') nodesPagerBar: TemplateRef; /** * 表头模板 */ headerTemplate: TemplateRef; /** * 表体模板 */ bodyTemplate: TemplateRef; /** 选中项集合 */ private _selections = []; get selections(): TreeNode[] { return this._selections; } set selections(vals: TreeNode[]) { if (vals && vals.length) { // 过滤掉数据源中不存在节点 this._selections = vals.filter(tn => tn && this.findRowNode(tn.id)); const ids = this._selections.map(tn => tn.id); if (ids.length !== this.selectValues.length || (ids.length === this.selectValues.length && this.difference(ids, this.selectValues).length)) { this.selectValues = ids; setTimeout(() => { this.selectValuesChange.emit(ids); }); } } else { this._selections = []; this.selectValues = []; this.selectValuesChange.emit([]); } } /** * 选中项索引 */ selectRowIndex = -1; /** 选中当前行 */ private _selectedRow = null; get selectedRow(): TreeNode { return this._selectedRow; } set selectedRow(treeNode: TreeNode) { this._selectedRow = treeNode; if (treeNode) { if (this.selectValue !== treeNode.id) { this.selectValue = treeNode.id; this.selectValueChange.emit(treeNode.id); } } else { this.selectValue = null; this.selectValueChange.emit(null); } } /** 钩选的记录集合 */ private _checkeds = []; get checkeds(): TreeNode[] { return this._checkeds; } set checkeds(treeNodes: TreeNode[]) { if (treeNodes && treeNodes.length) { this._checkeds = treeNodes.filter(tn => this.findRowNode(tn.id)); const ids = this._checkeds.map(tn => tn.id); this.checkValues = this.checkValues || []; if (ids.length !== this.checkValues.length || (ids.length === this.checkValues.length && this.difference(ids, this.checkValues).length)) { this.checkValues = ids; setTimeout(() => { this.checkValuesChange.emit(ids); }); } } else { this._checkeds = []; this.checkValues = []; this.checkValuesChange.emit([]); } } // 最外层DIV containerElement: HTMLDivElement; // 记录列拖动前的位置 lastResizerHelperX: number; searchData = { field: '*', value: '' }; scorllBodyHeight: number; searchHandle: SearchHandle; searchButtonText = ''; /** 显示查询工具条 */ showFindInput = false; private isDataChanged = false; scrollLoader: VirtualizedLoaderService; topHideHeight = 0; bottomHideHeight = 0; state = { searchRowNodes: null, searched: false, rowNodes: [], scrollY: 0, scrollX: 0 }; /** 待查询的文本 */ findValue = ''; /** 结果总数 */ findCount = 0; /** 当前索引 */ findCurrent = 0; /** 存储文本查询结果 */ findResult = []; private ro: ResizeObserver | null = null; private findKeysHandler = null; private _hotKeys: TTHotkeys = null; localeService: LocaleService; private idService: IdService; private styleSheetId = 'treetable-style-'; private styleElement; paginationOptions: PaginationInstance = { id: 'Farris-TreeTable-Pagination_', itemsPerPage: this.pageSize, currentPage: this.pageIndex, pageList: this.pageList, totalItems: this.total, remote: this.pagerOnServer }; selectNodeChangedSubject = new BehaviorSubject(null); private _UUID = ''; // 是否有固定列 fixedColumns = false; private destroy$ = new Subject(); private farrisInstances: FarrisComponentInstanceService = null; difference = (first: any[], second: any[]) => first.filter(item => !second.some(secondItem => secondItem == item)); constructor( public ttService: TreetableService, public el: ElementRef, public resizeSer: ResizeService, private renderer: Renderer2, public utils: CommonUtils, public colFormatSer: ColumnFormatService, private shortcuts: TTHotkeys, private cdRef: ChangeDetectorRef, private ngZone: NgZone, private injector: Injector) { this.searchHandle = new SearchHandle(this); this.scrollLoader = new VirtualizedLoaderService(this); if (this.injector) { this._hotKeys = this.injector.get(TTHotkeys, null); this.localeService = this.injector.get(LocaleService, null); this.idService = this.injector.get(IdService, null); this.farrisInstances = this.injector.get(FarrisComponentInstanceService, null); } if (this.idService) { this._UUID = this.idService.uuid(); this.styleSheetId += this.idService.uuid(); } else { this._UUID = ('' + Math.random()).slice(2); this.styleSheetId += ('' + Math.random()).slice(2); } this.ttService = new TreetableService(); } private checkOptons() { // 启用数据折行后,虚拟渲染功能失效 if (!this.nowrap) { this.virtualized = false; } setTimeout(() => { this.reSetSize(); this.setScorllBodyHeight(); }); if (this.multiSelect) { this.singleSelect = false; } if (this.singleSelect && this.showCheckbox) { this.checkOnSelect = true; this.selectOnCheck = true; } if (!this.beforeSortColumn) { this.beforeSortColumn = () => of(true); } if (this.fit) { this.renderer.addClass(this.el.nativeElement, 'f-utils-fill-flex-column'); const parentEl = this.el.nativeElement.parentElement || this.el.nativeElement.parentNode; if (parentEl) { // this.renderer.addClass(parentEl, 'f-utils-fill'); this.renderer.setStyle(parentEl, 'overflow', 'hidden'); } } if (!this.beforeClickNode) { this.beforeClickNode = () => of(true); } if (!this.beforeCheckAll) { this.beforeCheckAll = () => of(true); } if (!this.beforeUnCheckAll) { this.beforeUnCheckAll = () => of(true); } if (this.autoWidth) { this.fitColumns = false; this.resizableColumns = false; } } ngOnInit() { this.id = 'Treetable-' + this._UUID; // 创建 stylesheet this.styleElement = this.utils.createStyleSheet(this.styleSheetId); this.checkColumnOption(); this.checkOptons(); if (this.ngZone) { this.ngZone.runOutsideAngular(() => { if (this.el.nativeElement.parentElement) { this.ro = new ResizeObserver((entries, observer) => { if (entries && entries[0]) { const { width, height } = entries[0].contentRect; const target = entries[0].target; if (this.fit && target['offsetParent']) { this.resize({ width, height }); } } }); this.ro.observe(this.el.nativeElement.parentElement); } }); } this.paginationOptions = this.initPaginationOptions(); } ngOnDestroy() { if (this.ro) { if (this.el.nativeElement.parentElement) { this.ro.unobserve(this.el.nativeElement.parentElement); } this.ro.disconnect(); this.ro = null; } if (this.findKeysHandler) { this.findKeysHandler.unsubscribe(); } this.utils.removeStyleSheet(this.styleSheetId); this.styleElement = null; if (this.farrisInstances) { this.farrisInstances.destroy(this.el.nativeElement); } this.ttService = null; this.destroy$.next(); this.destroy$.complete(); } ngOnChanges(changes: SimpleChanges) { if (changes.data && !changes.data.isFirstChange()) { this.isDataChanged = changes.data.currentValue !== changes.data.previousValue; } if (changes.multiSelect && !changes.multiSelect.isFirstChange()) { this.singleSelect = !changes.multiSelect.currentValue; if (this.singleSelect && this.showCheckbox) { this.selectOnCheck = true; this.checkOnSelect = true; } } if (changes.disabled && !changes.disabled.isFirstChange()) { this.hovered = !changes.disabled.currentValue; } if (changes.enableFindText && !changes.enableFindText.isFirstChange()) { this.setFindText(changes.enableFindText.currentValue); } if (changes.selectValue && !changes.selectValue.isFirstChange()) { this.selectNode(changes.selectValue.currentValue); } if (changes.selectValues && !changes.selectValues.isFirstChange()) { this.selectNodes(changes.selectValues.currentValue); } if (changes.checkValues && !changes.checkValues.isFirstChange()) { this.checkedNodes(changes.checkValues.currentValue); } if (changes.pageIndex !== undefined && !changes.pageIndex.isFirstChange()) { this.paginationOptions = Object.assign(this.paginationOptions, { currentPage: this.pageIndex }); } if (changes.pageSize !== undefined && !changes.pageSize.isFirstChange()) { this.paginationOptions = Object.assign(this.paginationOptions, { itemsPerPage: this.pageSize }); } if (changes.pageList !== undefined && !changes.pageList.isFirstChange()) { this.paginationOptions = Object.assign(this.paginationOptions, { pageList: this.pageList }); } if (changes.total !== undefined && !changes.total.isFirstChange()) { this.paginationOptions = Object.assign(this.paginationOptions, { totalItems: this.total }); } if (changes.fitColumns && !changes.fitColumns.isFirstChange()) { this.setTableWidth(this.allColumnsWidth()); } } ngAfterViewChecked() { // if (this.isDataChanged) { // this.loadedData.emit(); // this.isDataChanged = false; // } else { // this.ngZone.runOutsideAngular(() => { // if (this.psRef) { // this.psRef.directiveRef.update(); // } // }); // } } ngAfterContentInit() { this.templates.forEach((item) => { switch (item.getType()) { case 'header': this.headerTemplate = item.template; break; case 'body': this.bodyTemplate = item.template; break; } }); if (this.columnsRef.length) { this.columns = this.ttc2columns(this.columnsRef); this.columnsRef.changes.subscribe((d: any) => { this.columns = this.ttc2columns(d); }); } } ngAfterViewInit(): void { this.containerElement = this.el.nativeElement.querySelector('.farris-treetable'); this.registerShortcuts(); // autowidth 时,表头宽度依据内容宽度作同步处理 this.setNewColumnsWidth(); this.setRightFixedColumnShadow(); if (this.el && this.farrisInstances) { this.farrisInstances.add(this.el.nativeElement, this); } this.loadDataSuccess(); } private loadDataSuccess() { timer(this.loadedTimer).pipe( takeUntil(this.destroy$) ).subscribe(() => { this.loadedData.emit(); }) } private setNewColumnsWidth() { if (!this.autoWidth) { return; } const newColWidth: any = {}; let headerCells = []; let firstRowCells = []; if (this.showHeader && this.headerTable) { headerCells = Array.from(this.headerTable.nativeElement.querySelectorAll('th')); } if (this.serializedValue && this.serializedValue.length && this.bodyTable) { firstRowCells = Array.from(this.bodyTable.nativeElement.querySelector('tr').querySelectorAll('td')); } firstRowCells.forEach((td: any, index) => { // console.log(td); const fieldName = td.getAttribute('field'); let colWidth = td.getBoundingClientRect().width.toFixed(2); newColWidth[fieldName] = colWidth; const col = this.columns.find(c => c.field === fieldName); if (col && col.width !== undefined) { colWidth = colWidth > col.width ? colWidth : col.width; td.style.minWidth = colWidth + 'px'; } headerCells[index].style.width = colWidth + 'px'; }); } columnIsVisible(col) { return this.ttService.columnIsVisible(col, this); } private columnCssName(n: any) { return `#${this.id} .treetable-cell[field="${n.field}"], #${this.id} .treetable-header-cell[field="${n.field}"]:not(.group-header-cell)`; } // 按固定列的顺序排序 private sortColumnsByFixed(updateColumns = true) { const fixedLeft = this.columns.filter(n => n.fixed === 'left'); const fixedCenter = this.columns.filter(n => !n.fixed || n.fixed === 'center'); const fixedRight = this.columns.filter(n => n.fixed === 'right'); if (fixedLeft && fixedLeft.length) { fixedLeft.forEach((n, index) => { n.left = index ? fixedLeft[index - 1].width : 0; }); } if (fixedRight && fixedRight.length) { let rightWidthTotal = fixedRight.reduce((r, c) => { return r + c.width; }, 0); fixedRight.forEach((n, index) => { rightWidthTotal -= n.width; n.right = rightWidthTotal; }); } if (updateColumns) { this.columns = fixedLeft.concat(fixedCenter).concat(fixedRight); } } private checkColumnOption() { const cssRules = []; if (!this.fitColumns) { this.sortColumnsByFixed(); } this.columns.forEach((n, i) => { if (n.visible === undefined) { n.visible = true; } if (n['halign']) { n.hAlign = n['halign']; } if (n.width === undefined) { n.width = 100; } if (!n.id) { n.id = 'treetable-header-' + (n.field || i); } }); const colWidthTotal = this.allColumnsWidth(); let fixedCols = 0; const fixedRightColumns = this.columns.filter(n => n.fixed === 'right'); if (fixedRightColumns && fixedRightColumns.length) { fixedRightColumns[0]['rightShadowCol'] = true; fixedRightColumns[fixedRightColumns.length - 1]['lastColumn'] = true; } else { if (this.columns && this.columns.length) { this.columns[this.columns.length - 1]['lastColumn'] = true; } } const fixedLeftColumns = this.columns.filter(n => n.fixed === 'left'); if (fixedLeftColumns && fixedLeftColumns.length) { fixedLeftColumns[fixedLeftColumns.length - 1]['leftShadowCol'] = true; } this.columns.forEach((n, index) => { const isVisible = this.columnIsVisible(n); const ruleName = this.columnCssName(n); const cssRule = this.utils.getCssRule(ruleName, this.styleElement.sheet); if (!cssRule) { let stylecss = ruleName + ' { '; stylecss += isVisible ? '' : 'display: none;'; if (this.fitColumns) { stylecss += `width: ${(n.width / colWidthTotal * 100).toFixed(2)}%;`; } else { stylecss += `width: ${n.width}px;`; if (n.fixed) { if (n.fixed === 'left') { stylecss += `left: ${n.left}px`; fixedCols++; } if (n.fixed === 'right') { stylecss += `right: ${n.right}px`; fixedCols++; } } } stylecss += '}'; cssRules.push(stylecss); } else { cssRule.style.display = isVisible ? '' : 'none'; } }); if (cssRules.length) { this.utils.appendCssRules(cssRules, this.styleElement.sheet); } this.setTableWidth(colWidthTotal); // 启用分页加载子节点 if (this.paginationForChildren) { this.columns.push({ field: '__nodespagerbar__', width: 100, title: '', align: 'center', template: this.nodesPagerBar }); } this.fixedColumns = !!fixedCols; } private setTableWidth(colWidthTotal: number) { if (this.headerTable && this.bodyTable) { if (!this.fitColumns) { this.headerTable.nativeElement.style.width = `${colWidthTotal}px`; this.bodyTable.nativeElement.style.width = `${colWidthTotal}px`; } else { this.headerTable.nativeElement.style.width = '100%'; this.bodyTable.nativeElement.style.width = '100%'; } } } private allColumnsWidth() { return this.columns.reduce((r, c) => { if (this.columnIsVisible(c)) { return r + c.width; } else { return r; } }, 0); } onPageChange($event) { this.pageChanged.emit($event); } onPageSizeChange($event) { this.pageSizeChanged.emit($event); } updatePageInfo(pageInfo: { pageIndex?: number, pageSize?: number, total?: number, pageList?: number[] }) { if (pageInfo && Object.keys(pageInfo).length) { const obj: any = {}; if (pageInfo.total !== undefined) { obj.totalItems = pageInfo.total; } if (pageInfo.pageIndex !== undefined) { obj.currentPage = pageInfo.pageIndex; } if (pageInfo.pageSize !== undefined) { obj.itemsPerPage = pageInfo.pageSize; } if (pageInfo.pageList !== undefined) { obj.pageList = pageInfo.pageList; } this.paginationOptions = Object.assign(this.paginationOptions, obj); } } private initPaginationOptions() { return { id: 'Farris-TreeTable-Pagination_' + this._UUID, itemsPerPage: this.pageSize, currentPage: this.pageIndex, pageList: this.pageList, totalItems: this.total, remote: this.pagerOnServer }; } getNodeText(col: TreeTableColumn, rowdata) { if (col.isMultilingualField) { const val = this.utils.getValue(col.field, rowdata); if (this.localeService) { const localeId = this.localeService.localeId || 'zh-CHS'; if (val && typeof val === 'object' && Object.keys(val).length > 0) { return val[localeId]; } else { return val; } } } else { return this.utils.getValue(col.field, rowdata, true); } } private initNodeStatus() { if (!this.singleSelect) { if (this.checkValues && this.checkValues.length) { this.checkedNodes(this.checkValues, false, false); } if (this.selectValues && this.selectValues.length && !this.onlySelectSelf) { this.selectNodes(this.selectValues, false, false); } } else { if (this.selectValue) { this.selectNode(this.selectValue, false); } } } /** 注册快捷键 */ private registerShortcuts() { if (this.showFilterBar && this._hotKeys) { this._hotKeys.addShortcut({ keys: 'enter', element: this.inputGroup.textbox.nativeElement }).subscribe(() => { this.onSearch(); }); } this.setFindText(this.enableFindText); } private setFindText(enable = true) { this.enableFindText = enable; if (this.enableFindText && this._hotKeys) { // 查询工具条 this.findKeysHandler = this._hotKeys.addShortcut({ keys: 'control.shift.f' }).subscribe((e) => { if (this.enableFindText) { this.showFindInput = true; this.detectChanges(); } }); } else { if (this.showFindInput) { this.showFindInput = false; this.cdRef.detectChanges(); } if (this.findKeysHandler) { this.findKeysHandler.unsubscribe(); this.findKeysHandler = null; } } } private getCustomCls(rowNode: RowNode, field: string = '', clsType: 'row' | 'cell' = 'row') { const cls = {}; let fn = this.rowStyler; if (clsType === 'cell') { fn = this.cellStyler; } if (fn) { const rowCls = fn(rowNode.node, field); if (rowCls) { let arrcls = []; if (typeof rowCls === 'string') { arrcls = [rowCls]; } else { if (Array.isArray(rowCls)) { arrcls = rowCls; } } if (arrcls.length) { const rowClsObj = {}; arrcls.forEach(c => { rowClsObj['' + c] = true; }); const _cls = { ...rowClsObj, ...cls }; return _cls; } else { return rowCls; } } else { return cls; } } else { return cls; } } getRowCls(rowNode: RowNode, field?: string) { return this.getCustomCls(rowNode, 'row'); } getCellCls(rowNode: RowNode, field: string) { return this.getCustomCls(rowNode, field, 'cell'); } getRealColWidth(currentCol: TreeTableColumn) { if (this.columns && this.columns.length && this.fitColumns) { const colWidthTotal = this.columns.reduce((r, c) => { return r + (this.columnIsVisible(c) ? c.width : 0); }, 0); if (this.columnIsVisible(currentCol)) { return (currentCol.width / colWidthTotal * 100).toFixed(2) + '%'; } else { return 0; } } return currentCol.width + 'px'; } @HostListener('window:resize') onResized() { this.resizeSer.windowResized(); } private setRightFixedColumnShadow() { if (this.psRef && this.psRef.directiveRef) { const ps = (this.psRef.directiveRef as any).instance; if (!ps) { return; } const { containerWidth, scrollbarXLeft, contentWidth } = ps; if (containerWidth + scrollbarXLeft < contentWidth) { const fixedRightColumns = this.columns.filter(n => n.fixed === 'right'); const ruleName = `#${this.id}.f-treetable--fixed-columns`; if (fixedRightColumns && fixedRightColumns.length) { const fixedRightCssRuleKey = `${ruleName} [r-tt-shadow-col]::before`; const rightCSSRule = `${fixedRightCssRuleKey}{ box-shadow:#d9d9d9 -12px 0px 8px -12px inset; }`; this.utils.appendCssRules([rightCSSRule], this.styleElement.sheet); } } } } private setFixedColumnShadow(x, e) { const fixedLeftColumns = this.columns.filter(n => n.fixed === 'left'); const fixedRightColumns = this.columns.filter(n => n.fixed === 'right'); const ruleName = `#${this.id}.f-treetable--fixed-columns`; let fixedLeftCssRuleKey = ''; let fixedRightCssRuleKey = ''; let leftCSSRule = ''; let rightCSSRule = ''; if (fixedLeftColumns && fixedLeftColumns.length) { const lastFixedLeftColumn = fixedLeftColumns[fixedLeftColumns.length - 1]; fixedLeftCssRuleKey = `${ruleName} [field="${lastFixedLeftColumn.field}"]::after`; leftCSSRule = `${fixedLeftCssRuleKey}{ box-shadow:#d9d9d9 12px 0px 8px -12px inset; }`; } if (fixedRightColumns && fixedRightColumns.length) { fixedRightCssRuleKey = `${ruleName} [r-tt-shadow-col]::before`; rightCSSRule = `${fixedRightCssRuleKey}{ box-shadow:#d9d9d9 -12px 0px 8px -12px inset; }`; } if (x === 0) { // 移除左侧固定列阴影样式 if (fixedLeftCssRuleKey) { this.utils.removeCssRule(fixedLeftCssRuleKey, this.styleElement.sheet); } } else if ((x + e.srcElement.offsetWidth).toFixed() == e.srcElement.scrollWidth) { // 移除右则固定列阴影样式 if (fixedRightCssRuleKey) { this.utils.removeCssRule(fixedRightCssRuleKey, this.styleElement.sheet); } } else { // 添加左右固定列样式 const arr = []; if (leftCSSRule) { arr.push(leftCSSRule); } if (rightCSSRule) { arr.push(rightCSSRule); } this.utils.appendCssRules(arr, this.styleElement.sheet); } } onScrollX(e: any) { if (this.showHeader && this.headerBox) { const x = e.srcElement.scrollLeft; this.state.scrollX = x; // this.renderer.setStyle(this.headerBox.nativeElement.querySelector('table'), 'transform', `translate3d(-${x}px, 0px, 0px)`); this.headerBox.nativeElement.scrollLeft = x; this.setFixedColumnShadow(x, e); } } onScrollY(e: any) { if (this.virtualized) { const scrollTop = e.target.scrollTop; this.renderData(scrollTop); } } private renderData(scrolltop: number) { const { data, topHideHeight, bottomHideHeight } = this.scrollLoader.getRowNodes(scrolltop); this.state.scrollY = scrolltop; this.serializedValue = data; this.topHideHeight = topHideHeight; this.bottomHideHeight = bottomHideHeight; this.detectChanges(); } onSearchValueChange($event) { if (!$event) { this.onClearSearchValue(); } } onClearSearchValue() { this.searchData.value = ''; this.state.searched = false; this.clearSearchValue.emit(); } onCellClick(event: Event, col: any, node: any) { this.cellClick.emit({ event, col, node }); } trackByFn(index, rowNode) { return rowNode.id; } /** * 格式化数据 */ formatData(col: any, data: any, formatter: any) { // const val = this.utils.getValue(field, data, true ); const val = this.getNodeText(col, data); return this.colFormatSer.format(val, data, formatter); } columnsChanged() { this.checkColumnOption(); this.detectChanges(); } private ttc2columns(list: QueryList) { return list.map((n: TreeTableColumnDirective) => { return { field: n.field, title: n.title, align: n.align, hAlign: n.hAlign, width: n.width, formatter: n.formatter }; }); } private reSetSize() { if (this.fit) { const parentEl = this.el.nativeElement.parentNode; this.width = parentEl.offsetWidth; this.height = parentEl.offsetHeight; // 如果在使用时,为组件设置的高度,则以此值为准,仅支持 固定值 ,不支持 百分比 if (this.el.nativeElement.style.height) { let h = this.el.nativeElement.style.height; if (h.indexOf('px') > -1) { h = parseInt(h.replace('px', ''), 10); if (h > 0) { this.height = h; } } } } } private renderVirtualRowNodes(nodePosY = null) { if (this.virtualized) { const scrollY = nodePosY || this.state.scrollY; const _rownodes = this.scrollLoader.getRowNodes(scrollY); this.serializedValue = _rownodes.data; this.topHideHeight = _rownodes.topHideHeight; this.bottomHideHeight = _rownodes.bottomHideHeight; this.detectChanges(); if (nodePosY) { this.ngZone.runOutsideAngular(() => { setTimeout(() => { this.psRef.directiveRef.elementRef.nativeElement.scrollTop = nodePosY; }, 20); }); } } } updateSerializedValue(nodePosY: any = null) { this.serializedValue = []; this.searchHandle.allNodes = []; this.state.rowNodes = []; this.serializeNodes(null, this.data, 0, true); if (this.virtualized && this.fixedHeader) { this.renderVirtualRowNodes(nodePosY); } else { this.serializedValue = this.state.rowNodes; this.detectChanges(); // if (emitChanges) { // } } } /** * 将树节点数据序列化为RowNode * @param parent 父级节点 * @param nodes 树节点集合 * @param level 级别 * @param visible 是否可见 */ serializeNodes(parent: TreeNode, nodes: TreeNode[], level: number, visible: boolean, parentIds?: any[]) { if (nodes && nodes.length) { if (!nodes[0].data) { return; } nodes.forEach((node, index) => { node.parent = parent; if (!node.hasOwnProperty('selectable')) { node.selectable = true; } if (!node.hasOwnProperty('showIcon')) { node.showIcon = true; } if (this.onlySelectLeaf === 'yes' && node.selectable) { node.selectable = !!node.leaf; } const keyid = node.data[this.idField]; node.id = keyid; const isSelected = this.findIndexInSelection(node) > -1; const isChecked = this.findIndexInCheckeds(node) > -1; const isLast = index === nodes.length - 1; const isFirst = index === 0; let parents = []; let parentRn = null; if (parent) { const parentID = parent.data[this.idField]; const _parents = parentIds || []; parents = parents.concat(_parents.map(n => n)); parents.push(parentID); parentRn = this.state.rowNodes.find(n => n.id === parent.id); } const rowNode: RowNode = { id: keyid, node, parent, level, visible: visible && (parent ? parent.expanded : true), isSelected, isChecked, parents, parentRowNode: parentRn, index: this.state.rowNodes.length, last: isLast, first: isFirst }; // this.serializedValue.push(rowNode); this.state.rowNodes.push(rowNode); this.serializeNodes(node, node.children, level + 1, rowNode.visible, parents); }); } } /** * 判断指定的节点是否被选中 * @param node 树节点 */ isSelected(node: TreeNode): boolean { if (this.singleSelect) { if (node && this.selectedRow) { return node.data[this.idField] === this.selectedRow.data[this.idField]; } return false; } else { if (this.selections && this.selections.length) { return this.selections.findIndex(n => n.data[this.idField] === node.data[this.idField]) > -1; } return false; } } /** 判断当前节点的钩选状态 */ isChecked(node: TreeNode): boolean { if (node && this.checkeds) { if (this.checkeds instanceof Array) { return this.checkeds.findIndex((v) => { return v.data[this.idField] === node.data[this.idField]; }) > -1; } return false; } return false; } private unSelectedCurrentRowEmit() { const currSelected = this.selectedRow; if (currSelected) { this.nodeUnSelect.emit({ node: currSelected, type: 'row', instance: this }); } } private _singleSelectNode(e: SelectEventArgs) { this.clearSelections(); this.selectedRow = e.rowNode.node; this.selectRowIndex = e.rowIndex; this.selections = [e.rowNode.node]; e.rowNode.isSelected = true; if (this.showCheckbox) { e.rowNode.isChecked = true; this.clearCheckeds(); this.setCheckeds(e.rowNode); } const sp: NodeSelectedParam = { originalEvent: e.originalEvent, node: e.rowNode.node, type: 'row', index: e.rowIndex, parents: e.rowNode.parents, instance: this }; this.nodeSelected.emit(sp); } private _singleUnSelectNode(e: SelectEventArgs) { this.selectedRow = null; this.selectRowIndex = -1; e.rowNode.isSelected = false; e.rowNode.isChecked = false; this.selections = []; this.nodeUnSelect.emit({ originalEvent: e.originalEvent, node: e.rowNode.node, type: 'row', parents: e.rowNode.parents, instance: this }); } private __singleSelectNode(event: SelectEventArgs, selected = false) { if (!selected) { this.unSelectedCurrentRowEmit(); this._singleSelectNode(event); } else { if (!this.keepSelect) { this._singleUnSelectNode(event); } } } private __multiSelectNode(event: SelectEventArgs, selected = false) { const rowNode = event.rowNode; if (!selected) { this.unSelectedCurrentRowEmit(); this.singleSelectWhenMultiSelect(rowNode, true); this.nodeSelected.emit({ originalEvent: event.originalEvent, node: rowNode.node, type: 'row', index: event.rowIndex, parents: rowNode.parents, instance: this }); } else { if (!this.keepSelect) { this.singleSelectWhenMultiSelect(rowNode, false); this.nodeUnSelect.emit({ originalEvent: event.originalEvent, node: rowNode.node, type: 'row', index: event.rowIndex, parents: rowNode.parents, instance: this }); } } } /** * 行单击事件 */ handleRowClick(event: SelectEventArgs) { const rowNode = event.rowNode; rowNode.node['elementRef'] = event.originalEvent.target; const selected = this.isSelected(rowNode.node); if (this.singleSelect) { this.__singleSelectNode(event, selected); } else { rowNode.indeterminate = false; if (this.onlySelectSelf) { this.__multiSelectNode(event, selected); } else { rowNode.isSelected = !selected; this.state.rowNodes.find(n => n.id === rowNode.id).isSelected = !selected; this.setSelections(rowNode); if (this.checkOnSelect) { rowNode.isChecked = !selected; this.state.rowNodes.find(n => n.id === rowNode.id).isChecked = !selected; this.setCheckeds(rowNode); const nodes = this.cascadeNodes(rowNode, rowNode.isChecked); this.ttService.onChangeCheckboxStatus(rowNode); if (rowNode.isChecked) { this.nodeChecked.emit({ originalEvent: event, node: rowNode.node, nodes, instance: this }); } else { this.nodeUnChecked.emit({ originalEvent: event, node: rowNode.node, nodes, instance: this }); } } } if (this.checkOnSelect) { const ids = this.checkeds.map(tn => tn.id); this.checkValues = ids; this.checkValuesChange.emit(ids); } } this.detectChanges(); } /** * 带有checkbox 的单选 * @param rowNode 当前节点 */ private singleSelectWhenMultiSelect(rowNode: RowNode, flag: boolean) { if (this.checkOnSelect) { rowNode.isChecked = flag; this.setCheckeds(rowNode); this.cascadeNodes(rowNode, rowNode.isChecked); if (rowNode.isChecked) { this.nodeChecked.emit({ originalEvent: event, node: rowNode.node, instance: this }); } else { this.nodeUnChecked.emit({ originalEvent: event, node: rowNode.node, instance: this }); } } this.clearSelections(); if (this.onlySelectSelf) { this.selectedRow = rowNode.node; } rowNode.isSelected = flag; this.setSelections(rowNode); } private selectWhenChecked(rowNode: RowNode, flag: boolean, event: any) { rowNode.isChecked = flag; this.setCheckeds(rowNode); if (this.selectOnCheck) { if (this.onlySelectSelf) { this.clearSelections(); } if (this.singleSelect && this.showCheckbox) { this.clearCheckeds(); } rowNode.isSelected = flag; this.setSelections(rowNode); this.state.rowNodes.find(n => n.id === rowNode.id).isSelected = flag; if (rowNode.isSelected) { this.nodeSelected.emit({ originalEvent: event, node: rowNode.node, instance: this, type: 'checkbox' }); } else { this.nodeUnSelect.emit({ originalEvent: event, node: rowNode.node, instance: this, type: 'checkbox' }); } } } private isInArray(rowNode: RowNode, items: TreeNode[]) { if (items && items.length === 0) { return false; } return items.findIndex(i => i.data[this.idField] === rowNode.id) > -1; } private setSelections(rowNode: RowNode) { if (rowNode.isSelected) { if (!this.selections) { this.selections = []; } if (!this.isInArray(rowNode, this.selections)) { // this.selections = [...this.selections, rowNode.node]; this.selections.push(rowNode.node); } } else { if (this.selections) { const i = this.selections.findIndex((tn: TreeNode) => tn.data[this.idField] === rowNode.node.data[this.idField]); if (i !== -1) { this.selections.splice(i, 1); } // this.selections = this.selections.filter((tn: TreeNode) => tn.data[this.idField] !== rowNode.node.data[this.idField]); } } } private setCheckeds(rowNode: RowNode) { if (rowNode.isChecked) { if (!this.checkeds) { this.checkeds = []; } if (!this.isInArray(rowNode, this.checkeds)) { // this.checkeds = [ ...this.checkeds, rowNode.node]; this.checkeds.push(rowNode.node); } } else { if (this.checkeds) { const i = this.checkeds.findIndex((tn: TreeNode) => tn.data[this.idField] === rowNode.node.data[this.idField]); if (i !== -1) { this.checkeds.splice(i, 1); } // this.checkeds = this.checkeds.filter((tn: TreeNode) => tn.data[this.idField] !== rowNode.node.data[this.idField]); } } } private getDiffrentData(data: TreeNode[]) { if (data.length) { const _nodes = []; data.forEach(n => { if (_nodes.findIndex(t => t.data[this.idField] === n.data[this.idField]) === -1) { _nodes.push(n); } }); return _nodes; } return data; } private cascadeCheckedNode(rowNode: RowNode, checked: boolean, sync = false): TreeNode[] { const nodes: TreeNode[] = []; if (rowNode.node.selectable || rowNode.node.selectable === undefined) { rowNode.isChecked = checked; this.setCheckeds(rowNode); if (this.selectOnCheck) { if (sync) { const status = this.isIndeterminate(rowNode) !== 2; checked = status ? checked: false; } rowNode.isSelected = checked; this.setSelections(rowNode); } if (!this.isInArray(rowNode, nodes)) { nodes.push(rowNode.node); } } return nodes; } /** * 向下级联选中 * @param node 当前节点 * @param checked 选中状态 * 返回受影响的行 */ propagateSelectionDown(rowNode: RowNode, checked: boolean) { let nodes = this.cascadeCheckedNode(rowNode, checked); if (this.cascadeCheck && this.cascadeDown && rowNode.node.children && rowNode.node.children.length) { rowNode.node.children.forEach((v, i) => { const rn = this.state.rowNodes.find((row: RowNode) => row.id === v.data[this.idField]); if (rn && rn.node.selectable) { rn.indeterminate = false; rn.isChecked = checked; nodes = nodes.concat(this.propagateSelectionDown(rn, checked)); this.ttService.onChangeCheckboxStatus(rn); } }); } return nodes; } /** * 向上级联选中 */ propagateSelectionUp(rowNode: RowNode, checked: boolean) { const nodes = this.cascadeCheckedNode(rowNode, checked); // console.log(rowNode.parents); if (this.cascadeCheck && this.cascadeUp && rowNode.node.parent) { if (rowNode.parents && rowNode.parents.length) { const rns: RowNode[] = []; rowNode.parents.forEach((n, i) => { const rn = this.findRowNode(n); if (rn && rn.node.selectable) { // rn.isChecked = checked; const checkedNodes = this.cascadeCheckedNode(rn, checked, true); if (checkedNodes.length) { nodes.push(checkedNodes[0]); } rns.push(rn); } }); rns.reverse().forEach(rn => { this.updateCheckboxStatus(rn); }); } } return nodes; } // 节点状态(选中,勾选)同步更新 private updateNodeStatus() { if (this.state.searched) { const rowNodes = this.state.searchRowNodes; if (!rowNodes) { return; } rowNodes.forEach(rn => { const checkedItem: RowNode = this.state.rowNodes.find(n => n.id === rn.id); const chked = checkedItem ? checkedItem.isChecked : false; rn.indeterminate = false; rn.isChecked = chked; if (this.selectOnCheck) { if (checkedItem.isSelected != rn.isChecked) { checkedItem.isSelected = checkedItem.isChecked; } rn.isSelected = checkedItem.isSelected; } }); } else { if (this.selectOnCheck) { this.state.rowNodes.forEach(rn => { if (this.onlySelectSelf) { if (!this.selections || !this.selections.length ) { rn.isSelected = false; } else { if (this.selections[0].id === rn.id) { rn.isSelected = true; } else { rn.isSelected = false; } } } else { if (rn.isSelected != rn.isChecked) { rn.isSelected = rn.isChecked; } } }); } } } /** * 级联选择节点 * @param rowNode 当前节点 * @param checked 是否钩选 */ private cascadeNodes(rowNode: RowNode, checked: boolean) { let checkedNodes = this.propagateSelectionDown(rowNode, checked); checkedNodes = checkedNodes.concat(this.propagateSelectionUp(rowNode, checked)); checkedNodes = this.getDiffrentData(checkedNodes); this.updateNodeStatus(); this.updateAllCheckboxStatus(); return checkedNodes; } /** * 行Checkbox 选中变化事件 * @param event 鼠标点击事件对象 * @param rowNode 当前树节点 */ toggleNodeWithCheckbox(event: Event, rowNode: RowNode, checked: boolean): any { this.state.rowNodes.find(n => n.id === rowNode.id).isChecked = checked; rowNode.indeterminate = false; // let checkedNodes = this.propagateSelectionDown(rowNode, checked); // checkedNodes = checkedNodes.concat(this.propagateSelectionUp(rowNode, checked)); // const data = this.getDiffrentData(checkedNodes); const data = this.cascadeNodes(rowNode, checked); this.ttService.onChangeCheckboxStatus(rowNode); if (checked) { this.nodeChecked.emit({ originalEvent: event, node: rowNode.node, nodes: data, instance: this }); } else { this.nodeUnChecked.emit({ originalEvent: event, node: rowNode.node, nodes: data, instance: this }); } this.selectWhenChecked(rowNode, checked, event); this.checkeds = [...this.checkeds]; this.detectChanges(); event.stopPropagation(); } /** * 全选 * @param event 鼠标事件对象 * @param checked 是否选中 */ toggleNodesWithCheckbox(event: Event, checked: boolean) { if (checked) { if (this.data && this.data.length) { this.checkAllNodes({ originalEvent: event, checked, instance: this }); } } else { this.selections = null; this.unCheckAllNodes({ originalEvent: event, checked, instance: this }); } this.detectChanges(); } /** 更新全选checkbox 的状态 */ private updateAllCheckboxStatus() { const status = this.isIndeterminate(); if (this.showCheckAll && !this.singleSelect && this.showCheckbox) { if (status !== undefined) { if (this.headerCheckboxCmp) { this.headerCheckboxCmp.chkAll.nativeElement.indeterminate = status === 2; this.headerCheckboxCmp.checked = status === 1; } } } } /** 更新父节点checkbox 选中状态 */ private updateParentCheckboxStatus(rowNode: RowNode) { const pNodes = []; rowNode.parents.forEach(rnid => { const rn = this.findRowNode(rnid); if (rn) { pNodes.push(rn); } }); pNodes.reverse().forEach(rn => { this.updateCheckboxStatus(rn); }); } /** 更新N个父节点复选框中的选中状态 */ private updateParentsCheckStatus(parentIds: any) { let pids = []; parentIds.forEach((ids: any) => { pids = [...pids, ...ids]; }); pids = Array.from(new Set(pids)); const pRowNodes = this.findRowNodes(pids); pRowNodes.sort((r, n) => { return n.level - r.level; }).forEach(rn => { this.updateCheckboxStatus(rn); }); // console.log(pRowNodes); } /** 更新节点中checkbox的状态 */ private updateCheckboxStatus(rowNode: RowNode) { const status = this.isIndeterminate(rowNode); if (status !== undefined && rowNode.node.selectable) { rowNode.indeterminate = status === 2; this.ttService.onChangeCheckboxStatus(rowNode); } } /** 判断指定节点checkbox 的状态 */ private isIndeterminate(rowNode?: RowNode): any { const status = this.getCheckboxStatus(rowNode); if (status !== undefined) { let checked = false; if (status === 2) { checked = false; } else { checked = !!status; } if (rowNode && rowNode.node.selectable) { rowNode.isChecked = checked; this.setCheckeds(rowNode); } } return status; } /** * 返回父级节点checkbox 的状态。0:不选中, 1: 选中, 2:不确定 * @param rowNode 父级节点;为空时返回全选checkbox 的状态 */ private getCheckboxStatus(rowNode?: RowNode) { let uncheckedLength = 0; let checkedLength = 0; let len = 0; let childNodes = []; if (rowNode) { childNodes = rowNode.node.children || []; } else { childNodes = this.state.rowNodes.filter(rn => rn.level === 0); } len = childNodes.length; childNodes.forEach(n => { let rn = n; if (rowNode) { rn = this.findRowNode(n.data[this.idField]); } if (rn && !rn.indeterminate) { if (rn.isChecked) { checkedLength++; } else { uncheckedLength++; } } }); if (len === 0) { return undefined; } let flag = 0; if (uncheckedLength === len) { flag = 0; } else { if (checkedLength === len) { flag = 1; } else { flag = 2; } } return flag; } private findIndexIn(node: TreeNode, treeNodes: TreeNode[]) { let index = -1; if (treeNodes && treeNodes.length) { index = treeNodes.findIndex((v, i) => { return this.equals(v, node); }); } return index; } /** * 在选中的集合中查找指定节点的索引 * @param node 树节点 */ findIndexInCheckeds(node: TreeNode): number { return this.findIndexIn(node, this.checkeds); } /** * 在选中的集合中查找指定节点的索引 * @param node 树节点 */ findIndexInSelection(node: TreeNode): number { return this.findIndexIn(node, this.selections); } /** 对比两个节点是否同一个节点 */ equals(target: TreeNode, source: TreeNode): boolean { return target.data[this.idField] === source.data[this.idField]; } /** * 展开所有节点 * @param rowNode 展开此节点下的所有节点;如果为空,则展开所有节点 * @param deep 设置为true 时,当前节点下的所有子节点全部展开 */ toggleExpand(treeNode?: TreeNode, deep = false) { if (treeNode) { treeNode.expanded = !treeNode.expanded; this.updateNodeProperty(treeNode.id, {expanded: treeNode.expanded}); this.toggleVisible(treeNode); if (this.virtualized) { if (treeNode.children && treeNode.children.length) { this.renderVirtualRowNodes(); } } if (treeNode.expanded) { this.expand.emit(treeNode); } else { this.collapse.emit(treeNode); } if (deep) { if (treeNode.children && treeNode.children.length) { treeNode.children.forEach((row, i) => { this.toggleExpand(row, deep); }); } } } else { if (this.data && this.data.length) { this.data.forEach((row: any) => { this.toggleExpand(row); }); } } this.setNewColumnsWidth(); } private toggleVisible(treeNode: TreeNode) { const rowNode = this.findRowNode(treeNode.id); if (rowNode) { const rid = rowNode.id; const setRowVisible = (rn, visible) => { const rnParent = this.findRowNode(rn.parent.data[this.idField]); if (rnParent) { rn.visible = rnParent.visible && rnParent.node.expanded; } else { rn.visible = visible; } const _r = this.serializedValue.find(n => n.id === rn.id); if (_r) { _r.visible = rn.visible; } }; this.state.rowNodes.filter(rn => rn.parents.indexOf(rid) > -1).forEach(rn => { setRowVisible(rn, treeNode.expanded); }); } } /** 展开指定节点 * emit: 触发展开事件 * render: 数据重新加载 */ expandNode(id: string, emit = true, render = true) { if (id) { const rowNode = this.findRowNode(id); if (rowNode) { rowNode.node.expanded = true; this.toggleVisible(rowNode.node); if (render) { if (this.virtualized) { if (rowNode.node.children && rowNode.node.children.length) { this.renderVirtualRowNodes(); } } else { this.detectChanges(); } } if (emit) { this.expand.emit(rowNode.node); } this.setNewColumnsWidth(); } else { console.error('Con\'t find tree node widh id:' + id); } } else { console.error('PARAM id is null'); } } /** 展开所有节点 */ expandAll() { this.state.rowNodes = this.state.rowNodes.map((row: RowNode) => { row.node.expanded = true; row.visible = true; return row; }); if (this.virtualized) { this.renderVirtualRowNodes(); } else { this.serializedValue = this.state.rowNodes; } this.setNewColumnsWidth(); this.expandAllNodes.emit(); this.detectChanges(); } expandNodes(id: string) { // const rowNode = this.findRowNode(id); // const rootNode = this.getRoot(id); // if (rowNode) { // const treeNode = rowNode.node; // treeNode.expanded = true; // rowNode.visible = true; // const parents = rowNode.parents; // this.state.rowNodes.filter(n => n.parents.indexOf(rootNode.id) > -1).forEach(r => { // if (parents.indexOf(r.id) > -1) { // r.visible = true; // r.node.expanded = true; // } // }); // this.detectChanges(); // } } /** 展开指定节点下的所有节点 */ expandAllNodesBy(rowNode: any) { if (rowNode) { const treeNode = rowNode.node; treeNode.expanded = true; rowNode.visible = true; this.state.rowNodes.filter(n => n.parents.indexOf(rowNode.id) > -1).forEach(r => { r.visible = true; r.node.expanded = true; }); this.detectChanges(); this.setNewColumnsWidth(); } } /** 折叠指定节点 */ collapseNode(id: string) { if (id) { const rowNode = this.findRowNode(id); if (rowNode) { rowNode.node.expanded = false; this.toggleVisible(rowNode.node); this.detectChanges(); this.setNewColumnsWidth(); this.collapse.emit(rowNode.node); } else { console.error('Con\'t find tree node widh id:' + id); } } else { console.error('PARAM id is null'); } } /** 折叠所有节点 */ collapseAll() { this.state.rowNodes = this.state.rowNodes.map((row: RowNode) => { row.node.expanded = false; row.visible = !row.parent; return row; }); if (this.virtualized) { this.renderVirtualRowNodes(); } else { this.serializedValue = this.state.rowNodes; } this.collapseAllNodes.emit(); this.detectChanges(); this.setNewColumnsWidth(); } getContainerOffset() { const rect = this.containerElement.getBoundingClientRect(); return { left: rect.left + document.body.scrollLeft, top: rect.top + document.body.scrollTop }; } onColumnResizeBegin(event) { const containerLeft = Math.round(this.getContainerOffset().left); this.lastResizerHelperX = (event.pageX - containerLeft + this.containerElement.scrollLeft); event.preventDefault(); } onColumnResize(event) { if (this.resizableColumns) { const containerLeft = this.getContainerOffset().left; this.renderer.addClass(this.containerElement, 'ui-unselectable-text'); this.resizerHelperVC.nativeElement.style.height = this.containerElement.offsetHeight + 'px'; this.resizerHelperVC.nativeElement.style.top = 0 + 'px'; this.resizerHelperVC.nativeElement.style.left = (event.pageX - containerLeft) + 'px'; this.resizerHelperVC.nativeElement.style.display = 'block'; } } onColumnResizeEnd(event: any, column: any) { // 偏移量 const delta = this.resizerHelperVC.nativeElement.offsetLeft - this.lastResizerHelperX; // 列宽 const columnWidth = column.offsetWidth; // 新宽度 const newColumnWidth = columnWidth + delta; // 最小宽度 const minWidth = column.style.minWidth || 15; let resizeColumnIndex = -1; if (column.id) { resizeColumnIndex = this.columns.findIndex(n => n.id === column.id); } else { const colField = column.getAttribute('field'); if (colField) { resizeColumnIndex = this.columns.findIndex(n => n.field === colField); } } if (!this.fitColumns) { this.resizeColGroup(this.headerTable.nativeElement, resizeColumnIndex, newColumnWidth, 0); this.resizeColGroup(this.bodyTable.nativeElement, resizeColumnIndex, newColumnWidth, 0); if (this.columns[resizeColumnIndex]) { this.columns[resizeColumnIndex].width = newColumnWidth; } this.sortColumnsByFixed(false); this.setTableWidth(this.allColumnsWidth()); // update css. const _colInfo = this.columns[resizeColumnIndex]; if (_colInfo) { const ruleName = this.columnCssName(_colInfo); const stylecss = ruleName + '{width:' + newColumnWidth + 'px}'; this.utils.appendCssRules([stylecss], this.styleElement.sheet); } const nextColumn = this.columns[resizeColumnIndex + 1]; if (nextColumn && nextColumn.fixed) { const ruleName = this.columnCssName(nextColumn); const stylecss = ruleName + '{' + nextColumn.fixed + ':' + nextColumn[nextColumn.fixed] + 'px}'; this.utils.appendCssRules([stylecss], this.styleElement.sheet); } } else { if (newColumnWidth > minWidth) { let nextColumn = column.nextElementSibling; while (!nextColumn.offsetParent) { nextColumn = nextColumn.nextElementSibling; } if (nextColumn) { const nextColumnWidth = nextColumn.offsetWidth - delta; const nextColumnMinWidth = nextColumn.style.minWidth || 15; if (newColumnWidth > 15 && nextColumnWidth > nextColumnMinWidth) { this.resizeColGroup(this.headerTable.nativeElement, resizeColumnIndex, newColumnWidth, nextColumnWidth); this.resizeColGroup(this.bodyTable.nativeElement, resizeColumnIndex, newColumnWidth, nextColumnWidth); } } } } this.resizerHelperVC.nativeElement.style.display = 'none'; this.renderer.removeClass(this.containerElement, 'ui-unselectable-text'); this.psRef.directiveRef.update(); } resizeColGroup(table, resizeColumnIndex, newColumnWidth, nextColumnWidth?: number) { if (table) { const colGroup = table.children[0].nodeName === 'COLGROUP' ? table.children[0] : null; if (colGroup) { const col = colGroup.children[resizeColumnIndex]; const nextCol = col.nextElementSibling; col.style.width = newColumnWidth + 'px'; if (nextCol) { if (nextColumnWidth && this.fitColumns) { nextCol.style.width = nextColumnWidth + 'px'; } } } else { throw new Error('Scrollable tables require a colgroup to support resizable columns'); } } } onSearch() { this.state.searchRowNodes = null; this.state.searched = !!this.searchData.value; this.search.emit(this.searchData); } insert(node: TreeNode, parent?: TreeNode) { if (parent) { if (!parent.children) { parent.children = []; } parent.children.push(node); this.expandNode(parent.data[this.idField]); this.appendNew.emit({ parent }); } else { this.data.push(node); } this.updateSerializedValue(); } append(data: { [key: string]: any }, parent?: TreeNode) { if (data && data[this.idField]) { this.insert({ data, id: data[this.idField] }, parent); } else { console.warn('Please set id value.'); } } private emitAppendNew(parent: TreeNode) { if (this.virtualized) { const nodePosY = this.psRef.directiveRef.elementRef.nativeElement.scrollTop; this.updateSerializedValue(nodePosY); } else { this.updateSerializedValue(); } } appendChildren(nodes: TreeNode[], parent: TreeNode, emit = true) { if (nodes && nodes.length) { if (parent) { parent.children.push(...nodes); this.emitAppendNew(parent); if (emit) { this.appendNew.emit({ parent }); } } else { throw new Error('未找到父级节点'); } } else { this.updateSerializedValue(); console.warn('添加子节点的数据为空'); } } loadChildren(nodes: TreeNode[], parent: TreeNode, emit = true) { if (parent) { parent.children = nodes || []; this.emitAppendNew(parent); if (emit) { this.appendNew.emit({ parent }); } } } remove(nodeId: string) { if (nodeId) { const rowNode = this.findRowNode(nodeId); if (rowNode) { // remove(this.state.rowNodes, (n) => { // return n.id === nodeId; // }); if (this.selectValue === nodeId) { this.selectedRow = null; this.selectValue = null; } if (rowNode.parent) { remove(rowNode.parent.children, (n) => { return n.data[this.idField] === nodeId; }); } else { remove(this.state.rowNodes, (n) => { return n.id === nodeId; }); this.data = this.data.filter(n => n.data[this.idField] !== nodeId); } this.updateSerializedValue(); this.detectChanges(); this.ttService.onRemove(rowNode); this.removed.emit(rowNode); } } } update(nodeId: string, data: { [key: string]: string }) { if (nodeId && data) { const rowNode = this.findRowNode(nodeId); if (rowNode) { rowNode.node.data = extend(rowNode.node.data, data); this.detectChanges(); this.ttService.onUpdate(rowNode); this.updated.emit(rowNode); } } } updateNodeProperty(nodeid: string, property: { [key: string]: any } ) { const rowNode = this.findRowNode(nodeid); if (rowNode && property) { Object.keys(property).forEach(k => { rowNode.node[k] = property[k]; }); } } private setScorllBodyHeight() { this.scorllBodyHeight = this.height; if (this.showFilterBar) { this.scorllBodyHeight = this.scorllBodyHeight - 46; } if (this.fixedHeader && this.showHeader) { if (this.headerBox && (this.headerWrap || this.useGroupHeader)) { const hh = this.headerBox.nativeElement.clientHeight; this.scorllBodyHeight = this.scorllBodyHeight - hh; } else { this.scorllBodyHeight = this.scorllBodyHeight - this.headerHeight; } } if (this.pagination) { this.scorllBodyHeight = this.scorllBodyHeight - this.pagerHeight; } this.detectChanges(); } resize(size?: { width: number, height: number }) { if (size) { const w = parseInt('' + size.width, 10); const h = parseInt('' + size.height, 10); if (this.width === w && this.height === h) { return; } this.width = w; this.height = h; } this.setScorllBodyHeight(); if (this.data && this.data.length) { this.renderVirtualRowNodes(); } // setTimeout(() => { // if (this.psRef) { // this.psRef.directiveRef.update(); // } // }, 100); } loadData(data: TreeNode[]) { this.data = data; } empty() { this.data = []; } private getCurrentAllRowNodes() { if (this.state.searched && this.state.searchRowNodes && this.state.searchRowNodes.length) { return this.state.searchRowNodes || this.state.rowNodes; } else { return this.state.rowNodes; } } clearAll(detectChanges = true): TreeTableComponent { this.state.rowNodes.filter(rn => rn.indeterminate).forEach(rn => { rn.indeterminate = false; this.ttService.onChangeCheckboxStatus(rn); }); this.state.rowNodes = this.state.rowNodes.map((row: RowNode) => { row.isChecked = false; row.isSelected = false; row.indeterminate = false; return row; }); // if (this.searchData && this.searchData.value) { if (this.state.searched && this.state.searchRowNodes && this.state.searchRowNodes.length) { this.serializedValue = this.state.searchRowNodes.map((row: RowNode) => { row.isChecked = false; row.isSelected = false; row.indeterminate = false; return row; }); } this.checkeds = []; this.selections = []; this.selectedRow = undefined; if (this.showCheckAll && this.headerCheckboxCmp) { this.headerCheckboxCmp.chkAll.nativeElement.indeterminate = false; this.headerCheckboxCmp.checked = false; } if (detectChanges) { this.detectChanges(); } return this; } detectChanges(): TreeTableComponent { this.cdRef.markForCheck(); // tslint:disable-next-line:no-string-literal if (!this.cdRef['destroyed']) { this.cdRef.detectChanges(); } return this; } clearCheckeds(): TreeTableComponent { if (this.checkeds && this.checkeds.length) { this.findRowNodes(this.checkeds.map(tn => tn.data[this.idField])).forEach(rn => { rn.isChecked = false; }); } this.checkeds = []; if (this.showCheckAll && this.headerCheckboxCmp) { this.headerCheckboxCmp.chkAll.nativeElement.indeterminate = false; this.headerCheckboxCmp.checked = false; } return this; } clearSelections(): TreeTableComponent { this.serializedValue.forEach(n => { n.isSelected = false; }); this.state.rowNodes.forEach(n => { n.isSelected = false; }); if (this.selections && this.selections.length) { const nodeIds = this.selections.map(tn => tn.data[this.idField]); this.findRowNodes(nodeIds).forEach(rn => { rn.isSelected = false; if (this.singleSelect) { rn.isChecked = false; } }); } this.selections = undefined; this.selectedRow = undefined; return this; } /** 选中第1个可以选中的节点 */ selectFirstNode() { if (this.state.rowNodes && this.state.rowNodes.length) { const firstCanSelectNode = this.state.rowNodes.find(n => n.node.selectable); if (firstCanSelectNode) { this.selectNode(firstCanSelectNode.id); } } } private scrollToCurrentRow(id: string) { const trId = 'rownode-' + id; // const trEle = document.getElementById(trId); const trEle = this.el.nativeElement.querySelector('#' + trId) || document.getElementById(trId); if (trEle && trEle.closest('.farris-treetable') && trEle.closest('.farris-treetable').id === this.id) { if (!this.isElementInViewport(trEle)) { if (trEle.scrollIntoViewIfNeeded) { trEle.scrollIntoViewIfNeeded(); return true; } const browserInfo = this.utils.getBrowserType(); if (browserInfo.ff && this.utils.getFFVer() < 58) { trEle.scrollIntoView(); } else { trEle.scrollIntoView({ block: 'nearest', inline: 'nearest' }); } } return true; } return false; } private isElementInViewport(el) { const containerEl = this.el.nativeElement.querySelector('.farris-treetable-body'); if (!containerEl) { return false; } const container = containerEl.getBoundingClientRect(); const viewPortHeight = container.height; const offsetTop = el.offsetTop + this.topHideHeight; const scrollTop = this.psRef.directiveRef.elementRef.nativeElement.scrollTop; const top = offsetTop - scrollTop; return top >= 0 && top <= viewPortHeight; } scrollToCurrentNode() { if (this.selectedRow) { const id = this.selectedRow.id; const result = this.scrollToCurrentRow(id); if (!result) { const rowNode = this.findRowNode(id); const showRowsOnePager = Math.ceil(this.scorllBodyHeight / this.rowHeight ) + 1; if (rowNode.parents && rowNode.parents.length) { rowNode.parents.forEach(pid => { this.expandNode(pid, false, false); }); } const rowIndex = this.state.rowNodes.filter(n => n.visible).findIndex(n => n.id === id); if (rowIndex < showRowsOnePager) { this.renderData(0); setTimeout(() => { this.scrollToCurrentRow(id); }); return; } const top = rowIndex * this.rowHeight; if (this.virtualized) { this.renderData(top - this.rowHeight); } else { this.scrollToY(top - this.rowHeight); } this.scrollToCurrentRow(id); } } } /** 选中指定ID 的节点, * detectChanges: 触发脏值检查,默认为 true * gotocurrent: 使用选中节点可见,默认为 true */ selectNode(id: string, detectChanges = true, gotocurrent = true): TreeTableComponent { if (!id) { return this; } const node = this.findRowNode(id); const emit = !this.selectedRow || (this.selectedRow && id !== this.selectedRow.id); if (node && emit) { if (this.singleSelect || this.onlySelectSelf) { this.clearSelections(); this.selections = [node.node]; node.isSelected = true; if (this.checkOnSelect && this.showCheckbox && !this.multiSelect) { this.clearCheckeds(); node.isChecked = true; this.checkeds = [node.node]; } } else { this.setSelections(node); } this.selectedRow = node.node; this.selectNodeChangedSubject.next(id); this.nodeSelected.emit({ node: node.node, instance: this }); if (detectChanges) { this.detectChanges(); } if (gotocurrent && this.fixedHeader) { this.scrollToCurrentNode(); } } else { if (!node) { this.selectedRow = null; console.warn(`未找到ID为${id}的数据!!`); } } return this; } /** 取消选中 */ unSelectNode(id: string, detectChanges = true) { const rowNode = this.findRowNode(id); if (rowNode) { rowNode.isSelected = false; if (this.singleSelect) { this.clearSelections(); this.selectedRow = null; rowNode.isChecked = false; this.selections = []; } else { if (this.onlySelectSelf) { this.selections = []; } else { this.setSelections(rowNode); } } if (detectChanges) { this.detectChanges(); } this.nodeUnSelect.emit({ node: rowNode.node, instance: this }); } else { console.warn(`未找到ID为${id}的数据!!`); } } /** 选中多行数据 */ selectNodes(ids: string[], detectChanges = true, emitNodeSelected = false): TreeTableComponent { if (ids && ids.length) { const nodes = this.findRowNodes(ids).map(rn => { rn.isSelected = true; this.setSelections(rn); return rn.node; }); this.selections = [...this.selections]; if (detectChanges) { this.detectChanges(); } this.selectNodeChangedSubject.next(ids.join(',')); if (emitNodeSelected) { this.nodeSelected.emit({ node: nodes, instance: this }); } } return this; } /** 取消选中 */ unSelectNodes(ids?: string[]) { if (ids && ids.length) { const rownodes = this.findRowNodes(ids); if (rownodes && rownodes.length) { rownodes.forEach(rn => { rn.isSelected = false; this.setSelections(rn); }); this.selections = [...this.selections]; this.detectChanges(); this.nodeUnSelect.emit({ node: rownodes.map(r => r.node), instance: this }); } } else { this.clearSelections(); this.detectChanges(); } } /** * 钩选指定的节点 * @param updateParentCheckboxStatus: 是否更新父级节点checkbox 状态, 默认为 true * @param detectChanges 触发变更检测。默认为 true */ checkedNode(id: string, updateParentCheckboxStatus = true, detectChanges = true) { if (this.isEnableMultiSelect()) { const node = this.findRowNode(id); if (node) { node.isChecked = true; if (!this.isChecked(node.node)) { // this.checkeds = [...this.checkeds || [], node.node]; this.setCheckeds(node); if (updateParentCheckboxStatus && this.cascadeCheck && this.cascadeUp) { this.updateParentCheckboxStatus(node); } } if (detectChanges) { this.detectChanges(); } this.nodeChecked.emit({ originalEvent: null, node: node.node, instance: this }); } else { console.warn(`未找到ID为${id}的数据。`); } } } unCheckedNode(id: string, updateParentCheckboxStatus = true) { if (this.isEnableMultiSelect()) { const rownode = this.findRowNode(id); if (rownode) { rownode.isChecked = false; this.setCheckeds(rownode); if (this.selectOnCheck) { rownode.isSelected = false; this.setSelections(rownode); } this.updateNodeStatus(); if (updateParentCheckboxStatus && this.cascadeCheck && this.cascadeUp) { this.updateParentCheckboxStatus(rownode); } this.detectChanges(); this.nodeUnChecked.emit({ node: rownode, instance: this }); } else { // console.warn(`未找到ID为${id}的数据。`); } } } private _checkedNodes(ids: string[], checked: boolean) { const parentIDS = []; const nodes = this.findRowNodes(ids).map(rn => { parentIDS.push(rn.parents); rn.isChecked = checked; this.setCheckeds(rn); return rn.node; }); this.checkeds = [...this.checkeds]; if (this.cascadeCheck && this.cascadeUp) { // 更新父节点复框的选中状态 this.updateParentsCheckStatus(parentIDS); } return nodes; } /** 批量选中节点 * @param ids: id 集合 * @param detectChanges: boolean 默认为 true ,是否触发变更检测 * closeCascade: 执行取消钩选时是否关闭级联功能。默认为 false * 设为true时,钩选将禁用级联的功能,仅处理当前节点的选中状态。执行完毕后,恢复原先的级联选项。 */ checkedNodes(ids: any[], detectChanges = true, emit = true, closeCascade?: boolean) { if (this.isEnableMultiSelect()) { if (ids && ids.length) { const _cascade = this.cascadeCheck; if (closeCascade && _cascade) { this.cascadeCheck = false; } const nodes = this._checkedNodes(ids, true); if (closeCascade && _cascade) { // 恢复级联 this.cascadeCheck = true; } this.updateAllCheckboxStatus(); // this.checkeds = nodes; if (detectChanges) { this.detectChanges(); } if (emit) { this.nodeChecked.emit({ originalEvent: null, node: nodes, nodes, instance: this }); } } } } /** * 取消选中的节点. * ids: 标识Id 数组 * closeCascade: 执行取消钩选时是否关闭级联功能。默认为 false * 设为true时,钩选将禁用级联的功能,仅处理当前节点的选中状态。执行完毕后,恢复原先的级联选项。 * emit: 是否触发取消勾选事件。默认为 true */ unCheckedNodes(ids: string[], closeCascade?: boolean, emit = true) { if (ids && ids.length) { const _cascade = this.cascadeCheck; if (closeCascade && _cascade) { this.cascadeCheck = false; } const nodes = this._checkedNodes(ids, false); if (closeCascade && _cascade) { this.cascadeCheck = true; } this.detectChanges(); // this.checkeds = nodes; if (emit) { this.nodeUnChecked.emit({ originalEvent: null, node: nodes, nodes, instance: this }); } } else { this.clearCheckeds(); this.detectChanges(); } } findRowNode(id: string): RowNode { if (id) { if (this.state.rowNodes) { return this.state.rowNodes.find(n => n.id === id); } } return undefined; } /** 将在 v0.2.x 废弃;请使用 findRowNode 方法 */ findNode(id: string): RowNode { return this.findRowNode(id); } /** * 根据当前节点ID,获取顶级父节点 * @param id 当前节点ID * @returns 返回顶级节点 */ getRoot(id: string) { const rowNode = this.findRowNode(id); if (rowNode) { if (rowNode.parents && rowNode.parents.length) { return this.findRowNode(rowNode.parents[0]); } return rowNode; } return null; } findRowNodes(ids: string[]): RowNode[] { return this.state.rowNodes.filter(rn => ids.indexOf(rn.id) > -1); } checkAllNodes(args?: any) { let rowNodes = this.getCurrentAllRowNodes(); rowNodes = rowNodes.filter(n => (n.node.selectable || n.node.selectable === undefined)); this.checkeds = rowNodes.map(n => { n.indeterminate = false; this.ttService.onChangeCheckboxStatus(n); if (n.node.selectable || n.node.selectable === undefined) { n.isChecked = true; } return n.node; }); if (this.selectOnCheck) { this.selections = rowNodes.map(n => { if (n.node.selectable || n.node.selectable === undefined) { n.isSelected = true; } return n.node; }); } if (this.state.searched) { rowNodes.forEach(rn => { const row = this.state.rowNodes.find(n => n.id === rn.id); if (row) { row.isSelected = rn.isSelected; row.isChecked = rn.isChecked; } }); } this.checkAll.emit(args); this.detectChanges(); } unCheckAllNodes(args?: any) { this.clearAll(); this.unCheckAll.emit(args); this.detectChanges(); } private isEnableMultiSelect() { if (this.singleSelect) { if (!this.showCheckbox) { console.warn('未启用多选!'); return false; } return true; } return true; } // 从数据中获取节点图标 // iconField 为 object // 如果 展开图标为空,折叠图标不为空,或反之,则展开与折叠图标保持一至; // 如果 叶子图标为空,则使用组件默认的图标 // iconField 为 字符串: 则叶子 展开 折叠 图标使用同一个。 如果为空, 则使用组件默认图标 private getNodeIconFromData(tn: TreeNode) { let leaf = null; let expand = null; let collapse = null; if (this.useIconFromData) { if (this.iconField) { let _iconField = ''; if (typeof this.iconField === 'string') { _iconField = this.iconField; if (tn[_iconField]) { leaf = tn[_iconField]; } else { leaf = this.utils.getValue(_iconField, tn.data); } expand = collapse = leaf; } else { if (this.iconField.leaf) { leaf = this.utils.getValue(this.iconField.leaf, tn.data); } if (this.iconField.expand) { expand = this.utils.getValue(this.iconField.expand, tn.data); } if (this.iconField.collapse) { collapse = this.utils.getValue(this.iconField.collapse, tn.data); } if (expand && !collapse) { collapse = expand; } if (!expand && collapse) { expand = collapse; } } } } return { leaf, expand, collapse }; } getTreeIcon(treeNode: TreeNode) { const tn = treeNode; let leafIcon = this.leafIcon || 'file'; let expandIcon = this.expandIcon || 'folder-open'; let collapseIcon = this.collapseIcon || 'folder'; const loading = 'treenode-loading'; if (tn['showLoading']) { return { [loading]: true }; } if (tn.icon) { leafIcon = tn.icon; } if (tn.expandedIcon) { expandIcon = tn.expandedIcon; } if (tn.collapsedIcon) { collapseIcon = tn.collapsedIcon; } if (this.useIconFromData) { const { leaf, expand, collapse } = this.getNodeIconFromData(treeNode); if (this.whenDataIconEmptyUseDefault) { if (leaf) { leafIcon = leaf; } if (expand) { expandIcon = expand; } if (collapse) { collapseIcon = collapse; } } else { leafIcon = leaf || ''; expandIcon = expand || ''; collapseIcon = collapse || ''; } } const collapsedExpression = (!tn.leaf || !!(tn.children && tn.children.length)) && !tn.expanded; const expandedExpression = !!tn.expanded; const leafExpression = tn.leaf === undefined ? (!tn.children || !tn.children.length) : tn.leaf; const icons = {}; if (leafExpression && leafIcon) { icons[leafIcon] = leafExpression; } else { if (tn.expanded && expandIcon) { icons[expandIcon] = expandedExpression; } else { if (collapseIcon) { icons[collapseIcon] = collapsedExpression; } } } if (this.iconCls) { icons[this.iconCls] = true; } return icons; } private compare(a, b) { if (typeof a === 'string') { return a.localeCompare(b); } else { return a === b ? 0 : (a > b ? 1 : -1); } } private _sort(r1, r2) { let r = 0; const sortFields = this.sortName.split(','); const orders = this.sortOrder.split(','); if (!this.sortName) { return r; } for (let i = 0; i < sortFields.length; i++) { const sn = sortFields[i]; const so = orders[i]; const col = this.columns.find(c => c.field === sn); const orderby = col.sorter || this.compare; let v1 = this.utils.getValue(sn, r1.data); if (v1 === null || v1 === undefined) { v1 = ''; } let v2 = this.utils.getValue(sn, r2.data); if (v2 === null || v2 === undefined) { v2 = ''; } r = orderby(v1, v2); if (r !== 0) { return r * (so === 'asc' ? 1 : -1); } } return r; } clientSort() { const sortedData = this.data.sort(this._sort.bind(this)); const sortChildNodes = (childNodes) => { childNodes = childNodes.sort(this._sort.bind(this)); childNodes.forEach(n => { if (n.children && n.children.length) { n.children = n.children.sort(this._sort.bind(this)); sortChildNodes(n.children); } }); return childNodes; }; sortedData.forEach(c => { c.children = sortChildNodes(c.children); }); this.loadData(sortedData); } onSortColumnClick($event, col) { if (!col.sortable) { return; } const sortName = this.sortName; const sortOrder = this.sortOrder; let sortFields = []; let sortOrders = []; if (sortName) { sortFields = sortName.split(','); sortOrders = sortOrder.split(','); } const colOrder = col.order || 'asc'; let newOrder = colOrder; const i = sortFields.findIndex(n => n === col.field); if (i >= 0) { const _order = sortOrders[i] === 'asc' ? 'desc' : 'asc'; newOrder = _order; if (this.multiSort && newOrder === 'asc') { newOrder = undefined; sortFields.splice(i, 1); sortOrders.splice(i, 1); } else { sortOrders[i] = _order; } } else { if (this.multiSort) { sortFields.push(col.field); sortOrders.push(colOrder); } else { sortFields = [col.field]; sortOrders = [colOrder]; } } col.order = newOrder; this.sortName = sortFields.join(','); this.sortOrder = sortOrders.join(','); if (!this.multiSort) { const updateFieldOrder = (cols: TreeTableColumn[]) => { if (!cols || !cols.length) { return; } cols.forEach(c => { c.order = undefined; const k = sortFields.findIndex(f => f === c.field); if (k >= 0) { c.order = sortOrders[k]; } }); }; updateFieldOrder(this.columns); this.cdRef.detectChanges(); } this.beforeSortColumn(this.sortName, this.sortOrder).subscribe(() => { if (this.remoteSort) { // this.reload(); } else { this.clientSort(); } this.columnSorted.emit({ sortName: this.sortName, sortOrder: this.sortOrder }); }); } /** 查询工具条前端字符查询 */ findKeyWords($event) { const searchValue = $event.value; const field = $event.field; this.findField = field; if (searchValue && searchValue.length >= 2) { this.findValue = searchValue; if (!field) { console.warn('Please set a field for the FindField property.'); return; } const result = this.searchHandle.searchOnClient(field, searchValue, this.state.rowNodes); if (result && result.length) { this.findResult = result.sort((a, b) => a.index === b.index ? 0 : (a.index > b.index ? 1 : -1)); const firstRowIndex = result[0].index; this.findCount = result.length; const parents = this.searchHandle.findParents(result, this.state.rowNodes); this.findCurrent = 1; [...parents, ...result].forEach((r: RowNode) => { this.expandNode(r.id); }); let y = 0; this.state.rowNodes.forEach((row: RowNode, index: number) => { if (row.visible && firstRowIndex > index) { y += this.rowHeight; } }); if (this.psRef && this.psRef.directiveRef) { this.psRef.directiveRef.scrollToY(y - 30); } this.highLightKeywords(); } else { this.findResult = []; this.findCurrent = 0; this.findCount = 0; } } else { this.resetFindText(); this.cdRef.detectChanges(); } } closeFindPanel() { this.showFindInput = false; this.resetFindText(); this.cdRef.detectChanges(); } private highLightKeywords() { this.ngZone.runOutsideAngular(() => { setTimeout(() => { const allHighLights = this.el.nativeElement.querySelectorAll('.search-highlight'); if (!allHighLights.length) { return; } // this.findResult = this.findResult.concat(Array.from(allHighLights)); // this.findResult = Array.from(new Set(this.findResult)); // this.findCount = allHighLights.length; this.cdRef.detectChanges(); this.renderer.addClass(allHighLights[0], 'active'); }, 100); }); } private resetFindText() { this.findValue = ''; this.findCurrent = 0; this.findCount = 0; this.findResult = []; } nextFindValue(step) { if (!this.findResult || !this.findResult.length) { return; } const allHighLights = Array.from(this.el.nativeElement.querySelectorAll('.search-highlight')); if (this.findCurrent === this.findResult.length) { this.findCurrent = 1; } else { if (step) { this.findCurrent = this.findCurrent + step; if (step === -1) { if (this.findCurrent <= 0) { this.findCurrent = 1; } } } else { this.findCurrent++; } } const active = this.el.nativeElement.querySelector('.search-highlight.active'); let _index = allHighLights.indexOf(active); if (active) { this.renderer.removeClass(active, 'active'); } if (this.virtualized) { _index += !step ? 1 : step; if (allHighLights[_index]) { this.renderer.addClass(allHighLights[_index], 'active'); } else { // 先滚动指定位置 const nextRow = this.findResult[this.findCurrent - 1]; let y = 0; this.state.rowNodes.forEach((row: RowNode, index: number) => { if (row.visible && nextRow.index > index) { y += this.rowHeight; } }); if (this.psRef && this.psRef.directiveRef) { this.psRef.directiveRef.scrollToY(y - 30, 200); } this.ngZone.runOutsideAngular(() => { setTimeout(() => { const keyWords = this.el.nativeElement.querySelectorAll('.search-highlight'); this.renderer.addClass(keyWords[0], 'active'); }, 100); }); } } else { const currentEl: any = allHighLights[this.findCurrent - 1]; if (currentEl) { this.renderer.addClass(currentEl, 'active'); if (this.psRef && this.psRef.directiveRef) { const sTop = currentEl.closest('td').offsetTop - 30; this.psRef.directiveRef.scrollToY(sTop, 200); } } } this.cdRef.detectChanges(); } scrollToY(y: number, animations = 200) { this.state.scrollY = y || 0; this.psRef.directiveRef.scrollToY(y, animations); } private showOrHideColumn(field: string | string[], isHide: boolean) { if (Array.isArray(field)) { this.columns.forEach(n => { if (field.indexOf(n.field) > -1) { n.visible = !isHide; } }); } else { this.columns.forEach(n => { if (field === n.field) { n.visible = !isHide; } }); } this.columnsChanged(); } showColumn(field: string | string[]) { this.showOrHideColumn(field, false); } hideColumn(field: string | string[]) { this.showOrHideColumn(field, true); } showChildsLoading(parentNode: TreeNode) { const colspan = this.columns.filter(n => n.visible !== false).length; const data = { [this.idField]: '正在加载中....', __colspan__: colspan }; this.loadChildren([{ data, __childs_loading__: true }], parentNode, false); } onChildsPageChanged($event) { // console.log($event); this.childsPageChanged.emit($event); } private trId2DataId(tr) { return tr.id.replace('rownode-', ''); } // 快捷键 onKeydownEvent($event) { if (!$event || !this.singleSelect || !this.data || !this.data.length) { return; } const keys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']; if (!keys.includes($event.key) || $event.target.nodeName === 'INPUT') { return; } $event.stopPropagation(); $event.preventDefault(); this.inputGroup.textbox.nativeElement.blur(); let newIdx; const trs = Array.from(this.bodyTable.nativeElement.querySelectorAll('tr')); const trCount = trs.length; let selectedTrIndex = -1; if (this.selectedRow) { selectedTrIndex = trs.findIndex((n: any) => this.trId2DataId(n) === this.selectedRow.id); } if ($event.code === 'ArrowDown') { newIdx = selectedTrIndex + 1; } else if ($event.code === 'ArrowUp') { if (selectedTrIndex !== -1) { newIdx = selectedTrIndex - 1; } } else { if (this.selectedRow && ($event.code === 'ArrowRight' || $event.code === 'ArrowLeft')) { this.toggleExpand(this.selectedRow); } return; } if (newIdx >= 0 && newIdx < trCount) { const _tr = trs[newIdx] as any; if (_tr) { const id = this.trId2DataId(_tr); this.selectNode(id); _tr.scrollIntoView({ block: 'nearest' }); } } } onBeforeShowContextMenu = (e: any) => { // row_1_fixedleft row_1 row_1_fixedright const { contextMenuDom, event } = e; let rowID = event.target.id; if (contextMenuDom) { rowID = this.trId2DataId(contextMenuDom); const rowNode = this.findRowNode(rowID); let go$ = of({show: true}); if (this.beforeShowContextMenu) { go$ = this.beforeShowContextMenu({ data: rowNode, tree: this }); } return go$.pipe( switchMap((r: any) => { if (r.show) { if (r.menus) { this.contextMenuItems = r.menus; this.detectChanges(); } const viewportW = contextMenuDom.closest('.farris-treetable').offsetWidth; const targetW = contextMenuDom.offsetWidth; const focusTargetWidth = viewportW > targetW ? targetW : viewportW; return of({ show: true, data: { data: rowNode, tree: this }, focusTargetWidth }); } else { return of({ show: false }); } }) ); } return of({ show: false }); } } @Component({ selector: '[treeTableBody]', template: ` ` }) export class TTBodyComponent { /** * 列集合 */ @Input() columns: any[]; /** 表体模板 */ @Input() template: TemplateRef; constructor(public tt: TreeTableComponent) { } } // [style.marginLeft]="rowNode.level * 16 + 'px'" /** * 树节点展开折叠 */ @Component({ selector: 'farris-treeTableToggler', template: ` `, encapsulation: ViewEncapsulation.None, exportAs: 'ttToggler' }) export class TreeTableTogglerComponent implements OnInit, AfterViewInit, OnDestroy, DoCheck, OnChanges, AfterViewChecked { @HostBinding('class.f-cmp-treetable-toggler') cls = true; /** * 树节点 */ @Input() rowNode: RowNode; @Input() disabled: boolean; @Input() showLines = false; @Input() indentSpacing = 16; treeNodeIcon = {}; isShowTriangle = false; noChilds = false; subscriptions: Subscription[] = []; get offsetWidth() { return this.togglerEl.nativeElement.parentElement.offsetWidth; } @ViewChild('toggler') togglerEl: ElementRef; constructor(public tt: TreeTableComponent, private ngzone: NgZone, private render: Renderer2) { } ngOnInit() { this.treeNodeIcon = this.tt.getTreeIcon(this.rowNode.node); this.isShowTriangle = this.showTriangle(); this.noChilds = this.hasChildren(); const expandSub = this.tt.expand.subscribe((node: TreeNode) => { if (this.rowNode.id === node.data[this.tt.idField]) { this.updateIcon(this.rowNode.node); if (this.tt.psRef) { this.tt.psRef.directiveRef.update(); } this.tt.detectChanges(); } }); const collapseSub = this.tt.collapse.subscribe((node: TreeNode) => { if (this.rowNode.id === node.data[this.tt.idField]) { this.updateIcon(this.rowNode.node); if (this.tt.psRef) { this.tt.psRef.directiveRef.update(); } this.tt.detectChanges(); } }); const addNewSub = this.tt.appendNew.subscribe((e: any) => { if (e.parent) { if (this.rowNode.id === e.parent.data[this.tt.idField]) { if (this.rowNode.node.leaf || this.rowNode.node.leaf === undefined) { this.rowNode.node.leaf = false; } } this.updateIcon(this.rowNode.node); } }); this.subscriptions = [expandSub, collapseSub, addNewSub]; } ngOnChanges(changes: SimpleChanges) { if (changes.showLines && !changes.showLines.isFirstChange()) { if (this.showLines) { this.render.setStyle(this.togglerEl.nativeElement, 'display', this.isShowTriangle ? '' : 'none'); } else { this.render.removeStyle(this.togglerEl.nativeElement, 'display'); } this.reSetLines(); } if (changes.rowNode && !changes.rowNode.isFirstChange()) { this.updateIcon(this.rowNode.node); } } ngAfterViewInit() { setTimeout(() => { this.reSetLines(); }); } ngDoCheck() { } ngAfterViewChecked() { // if (this.tt && this.tt.data) { // this.reSetLines(); // } } ngOnDestroy() { this.unAllSubscriptions(); } private unAllSubscriptions() { this.subscriptions.forEach(s => { s.unsubscribe(); }); this.subscriptions = []; } hasChildren() { const tn = this.rowNode.node; return tn.leaf === false || this.rowNode.level !== 0 || (tn.children && tn.children.length > 0); } showTriangle(): boolean { const tn = this.rowNode.node; if (this.tt.loadDataType === 'all') { return tn.leaf === false || (tn.children && tn.children.length > 0); } else { return tn.leaf === false; } } reSetLines() { if (this.togglerEl) { const parentEl = this.togglerEl.nativeElement.closest('.treetable-cell-content'); if (parentEl) { const lineEls = parentEl.querySelectorAll('.treetable-lines'); for (let k = 0; k < lineEls.length; k++) { lineEls[k].remove(); } } } this._renderLines(); } private _renderLines() { if (!this.tt.bodyTemplate) { if (this.showLines) { this.renderLines(); } else { // this.noLineStyle(); } } else { if (this.togglerEl) { // this.noLineStyle(); if (this.togglerEl.nativeElement.closest('tr')) { const tds = this.togglerEl.nativeElement.closest('tr').querySelectorAll('td'); Array.from(tds).forEach(n => { this.render.setStyle(n, 'padding', '0.25rem 0.75rem'); }); } } } } private noLineStyle() { // this.cls = false; if (this.togglerEl) { // this.render.setStyle(this.togglerEl.nativeElement, 'margin-left', `${this.rowNode.level * this.indentSpacing}px`); this.render.setStyle(this.togglerEl.nativeElement, 'visibility', this.isShowTriangle ? 'visible' : 'hidden'); this.render.removeStyle(this.togglerEl.nativeElement, 'display'); } } private renderLines() { const len = this.rowNode.level; if ( !this.togglerEl) { return; } const parentEl = this.togglerEl.nativeElement.closest('.treetable-cell-content'); if (!parentEl) { return; } const referEl = this.togglerEl.nativeElement.parentElement; if (len === 0 && !this.showTriangle()) { this.createLeafNodeLines(referEl, parentEl, true); } for (let i = 0; i < len; i++) { parentEl.insertBefore(this.newLineElement(), referEl); const nearLine = this.newLineElement(false); parentEl.insertBefore(nearLine, referEl); const isLeaf = !this.showTriangle(); if (isLeaf && i === len - 1) { this.createLeafNodeLines(referEl, parentEl); } } } private newLineElement(border = true) { const newLineEl = this.render.createElement('div'); this.render.addClass(newLineEl, 'treetable-lines'); if (this.showLines) { if (border) { this.render.addClass(newLineEl, 'treetable-lines-border-color'); } } return newLineEl; } /** * 构造 ∟ or ├ */ private createLeafNodeLines(containerEl: HTMLElement, parentEl: Element, isTopNode = false) { // ├ //
//
//
//
// ∟ //
//
//
//
const l1 = this.newLineElement(); const l2 = this.newLineElement(false); this.render.addClass(l2, 'treetable-lines-leaf'); const dessert = this.render.createElement('div'); if (this.showLines) { let cls = 'normal'; if (this.rowNode.last) { cls = 'corner'; this.render.removeClass(l1, 'treetable-lines-border-color'); } l2.appendChild(dessert); this.render.addClass(dessert, cls); this.render.addClass(dessert, 'treetable-lines-border-color'); } parentEl.insertBefore(l1, containerEl); parentEl.insertBefore(l2, containerEl); if (isTopNode) { this.render.setStyle(l2, 'marginRight', '3px'); } } private registerClickEvent() { if (this.togglerEl) { this.render.listen(this.togglerEl.nativeElement, 'click', this.onClick.bind(this)); } } onClick(event: Event) { event.stopPropagation(); event.preventDefault(); if (!this.tt.disabled) { this.tt.toggleExpand(this.rowNode.node); } return false; } private updateIcon(treeNode: TreeNode) { this.isShowTriangle = this.showTriangle(); this.noChilds = this.hasChildren(); this.treeNodeIcon = this.tt.getTreeIcon(treeNode); } } /** * 选中行组件 */ @Directive({ selector: '[selectRow]', exportAs: 'row' }) export class TreeTableSelectRowDirective implements DoCheck, OnDestroy, OnInit, OnChanges { @Input('selectRow') rowNode: RowNode; @Input() rowIndex: number; @Input() selectRowDisabled: boolean; subscription: Subscription; public prevent = false; public timer: any; @Input() delay = 0; constructor(public tt: TreeTableComponent, private el: ElementRef, private render: Renderer2, private ngZone: NgZone) { this.subscription = this.tt.selectNodeChangedSubject.subscribe((ids: string) => { if (ids && this.rowNode) { const idArr = ids.split(','); if (idArr.indexOf(this.rowNode.id) > -1) { this.toggleClass(); this.rowNode.node['elementRef'] = this.el; } } }); } ngOnInit() { // this.rowNode.node.selectable = !this.selectRowDisabled; } ngOnChanges(changes: SimpleChanges) { // if (changes.selectRowDisabled && !changes.selectRowDisabled.isFirstChange()) { // this.rowNode.node.selectable = !changes.selectRowDisabled.currentValue; // } } ngOnDestroy() { if (this.subscription) { this.subscription.unsubscribe(); this.subscription = null; } } ngDoCheck() { if (this.tt.bodyTemplate) { if (!this.tt.isSelected(this.rowNode.node)) { this.render.removeClass(this.el.nativeElement, 'farris-table-selectedrow'); } } } @HostListener('click', ['$event']) onclick(event: MouseEvent) { if (this.isEnabled()) { // this.rowClick(event); if (this.delay !== 0) { if (!this.prevent) { this.prevent = true; this.timer = setTimeout(() => { this.rowClick(event); this.prevent = false; }, this.delay); } } else { this.rowClick(event); } } // event.stopPropagation(); } isEnabled() { return !this.tt.disabled && this.selectRowDisabled !== true; } private rowClick(event: Event) { event.stopPropagation(); this.tt.beforeClickNode({ instance: this.tt, rowNode: this.rowNode, event, type: 'row' }).subscribe((e: boolean) => { if (!e) { return; } this.tt.handleRowClick({ originalEvent: event, rowNode: this.rowNode, rowIndex: this.rowIndex }); this.ngZone.runOutsideAngular(() => { this.toggleClass(); }); }); } toggleClass() { if (!this.tt.bodyTemplate) { if (this.tt.isSelected(this.rowNode.node)) { if (this.tt.singleSelect) { this.tt.el.nativeElement.querySelectorAll('.farris-treetable-tbody>tr.farris-table-selectedrow').forEach(n => { this.render.removeClass(n, 'farris-table-selectedrow'); }); } this.render.addClass(this.el.nativeElement, 'farris-table-selectedrow'); } else { if (!this.tt.keepSelect) { this.render.removeClass(this.el.nativeElement, 'farris-table-selectedrow'); } } } } } /** * 表头全选组件 */ @Component({ selector: 'tt-header-checkbox', template: `
`, styles: [ ` :host { vertical-align: middle; } ` ] }) export class TreeTableHeaderCheckboxComponent implements OnInit { @Input() checked: boolean; @Input() disabled: boolean; @Output() checkedChange = new EventEmitter(); @ViewChild('chkAll') chkAll: ElementRef; constructor(public tt: TreeTableComponent, public ttService: TreetableService) { } ngOnInit() { } handleClick(event) { if (!this.disabled && !this.tt.disabled) { const beforeEvent = !this.checked ? this.tt.beforeCheckAll : this.tt.beforeUnCheckAll; beforeEvent({ instance: this.tt, checked: !this.checked }).subscribe(e => { // 全选功能 this.chkAll.nativeElement.indeterminate = false; this.checked = !this.checked; this.tt.toggleNodesWithCheckbox(event, this.checked); }); } event.stopPropagation(); return false; } } /** * 表体checkbox 组件 */ @Component({ selector: 'tt-checkbox', template: `
`, styles: [ ` :host { vertical-align: middle; } :host .custom-checkbox { opacity: 1; float: none; } ` ] }) export class TreeTableCheckboxComponent implements OnInit, OnDestroy { @HostBinding('class.tree-toggler-checkbox') cls1 = true; @Input() checked: boolean; @Input() disabled: boolean; @Input() rowNode: RowNode; @Output() checkedChange = new EventEmitter(); @ViewChild('chk') chk: ElementRef; destroy$ = new Subject(); constructor(public tt: TreeTableComponent, public ttService: TreetableService) { this.ttService = this.tt.ttService; } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } ngOnInit() { this.chk.nativeElement.indeterminate = this.rowNode.indeterminate || false; this.ttService.changeCheckboxStatus$.pipe( takeUntil(this.destroy$) ).pipe( map(n => { if (n.id === this.rowNode.id) { return n; } return null; }) ).subscribe(n => { if (n) { this.chk.nativeElement.indeterminate = n.indeterminate; this.checked = n.isChecked; this.chk.nativeElement.checked = this.checked; } }); } handleClick(event) { event.stopPropagation(); if (!this.disabled) { this.tt.beforeClickNode({ instance: this.tt, rowNode: this.rowNode, event, type: 'checkbox' }).subscribe((e: boolean) => { if (!e) { return; } this.checked = !this.checked; if (this.tt.singleSelect) { this.tt.clearAll(); if (this.checked) { this.tt.selectNode(this.rowNode.id, true); } else { this.tt.unSelectNode(this.rowNode.id, true); } } else { this.tt.toggleNodeWithCheckbox(event, this.rowNode, this.checked); } }); } return false; } } @Directive({ selector: '[dblclick]' }) export class DblClickRowDirective implements OnInit { @Input('dblclick') enableDblClick = false; @Input() dblclickNode: RowNode; constructor( public tt: TreeTableComponent, public el: ElementRef, public ttService: TreetableService, @Optional() @Self() public selectRowDir: TreeTableSelectRowDirective) { this.ttService = this.tt.ttService; } ngOnInit() { if (!this.enableDblClick) { this.selectRowDir.delay = 0; } } @HostListener('dblclick', ['$event']) ondblclick(event: Event) { const prevEl = ( event.target as any).previousSibling; if (prevEl && prevEl.type === 'checkbox') { return; } if (this.tt.disabled) { return false; } if (this.tt.multiSelect && this.dblclickNode) { if (this.dblclickNode.node.leaf) { return false; } } const clsName = event.target['className']; let togglerTag = null; if (clsName.indexOf('f-cmp-treetable-toggler') > -1) { togglerTag = event.target['querySelector']('.treenode-fold'); } else { if (event.target['className'].indexOf('treenode-fold') > -1) { togglerTag = event.target as any; } } if (togglerTag && togglerTag.style.visibility === 'visible') { return false; } if (this.selectRowDir) { this.selectRowDir.prevent = false; clearTimeout(this.selectRowDir.timer); if (this.selectRowDir.rowNode && this.enableDblClick) { const rn = this.selectRowDir.rowNode; if (this.tt.loadDataType === 'all' && !rn.node.leaf && this.tt.dblClickExpand) { this.tt.toggleExpand(rn.node); } this.tt.dblClick.emit(rn.node); } } // event.stopPropagation(); } } @Directive({ selector: '[customRowStyle]' }) export class CustomRowStyleDirective implements OnInit, OnChanges { @Input('customRowStyle') rowNode: RowNode; constructor(private injector: Injector, private ngZone: NgZone, private el: ElementRef, private tt: TreeTableComponent, public utils: CommonUtils, private render: Renderer2) { } ngOnInit() { this.setRowStyle(); } private setRowStyle() { const fn = this.tt.rowStyler; if (fn) { const sty = fn(this.rowNode.node); if (sty) { if (typeof sty === 'object' && (sty.style || sty.cls)) { if (sty.style) { Object.keys(sty.style).forEach(s => { this.render.setStyle(this.el.nativeElement, s, sty.style[s]); }); } if (sty.cls) { sty.cls.split(',').forEach(c => { this.render.addClass(this.el.nativeElement, c); }); } } else { if (Array.isArray(sty) || typeof sty === 'string') { const cls = this.tt.getRowCls(this.rowNode); if (cls && Object.keys(cls).length) { Object.keys(cls).forEach(c => { if (cls[c]) { this.render.addClass(this.el.nativeElement, c); } }); } } else { Object.keys(sty).forEach(s => { this.render.setStyle(this.el.nativeElement, s, sty[s]); }); } } } } } ngOnChanges(changes: SimpleChanges) { if (changes.rowNode && !changes.rowNode.isFirstChange()) { this.setRowStyle(); } } } @Directive({ selector: '[customCellStyle]' }) export class TTCustomCellStyleDirective implements OnInit, OnChanges { @Input('customCellStyle') column: TreeTableColumn; @Input() rowNode: RowNode; @Input() rowIndex: any; constructor(private injector: Injector, private ngZone: NgZone, private el: ElementRef, private tt: TreeTableComponent, public utils: CommonUtils, private render: Renderer2) { } ngOnInit() { if (this.tt && this.tt.getCellCls) { this.setCellStyle(); } if (!this.tt.showBorder && this.tt.columns.length === 1) { this.render.setStyle(this.el.nativeElement, 'border-width', '0px'); } } private setCellStyle() { const cellCls = this.tt.getCellCls(this.rowNode, this.column.field); if (this.column && this.column.styler) { if (cellCls && Object.keys(cellCls).length) { Object.keys(cellCls).forEach(c => { if (cellCls[c]) { this.render.addClass(this.el.nativeElement, c); } }); } const rowData = this.rowNode.node.data; const ccs = this.column.styler(this.utils.getValue(this.column.field, rowData), rowData, this.rowIndex, this.rowNode); if (ccs) { if (ccs.style || ccs.cls) { if (ccs.style) { Object.keys(ccs.style).forEach(s => { this.render.setStyle(this.el.nativeElement, s, ccs.style[s]); }); } if (ccs.cls) { ccs.cls.split(',').forEach(c => { this.render.addClass(this.el.nativeElement, c); }); } } } } else { Object.keys(cellCls).forEach(s => { this.render.setStyle(this.el.nativeElement, s, cellCls[s]); }); } } ngOnChanges(changes: SimpleChanges) { if (changes.rowNode && !changes.rowNode.isFirstChange()) { this.setCellStyle(); } } } @Directive({ selector: '[resizable-column]' }) export class TTResizableColumnDirective implements AfterViewInit, OnDestroy { @Input('resizable-column') enableResize = true; resizer: HTMLSpanElement; resizerMouseDownListener: any; documentMouseMoveListener: any; documentMouseUpListener: any; constructor( public tt: TreeTableComponent, public ngzone: NgZone, public el: ElementRef, public render: Renderer2) { } ngAfterViewInit() { if (this.isEnable()) { this.render.addClass(this.el.nativeElement, 'resizable-column'); this.resizer = document.createElement('span'); this.resizer.className = 'column-resizer'; this.el.nativeElement.appendChild(this.resizer); this.ngzone.runOutsideAngular(() => { this.resizerMouseDownListener = this.onMouseDown.bind(this); this.resizer.addEventListener('mousedown', this.resizerMouseDownListener); this.el.nativeElement.addEventListener('mouseenter', this.onMouseEnter.bind(this)); }); } } isEnable() { return this.tt.resizableColumns && !this.tt.autoWidth && this.enableResize; } bindDocumentEvents() { this.ngzone.runOutsideAngular(() => { this.documentMouseMoveListener = this.onMouseMove.bind(this); document.addEventListener('mousemove', this.documentMouseMoveListener); this.documentMouseUpListener = this.onMouseUp.bind(this); document.addEventListener('mouseup', this.documentMouseUpListener); }); } unbindDocumentEvents() { if (this.documentMouseMoveListener) { document.removeEventListener('mousemove', this.documentMouseMoveListener); this.documentMouseMoveListener = null; } if (this.documentMouseUpListener) { document.removeEventListener('mouseup', this.documentMouseUpListener); this.documentMouseUpListener = null; } } onMouseDown(event: Event) { this.tt.onColumnResizeBegin(event); this.bindDocumentEvents(); } onMouseEnter() { // console.log(this.el); } onMouseMove(event: Event) { this.tt.onColumnResize(event); } onMouseUp(event: Event) { this.tt.onColumnResizeEnd(event, this.el.nativeElement); this.unbindDocumentEvents(); } ngOnDestroy() { if (this.resizerMouseDownListener) { this.resizer.removeEventListener('mousedown', this.resizerMouseDownListener); } this.unbindDocumentEvents(); } }