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 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 { +}