thingsboard/ui-ngx/src/app/shared/components/nav-tree.component.ts
2021-01-11 13:42:16 +02:00

276 lines
9.0 KiB
TypeScript

///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, ElementRef, Input, NgZone, OnInit, ViewEncapsulation } from '@angular/core';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { deepClone } from '@core/utils';
export interface NavTreeNodeState {
disabled?: boolean;
opened?: boolean;
loaded?: boolean;
}
export interface NavTreeNode {
id: string;
icon?: boolean;
text?: string;
state?: NavTreeNodeState;
children?: NavTreeNode[] | boolean;
data?: any;
}
export interface NavTreeEditCallbacks {
selectNode?: (id: string) => void;
deselectAll?: () => void;
getNode?: (id: string) => NavTreeNode;
getParentNodeId?: (id: string) => string;
openNode?: (id: string, cb?: () => void) => void;
nodeIsOpen?: (id: string) => boolean;
nodeIsLoaded?: (id: string) => boolean;
refreshNode?: (id: string) => void;
updateNode?: (id: string, newName: string) => void;
createNode?: (parentId: string, node: NavTreeNode, pos: number | string) => void;
deleteNode?: (id: string) => void;
disableNode?: (id: string) => void;
enableNode?: (id: string) => void;
setNodeHasChildren?: (id: string, hasChildren: boolean) => void;
search?: (searchText: string) => void;
clearSearch?: () => void;
}
export type NodesCallback = (nodes: NavTreeNode[]) => void;
export type LoadNodesCallback = (node: NavTreeNode, cb: NodesCallback) => void;
export type NodeSearchCallback = (searchText: string, node: NavTreeNode) => boolean;
export type NodeSelectedCallback = (node: NavTreeNode, event: Event) => void;
export type NodesInsertedCallback = (nodes: string[], parent: string) => void;
@Component({
selector: 'tb-nav-tree',
templateUrl: './nav-tree.component.html',
styleUrls: ['./nav-tree.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class NavTreeComponent implements OnInit {
private enableSearchValue: boolean;
get enableSearch(): boolean {
return this.enableSearchValue;
}
@Input()
set enableSearch(value: boolean) {
this.enableSearchValue = coerceBooleanProperty(value);
}
@Input()
private loadNodes: LoadNodesCallback;
@Input()
private searchCallback: NodeSearchCallback;
@Input()
private onNodeSelected: NodeSelectedCallback;
@Input()
private onNodesInserted: NodesInsertedCallback;
@Input()
private editCallbacks: NavTreeEditCallbacks;
private treeElement: JSTree;
constructor(private elementRef: ElementRef<HTMLElement>,
private ngZone: NgZone) {
}
ngOnInit(): void {
this.initTree();
}
private initTree() {
const loadNodes: LoadNodesCallback = (node, cb) => {
const outCb = (nodes: NavTreeNode[]) => {
const copied: NavTreeNode[] = [];
if (nodes) {
nodes.forEach((n) => {
copied.push(deepClone(n, ['data']));
});
}
cb(copied);
};
this.ngZone.runOutsideAngular(() => {
this.loadNodes(node, outCb);
});
};
const config: JSTreeStaticDefaults = {
core: {
worker: false,
multiple: false,
check_callback: true,
themes: { name: 'proton', responsive: true },
data: loadNodes,
error: () => {
console.error('Unexpected jstree error!');
}
},
plugins: []
};
if (this.enableSearch) {
config.plugins.push('search');
config.search = {
ajax: false,
fuzzy: false,
close_opened_onclear: true,
case_sensitive: false,
show_only_matches: true,
show_only_matches_children: false,
search_leaves_only: false,
search_callback: this.searchCallback
};
}
import('jstree').then(() => {
this.treeElement = $('.tb-nav-tree-container', this.elementRef.nativeElement).jstree(config);
this.treeElement.on('changed.jstree', (e: any, data) => {
const node: NavTreeNode = data.instance.get_selected(true)[0];
if (this.onNodeSelected) {
this.ngZone.run(() => this.onNodeSelected(node, e as Event));
}
});
this.treeElement.on('model.jstree', (e: any, data) => {
if (this.onNodesInserted) {
this.ngZone.run(() => this.onNodesInserted(data.nodes, data.parent));
}
});
if (this.editCallbacks) {
this.editCallbacks.selectNode = id => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('deselect_all', true);
this.treeElement.jstree('select_node', node);
}
};
this.editCallbacks.deselectAll = () => {
this.treeElement.jstree('deselect_all');
};
this.editCallbacks.getNode = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
return node;
};
this.editCallbacks.getParentNodeId = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
return this.treeElement.jstree('get_parent', node);
}
};
this.editCallbacks.openNode = (id, cb) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('open_node', node, cb);
}
};
this.editCallbacks.nodeIsOpen = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
return this.treeElement.jstree('is_open', node);
} else {
return true;
}
};
this.editCallbacks.nodeIsLoaded = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
return this.treeElement.jstree('is_loaded', node);
} else {
return true;
}
};
this.editCallbacks.refreshNode = (id) => {
if (id === '#') {
this.treeElement.jstree('refresh');
this.treeElement.jstree('redraw');
} else {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
const opened = this.treeElement.jstree('is_open', node);
this.treeElement.jstree('refresh_node', node);
this.treeElement.jstree('redraw');
if (node.children && opened/* && !node.children.length*/) {
this.treeElement.jstree('open_node', node);
}
}
}
};
this.editCallbacks.updateNode = (id, newName) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('rename_node', node, newName);
}
};
this.editCallbacks.createNode = (parentId, node, pos) => {
const parentNode: NavTreeNode = this.treeElement.jstree('get_node', parentId);
if (parentNode) {
this.treeElement.jstree('create_node', parentNode, node, pos);
}
};
this.editCallbacks.deleteNode = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('delete_node', node);
}
};
this.editCallbacks.disableNode = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('disable_node', node);
}
};
this.editCallbacks.enableNode = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('enable_node', node);
}
};
this.editCallbacks.setNodeHasChildren = (id, hasChildren) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
if (!node.children || (Array.isArray(node.children) && !node.children.length)) {
node.children = hasChildren;
node.state.loaded = !hasChildren;
node.state.opened = false;
this.treeElement.jstree('_node_changed', node.id);
this.treeElement.jstree('redraw');
}
}
};
this.editCallbacks.search = (searchText) => {
this.treeElement.jstree('search', searchText);
};
this.editCallbacks.clearSearch = () => {
this.treeElement.jstree('clear_search');
};
}
});
}
}