mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[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
This commit is contained in:
@@ -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": {
|
||||
|
@@ -12,8 +12,8 @@
|
||||
"clientId": "activiti",
|
||||
"scope": "openid",
|
||||
"secret": "",
|
||||
"implicitFlow": true,
|
||||
"silentLogin": true,
|
||||
"implicitFlow": false,
|
||||
"silentLogin": false,
|
||||
"redirectUri": "/",
|
||||
"redirectUriLogout": "/logout"
|
||||
},
|
||||
|
@@ -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: [
|
||||
{
|
||||
|
@@ -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'
|
||||
|
@@ -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' }
|
||||
];
|
||||
|
||||
|
@@ -0,0 +1,10 @@
|
||||
<div>TREE VIEW TEST</div>
|
||||
<mat-form-field class="example-full-width">
|
||||
<input matInput placeholder="Node Id" [(ngModel)]="nodeIdSample">
|
||||
</mat-form-field>
|
||||
<span>
|
||||
CLICKED NODE: {{clickedNodeName}}
|
||||
</span>
|
||||
|
||||
<adf-tree-view-list [nodeId]="nodeIdSample" (nodeClicked)="onClick($event)">
|
||||
</adf-tree-view-list>
|
@@ -0,0 +1,3 @@
|
||||
.example-full-width {
|
||||
width: 100%;
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
32
docs/content-services/tree-view.component.md
Normal file
32
docs/content-services/tree-view.component.md
Normal file
@@ -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
|
||||
<adf-tree-view-list [nodeId]="'74cd8a96-8a21-47e5-9b3b-a1b3e296787d'"
|
||||
(nodeClicked)="onClick($event)">
|
||||
</adf-tree-view-list>
|
||||
```
|
||||
|
||||
## 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)`<NodeEntry>` | Emitted when a node on the tree view is clicked |
|
BIN
docs/docassets/images/tree-view.png
Normal file
BIN
docs/docassets/images/tree-view.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@@ -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 {
|
||||
|
@@ -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!"
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -0,0 +1,21 @@
|
||||
<mat-tree class="adf-tree-view-main" [dataSource]="dataSource" [treeControl]="treeControl" *ngIf="nodeId; else missingNodeId">
|
||||
<mat-tree-node class="adf-tree-view-node" *matTreeNodeDef="let treeNode" id="{{treeNode.name + '-tree-node'}}"
|
||||
matTreeNodePadding [matTreeNodePaddingIndent]="15">
|
||||
{{treeNode.name}}
|
||||
</mat-tree-node>
|
||||
<mat-tree-node class="adf-tree-view-node" id="{{treeNode.name + '-tree-child-node'}}" *matTreeNodeDef="let treeNode; when: hasChild"
|
||||
matTreeNodePadding [matTreeNodePaddingIndent]="15">
|
||||
<button id="{{'button-'+treeNode.name}}" (click)="onNodeClicked(treeNode.node)"
|
||||
mat-icon-button [attr.aria-label]="'toggle ' + treeNode.filename" matTreeNodeToggle>
|
||||
<mat-icon class="adf-tree-view-icon">
|
||||
{{treeControl.isExpanded(treeNode) ? 'folder_open' : 'folder'}}
|
||||
</mat-icon>
|
||||
</button>
|
||||
{{treeNode.name}}
|
||||
</mat-tree-node>
|
||||
</mat-tree>
|
||||
<ng-template #missingNodeId>
|
||||
<div id="adf-tree-view-missing-node">
|
||||
{{'ADF-TREE-VIEW.MISSING-ID' | translate}}
|
||||
</div>
|
||||
</ng-template>
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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<TreeViewComponent>;
|
||||
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 = <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 = <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 = <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 = <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 = <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 = <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();
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
@@ -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<NodeEntry> = new EventEmitter();
|
||||
|
||||
treeControl: FlatTreeControl<TreeBaseNode>;
|
||||
dataSource: TreeViewDataSource;
|
||||
|
||||
constructor(private treeViewService: TreeViewService) {
|
||||
this.treeControl = new FlatTreeControl<TreeBaseNode>(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;
|
||||
});
|
||||
}
|
||||
}
|
91
lib/content-services/tree-view/data/tree-view-datasource.ts
Normal file
91
lib/content-services/tree-view/data/tree-view-datasource.ts
Normal file
@@ -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<TreeBaseNode[]>([]);
|
||||
|
||||
get data(): TreeBaseNode[] {
|
||||
return this.treeNodes;
|
||||
}
|
||||
|
||||
set data(value: TreeBaseNode[]) {
|
||||
this.treeControl.dataNodes = value;
|
||||
this.dataChange.next(value);
|
||||
}
|
||||
|
||||
constructor(private treeControl: FlatTreeControl<TreeBaseNode>,
|
||||
private treeViewService: TreeViewService) {
|
||||
this.dataChange.subscribe((treeNodes) => this.treeNodes = treeNodes);
|
||||
}
|
||||
|
||||
connect(collectionViewer: CollectionViewer): Observable<TreeBaseNode[]> {
|
||||
this.treeControl.expansionModel.onChange!.subscribe(change => {
|
||||
if ((change as SelectionChange<TreeBaseNode>).added &&
|
||||
(change as SelectionChange<TreeBaseNode>).added.length > 0) {
|
||||
this.expandTreeNodes(change as SelectionChange<TreeBaseNode>);
|
||||
} else if ((change as SelectionChange<TreeBaseNode>).removed) {
|
||||
this.reduceTreeNodes(change as SelectionChange<TreeBaseNode>);
|
||||
}
|
||||
});
|
||||
return merge(collectionViewer.viewChange, this.dataChange).pipe(map(() => this.data));
|
||||
}
|
||||
|
||||
private expandTreeNodes(change: SelectionChange<TreeBaseNode>) {
|
||||
change.added.forEach(node => this.expandNode(node));
|
||||
}
|
||||
|
||||
private reduceTreeNodes(change: SelectionChange<TreeBaseNode>) {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
18
lib/content-services/tree-view/index.ts
Normal file
18
lib/content-services/tree-view/index.ts
Normal file
@@ -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';
|
34
lib/content-services/tree-view/models/tree-view.model.ts
Normal file
34
lib/content-services/tree-view/models/tree-view.model.ts
Normal file
@@ -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;
|
||||
}
|
||||
}
|
21
lib/content-services/tree-view/public-api.ts
Normal file
21
lib/content-services/tree-view/public-api.ts
Normal file
@@ -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';
|
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
43
lib/content-services/tree-view/services/tree-view.service.ts
Normal file
43
lib/content-services/tree-view/services/tree-view.service.ts
Normal file
@@ -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<TreeBaseNode[]> {
|
||||
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)))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
38
lib/content-services/tree-view/tree-view.module.ts
Normal file
38
lib/content-services/tree-view/tree-view.module.ts
Normal file
@@ -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 {
|
||||
}
|
Reference in New Issue
Block a user