From 6ead388e55b08431a2c1a71ac1a4fa476886d6bb Mon Sep 17 00:00:00 2001 From: Vito Date: Wed, 7 Nov 2018 19:32:55 +0000 Subject: [PATCH] [ADF-3723] Tree view component (#3939) * [ADF-3723] added first step to adf tree view component * [ADF-3723] start adding tests for the new component * [ADF-3723] fixed style and start adding tests * [ADF-3723] working on unit test * [ADF-3723] added test for the new tree view component * [ADF-3723] added event when clicked on a tree node * [ADF-3723] refactored code * [ADF-3723 added peer review changes * [ADF-3723] fixed extra space * [ADF-3723] fixed unit test * [ADF-3723] fixed failing unit test --- demo-shell/resources/i18n/en.json | 3 +- demo-shell/src/app.config.json | 4 +- demo-shell/src/app/app.module.ts | 4 +- demo-shell/src/app/app.routes.ts | 6 + .../app-layout/app-layout.component.ts | 1 + .../tree-view/tree-view-sample.component.html | 10 + .../tree-view/tree-view-sample.component.scss | 3 + .../tree-view/tree-view-sample.component.ts | 34 ++++ docs/content-services/tree-view.component.md | 32 ++++ docs/docassets/images/tree-view.png | Bin 0 -> 13943 bytes lib/content-services/content.module.ts | 13 +- lib/content-services/i18n/en.json | 3 + lib/content-services/material.module.ts | 6 +- lib/content-services/styles/_index.scss | 2 + .../components/tree-view.component.html | 21 ++ .../components/tree-view.component.scss | 17 ++ .../components/tree-view.component.spec.ts | 181 ++++++++++++++++++ .../components/tree-view.component.ts | 77 ++++++++ .../tree-view/data/tree-view-datasource.ts | 91 +++++++++ lib/content-services/tree-view/index.ts | 18 ++ .../tree-view/models/tree-view.model.ts | 34 ++++ lib/content-services/tree-view/public-api.ts | 21 ++ .../services/tree-view.service.spec.ts | 68 +++++++ .../tree-view/services/tree-view.service.ts | 43 +++++ .../tree-view/tree-view.module.ts | 38 ++++ 25 files changed, 720 insertions(+), 10 deletions(-) create mode 100644 demo-shell/src/app/components/tree-view/tree-view-sample.component.html create mode 100644 demo-shell/src/app/components/tree-view/tree-view-sample.component.scss create mode 100644 demo-shell/src/app/components/tree-view/tree-view-sample.component.ts create mode 100644 docs/content-services/tree-view.component.md create mode 100644 docs/docassets/images/tree-view.png create mode 100644 lib/content-services/tree-view/components/tree-view.component.html create mode 100644 lib/content-services/tree-view/components/tree-view.component.scss create mode 100644 lib/content-services/tree-view/components/tree-view.component.spec.ts create mode 100644 lib/content-services/tree-view/components/tree-view.component.ts create mode 100644 lib/content-services/tree-view/data/tree-view-datasource.ts create mode 100644 lib/content-services/tree-view/index.ts create mode 100644 lib/content-services/tree-view/models/tree-view.model.ts create mode 100644 lib/content-services/tree-view/public-api.ts create mode 100644 lib/content-services/tree-view/services/tree-view.service.spec.ts create mode 100644 lib/content-services/tree-view/services/tree-view.service.ts create mode 100644 lib/content-services/tree-view/tree-view.module.ts 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 + +![TreeView component screenshot](../docassets/images/tree-view.png) + +## 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 0000000000000000000000000000000000000000..745f47c47ce6e095f0384f713518036abe6910e1 GIT binary patch literal 13943 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 literal 0 HcmV?d00001 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 { +}