TSK-207 Add classification details JSON+HAL support for classifications.

This commit is contained in:
Martin Rojas Miguel Angel 2018-04-09 09:45:33 +02:00 committed by Holger Hagen
parent b62563f463
commit 441c276946
38 changed files with 652 additions and 182 deletions

View File

@ -0,0 +1,133 @@
<div class="container-scrollable">
<div id="classification-details" *ngIf="classification && !requestInProgress">
<ul class="nav nav-tabs" role="tablist">
<li *ngIf="showDetail" class="visible-xs visible-sm hidden">
<a (click)="backClicked()">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>Back</a>
</li>
</ul>
<taskana-spinner [isRunning]="requestInProgress" [isModal]="modalSpinner" class="centered-horizontally floating"></taskana-spinner>
<div *ngIf="classification" id="classification" class="panel panel-default classification">
<div class="panel-heading">
<div class="pull-right">
<button type="button" [disabled]="!ClassificationForm.form.valid" (click)="onSave()" class="btn btn-default btn-primary">Save</button>
<button type="button" (click)="onClear()" class="btn btn-default">Undo</button>
<button type="button" (click)="removeClassification()" data-toggle="tooltip" title="Remove" class="btn btn-default remove">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button>
</div>
<h4 class="panel-header">{{classification.name}}&nbsp;
<span *ngIf="!classification.classificationId" class="badge warning"> {{badgeMessage}}</span>
</h4>
</div>
<div class="panel-body">
<form #ClassificationForm="ngForm">
<div class="col-md-6">
<div class="form-group required">
<label for="classification-key" class="control-label">Key</label>
<input type="text" required #key="ngModel" class="form-control" id="classification-key" placeholder="Key" [(ngModel)]="classification.key"
name="classification.key">
<div [hidden]="key.valid" class="required-text">
* Key is required
</div>
</div>
<div class="form-group required">
<label for="classification-name" class="control-label">Name</label>
<input type="text" required #name="ngModel" class="form-control" id="classification-name" placeholder="Name" [(ngModel)]="classification.name"
name="classification.name">
<div [hidden]="name.valid" class="required-text">
* Name is required
</div>
</div>
<div class="form-group required">
<label for="classification-domain" class="control-label">Domain</label>
<input type="text" required #domain="ngModel" class="form-control" id="classification-domain" placeholder="Domain" [(ngModel)]="classification.domain"
name="classification.domain">
<div [hidden]="domain.valid" class="required-text">
* Domain is required
</div>
</div>
<div class="form-group">
<label for="classification-type" class="control-label">Type</label>
<div class="dropdown clearfix btn-group">
<button class="btn btn-default" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
{{classification.type}}
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu" aria-labelledby="dropdownMenu">
<li *ngFor="let type of classificationTypes" (click)="selectType(type)">
<a>
<label>
{{type}}
</label>
</a>
</li>
</ul>
</div>
</div>
<div class="form-group">
<label for="classification-description" class="control-label">Description</label>
<textarea class="form-control" rows="5" id="classification-description" placeholder="Description" [(ngModel)]="classification.description"
name="classification.description"></textarea>
</div>
<div class="form-group">
<label for="classification-service-level" class="control-label">Service Level</label>
<input type="text" class="form-control" id="classification-service-level" placeholder="Service Level" [(ngModel)]="classification.serviceLevel"
name="classification.serviceLevel">
</div>
<div class="form-group">
<label for="classification-application-entry-point" class="control-label">Application entry point</label>
<input type="text" class="form-control" id="classification-application-entry-point" placeholder="Application entry point"
[(ngModel)]="classification.applicationEntryPoint" name="classification.applicationEntryPoint">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="classification-custom-1" class="control-label">Custom 1</label>
<input type="text" class="form-control" id="classification-custom-1" placeholder="Custom 1" [(ngModel)]="classification.custom1"
name="classification.custom1">
</div>
<div class="form-group">
<label for="classification-custom-2" class="control-label">Custom 2</label>
<input type="text" class="form-control" id="classification-custom-2" placeholder="Custom 2" [(ngModel)]="classification.custom2"
name="classification.custom2">
</div>
<div class="form-group">
<label for="classification-custom-3" class="control-label">Custom 3</label>
<input type="text" class="form-control" id="classification-custom-3" placeholder="Custom 3" [(ngModel)]="classification.custom3"
name="classification.custom3">
</div>
<div class="form-group">
<label for="classification-custom-4" class="control-label">Custom 4</label>
<input type="text" class="form-control" id="classification-custom-4" placeholder="Custom 4" [(ngModel)]="classification.custom4"
name="classification.custom4">
</div>
<div class="form-group">
<label for="classification-custom-5" class="control-label">Custom 5</label>
<input type="text" class="form-control" id="classification-custom-5" placeholder="Custom 5" [(ngModel)]="classification.custom5"
name="classification.custom5">
</div>
<div class="form-group">
<label for="classification-custom-6" class="control-label">Custom 6</label>
<input type="text" class="form-control" id="classification-custom-6" placeholder="Custom 6" [(ngModel)]="classification.custom6"
name="classification.custom6">
</div>
<div class="form-group">
<label for="classification-custom-7" class="control-label">Custom 7</label>
<input type="text" class="form-control" id="classification-custom-7" placeholder="Custom 7" [(ngModel)]="classification.custom7"
name="classification.custom7">
</div>
<div class="form-group">
<label for="classification-custom-8" class="control-label">Custom 8</label>
<input type="text" class="form-control" id="classification-custom-8" placeholder="Custom 8" [(ngModel)]="classification.custom8"
name="classification.custom8">
</div>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,11 @@
.classification.panel{
border: none;
box-shadow: none;
margin-bottom: 0px;
&> .panel-body {
height: 80vh;
max-height: 80vh;
overflow-y: auto;
}
}

View File

@ -0,0 +1,59 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { Routes } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { ClassificationDetailsComponent } from './classification-details.component';
import { SpinnerComponent } from 'app/shared/spinner/spinner.component';
import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-detail.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { ClassificationsService } from 'app/services/classifications/classifications.service';
import { TreeNodeModel } from 'app/models/tree-node';
@Component({
selector: 'taskana-dummy-detail',
template: 'dummydetail'
})
class DummyDetailComponent {
}
const routes: Routes = [
{ path: ':id', component: DummyDetailComponent, outlet: 'detail' },
{ path: 'classifications', component: DummyDetailComponent }
];
describe('ClassificationDetailsComponent', () => {
let component: ClassificationDetailsComponent;
let fixture: ComponentFixture<ClassificationDetailsComponent>;
const treeNodes: Array<TreeNodeModel> = new Array(new TreeNodeModel());
const classificationTypes: Array<string> = new Array<string>('type1', 'type2');
let classificationsSpy, classificationsTypesSpy;
let classificationsService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule, HttpClientModule, RouterTestingModule.withRoutes(routes)],
declarations: [ClassificationDetailsComponent, SpinnerComponent, DummyDetailComponent],
providers: [MasterAndDetailService, RequestInProgressService, ClassificationsService, HttpClient]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ClassificationDetailsComponent);
component = fixture.componentInstance;
classificationsService = TestBed.get(ClassificationsService);
classificationsSpy = spyOn(classificationsService, 'getClassifications').and.returnValue(Observable.of(treeNodes));
classificationsTypesSpy = spyOn(classificationsService, 'getClassificationTypes').and.returnValue(Observable.of(classificationTypes));
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,110 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';
import { ClassificationDefinition } from 'app/models/classification-definition';
import { ACTION } from 'app/models/action';
import { ClassificationsService } from 'app/services/classifications/classifications.service';
import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-detail.service';
@Component({
selector: 'taskana-classification-details',
templateUrl: './classification-details.component.html',
styleUrls: ['./classification-details.component.scss']
})
export class ClassificationDetailsComponent implements OnInit, OnDestroy {
classification: ClassificationDefinition;
selectedId: string = undefined;
showDetail = false;
requestInProgress = false;
classificationTypes: Array<string> = [];
badgeMessage = '';
private action: any;
private classificationServiceSubscription: Subscription;
private classificationSelectedSubscription: Subscription;
private routeSubscription: Subscription;
private masterAndDetailSubscription: Subscription;
constructor(private classificationsService: ClassificationsService,
private route: ActivatedRoute,
private router: Router,
private masterAndDetailService: MasterAndDetailService) { }
ngOnInit() {
this.classificationsService.getClassificationTypes().subscribe((classificationTypes: Array<string>) => {
this.classificationTypes = classificationTypes;
})
this.classificationSelectedSubscription = this.classificationsService.getSelectedClassification()
.subscribe(classificationIdSelected => {
this.classification = undefined;
if (classificationIdSelected) {
this.getClassificationInformation(classificationIdSelected);
}
});
this.routeSubscription = this.route.params.subscribe(params => {
let id = params['id'];
this.action = undefined;
if (id && id.indexOf('new-classification') !== -1) {
this.action = ACTION.CREATE;
id = undefined;
this.badgeMessage = 'Creating new workbasket';
this.getClassificationInformation(id);
}
if (id && id !== '') {
this.selectClassification(id);
}
});
this.masterAndDetailSubscription = this.masterAndDetailService.getShowDetail().subscribe(showDetail => {
this.showDetail = showDetail;
});
}
backClicked(): void {
this.classificationsService.selectClassification(undefined);
this.router.navigate(['./'], { relativeTo: this.route.parent });
}
selectType(type: string) {
this.classification.type = type;
}
removeClassification() { }
onSave() { }
onClear() { }
private selectClassification(id: string) {
this.selectedId = id;
this.classificationsService.selectClassification(id);
}
private getClassificationInformation(classificationIdSelected: string) {
if (this.action === ACTION.CREATE) { // CREATE
this.classification = new ClassificationDefinition();
} else {
this.requestInProgress = true;
this.classificationServiceSubscription = this.classificationsService.getClassification(classificationIdSelected)
.subscribe((classification: ClassificationDefinition) => {
this.classification = classification;
this.requestInProgress = false;
});
}
}
ngOnDestroy(): void {
if (this.masterAndDetailSubscription) { this.masterAndDetailSubscription.unsubscribe(); }
if (this.routeSubscription) { this.routeSubscription.unsubscribe(); }
if (this.classificationSelectedSubscription) { this.classificationSelectedSubscription.unsubscribe(); }
if (this.classificationServiceSubscription) { this.classificationServiceSubscription.unsubscribe(); }
}
}

View File

@ -5,9 +5,6 @@
<button type="button" (click)="addClassification()" data-toggle="tooltip" title="Add" class="btn btn-default">
<span class="glyphicon glyphicon-plus green" aria-hidden="true"></span>
</button>
<button type="button" (click)="removeClassification()" data-toggle="tooltip" title="Remove" class="btn btn-default remove">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button>
<taskana-import-export-component [currentSelection]="selectionToImport"></taskana-import-export-component>
<taskana-classification-types-selector class="pull-right" [classificationTypes]="classificationsTypes" [(classificationTypeSelected)]="classificationTypeSelected"
(classificationTypeChanged)=selectClassificationType($event)></taskana-classification-types-selector>
@ -15,6 +12,6 @@
</div>
</li>
<taskana-spinner [isRunning]="requestInProgress" class="centered-horizontally"></taskana-spinner>
<taskana-tree [treeNodes]="classifications"></taskana-tree>
<taskana-tree [treeNodes]="classifications" [selectNodeId] ="selectedId" (selectNodeIdChanged) ="selectClassification($event)"></taskana-tree>
</div>

View File

@ -3,7 +3,7 @@
}
.list-group-item {
padding: 5px 0px;
padding: 5px 0px 2px 1px;
border: none;
}

View File

@ -2,14 +2,15 @@ import { Component, Input } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { Routes } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { TreeNode } from 'app/models/tree-node';
import { TreeNodeModel } from 'app/models/tree-node';
import { ClassificationListComponent } from './classification-list.component';
import { ImportExportComponent } from 'app/shared/import-export/import-export.component';
import { SpinnerComponent } from 'app/shared/spinner/spinner.component';
import { ClassificationTypesSelectorComponent } from 'app/shared/classification-types-selector/classification-types-selector.component';
import { MapValuesPipe } from 'app/pipes/mapValues/map-values.pipe';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { WorkbasketDefinitionService } from 'app/services/workbasket-definition/workbasket-definition.service';
@ -22,25 +23,40 @@ import { DomainService } from 'app/services/domains/domain.service';
selector: 'taskana-tree',
template: ''
})
class TreeComponent {
class TaskanaTreeComponent {
@Input() treeNodes;
@Input() selectNodeId;
}
@Component({
selector: 'taskana-dummy-detail',
template: 'dummydetail'
})
class DummyDetailComponent {
}
const routes: Routes = [
{ path: ':id', component: DummyDetailComponent, outlet: 'detail' },
{ path: 'classifications', component: DummyDetailComponent }
];
describe('ClassificationListComponent', () => {
let component: ClassificationListComponent;
let fixture: ComponentFixture<ClassificationListComponent>;
const treeNodes: Array<TreeNode> = new Array(new TreeNode());
const classificationTypes: Map<string, string> = new Map<string, string>([['type1', 'type1'], ['type2', 'type2']])
const treeNodes: Array<TreeNodeModel> = new Array(new TreeNodeModel());
const classificationTypes: Array<string> = new Array<string>('type1', 'type2');
let classificationsSpy, classificationsTypesSpy;
let classificationsService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ClassificationListComponent, ImportExportComponent, SpinnerComponent, ClassificationTypesSelectorComponent,
TreeComponent, MapValuesPipe],
imports: [HttpClientModule],
TaskanaTreeComponent, DummyDetailComponent],
imports: [HttpClientModule, RouterTestingModule.withRoutes(routes)],
providers: [
HttpClient, WorkbasketDefinitionService, AlertService, ClassificationsService, DomainService, ClassificationDefinitionService
]
})
.compileComponents();

View File

@ -1,9 +1,10 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { Router, ActivatedRoute } from '@angular/router';
import { ImportType } from 'app/models/import-type';
import { Classification } from 'app/models/classification';
import { TreeNode } from 'app/models/tree-node';
import { TreeNodeModel } from 'app/models/tree-node';
import { ClassificationsService } from 'app/services/classifications/classifications.service';
@ -15,41 +16,61 @@ import { ClassificationsService } from 'app/services/classifications/classificat
export class ClassificationListComponent implements OnInit, OnDestroy {
selectedId: string;
selectionToImport = ImportType.CLASSIFICATIONS;
requestInProgress = false;
classifications: Array<Classification> = [];
classificationsTypes: Map<string, string> = new Map();
classificationsTypes: Array<string> = [];
classificationTypeSelected: string;
classificationServiceSubscription: Subscription;
classificationTypeServiceSubscription: Subscription;
constructor(private classificationService: ClassificationsService) {
classificationSelectedSubscription: Subscription;
constructor(
private classificationService: ClassificationsService,
private router: Router,
private route: ActivatedRoute, ) {
}
ngOnInit() {
this.classificationServiceSubscription = this.classificationService.getClassifications()
.subscribe((classifications: Array<TreeNode>) => {
.subscribe((classifications: Array<TreeNodeModel>) => {
this.classifications = classifications;
this.classificationTypeServiceSubscription = this.classificationService.getClassificationTypes()
.subscribe((classificationsTypes: Map<string, string>) => {
.subscribe((classificationsTypes: Array<string>) => {
this.classificationsTypes = classificationsTypes;
this.classificationTypeSelected = this.classifications[0].type;
});
});
this.classificationSelectedSubscription = this.classificationService.getSelectedClassification()
.subscribe((classificationSelected: string) => {
// TODO should be done in a different way.
setTimeout(() => { this.selectedId = classificationSelected; }, 0);
});
}
selectClassificationType(classificationTypeSelected: string) {
this.classificationService.getClassifications(true, classificationTypeSelected)
.subscribe((classifications: Array<TreeNode>) => {
.subscribe((classifications: Array<TreeNodeModel>) => {
this.classifications = classifications;
});
}
selectClassification(id: string) {
this.selectedId = id;
this.router.navigate([{ outlets: { detail: [this.selectedId] } }], { relativeTo: this.route });
}
addClassification() { }
removeClassification() { }
addClassification() {
this.selectedId = undefined;
this.router.navigate([{ outlets: { detail: ['new-classification'] } }], { relativeTo: this.route });
}
ngOnDestroy(): void {
if (this.classificationServiceSubscription) { this.classificationServiceSubscription.unsubscribe(); }
if (this.classificationTypeServiceSubscription) { this.classificationTypeServiceSubscription.unsubscribe(); }
if (this.classificationSelectedSubscription) { this.classificationSelectedSubscription.unsubscribe(); }
}
}

View File

@ -1,9 +1,9 @@
<taskana-spinner [isRunning]="requestInProgress" [isModal]="modalSpinner" class="centered-horizontally floating"></taskana-spinner>
<div *ngIf="workbasket" id="wb-information" class="panel panel-default">
<div class="panel-heading">
<div class="btn-group pull-right">
<div class="pull-right">
<button type="button" (click)="onSave()" [disabled]="!AccessItemsForm.form.valid || action === 'COPY'" class="btn btn-default btn-primary">Save</button>
<button type="button" (click)="clear()" class="btn btn-default">Undo changes</button>
<button type="button" (click)="clear()" class="btn btn-default">Undo</button>
</div>
<h4 class="panel-header">{{workbasket.name}}
<span *ngIf="!workbasket.workbasketId" class="badge warning"> {{badgeMessage}}</span>

View File

@ -54,6 +54,9 @@ export class AccessItemsComponent implements OnChanges, OnDestroy {
if (!this.initialized && changes.active && changes.active.currentValue === 'accessItems') {
this.init();
}
if (changes.action) {
this.setBadge();
}
}
private init() {
this.initialized = true;
@ -75,9 +78,7 @@ export class AccessItemsComponent implements OnChanges, OnDestroy {
this.onSave();
}
})
if (this.action === ACTION.COPY) {
this.badgeMessage = `Copying workbasket: ${this.workbasket.key}`;
}
}
addAccessItem() {
@ -113,6 +114,11 @@ export class AccessItemsComponent implements OnChanges, OnDestroy {
})
return false;
}
private setBadge() {
if (this.action === ACTION.COPY) {
this.badgeMessage = `Copying workbasket: ${this.workbasket.key}`;
}
}
private cloneAccessItems(inputaccessItem): Array<WorkbasketAccessItems> {
const accessItemClone = new Array<WorkbasketAccessItems>();

View File

@ -2,9 +2,9 @@
class="centered-horizontally floating"></taskana-spinner>
<div *ngIf="workbasket" id="wb-information" class="panel panel-default">
<div class="panel-heading">
<div class="btn-group pull-right">
<div class="pull-right">
<button type="button" (click)="onSave()" [disabled]="action === 'COPY'" class="btn btn-default btn-primary">Save</button>
<button type="button" (click)="onClear()" class="btn btn-default">Undo changes</button>
<button type="button" (click)="onClear()" class="btn btn-default">Undo</button>
</div>
<h4 class="panel-header">{{workbasket.name}}
<span *ngIf="!workbasket.workbasketId" class="badge warning"> {{badgeMessage}}</span>

View File

@ -60,9 +60,12 @@ export class DistributionTargetsComponent implements OnChanges, OnDestroy {
private errorModalService: ErrorModalService) { }
ngOnChanges(changes: SimpleChanges): void {
if (!this.initialized && changes.active && changes.active.currentValue === 'distributionTargets' ) {
if (!this.initialized && changes.active && changes.active.currentValue === 'distributionTargets') {
this.init();
}
if (changes.action) {
this.setBadge();
}
}
private init() {
@ -97,9 +100,6 @@ export class DistributionTargetsComponent implements OnChanges, OnDestroy {
}
});
if (this.action === ACTION.COPY) {
this.badgeMessage = `Copying workbasket: ${this.workbasket.key}`;
}
}
moveDistributionTargets(side: number) {
@ -162,6 +162,12 @@ export class DistributionTargetsComponent implements OnChanges, OnDestroy {
});
}
private setBadge() {
if (this.action === ACTION.COPY) {
this.badgeMessage = `Copying workbasket: ${this.workbasket.key}`;
}
}
private getSelectedItems(originList: any, destinationList: any): Array<any> {
return originList.filter((item: any) => { return (item.selected === true) });
}

View File

@ -1,9 +1,15 @@
<taskana-spinner [isRunning]="requestInProgress" [isModal]="modalSpinner" class="centered-horizontally floating"></taskana-spinner>
<div *ngIf="workbasket" id="wb-information" class="panel panel-default">
<div class="panel-heading">
<div class="btn-group pull-right">
<div class="pull-right">
<button type="button" [disabled]="!WorkbasketForm.form.valid" (click)="onSave()" class="btn btn-default btn-primary">Save</button>
<button type="button" (click)="onClear()" class="btn btn-default">Undo changes</button>
<button type="button" (click)="onClear()" class="btn btn-default">Undo</button>
<button type="button" (click)="copyWorkbasket()" data-toggle="tooltip" title="copy" class="btn btn-default">
<span class="glyphicon glyphicon-copy green" aria-hidden="true"></span>
</button>
<button type="button" (click)="removeWorkbasket()" data-toggle="tooltip" title="Remove" class="btn btn-default remove">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button>
</div>
<h4 class="panel-header">{{workbasket.name}}&nbsp;
<span *ngIf="!workbasket.workbasketId" class="badge warning"> {{badgeMessage}}</span>

View File

@ -24,6 +24,7 @@ import { RemoveNoneTypePipe } from 'app/pipes/removeNoneType/remove-none-type.pi
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { SavingWorkbasketService, SavingInformation } from 'app/services/saving-workbaskets/saving-workbaskets.service';
import { AlertService } from 'app/services/alert/alert.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
@Component({
selector: 'taskana-dummy-detail',
@ -47,7 +48,7 @@ describe('InformationComponent', () => {
declarations: [WorkbasketInformationComponent, IconTypeComponent, MapValuesPipe,
RemoveNoneTypePipe, SpinnerComponent, GeneralMessageModalComponent, DummyDetailComponent],
imports: [FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule, RouterTestingModule.withRoutes(routes)],
providers: [WorkbasketService, AlertService, SavingWorkbasketService, ErrorModalService]
providers: [WorkbasketService, AlertService, SavingWorkbasketService, ErrorModalService, RequestInProgressService]
})
.compileComponents();
@ -162,4 +163,15 @@ describe('InformationComponent', () => {
expect(savingWorkbasketService.triggerAccessItemsSaving).toHaveBeenCalled();
});
// it('should call to workbasket service to remove workbasket after click on remove workbasket', () => {
// const spy = spyOn(router, 'navigate');
// component.removeWorkbasket();
// expect(requestInProgressService.setRequestInProgress).toHaveBeenCalledWith(true);
// expect(workbasketService.deleteWorkbasket).toHaveBeenCalledWith('selfLink');
// expect(requestInProgressService.setRequestInProgress).toHaveBeenCalledWith(false);
// expect(workbasketService.triggerWorkBasketSaved).toHaveBeenCalled();
// expect(spy.calls.first().args[0][0]).toBe('/workbaskets');
// });
});

View File

@ -1,4 +1,5 @@
import { Component, OnInit, Input, Output, OnDestroy, OnChanges, SimpleChanges } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { DatePipe } from '@angular/common';
import { ActivatedRoute, Params, Router, NavigationStart } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';
@ -8,13 +9,14 @@ import { ICONTYPES } from 'app/models/type';
import { ErrorModel } from 'app/models/modal-error';
import { ACTION } from 'app/models/action';
import { Workbasket } from 'app/models/workbasket';
import { AlertModel, AlertType } from 'app/models/alert';
import { AlertService } from 'app/services/alert/alert.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { FormGroup } from '@angular/forms';
import { SavingWorkbasketService, SavingInformation } from 'app/services/saving-workbaskets/saving-workbaskets.service';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { AlertModel, AlertType } from 'app/models/alert';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
const dateFormat = 'yyyy-MM-ddTHH:mm:ss';
const dateLocale = 'en-US';
@ -46,7 +48,8 @@ export class WorkbasketInformationComponent implements OnChanges, OnDestroy {
private route: ActivatedRoute,
private router: Router,
private errorModalService: ErrorModalService,
private savingWorkbasket: SavingWorkbasketService) {
private savingWorkbasket: SavingWorkbasketService,
private requestInProgressService: RequestInProgressService) {
this.allTypes = IconTypeComponent.allTypes;
}
@ -91,13 +94,33 @@ export class WorkbasketInformationComponent implements OnChanges, OnDestroy {
this.hasChanges = false;
}
removeWorkbasket() {
this.requestInProgressService.setRequestInProgress(true);
this.workbasketService.deleteWorkbasket(this.workbasket._links.self.href).subscribe(response => {
this.requestInProgressService.setRequestInProgress(false);
this.workbasketService.triggerWorkBasketSaved();
this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS,
`Workbasket ${this.workbasket.workbasketId} was removed successfully`))
this.router.navigate(['administration/workbaskets']);
}, error => {
this.requestInProgressService.setRequestInProgress(false);
this.errorModalService.triggerError(new ErrorModel(
`There was an error deleting workbasket ${this.workbasket.workbasketId}`, error.error.message))
});
}
copyWorkbasket() {
this.router.navigate([{ outlets: { detail: ['copy-workbasket'] } }], { relativeTo: this.route.parent });
}
private beforeRequest() {
this.requestInProgress = true;
this.requestInProgressService.setRequestInProgress(true);
this.modalSpinner = true;
}
private afterRequest() {
this.requestInProgress = false;
this.requestInProgressService.setRequestInProgress(false);
this.workbasketService.triggerWorkBasketSaved();
}

View File

@ -16,12 +16,12 @@ import { WorkbasketAccessItemsResource } from 'app/models/workbasket-access-item
import { ICONTYPES } from 'app/models/type';
import { Links } from 'app/models/links';
import { WorkbasketAccessItems } from 'app/models/workbasket-access-items';
import { LinksWorkbasketSummary } from 'app/models/links-workbasket-summary';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-detail.service';
import { PermissionService } from 'app/services/permission/permission.service';
import { AlertService } from 'app/services/alert/alert.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { SavingWorkbasketService } from 'app/services/saving-workbaskets/saving-workbaskets.service';
import { WorkbasketDetailsComponent } from './workbasket-details.component';
@ -37,7 +37,9 @@ import { GeneralMessageModalComponent } from 'app/shared/general-message-modal/g
import { MapValuesPipe } from 'app/pipes/mapValues/map-values.pipe';
import { RemoveNoneTypePipe } from 'app/pipes/removeNoneType/remove-none-type.pipe';
import { SelectWorkBasketPipe } from 'app/pipes/selectedWorkbasket/seleted-workbasket.pipe';
import { LinksWorkbasketSummary } from '../../../models/links-workbasket-summary';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
@Component({
selector: 'taskana-filter',
@ -76,8 +78,8 @@ describe('WorkbasketDetailsComponent', () => {
declarations: [WorkbasketDetailsComponent, NoAccessComponent, WorkbasketInformationComponent, SpinnerComponent,
IconTypeComponent, MapValuesPipe, RemoveNoneTypePipe, AlertComponent, GeneralMessageModalComponent, AccessItemsComponent,
DistributionTargetsComponent, FilterComponent, DualListComponent, DummyDetailComponent, SelectWorkBasketPipe],
providers: [WorkbasketService, MasterAndDetailService, PermissionService,
AlertService, ErrorModalService, SavingWorkbasketService]
providers: [WorkbasketService, MasterAndDetailService, PermissionService, ErrorModalService, RequestInProgressService,
AlertService, SavingWorkbasketService]
})
.compileComponents();
}));

View File

@ -41,8 +41,7 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
private route: ActivatedRoute,
private router: Router,
private masterAndDetailService: MasterAndDetailService,
private permissionService: PermissionService,
private errorModalService: ErrorModalService) { }
private permissionService: PermissionService) { }

View File

@ -4,12 +4,6 @@
<button type="button" (click)="addWorkbasket()" data-toggle="tooltip" title="Add" class="btn btn-default">
<span class="glyphicon glyphicon-plus green" aria-hidden="true"></span>
</button>
<button *ngIf="workbasketIdSelected" type="button" (click)="copyWorkbasket()" data-toggle="tooltip" title="copy" class="btn btn-default">
<span class="glyphicon glyphicon-copy" aria-hidden="true"></span>
</button>
<button *ngIf="workbasketIdSelected" type="button" (click)="removeWorkbasket()" data-toggle="tooltip" title="Remove" class="btn btn-default remove">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button>
<taskana-import-export-component [currentSelection]="'workbaskets'"></taskana-import-export-component>
</div>
<div class="pull-right margin-right">

View File

@ -1,5 +1,5 @@
.list-group-item {
padding: 5px 0px;
padding: 5px 0px 2px 1px;
border: none;
}

View File

@ -73,7 +73,6 @@ describe('WorkbasketListToolbarComponent', () => {
component.workbaskets = new Array<WorkbasketSummary>(
new WorkbasketSummary('1', 'key1', 'NAME1', 'description 1', 'owner 1',
undefined, undefined, undefined, undefined, undefined, undefined, undefined, new Links({ 'href': 'selfLink' })));
component.workbasketIdSelected = '1';
fixture.detectChanges();
});
@ -93,23 +92,6 @@ describe('WorkbasketListToolbarComponent', () => {
});
it('should navigate to copy-workbasket when click on add copy workbasket', () => {
const spy = spyOn(router, 'navigate');
component.copyWorkbasket();
expect(spy.calls.first().args[0][0].outlets.detail[0]).toBe('copy-workbasket');
});
it('should call to workbasket service to remove workbasket after click on remove workbasket', () => {
const spy = spyOn(router, 'navigate');
component.removeWorkbasket();
expect(requestInProgressService.setRequestInProgress).toHaveBeenCalledWith(true);
expect(workbasketService.deleteWorkbasket).toHaveBeenCalledWith('selfLink');
expect(requestInProgressService.setRequestInProgress).toHaveBeenCalledWith(false);
expect(workbasketService.triggerWorkBasketSaved).toHaveBeenCalled();
expect(spy.calls.first().args[0][0]).toBe('/workbaskets');
});
it('should emit performSorting when sorting is triggered', () => {
let sort: SortingModel;
const compareSort = new SortingModel();

View File

@ -38,8 +38,6 @@ export class WorkbasketListToolbarComponent implements OnInit {
@Input() workbaskets: Array<WorkbasketSummary>;
@Input() workbasketIdSelected: string;
@Output() workbasketIdSelectedChanged: string;
@Output() performSorting = new EventEmitter<SortingModel>();
@Output() performFilter = new EventEmitter<FilterModel>();
workbasketServiceSubscription: Subscription;
@ -66,31 +64,7 @@ export class WorkbasketListToolbarComponent implements OnInit {
}
addWorkbasket() {
this.workbasketIdSelected = undefined;
this.workbasketService.selectWorkBasket(undefined);
this.router.navigate([{ outlets: { detail: ['new-workbasket'] } }], { relativeTo: this.route });
}
removeWorkbasket() {
this.requestInProgressService.setRequestInProgress(true);
this.workbasketService.deleteWorkbasket(this.findWorkbasketSelectedObject()._links.self.href).subscribe(response => {
this.requestInProgressService.setRequestInProgress(false);
this.workbasketService.triggerWorkBasketSaved();
this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS,
`Workbasket ${this.workbasketIdSelected} was removed successfully`))
this.router.navigate(['/workbaskets']);
}, error => {
this.requestInProgressService.setRequestInProgress(false);
this.errorModalService.triggerError(new ErrorModel(
`There was an error deleting workbasket ${this.workbasketIdSelected}`, error.error.message))
});
}
copyWorkbasket() {
this.workbasketIdSelected = undefined;
this.router.navigate([{ outlets: { detail: ['copy-workbasket'] } }], { relativeTo: this.route });
}
private findWorkbasketSelectedObject() {
return this.workbaskets.find(element => element.workbasketId === this.workbasketIdSelected);
}
}

View File

@ -1,8 +1,7 @@
<div class="workbasket-list-full-height">
<div class="footer-space">
<div #wbToolbar>
<taskana-workbasket-list-toolbar [workbaskets]="workbaskets" (performFilter)="performFilter($event)" (performSorting)="performSorting ($event)"
[(workbasketIdSelected)]="selectedId"></taskana-workbasket-list-toolbar>
<taskana-workbasket-list-toolbar [workbaskets]="workbaskets" (performFilter)="performFilter($event)" (performSorting)="performSorting ($event)"></taskana-workbasket-list-toolbar>
</div>
<taskana-spinner [isRunning]="requestInProgress" class="centered-horizontally"></taskana-spinner>
<div>

View File

@ -1,11 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component';
import { WorkbasketListComponent } from './administration/workbasket/master/list/workbasket-list.component';
import { WorkbasketDetailsComponent } from './administration/workbasket/details/workbasket-details.component';
import { MasterAndDetailComponent } from './shared/master-and-detail/master-and-detail.component';
import { NoAccessComponent } from './administration/workbasket/details/noAccess/no-access.component';
import {ClassificationListComponent} from './administration/classification/master/list/classification-list.component';
import { ClassificationListComponent } from './administration/classification/master/list/classification-list.component';
import { ClassificationDetailsComponent } from 'app/administration/classification/details/classification-details.component';
const appRoutes: Routes = [
{
@ -30,15 +32,20 @@ const appRoutes: Routes = [
]
},
{
path: 'administration/classifications',
component: MasterAndDetailComponent,
children: [
{
path: '',
component: ClassificationListComponent,
outlet: 'master'
}
]
path: 'administration/classifications',
component: MasterAndDetailComponent,
children: [
{
path: '',
component: ClassificationListComponent,
outlet: 'master'
},
{
path: ':id',
component: ClassificationDetailsComponent,
outlet: 'detail'
}
]
},
{
path: '',

View File

@ -32,10 +32,11 @@ import { SortComponent } from './shared/sort/sort.component';
import { GeneralMessageModalComponent } from './shared/general-message-modal/general-message-modal.component';
import { PaginationComponent } from './administration/workbasket/master/list/pagination/pagination.component';
import { ClassificationListComponent } from './administration/classification/master/list/classification-list.component';
import { ClassificationDetailsComponent } from './administration/classification/details/classification-details.component';
import { ImportExportComponent } from './shared/import-export/import-export.component';
import { MasterAndDetailComponent } from './shared/master-and-detail/master-and-detail.component';
import { ClassificationTypesSelectorComponent } from './shared/classification-types-selector/classification-types-selector.component';
import { TreeComponent } from './shared/tree/tree.component';
import { TaskanaTreeComponent } from './shared/tree/tree.component';
/**
* Services
@ -96,8 +97,9 @@ const DECLARATIONS = [
PaginationComponent,
ClassificationListComponent,
ImportExportComponent,
TreeComponent,
TaskanaTreeComponent,
ClassificationTypesSelectorComponent,
ClassificationDetailsComponent,
MapValuesPipe,
RemoveNoneTypePipe,
SelectWorkBasketPipe,

View File

@ -1,24 +1,28 @@
import { LinksClassification } from 'app/models/links-classfication';
export class ClassificationDefinition {
constructor(public classificationId: string,
public key: string,
public parentId: string,
public category: string,
public domain: string,
public isValidInDomain: boolean,
public created: string,
public modifies: string,
public name: string,
public description: string,
public priority: number,
public serviceLevel: string,
public applicationEntryPoint: string,
public custom1: string,
public custom2: string,
public custom3: string,
public custom4: string,
public custom5: string,
public custom6: string,
public custom7: string,
public custom8: string) {
constructor(public classificationId: string = undefined,
public key: string = undefined,
public parentId: string = undefined,
public category: string = undefined,
public domain: string = undefined,
public type: string = undefined,
public isValidInDomain: boolean = undefined,
public created: string = undefined,
public modifies: string = undefined,
public name: string = undefined,
public description: string = undefined,
public priority: number = undefined,
public serviceLevel: string = undefined,
public applicationEntryPoint: string = undefined,
public custom1: string = undefined,
public custom2: string = undefined,
public custom3: string = undefined,
public custom4: string = undefined,
public custom5: string = undefined,
public custom6: string = undefined,
public custom7: string = undefined,
public custom8: string = undefined,
public _links: LinksClassification = new LinksClassification()) {
}
}

View File

@ -0,0 +1,13 @@
import { Classification } from './classification';
import { Links } from './links';
export class ClassificationResource {
constructor(
public _embedded: {
'classificationSummaryResourceList': Array<Classification>
} = { 'classificationSummaryResourceList': [] },
public _links: Links = new Links(),
) {
}
}

View File

@ -1,5 +1,7 @@
import { Links } from 'app/models/links';
export class Classification {
constructor(public id: string,
constructor(public classificationId: string,
public key: string,
public category: string,
public type: string,
@ -7,6 +9,7 @@ export class Classification {
public name: string,
public parentId: string,
public priority: number,
public serviceLevel: string) {
public serviceLevel: string,
public _links: Links = new Links()) {
}
}

View File

@ -0,0 +1,12 @@
import { Links } from './links';
export class LinksClassification extends Links {
constructor(
self = undefined,
distributionTargets = undefined,
accessItems = undefined,
public getAllClassifications: { 'href': string } = undefined,
public createClassification: { 'href': string } = undefined,
public updateClassification: { 'href': string } = undefined,
) { super(self, distributionTargets, accessItems) }
}

View File

@ -1,6 +1,6 @@
import { Classification } from 'app/models/classification';
export class TreeNode extends Classification {
export class TreeNodeModel extends Classification {
constructor(public id: string = '',
public key: string = '',
public category: string = '',
@ -10,7 +10,7 @@ export class TreeNode extends Classification {
public parentId: string = '',
public priority: number = 0,
public serviceLevel: string = '',
public children: Array<TreeNode> = undefined) {
public children: Array<TreeNodeModel> = undefined) {
super(id, key, category, type, domain, name, parentId, priority, serviceLevel);
}
}

View File

@ -3,14 +3,18 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
import { environment } from '../../../environments/environment';
import { Classification } from 'app/models/classification';
import { TreeNode } from 'app/models/tree-node';
import { TreeNodeModel } from 'app/models/tree-node';
import { ClassificationDefinition } from 'app/models/classification-definition';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { ClassificationResource } from '../../models/classification-resource';
@Injectable()
export class ClassificationsService {
url = environment.taskanaRestUrl + '/v1/classifications';
classificationSelected = new Subject<string>();
httpOptions = {
headers: new HttpHeaders({
@ -19,39 +23,65 @@ export class ClassificationsService {
})
};
private classificationRef: Observable<Array<Classification>>;
private classificationRef: Observable<ClassificationResource>;
private classificationTypes: Array<string>;
constructor(private httpClient: HttpClient) {
}
// GET
getClassifications(forceRequest = false, type = 'TASK', domain = ''): Observable<Array<TreeNode>> {
getClassifications(forceRequest = false, type = 'TASK', domain = ''): Observable<Array<TreeNodeModel>> {
if (!forceRequest && this.classificationRef) {
return this.classificationRef.map((response: Array<Classification>) => {
return this.buildHierarchy(response, type, domain);
return this.classificationRef.map((response: ClassificationResource) => {
if (!response._embedded) {
return [];
}
return this.buildHierarchy(response._embedded.classificationSummaryResourceList, type, domain);
});
}
this.classificationRef = this.httpClient.get<Array<Classification>>(`${environment.taskanaRestUrl}/v1/classifications`,
this.classificationRef = this.httpClient.get<ClassificationResource>(`${environment.taskanaRestUrl}/v1/classifications`,
this.httpOptions);
return this.classificationRef.map((response: Array<Classification>) => {
return this.buildHierarchy(response, type, domain);
return this.classificationRef.map((response: ClassificationResource) => {
if (!response._embedded) {
return [];
}
return this.buildHierarchy(response._embedded.classificationSummaryResourceList, type, domain);
});
}
getClassificationTypes(): Observable<Map<string, string>> {
const typesSubject = new Subject<Map<string, string>>();
this.classificationRef.subscribe((classifications: Array<Classification>) => {
// GET
getClassification(id: string): Observable<ClassificationDefinition> {
return this.httpClient.get<ClassificationDefinition>(`${environment.taskanaRestUrl}/v1/classifications/${id}`, this.httpOptions);
}
getClassificationTypes(): Observable<Array<string>> {
const typesSubject = new Subject<Array<string>>();
this.classificationRef.subscribe((classifications: ClassificationResource) => {
if (!classifications._embedded) {
return typesSubject;
}
const types = new Map<string, string>();
classifications.forEach(element => {
classifications._embedded.classificationSummaryResourceList.forEach(element => {
types.set(element.type, element.type);
});
typesSubject.next(types);
typesSubject.next(this.map2Array(types));
});
return typesSubject.asObservable();
}
// #region "Service extras"
selectClassification(id: string) {
this.classificationSelected.next(id);
}
getSelectedClassification(): Observable<string> {
return this.classificationSelected.asObservable();
}
// #endregion
private buildHierarchy(classifications: Array<Classification>, type: string, domain: string) {
const roots = []
const children = new Array<any>();
@ -73,12 +103,22 @@ export class ClassificationsService {
private findChildren(parent: any, children: Array<any>) {
if (children[parent.id]) {
parent.children = children[parent.id];
if (children[parent.classificationId]) {
parent.children = children[parent.classificationId];
for (let index = 0, len = parent.children.length; index < len; ++index) {
this.findChildren(parent.children[index], children);
}
}
}
private map2Array(map: Map<string, string>): Array<string> {
const returnArray = [];
map.forEach((entryVal, entryKey) => {
returnArray.push(entryKey);
});
return returnArray;
}
}

View File

@ -1,17 +1,17 @@
<div class="dropdown clearfix btn-group">
<button type="button" class="btn btn-default"> {{classificationTypeSelected}}</button>
<button type="button" class="btn btn-default"> {{classificationTypeSelected}}</button>
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu dropdown-menu-right sortby-dropdown popup" aria-labelledby="sortingDropdown">
<li *ngFor="let classificationType of classificationTypes | mapValues">
<a (click)="select(classificationType.key)">
<li *ngFor="let classificationType of classificationTypes">
<a (click)="select(classificationType)">
<label>
<span class="glyphicon {{classificationTypeSelected === classificationType.key? 'glyphicon-check': 'glyphicon-unchecked'}} blue"
<span class="glyphicon {{classificationTypeSelected === classificationType? 'glyphicon-check': 'glyphicon-unchecked'}} blue"
aria-hidden="true"></span>
{{classificationType.key}}
{{classificationType}}
</label>
</a>
</li>

View File

@ -1,7 +1,6 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ClassificationTypesSelectorComponent } from './classification-types-selector.component';
import { MapValuesPipe } from 'app/pipes/mapValues/map-values.pipe';
describe('ClassificationTypesSelectorComponent', () => {
let component: ClassificationTypesSelectorComponent;
@ -9,9 +8,9 @@ describe('ClassificationTypesSelectorComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ClassificationTypesSelectorComponent, MapValuesPipe ]
declarations: [ClassificationTypesSelectorComponent]
})
.compileComponents();
.compileComponents();
}));
beforeEach(() => {

View File

@ -7,7 +7,7 @@ import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
})
export class ClassificationTypesSelectorComponent implements OnInit {
@Input() classificationTypes: Map<string, string> = new Map<string, string>();
@Input() classificationTypes: Array<string> = [];
@Input()
classificationTypeSelected: string = undefined;
@Output()

View File

@ -1,4 +1,4 @@
<tree-root [nodes]="treeNodes" [state]="state" [options]="options">
<tree-root #tree [nodes]="treeNodes" [options]="options" (activate)="onActivate($event)">
<ng-template #treeNodeTemplate let-node let-index="index">
<span class="text-top">
<svg-icon class="blue small fa-fw" src="./assets/icons/{{node.data.category === 'EXTERN'? 'external':

View File

@ -1,7 +1,7 @@
import { Input, Component } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TreeComponent } from './tree.component';
import { TaskanaTreeComponent } from './tree.component';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
@ -16,23 +16,26 @@ class TreeVendorComponent {
@Input() options;
@Input() state;
@Input() nodes;
treeModel = {
getActiveNode() { }
}
}
// tslint:enable:component-selector
fdescribe('TreeComponent', () => {
let component: TreeComponent;
let fixture: ComponentFixture<TreeComponent>;
describe('TaskanaTreeComponent', () => {
let component: TaskanaTreeComponent;
let fixture: ComponentFixture<TaskanaTreeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [AngularSvgIconModule, HttpClientModule, HttpModule],
declarations: [TreeComponent, TreeVendorComponent]
declarations: [TaskanaTreeComponent, TreeVendorComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TreeComponent);
fixture = TestBed.createComponent(TaskanaTreeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@ -1,20 +1,27 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { TreeNode } from 'app/models/tree-node';
import { TREE_ACTIONS, KEYS, IActionMapping, ITreeOptions, ITreeState } from 'angular-tree-component';
import { Component, OnInit, Input, Output, EventEmitter, ViewChild, AfterViewChecked } from '@angular/core';
import { TreeNodeModel } from 'app/models/tree-node';
import { TREE_ACTIONS, KEYS, IActionMapping, ITreeOptions, ITreeState, TreeComponent, TreeNode } from 'angular-tree-component';
@Component({
selector: 'taskana-tree',
templateUrl: './tree.component.html',
styleUrls: ['./tree.component.scss']
})
export class TreeComponent implements OnInit {
export class TaskanaTreeComponent implements OnInit, AfterViewChecked {
@Input() treeNodes: TreeNode;
@Output() treeNodesChange = new EventEmitter<Array<TreeNode>>();
@ViewChild('tree')
private tree: TreeComponent;
@Input() treeNodes: TreeNodeModel;
@Output() treeNodesChange = new EventEmitter<Array<TreeNodeModel>>();
@Input() selectNodeId: string;
@Output() selectNodeIdChanged = new EventEmitter<string>();
options: ITreeOptions = {
displayField: 'name',
idField: 'id',
idField: 'classificationId',
actionMapping: {
keys: {
[KEYS.ENTER]: (tree, node, $event) => {
@ -27,14 +34,41 @@ export class TreeComponent implements OnInit {
levelPadding: 20
}
state: ITreeState = {
activeNodeIds: { ['']: true },
}
constructor() { }
ngOnInit() {
this.selectNode(this.selectNodeId);
}
ngAfterViewChecked(): void {
if (this.selectNodeId && !this.tree.treeModel.getActiveNode()) {
this.selectNode(this.selectNodeId);
} else if (!this.selectNodeId && this.tree.treeModel.getActiveNode()) {
this.unSelectActiveNode();
}
}
onActivate(treeNode: any) {
this.selectNodeIdChanged.emit(treeNode.node.data.classificationId + '');
}
private selectNode(nodeId: string) {
if (nodeId) {
const selectedNode = this.getSelectedNode(nodeId)
if (selectedNode) {
selectedNode.setIsActive(true)
}
}
}
private unSelectActiveNode() {
const activeNode = this.tree.treeModel.getActiveNode();
activeNode.setIsActive(false);
activeNode.blur();
}
private getSelectedNode(nodeId: string) {
return this.tree.treeModel.getNodeById(nodeId);
}
}

View File

@ -1,4 +1,3 @@
.ng-invalid:not(form) {
border-color: $invalid;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
@ -8,4 +7,8 @@
.required-text {
padding-left: 15px;
color: $invalid;
}
}
textarea {
resize: none;
}

View File

@ -230,7 +230,7 @@ svg-icon.fa-fw > svg {
}
.panel-heading{
min-height: 60px;
padding: 8px 15px 4px 15px;
}
.panel {
border-radius: 0px;