diff --git a/demo-shell/resources/i18n/en.json b/demo-shell/resources/i18n/en.json
index 72433c7b3b..e7d40c32a1 100644
--- a/demo-shell/resources/i18n/en.json
+++ b/demo-shell/resources/i18n/en.json
@@ -85,7 +85,8 @@
"WORD_TO_SEARCH": "Search Word",
"SEARCH_CREATED_BY": "Created By",
"SEARCH_SERVICE_APPROACH": "Check this to disable the input property and configure using the service",
- "HEADER_DATA": "Header Data"
+ "HEADER_DATA": "Header Data",
+ "TREE_VIEW": "Tree View"
},
"TRASHCAN": {
"ACTIONS": {
diff --git a/demo-shell/src/app.config.json b/demo-shell/src/app.config.json
index 2ddf953412..e237c03dc5 100644
--- a/demo-shell/src/app.config.json
+++ b/demo-shell/src/app.config.json
@@ -12,8 +12,8 @@
"clientId": "activiti",
"scope": "openid",
"secret": "",
- "implicitFlow": true,
- "silentLogin": true,
+ "implicitFlow": false,
+ "silentLogin": false,
"redirectUri": "/",
"redirectUriLogout": "/logout"
},
diff --git a/demo-shell/src/app/app.module.ts b/demo-shell/src/app/app.module.ts
index 4e79298873..b215726934 100644
--- a/demo-shell/src/app/app.module.ts
+++ b/demo-shell/src/app/app.module.ts
@@ -67,6 +67,7 @@ import { ProcessServicesCloudModule } from '@alfresco/adf-process-services-cloud
import { CloudComponent } from './components/cloud/cloud.component';
import { TaskListCloudDemoComponent } from './components/task-list-cloud-demo/task-list-cloud-demo.component';
import { ProcessListCloudExampleComponent } from './components/cloud/process-list-cloud-example.component';
+import { TreeViewSampleComponent } from './components/tree-view/tree-view-sample.component';
@NgModule({
imports: [
@@ -117,7 +118,8 @@ import { ProcessListCloudExampleComponent } from './components/cloud/process-lis
FormLoadingComponent,
ReportIssueComponent,
TaskListCloudDemoComponent,
- ProcessListCloudExampleComponent
+ ProcessListCloudExampleComponent,
+ TreeViewSampleComponent
],
providers: [
{
diff --git a/demo-shell/src/app/app.routes.ts b/demo-shell/src/app/app.routes.ts
index 08a12bf7d7..fa10bb05d1 100644
--- a/demo-shell/src/app/app.routes.ts
+++ b/demo-shell/src/app/app.routes.ts
@@ -43,6 +43,7 @@ import { AppComponent } from './app.component';
import { CloudComponent } from './components/cloud/cloud.component';
import { TaskListCloudDemoComponent } from './components/task-list-cloud-demo/task-list-cloud-demo.component';
import { ProcessListCloudExampleComponent } from './components/cloud/process-list-cloud-example.component';
+import { TreeViewSampleComponent } from './components/tree-view/tree-view-sample.component';
export const appRoutes: Routes = [
{ path: 'login', component: LoginComponent },
@@ -279,6 +280,11 @@ export const appRoutes: Routes = [
component: DemoPermissionComponent,
canActivate: [AuthGuardEcm]
},
+ {
+ path: 'treeview',
+ component: TreeViewSampleComponent,
+ canActivate: [AuthGuardEcm]
+ },
{
path: 'about',
loadChildren: 'app/components/about/about.module#AppAboutModule'
diff --git a/demo-shell/src/app/components/app-layout/app-layout.component.ts b/demo-shell/src/app/components/app-layout/app-layout.component.ts
index 2ee3efe301..950a0e67e7 100644
--- a/demo-shell/src/app/components/app-layout/app-layout.component.ts
+++ b/demo-shell/src/app/components/app-layout/app-layout.component.ts
@@ -58,6 +58,7 @@ export class AppLayoutComponent implements OnInit {
{ href: '/extendedSearch', icon: 'search', title: 'APP_LAYOUT.SEARCH' },
/* cspell:disable-next-line */
{ href: '/overlay-viewer', icon: 'pageview', title: 'APP_LAYOUT.OVERLAY_VIEWER' },
+ { href: '/treeview', icon: 'nature', title: 'APP_LAYOUT.TREE_VIEW' },
{ href: '/about', icon: 'info_outline', title: 'APP_LAYOUT.ABOUT' }
];
diff --git a/demo-shell/src/app/components/tree-view/tree-view-sample.component.html b/demo-shell/src/app/components/tree-view/tree-view-sample.component.html
new file mode 100644
index 0000000000..9dfb0850e5
--- /dev/null
+++ b/demo-shell/src/app/components/tree-view/tree-view-sample.component.html
@@ -0,0 +1,10 @@
+
TREE VIEW TEST
+
+
+
+
+ CLICKED NODE: {{clickedNodeName}}
+
+
+
+
diff --git a/demo-shell/src/app/components/tree-view/tree-view-sample.component.scss b/demo-shell/src/app/components/tree-view/tree-view-sample.component.scss
new file mode 100644
index 0000000000..74b7bccd95
--- /dev/null
+++ b/demo-shell/src/app/components/tree-view/tree-view-sample.component.scss
@@ -0,0 +1,3 @@
+.example-full-width {
+ width: 100%;
+ }
diff --git a/demo-shell/src/app/components/tree-view/tree-view-sample.component.ts b/demo-shell/src/app/components/tree-view/tree-view-sample.component.ts
new file mode 100644
index 0000000000..fea4caae6e
--- /dev/null
+++ b/demo-shell/src/app/components/tree-view/tree-view-sample.component.ts
@@ -0,0 +1,34 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * 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 } from '@angular/core';
+
+@Component({
+ selector: 'app-tree-view',
+ templateUrl: 'tree-view-sample.component.html',
+ styleUrls: ['tree-view-sample.component.scss']
+})
+export class TreeViewSampleComponent {
+
+ clickedNodeName: string = '';
+
+ nodeIdSample: string = '-my-';
+
+ onClick(node) {
+ this.clickedNodeName = node.entry.name;
+ }
+}
diff --git a/docs/content-services/tree-view.component.md b/docs/content-services/tree-view.component.md
new file mode 100644
index 0000000000..9171f18210
--- /dev/null
+++ b/docs/content-services/tree-view.component.md
@@ -0,0 +1,32 @@
+---
+Added: v2.6.1
+Status: Active
+---
+
+# Rating component
+
+Allow a user to show the folder and subfolders of a node in a tree view
+
+
+
+## Basic Usage
+
+```html
+
+
+```
+
+## Class members
+
+### Properties
+
+| Name | Type | Default value | Description |
+| ---- | ---- | ------------- | ----------- |
+| nodeId | `string` | | Identifier of the node to apply the rating to. |
+
+### Events
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| nodeClicked | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when a node on the tree view is clicked |
diff --git a/docs/docassets/images/tree-view.png b/docs/docassets/images/tree-view.png
new file mode 100644
index 0000000000..745f47c47c
Binary files /dev/null and b/docs/docassets/images/tree-view.png differ
diff --git a/lib/content-services/content.module.ts b/lib/content-services/content.module.ts
index 78d8967c9d..1b64ab4b13 100644
--- a/lib/content-services/content.module.ts
+++ b/lib/content-services/content.module.ts
@@ -38,6 +38,7 @@ import { DialogModule } from './dialogs/dialog.module';
import { FolderDirectiveModule } from './folder-directive/folder-directive.module';
import { ContentMetadataModule } from './content-metadata/content-metadata.module';
import { PermissionManagerModule } from './permission-manager/permission-manager.module';
+import { TreeViewModule } from './tree-view/tree-view.module';
@NgModule({
imports: [
@@ -61,7 +62,8 @@ import { PermissionManagerModule } from './permission-manager/permission-manager
FolderDirectiveModule,
ContentDirectiveModule,
PermissionManagerModule,
- VersionManagerModule
+ VersionManagerModule,
+ TreeViewModule
],
exports: [
SocialModule,
@@ -79,7 +81,8 @@ import { PermissionManagerModule } from './permission-manager/permission-manager
FolderDirectiveModule,
ContentDirectiveModule,
PermissionManagerModule,
- VersionManagerModule
+ VersionManagerModule,
+ TreeViewModule
]
})
export class ContentModuleLazy {}
@@ -106,7 +109,8 @@ export class ContentModuleLazy {}
FolderDirectiveModule,
ContentDirectiveModule,
PermissionManagerModule,
- VersionManagerModule
+ VersionManagerModule,
+ TreeViewModule
],
providers: [
{
@@ -134,7 +138,8 @@ export class ContentModuleLazy {}
FolderDirectiveModule,
ContentDirectiveModule,
PermissionManagerModule,
- VersionManagerModule
+ VersionManagerModule,
+ TreeViewModule
]
})
export class ContentModule {
diff --git a/lib/content-services/i18n/en.json b/lib/content-services/i18n/en.json
index 6798daf1ef..957dc22782 100644
--- a/lib/content-services/i18n/en.json
+++ b/lib/content-services/i18n/en.json
@@ -289,5 +289,8 @@
"DUPLICATE-PERMISSION": "One or more of the permissions you have set is already present : {{list}}",
"NOT-ALLOWED": "You are not allowed to change permissions"
}
+ },
+ "ADF-TREE-VIEW": {
+ "MISSING-ID": "No nodeId provided!"
}
}
diff --git a/lib/content-services/material.module.ts b/lib/content-services/material.module.ts
index c5ea70d2dc..6df5b89047 100644
--- a/lib/content-services/material.module.ts
+++ b/lib/content-services/material.module.ts
@@ -35,7 +35,8 @@ import {
MatDatepickerModule,
MatSlideToggleModule,
MatRadioModule,
- MatSliderModule
+ MatSliderModule,
+ MatTreeModule
} from '@angular/material';
export function modules() {
@@ -58,7 +59,8 @@ export function modules() {
MatDatepickerModule,
MatSlideToggleModule,
MatRadioModule,
- MatSliderModule
+ MatSliderModule,
+ MatTreeModule
];
}
diff --git a/lib/content-services/styles/_index.scss b/lib/content-services/styles/_index.scss
index 547bbbbf41..49d259993e 100644
--- a/lib/content-services/styles/_index.scss
+++ b/lib/content-services/styles/_index.scss
@@ -18,6 +18,7 @@
@import '../permission-manager/components/add-permission/add-permission.component';
@import '../permission-manager/components/add-permission/add-permission-dialog.component';
@import '../permission-manager/components/add-permission/add-permission-panel.component';
+@import '../tree-view/components/tree-view.component';
@mixin adf-content-services-theme($theme) {
@include adf-breadcrumb-theme($theme);
@@ -36,4 +37,5 @@
@include adf-add-permission-theme($theme);
@include adf-add-permission-dialog-theme($theme);
@include adf-add-permission-panel-theme($theme);
+ @include adf-tree-view-theme($theme);
}
diff --git a/lib/content-services/tree-view/components/tree-view.component.html b/lib/content-services/tree-view/components/tree-view.component.html
new file mode 100644
index 0000000000..92b1504f8b
--- /dev/null
+++ b/lib/content-services/tree-view/components/tree-view.component.html
@@ -0,0 +1,21 @@
+
+
+ {{treeNode.name}}
+
+
+
+ {{treeNode.name}}
+
+
+
+
+ {{'ADF-TREE-VIEW.MISSING-ID' | translate}}
+
+
diff --git a/lib/content-services/tree-view/components/tree-view.component.scss b/lib/content-services/tree-view/components/tree-view.component.scss
new file mode 100644
index 0000000000..eb6215a8ef
--- /dev/null
+++ b/lib/content-services/tree-view/components/tree-view.component.scss
@@ -0,0 +1,17 @@
+@mixin adf-tree-view-theme($theme) {
+ $primary: map-get($theme, primary);
+ $foreground: map-get($theme, foreground);
+ $background: map-get($theme, background);
+
+ .adf {
+ &-tree-view-icon {
+ color: #D9E022;
+ }
+
+ &-tree-view-node.mat-tree-node {
+ min-height: 40px;
+ font-size: 12px;
+ align-items: baseline;
+ }
+ }
+}
diff --git a/lib/content-services/tree-view/components/tree-view.component.spec.ts b/lib/content-services/tree-view/components/tree-view.component.spec.ts
new file mode 100644
index 0000000000..45b949f62d
--- /dev/null
+++ b/lib/content-services/tree-view/components/tree-view.component.spec.ts
@@ -0,0 +1,181 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * 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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { setupTestBed } from '@alfresco/adf-core';
+import { TreeViewComponent } from './tree-view.component';
+import { ContentTestingModule } from '../../testing/content.testing.module';
+import { TreeViewService } from '../services/tree-view.service';
+import { of } from 'rxjs';
+import { TreeBaseNode } from '../models/tree-view.model';
+import { NodeEntry } from 'alfresco-js-api';
+
+describe('TreeViewComponent', () => {
+
+ let fixture: ComponentFixture;
+ let element: HTMLElement;
+ let treeService: TreeViewService;
+ let component: any;
+
+ let fakeNodeList: TreeBaseNode[] = [
+ { nodeId: 'fake-node-id', name: 'fake-node-name', level: 0, expandable: true,
+ node: { entry: { name: 'fake-node-name', id: 'fake-node-id' } } }
+ ];
+
+ let fakeChildrenList: TreeBaseNode[] = [
+ { nodeId: 'fake-child-id', name: 'fake-child-name', level: 0, expandable: true, node : {} },
+ { nodeId: 'fake-second-id', name: 'fake-second-name', level: 0, expandable: true, node : {} }
+ ];
+
+ let fakeNextChildrenList: TreeBaseNode[] = [
+ { nodeId: 'fake-next-child-id', name: 'fake-next-child-name', level: 0, expandable: true, node : {} },
+ { nodeId: 'fake-next-second-id', name: 'fake-next-second-name', level: 0, expandable: true, node : {} }
+ ];
+
+ let returnRootOrChildrenNode = function (nodeId: string) {
+ if (nodeId === '9999999') {
+ return of(fakeNodeList);
+ } else if (nodeId === 'fake-second-id') {
+ return of(fakeNextChildrenList);
+ } else {
+ return of(fakeChildrenList);
+ }
+ };
+
+ setupTestBed({
+ imports: [
+ ContentTestingModule
+ ],
+ declarations: [
+ ]
+ });
+
+ describe('When there is a nodeId', () => {
+
+ beforeEach(async(() => {
+ treeService = TestBed.get(TreeViewService);
+ fixture = TestBed.createComponent(TreeViewComponent);
+ element = fixture.nativeElement;
+ component = fixture.componentInstance;
+ spyOn(treeService, 'getTreeNodes').and.callFake((nodeId) => returnRootOrChildrenNode(nodeId));
+ component.nodeId = '9999999';
+ fixture.detectChanges();
+ }));
+
+ afterEach(() => {
+ fixture.destroy();
+ });
+
+ it('should show the folder', async(() => {
+ expect(element.querySelector('#fake-node-name-tree-child-node')).not.toBeNull();
+ }));
+
+ it('should show the subfolders when the folder is clicked', async(() => {
+ let rootFolderButton: HTMLButtonElement = element.querySelector('#button-fake-node-name');
+ expect(rootFolderButton).not.toBeNull();
+ rootFolderButton.click();
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ expect(element.querySelector('#fake-child-name-tree-child-node')).not.toBeNull();
+ expect(element.querySelector('#fake-second-name-tree-child-node')).not.toBeNull();
+ });
+ }));
+
+ it('should throw a nodeClicked event when a node is clicked', (done) => {
+ component.nodeClicked.subscribe((nodeClicked: NodeEntry) => {
+ expect(nodeClicked).toBeDefined();
+ expect(nodeClicked).not.toBeNull();
+ expect(nodeClicked.entry.name).toBe('fake-node-name');
+ expect(nodeClicked.entry.id).toBe('fake-node-id');
+ done();
+ });
+ let rootFolderButton: HTMLButtonElement = element.querySelector('#button-fake-node-name');
+ expect(rootFolderButton).not.toBeNull();
+ rootFolderButton.click();
+ });
+
+ it('should change the icon of the opened folders', async(() => {
+ let rootFolderButton: HTMLButtonElement = element.querySelector('#button-fake-node-name');
+ expect(rootFolderButton).not.toBeNull();
+ expect(element.querySelector('#button-fake-node-name .mat-icon').textContent.trim()).toBe('folder');
+ rootFolderButton.click();
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ expect(element.querySelector('#button-fake-node-name .mat-icon').textContent.trim()).toBe('folder_open');
+ });
+ }));
+
+ it('should show the subfolders of a subfolder if there are any', async(() => {
+ let rootFolderButton: HTMLButtonElement = element.querySelector('#button-fake-node-name');
+ expect(rootFolderButton).not.toBeNull();
+ rootFolderButton.click();
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ expect(element.querySelector('#fake-second-name-tree-child-node')).not.toBeNull();
+ let childButton: HTMLButtonElement = element.querySelector('#button-fake-second-name');
+ expect(childButton).not.toBeNull();
+ childButton.click();
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ expect(element.querySelector('#fake-next-child-name-tree-child-node')).not.toBeNull();
+ expect(element.querySelector('#fake-next-second-name-tree-child-node')).not.toBeNull();
+ });
+ });
+ }));
+
+ it('should hide the subfolders when clicked again', async(() => {
+ let rootFolderButton: HTMLButtonElement = element.querySelector('#button-fake-node-name');
+ expect(rootFolderButton).not.toBeNull();
+ rootFolderButton.click();
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ expect(element.querySelector('#fake-child-name-tree-child-node')).not.toBeNull();
+ expect(element.querySelector('#fake-second-name-tree-child-node')).not.toBeNull();
+ rootFolderButton.click();
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ expect(element.querySelector('#button-fake-node-name .mat-icon').textContent.trim()).toBe('folder');
+ expect(element.querySelector('#fake-child-name-tree-child-node')).toBeNull();
+ expect(element.querySelector('#fake-second-name-tree-child-node')).toBeNull();
+ });
+ });
+ }));
+ });
+
+ describe('When no nodeId is given', () => {
+
+ let emptyElement: HTMLElement;
+
+ beforeEach(async(() => {
+ fixture = TestBed.createComponent(TreeViewComponent);
+ emptyElement = fixture.nativeElement;
+ }));
+
+ afterEach(() => {
+ fixture.destroy();
+ });
+
+ it('should show an error message when no nodeId is provided', async(() => {
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ fixture.detectChanges();
+ expect(emptyElement.querySelector('#adf-tree-view-missing-node')).toBeDefined();
+ expect(emptyElement.querySelector('#adf-tree-view-missing-node')).not.toBeNull();
+ });
+ }));
+ });
+});
diff --git a/lib/content-services/tree-view/components/tree-view.component.ts b/lib/content-services/tree-view/components/tree-view.component.ts
new file mode 100644
index 0000000000..95a0517a79
--- /dev/null
+++ b/lib/content-services/tree-view/components/tree-view.component.ts
@@ -0,0 +1,77 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * 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 { FlatTreeControl } from '@angular/cdk/tree';
+import { Component, Input, OnInit, OnChanges, SimpleChanges, Output, EventEmitter } from '@angular/core';
+import { TreeBaseNode } from '../models/tree-view.model';
+import { TreeViewDataSource } from '../data/tree-view-datasource';
+import { TreeViewService } from '../services/tree-view.service';
+import { NodeEntry } from 'alfresco-js-api';
+
+@Component({
+ selector: 'adf-tree-view-list',
+ templateUrl: 'tree-view.component.html',
+ styleUrls: ['tree-view.component.scss']
+})
+
+export class TreeViewComponent implements OnInit, OnChanges {
+
+ @Input()
+ nodeId: string;
+
+ @Output()
+ nodeClicked: EventEmitter = new EventEmitter();
+
+ treeControl: FlatTreeControl;
+ dataSource: TreeViewDataSource;
+
+ constructor(private treeViewService: TreeViewService) {
+ this.treeControl = new FlatTreeControl(this.getLevel, this.isExpandable);
+ this.dataSource = new TreeViewDataSource(this.treeControl, this.treeViewService);
+ }
+
+ ngOnInit() {
+ if (this.nodeId) {
+ this.loadTreeNode();
+ }
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes['nodeId'].currentValue &&
+ changes['nodeId'].currentValue !== changes['nodeId'].previousValue) {
+ this.loadTreeNode();
+ }
+ }
+
+ onNodeClicked(node: NodeEntry) {
+ this.nodeClicked.emit(node);
+ }
+
+ getLevel = (node: TreeBaseNode) => node.level;
+
+ isExpandable = (node: TreeBaseNode) => node.expandable;
+
+ hasChild = (level: number, nodeData: TreeBaseNode) => nodeData.expandable;
+
+ private loadTreeNode() {
+ this.treeViewService.getTreeNodes(this.nodeId)
+ .subscribe(
+ (treeNode: TreeBaseNode[]) => {
+ this.dataSource.data = treeNode;
+ });
+ }
+}
diff --git a/lib/content-services/tree-view/data/tree-view-datasource.ts b/lib/content-services/tree-view/data/tree-view-datasource.ts
new file mode 100644
index 0000000000..3eac69d7a9
--- /dev/null
+++ b/lib/content-services/tree-view/data/tree-view-datasource.ts
@@ -0,0 +1,91 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * 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 { Injectable } from '@angular/core';
+import { CollectionViewer, SelectionChange } from '@angular/cdk/collections';
+import { BehaviorSubject, merge, Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { FlatTreeControl } from '@angular/cdk/tree';
+import { TreeBaseNode } from '../models/tree-view.model';
+import { TreeViewService } from '../services/tree-view.service';
+
+@Injectable()
+export class TreeViewDataSource {
+
+ treeNodes: TreeBaseNode[];
+ dataChange = new BehaviorSubject([]);
+
+ get data(): TreeBaseNode[] {
+ return this.treeNodes;
+ }
+
+ set data(value: TreeBaseNode[]) {
+ this.treeControl.dataNodes = value;
+ this.dataChange.next(value);
+ }
+
+ constructor(private treeControl: FlatTreeControl,
+ private treeViewService: TreeViewService) {
+ this.dataChange.subscribe((treeNodes) => this.treeNodes = treeNodes);
+ }
+
+ connect(collectionViewer: CollectionViewer): Observable {
+ this.treeControl.expansionModel.onChange!.subscribe(change => {
+ if ((change as SelectionChange).added &&
+ (change as SelectionChange).added.length > 0) {
+ this.expandTreeNodes(change as SelectionChange);
+ } else if ((change as SelectionChange).removed) {
+ this.reduceTreeNodes(change as SelectionChange);
+ }
+ });
+ return merge(collectionViewer.viewChange, this.dataChange).pipe(map(() => this.data));
+ }
+
+ private expandTreeNodes(change: SelectionChange) {
+ change.added.forEach(node => this.expandNode(node));
+ }
+
+ private reduceTreeNodes(change: SelectionChange) {
+ change.removed.slice().reverse().forEach(node => this.toggleNode(node));
+ }
+
+ private expandNode(node: TreeBaseNode) {
+ this.treeViewService.getTreeNodes(node.nodeId).subscribe((children) => {
+ const index = this.data.indexOf(node);
+ if (!children || index < 0) {
+ node.expandable = false;
+ return;
+ }
+ const nodes = children.map(actualNode => {
+ actualNode.level = node.level + 1;
+ return actualNode;
+ });
+ this.data.splice(index + 1, 0, ...nodes);
+ this.dataChange.next(this.data);
+ });
+ }
+
+ toggleNode(node: TreeBaseNode) {
+ const index = this.data.indexOf(node);
+ let count = 0;
+ for (let i = index + 1; i < this.data.length
+ && this.data[i].level > node.level; i++ , count++) { }
+ this.data.splice(index + 1, count);
+ this.dataChange.next(this.data);
+ }
+
+}
diff --git a/lib/content-services/tree-view/index.ts b/lib/content-services/tree-view/index.ts
new file mode 100644
index 0000000000..4c6ac1d58f
--- /dev/null
+++ b/lib/content-services/tree-view/index.ts
@@ -0,0 +1,18 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * 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.
+ */
+
+export * from './public-api';
diff --git a/lib/content-services/tree-view/models/tree-view.model.ts b/lib/content-services/tree-view/models/tree-view.model.ts
new file mode 100644
index 0000000000..877d376026
--- /dev/null
+++ b/lib/content-services/tree-view/models/tree-view.model.ts
@@ -0,0 +1,34 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * 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 { NodeEntry } from 'alfresco-js-api';
+
+export class TreeBaseNode {
+
+ name: string;
+ nodeId: string;
+ level: number;
+ expandable = true;
+ node: NodeEntry;
+
+ constructor(nodeEntry: NodeEntry, level?: number) {
+ this.name = nodeEntry.entry.name;
+ this.level = level ? level : 0;
+ this.nodeId = nodeEntry.entry.id;
+ this.node = nodeEntry;
+ }
+}
diff --git a/lib/content-services/tree-view/public-api.ts b/lib/content-services/tree-view/public-api.ts
new file mode 100644
index 0000000000..5366f5b428
--- /dev/null
+++ b/lib/content-services/tree-view/public-api.ts
@@ -0,0 +1,21 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * 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.
+ */
+
+export * from './tree-view.module';
+export * from './components/tree-view.component';
+export * from './data/tree-view-datasource';
+export * from './models/tree-view.model';
diff --git a/lib/content-services/tree-view/services/tree-view.service.spec.ts b/lib/content-services/tree-view/services/tree-view.service.spec.ts
new file mode 100644
index 0000000000..0bec30994e
--- /dev/null
+++ b/lib/content-services/tree-view/services/tree-view.service.spec.ts
@@ -0,0 +1,68 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * 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 { setupTestBed, NodesApiService } from '@alfresco/adf-core';
+import { TreeViewService } from './tree-view.service';
+import { TestBed } from '@angular/core/testing';
+import { ContentTestingModule } from '../../testing/content.testing.module';
+import { of } from 'rxjs';
+import { TreeBaseNode } from 'tree-view/models/tree-view.model';
+
+describe('TreeViewService', () => {
+
+ let service: TreeViewService;
+ let nodeService: NodesApiService;
+
+ let fakeNodeList = { list: { entries: [
+ { entry: { id: 'fake-node-id', name: 'fake-node-name', isFolder: true } }
+ ] } };
+
+ let fakeMixedNodeList = { list: { entries: [
+ { entry: { id: 'fake-node-id', name: 'fake-node-name', isFolder: true } },
+ { entry: { id: 'fake-file-id', name: 'fake-file-name', isFolder: false } }
+ ] } };
+
+ setupTestBed({
+ imports: [ContentTestingModule]
+ });
+
+ beforeEach(() => {
+ service = TestBed.get(TreeViewService);
+ nodeService = TestBed.get(NodesApiService);
+ });
+
+ it('should returns TreeBaseNode elements', (done) => {
+ spyOn(nodeService, 'getNodeChildren').and.returnValue(of(fakeNodeList));
+ service.getTreeNodes('fake-node-id').subscribe((nodes: TreeBaseNode[]) => {
+ expect(nodes.length).toBe(1);
+ expect(nodes[0].nodeId).toBe('fake-node-id');
+ expect(nodes[0].name).toBe('fake-node-name');
+ done();
+ });
+ });
+
+ it('should returns only folders elements', (done) => {
+ spyOn(nodeService, 'getNodeChildren').and.returnValue(of(fakeMixedNodeList));
+ service.getTreeNodes('fake-node-id').subscribe((nodes: TreeBaseNode[]) => {
+ expect(nodes.length).toBe(1);
+ expect(nodes[0].nodeId).toBe('fake-node-id');
+ expect(nodes[0].name).toBe('fake-node-name');
+ done();
+ });
+ });
+
+});
diff --git a/lib/content-services/tree-view/services/tree-view.service.ts b/lib/content-services/tree-view/services/tree-view.service.ts
new file mode 100644
index 0000000000..cbf8f7a633
--- /dev/null
+++ b/lib/content-services/tree-view/services/tree-view.service.ts
@@ -0,0 +1,43 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * 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 { NodesApiService } from '@alfresco/adf-core';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { TreeBaseNode } from '../models/tree-view.model';
+import { NodePaging, NodeEntry } from 'alfresco-js-api';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class TreeViewService {
+
+ constructor(private nodeApi: NodesApiService) {
+ }
+
+ getTreeNodes(nodeId): Observable {
+ return this.nodeApi.getNodeChildren(nodeId)
+ .pipe(
+ map((nodePage: NodePaging) => {
+ return nodePage.list.entries.filter((node) => node.entry.isFolder ? node : null);
+ }),
+ map((nodes: NodeEntry[]) => nodes.map(node => new TreeBaseNode(node)))
+ );
+ }
+
+}
diff --git a/lib/content-services/tree-view/tree-view.module.ts b/lib/content-services/tree-view/tree-view.module.ts
new file mode 100644
index 0000000000..faf62bc928
--- /dev/null
+++ b/lib/content-services/tree-view/tree-view.module.ts
@@ -0,0 +1,38 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * 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 { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { TranslateModule } from '@ngx-translate/core';
+import { MaterialModule } from '../material.module';
+import { TreeViewComponent } from './components/tree-view.component';
+
+@NgModule({
+ imports: [
+ CommonModule,
+ MaterialModule,
+ TranslateModule.forChild()
+ ],
+ declarations: [
+ TreeViewComponent
+ ],
+ exports: [
+ TreeViewComponent
+ ]
+})
+export class TreeViewModule {
+}