From 5641313d0bfdbbea00ba38f76be48d9291aa53bd Mon Sep 17 00:00:00 2001 From: ViktorSlavov Date: Thu, 21 Jan 2021 09:10:17 +0200 Subject: [PATCH] feat(tree): add files, create structure, add sample #7475 --- .../igniteui-angular/src/lib/tree/README.md | 0 .../igniteui-angular/src/lib/tree/common.ts | 73 ++++++ .../src/lib/tree/public_api.ts | 4 + .../tree/tree-node/tree-node.component.html | 20 ++ .../tree/tree-node/tree-node.component.scss | 4 + .../lib/tree/tree-node/tree-node.component.ts | 121 ++++++++++ .../src/lib/tree/tree.component.html | 28 +++ .../src/lib/tree/tree.component.scss | 0 .../src/lib/tree/tree.component.ts | 219 ++++++++++++++++++ .../src/lib/tree/tree.service.ts | 15 ++ projects/igniteui-angular/src/public_api.ts | 1 + src/app/app.module.ts | 3 +- src/app/app.routing.ts | 5 + src/app/routing.ts | 4 + src/app/tree/tree.sample.html | 1 + src/app/tree/tree.sample.scss | 0 src/app/tree/tree.sample.ts | 9 + 17 files changed, 506 insertions(+), 1 deletion(-) create mode 100644 projects/igniteui-angular/src/lib/tree/README.md create mode 100644 projects/igniteui-angular/src/lib/tree/common.ts create mode 100644 projects/igniteui-angular/src/lib/tree/public_api.ts create mode 100644 projects/igniteui-angular/src/lib/tree/tree-node/tree-node.component.html create mode 100644 projects/igniteui-angular/src/lib/tree/tree-node/tree-node.component.scss create mode 100644 projects/igniteui-angular/src/lib/tree/tree-node/tree-node.component.ts create mode 100644 projects/igniteui-angular/src/lib/tree/tree.component.html create mode 100644 projects/igniteui-angular/src/lib/tree/tree.component.scss create mode 100644 projects/igniteui-angular/src/lib/tree/tree.component.ts create mode 100644 projects/igniteui-angular/src/lib/tree/tree.service.ts create mode 100644 src/app/tree/tree.sample.html create mode 100644 src/app/tree/tree.sample.scss create mode 100644 src/app/tree/tree.sample.ts diff --git a/projects/igniteui-angular/src/lib/tree/README.md b/projects/igniteui-angular/src/lib/tree/README.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/projects/igniteui-angular/src/lib/tree/common.ts b/projects/igniteui-angular/src/lib/tree/common.ts new file mode 100644 index 00000000000..3a59d881514 --- /dev/null +++ b/projects/igniteui-angular/src/lib/tree/common.ts @@ -0,0 +1,73 @@ +import { EventEmitter, InjectionToken, TemplateRef } from '@angular/core'; +import { IBaseCancelableBrowserEventArgs, IBaseCancelableEventArgs, IBaseEventArgs } from '../core/utils'; + +// Component interfaces + +export interface ITreeComponent { + id: string, + nodeTemplate: TemplateRef, + nodeEditTemplate: TemplateRef, + selectMarker: TemplateRef, + expandIndicator: TemplateRef, + valueKey: string, + textKey: string, + childKey: string, + nodeEdited: EventEmitter, + nodeEditing: EventEmitter + nodeExpanding: EventEmitter, + nodeExpanded: EventEmitter, + selectNode(id: string): void, + updateNodeText(id: string, value: string): void, + updateNode(id: string, value: any): void, + deleteNode(id: string): void, + addNode(data: any, parentPath?: string[]): void +} + +// Item interfaces +export interface ITreeNode { + id: string, + depth: number, + fullPath: string[], + children?: ITreeNode[] +} + +export interface ITreeRoot { +} + +export interface ITreeBranch extends ITreeNode { + children: ITreeNode[], + childKey: string +} + +// Events +export interface ITreeNodeSelectionEvent extends IBaseCancelableBrowserEventArgs { + nodeId: string; +} + +export interface ITreeNodeEditingEvent extends IBaseCancelableBrowserEventArgs { + nodeId: string; + value: string; +} + +export interface ITreeNodeEditedEvent extends IBaseEventArgs { + nodeId: string; + value: any; +} + +export interface ITreeNodeTogglingEventArgs extends IBaseEventArgs, IBaseCancelableBrowserEventArgs { + nodeId: string; +} + +export interface ITreeNodeToggledEventArgs extends IBaseEventArgs { + nodeId: string; +} + +// Enums +export enum IGX_TREE_SELECTION_TYPE { + Non = "Non", + BiState = "BiState", + Cascading = "Cascading" +} + +// Token +export const IGX_TREE_COMPONENT = new InjectionToken('IgxComboComponentToken'); \ No newline at end of file diff --git a/projects/igniteui-angular/src/lib/tree/public_api.ts b/projects/igniteui-angular/src/lib/tree/public_api.ts new file mode 100644 index 00000000000..a7398fb7532 --- /dev/null +++ b/projects/igniteui-angular/src/lib/tree/public_api.ts @@ -0,0 +1,4 @@ +export * from './tree.component'; +export * from './tree.service'; +export * from './tree-node/tree-node.component'; +export * from './common'; \ No newline at end of file diff --git a/projects/igniteui-angular/src/lib/tree/tree-node/tree-node.component.html b/projects/igniteui-angular/src/lib/tree/tree-node/tree-node.component.html new file mode 100644 index 00000000000..02cd02f6ec8 --- /dev/null +++ b/projects/igniteui-angular/src/lib/tree/tree-node/tree-node.component.html @@ -0,0 +1,20 @@ +
+ + + + + +
+ + +
+
+ + + + + diff --git a/projects/igniteui-angular/src/lib/tree/tree-node/tree-node.component.scss b/projects/igniteui-angular/src/lib/tree/tree-node/tree-node.component.scss new file mode 100644 index 00000000000..2ae26e71219 --- /dev/null +++ b/projects/igniteui-angular/src/lib/tree/tree-node/tree-node.component.scss @@ -0,0 +1,4 @@ +:host { + display: flex; + flex-direction: column; +} diff --git a/projects/igniteui-angular/src/lib/tree/tree-node/tree-node.component.ts b/projects/igniteui-angular/src/lib/tree/tree-node/tree-node.component.ts new file mode 100644 index 00000000000..4205501e337 --- /dev/null +++ b/projects/igniteui-angular/src/lib/tree/tree-node/tree-node.component.ts @@ -0,0 +1,121 @@ +import { + Component, OnInit, + OnDestroy, ChangeDetectionStrategy, Input, Inject, ViewChild, TemplateRef +} from '@angular/core'; +import { IgxSelectionAPIService } from '../../core/selection'; +import { IGX_TREE_COMPONENT, ITreeComponent, ITreeNode } from '../common'; +import { IgxTreeExpandIndicatorDirective, IgxTreeNodeDirective, IgxTreeSelectMarkerDirective } from '../tree.component'; +import { IgxTreeService } from '../tree.service'; +/** + * + * The tree node component represents a child node of the tree component or another tree node. + * Usage: + * + * ```html + * + * ``` + */ +@Component({ + selector: "igx-tree-node", + templateUrl: "tree-node.component.html", + styleUrls: ["tree-node.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class IgxTreeNodeComponent implements ITreeNode, OnInit, OnDestroy { + constructor(@Inject(IGX_TREE_COMPONENT) public tree: ITreeComponent, private selectionService: IgxSelectionAPIService, private treeService: IgxTreeService) { } + + @Input() + private _parentPath: string; + + @Input() + public id: string; + + @Input() + public depth: number; + + @Input() + public data: any; + + public nodeTemplate: TemplateRef; + + public nodeEditTemplate: TemplateRef; + + public selectMarker: TemplateRef; + + public expandIndicator: TemplateRef; + + @ViewChild(IgxTreeNodeDirective, { read: TemplateRef }) + private _defaultNodeTemplate: TemplateRef; + + public get nodeTemplateRef(): TemplateRef { + return this.nodeTemplate || this._defaultNodeTemplate + } + + @ViewChild(IgxTreeExpandIndicatorDirective, { read: TemplateRef }) + private _defaultNodeEdit: TemplateRef; + + public get nodeEditTemplateRef(): TemplateRef { + return this.nodeEditTemplate || this._defaultNodeEdit + } + @ViewChild(IgxTreeSelectMarkerDirective, { read: TemplateRef }) + private _defaultSelectMarker: TemplateRef; + + public get selectMarkerTemplate(): TemplateRef { + return this.selectMarker || this._defaultSelectMarker + } + + @ViewChild(IgxTreeExpandIndicatorDirective, { read: TemplateRef }) + private _defaultExpandIndicatorTemplate: TemplateRef; + + public get expandIndicatorTemplate(): TemplateRef { + return this.expandIndicator || this._defaultExpandIndicatorTemplate + } + + public get selected(): boolean { + return this.selectionService.get(this.tree.id).has(this.id); + } + + public get expanded(): boolean { + return this.treeService.isExpanded(this.id); + } + + public get templateContext(): any { + return { + $implicit: { + data: this.data, + id: this.id, + expanded: this.expanded, + selected: this.selected + } + } + } + + public get valueKey(): string { + return this.tree.valueKey; + } + + public get textKey(): string { + return this.tree.textKey + } + + public get childKey(): string { + return this.tree.childKey + } + + public get fullPath(): string[] { + return [...this._parentPath, this.id]; + } + + ngOnInit() { + console.log(this.tree); + } + + ngAfterViewInit() { + this.nodeEditTemplate = this.tree.nodeEditTemplate; + this.nodeTemplate = this.tree.nodeTemplate; + this.selectMarker = this.tree.selectMarker; + this.expandIndicator = this.tree.expandIndicator; + } + + ngOnDestroy() { } +} diff --git a/projects/igniteui-angular/src/lib/tree/tree.component.html b/projects/igniteui-angular/src/lib/tree/tree.component.html new file mode 100644 index 00000000000..7c644cce2b1 --- /dev/null +++ b/projects/igniteui-angular/src/lib/tree/tree.component.html @@ -0,0 +1,28 @@ +
+ + +
+ + + isNodeExpanded(item.id) ?? "expand_less" : "expand_more" + + + + + + + + + + {{ item.data[valueKey] }} + + + + + + + \ No newline at end of file diff --git a/projects/igniteui-angular/src/lib/tree/tree.component.scss b/projects/igniteui-angular/src/lib/tree/tree.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/projects/igniteui-angular/src/lib/tree/tree.component.ts b/projects/igniteui-angular/src/lib/tree/tree.component.ts new file mode 100644 index 00000000000..e449105e426 --- /dev/null +++ b/projects/igniteui-angular/src/lib/tree/tree.component.ts @@ -0,0 +1,219 @@ +import { Component, QueryList, ViewChildren, Input, Output, EventEmitter, ContentChild, Directive, ElementRef, NgModule, ViewChild, TemplateRef } from '@angular/core'; +import { IgxCheckboxModule } from '../checkbox/checkbox.component'; +import { IgxSelectionAPIService } from '../core/selection'; +import { IgxIconModule } from '../icon/public_api'; +import { IgxInputGroupModule } from '../input-group/public_api'; +import { IGX_TREE_COMPONENT, IGX_TREE_SELECTION_TYPE, ITreeComponent, ITreeNodeEditedEvent, ITreeNodeEditingEvent, ITreeNodeToggledEventArgs, ITreeNodeTogglingEventArgs, ITreeNodeSelectionEvent, ITreeRoot, ITreeNode } from './common'; +import { IgxTreeNodeComponent } from './tree-node/tree-node.component'; +import { IgxTreeService } from './tree.service'; + +let init_id = 0; + +@Directive({ + selector: '[igxTreeNode]' +}) +export class IgxTreeNodeDirective { + constructor(public element: ElementRef) {} +} + +@Directive({ + selector: '[igxTreeNodeEdit]' +}) +export class IgxTreeNodeEditingDirective { + constructor(public element: ElementRef) {} +} + +@Directive({ + selector: '[igxTreeSelectMarker]' +}) +export class IgxTreeSelectMarkerDirective { + constructor(public element: ElementRef) {} +} + +@Directive({ + selector: '[igxTreeExpandIndicator]' +}) +export class IgxTreeExpandIndicatorDirective { + constructor(public element: ElementRef) {} +} + +@Component({ + selector: "igx-tree", + templateUrl: "tree.component.html", + styleUrls: ["tree.component.scss"], + providers: [ + { provide: IgxTreeService }, + { provide: IGX_TREE_COMPONENT, useExisting: IgxTreeComponent} + ] +}) +export class IgxTreeComponent implements ITreeRoot, ITreeComponent { + /** @hidden @internal */ + public treeService = new IgxTreeService(); + + public id = `tree-${init_id++}`; + + constructor(private selectionService: IgxSelectionAPIService) {} + + @ViewChildren(IgxTreeNodeComponent) + public nodes: QueryList + + @Input() + public dataSource: any[]; + + @Input() + public valueKey: string; + + @Input() + public textKey: string; + + @Input() + public childKey: string; + + @Input() + public selectionMode: IGX_TREE_SELECTION_TYPE = IGX_TREE_SELECTION_TYPE.BiState; + + @Output() + public nodeSelection = new EventEmitter() + + @Output() + public nodeEditing = new EventEmitter(); + + @Output() + public nodeEdited = new EventEmitter(); + + @Output() + public nodeExpanding = new EventEmitter(); + + @Output() + public nodeExpanded = new EventEmitter(); + + @Output() + public nodeCollapsing = new EventEmitter(); + + @Output() + public nodeCollapsed = new EventEmitter(); + + @Input() + @ContentChild(IgxTreeNodeDirective, { read: TemplateRef }) + public nodeTemplate: TemplateRef; + + @Input() + @ContentChild(IgxTreeNodeEditingDirective, { read: TemplateRef }) + public nodeEditTemplate: TemplateRef; + + @Input() + @ContentChild(IgxTreeSelectMarkerDirective, { read: TemplateRef }) + public selectMarker: TemplateRef; + + @Input() + @ContentChild(IgxTreeExpandIndicatorDirective, { read: TemplateRef }) + public expandIndicator: TemplateRef; + + public addNode(data: any, parentPath?: string[]): void { + if (parentPath === null || parentPath === undefined) { + this.dataSource.push(data); + return; + } + var targetNode = this.findNode(parentPath, this.dataSource); + if (targetNode === null) { + throw new Error("Parent not with specified path not found.") + } + if (!targetNode[this.childKey]) { + targetNode[this.childKey] = []; + } + targetNode[this.childKey].push(data); + } + + public deleteNode(nodePath: any): void { + } + + public updateNode(nodePath: any, data: any): void {} + + public updateNodeText(nodePath: any, text: any): void {} + + public selectNode(nodeId: string): void { + this.selectionService.add_item(this.id, nodeId); + } + + public expandNode(nodeId: string): void { + if (this.treeService.isExpanded(nodeId)) { + return; + } + const args: ITreeNodeTogglingEventArgs = { + owner: this, + nodeId, + cancel: false + + } + this.nodeExpanding.emit(args); + if (!args.cancel) { + this.treeService.expand(nodeId); + this.nodeExpanded.emit({ + owner: this, + nodeId + }); + } + } + + public collapseNode(nodeId: string): void { + if (!this.treeService.isExpanded(nodeId)) { + return; + } + const args: ITreeNodeTogglingEventArgs = { + owner: this, + nodeId, + cancel: false + + } + this.nodeCollapsing.emit(args); + if (!args.cancel) { + this.treeService.collapse(nodeId); + this.nodeCollapsed.emit({ + owner: this, + nodeId + }); + } + } + + public toggleNode(nodeId: string): void { + if (this.treeService.isExpanded(nodeId)) { + this.collapseNode(nodeId); + } else { + this.expandNode(nodeId); + } + } + + private findNode(nodePath: string[], data: { [key: string] : any }): any { + if (nodePath.length === 1) { + return data.find(e => e[this.valueKey] === nodePath[0]); + } else { + return this.findNode(nodePath.slice(-1 * (nodePath.length - 1)), data[this.childKey]) + } + } +} + +@NgModule({ + declarations: [ + IgxTreeComponent, + IgxTreeNodeComponent, + IgxTreeNodeDirective, + IgxTreeSelectMarkerDirective, + IgxTreeSelectMarkerDirective + ], + imports: [ + IgxIconModule, + IgxInputGroupModule, + IgxCheckboxModule + ], + exports: [ + IgxTreeComponent, + IgxTreeNodeComponent, + IgxTreeNodeDirective, + IgxTreeSelectMarkerDirective, + IgxIconModule, + IgxInputGroupModule, + IgxCheckboxModule + ] +}) +export class IgxTreeModule { +} diff --git a/projects/igniteui-angular/src/lib/tree/tree.service.ts b/projects/igniteui-angular/src/lib/tree/tree.service.ts new file mode 100644 index 00000000000..b0225033261 --- /dev/null +++ b/projects/igniteui-angular/src/lib/tree/tree.service.ts @@ -0,0 +1,15 @@ +export class IgxTreeService { + public expandedNodes: Set = new Set(); + + expand(id: string): void { + this.expandedNodes.add(id); + } + + collapse(id: string): void { + this.expandedNodes.delete(id); + } + + isExpanded(id: string): boolean { + return this.expandedNodes.has(id); + } +} \ No newline at end of file diff --git a/projects/igniteui-angular/src/public_api.ts b/projects/igniteui-angular/src/public_api.ts index 063e20fcf51..46071eac537 100644 --- a/projects/igniteui-angular/src/public_api.ts +++ b/projects/igniteui-angular/src/public_api.ts @@ -101,6 +101,7 @@ export * from './lib/date-range-picker/public_api'; export * from './lib/grids/column-actions/column-actions-base.directive'; export * from './lib/grids/column-actions/column-actions.component'; +export * from './lib/tree/public_api'; /** * Exporter services, classes, interfaces and enums diff --git a/src/app/app.module.ts b/src/app/app.module.ts index fb20bce3888..b8bb8e8252e 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -8,7 +8,7 @@ import { IgxIconModule, IgxGridModule, IgxExcelExporterService, IgxCsvExporterService, IgxOverlayService, IgxDragDropModule, IgxDividerModule, IgxTreeGridModule, IgxHierarchicalGridModule, IgxInputGroupModule, IgxIconService, DisplayDensityToken, DisplayDensity, - IgxDateTimeEditorModule, IgxDateRangePickerModule, IgxButtonModule, IgxButtonGroupModule, IgxActionStripModule, GridBaseAPIService + IgxDateTimeEditorModule, IgxDateRangePickerModule, IgxButtonModule, IgxButtonGroupModule, IgxActionStripModule, GridBaseAPIService, IgxTreeModule } from 'igniteui-angular'; import { SharedModule } from './shared/shared.module'; @@ -284,6 +284,7 @@ const components = [ IgxActionStripModule, IgxGridModule, IgxTreeGridModule, + IgxTreeModule, IgxHierarchicalGridModule, IgxDragDropModule, IgxDateRangePickerModule, diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index d584d94ada0..8ba1fe231a4 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -82,6 +82,7 @@ import { HierarchicalGridAddRowSampleComponent } from './hierarchical-grid-add-r import { AnimationsSampleComponent } from './styleguide/animations/animations.sample'; import { GridFormattingComponent } from './grid-formatting/grid-formatting.component'; import { MainComponent } from './grid-finjs/main.component'; +import { TreeSampleComponent } from './tree/tree.sample'; const appRoutes = [ { @@ -338,6 +339,10 @@ const appRoutes = [ path: 'gridMasterDetail', component: GridMasterDetailSampleComponent }, + { + path: 'tree', + component: TreeSampleComponent + }, { path: 'treeGrid', component: TreeGridSampleComponent diff --git a/src/app/routing.ts b/src/app/routing.ts index 82634e560e8..5d7146a1963 100644 --- a/src/app/routing.ts +++ b/src/app/routing.ts @@ -115,6 +115,7 @@ import { HierarchicalGridAddRowSampleComponent } from './hierarchical-grid-add-r import { AnimationsSampleComponent } from './styleguide/animations/animations.sample'; import { GridFormattingComponent } from './grid-formatting/grid-formatting.component'; import { MainComponent } from './grid-finjs/main.component'; +import { TreeSampleComponent } from './tree/tree.sample'; const appRoutes = [ { @@ -482,6 +483,9 @@ const appRoutes = [ { path: 'gridFinJS', component: MainComponent + },{ + path: 'tree', + component: TreeSampleComponent }, { path: 'treeGrid', diff --git a/src/app/tree/tree.sample.html b/src/app/tree/tree.sample.html new file mode 100644 index 00000000000..bb82fe3aa48 --- /dev/null +++ b/src/app/tree/tree.sample.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/tree/tree.sample.scss b/src/app/tree/tree.sample.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/tree/tree.sample.ts b/src/app/tree/tree.sample.ts new file mode 100644 index 00000000000..dfb9b139a4b --- /dev/null +++ b/src/app/tree/tree.sample.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-tree-sample', + templateUrl: 'tree.sample.html', + styleUrls: ['tree.sample.scss'] +}) +export class TreeSampleComponent { +}