TSK-492 Access validation, type ahead component created.

This commit is contained in:
Martin Rojas Miguel Angel 2018-05-21 14:39:09 +02:00 committed by Holger Hagen
parent e2a781521e
commit 1c93702063
26 changed files with 527 additions and 94 deletions

56
web/package-lock.json generated
View File

@ -38,10 +38,19 @@
"requires": {
"ajv": "5.5.2",
"chokidar": "1.7.0",
"rxjs": "5.5.6",
"rxjs": "5.5.10",
"source-map": "0.5.7"
},
"dependencies": {
"rxjs": {
"version": "5.5.10",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz",
"integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==",
"dev": true,
"requires": {
"symbol-observable": "1.0.1"
}
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@ -57,7 +66,18 @@
"dev": true,
"requires": {
"@ngtools/json-schema": "1.2.0",
"rxjs": "5.5.6"
"rxjs": "5.5.10"
},
"dependencies": {
"rxjs": {
"version": "5.5.10",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz",
"integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==",
"dev": true,
"requires": {
"symbol-observable": "1.0.1"
}
}
}
},
"@angular/animations": {
@ -117,7 +137,7 @@
"postcss-url": "7.3.1",
"raw-loader": "0.5.1",
"resolve": "1.5.0",
"rxjs": "5.5.6",
"rxjs": "5.5.10",
"sass-loader": "6.0.7",
"semver": "5.5.0",
"silent-error": "1.1.0",
@ -176,6 +196,15 @@
"osenv": "0.1.4"
}
},
"rxjs": {
"version": "5.5.10",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz",
"integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==",
"dev": true,
"requires": {
"symbol-observable": "1.0.1"
}
},
"supports-color": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
@ -345,9 +374,20 @@
"integrity": "sha512-7aVP4994Hu8vRdTTohXkfGWEwLhrdNP3EZnWyBootm5zshWqlQojUGweZe5zwewsKcixeVOiy2YtW+aI4aGSLA==",
"dev": true,
"requires": {
"rxjs": "5.5.6",
"rxjs": "5.5.10",
"semver": "5.5.0",
"semver-intersect": "1.3.1"
},
"dependencies": {
"rxjs": {
"version": "5.5.10",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz",
"integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==",
"dev": true,
"requires": {
"symbol-observable": "1.0.1"
}
}
}
},
"@types/jasmine": {
@ -2096,7 +2136,7 @@
},
"compression": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.7.2.tgz",
"resolved": "http://registry.npmjs.org/compression/-/compression-1.7.2.tgz",
"integrity": "sha1-qv+81qr4VLROuygDU9WtFlH1mmk=",
"dev": true,
"requires": {
@ -9482,9 +9522,9 @@
}
},
"rxjs": {
"version": "5.5.6",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.6.tgz",
"integrity": "sha512-v4Q5HDC0FHAQ7zcBX7T2IL6O5ltl1a2GX4ENjPXg6SjDY69Cmx9v4113C99a4wGF16ClPv5Z8mghuYorVkg/kg==",
"version": "5.5.9",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.9.tgz",
"integrity": "sha512-DHG9AHmCmgaFWgjBcXp6NxFDmh3MvIA62GqTWmLnTzr/3oZ6h5hLD8NA+9j+GF0jEwklNIpI4KuuyLG8UWMEvQ==",
"requires": {
"symbol-observable": "1.0.1"
}

View File

@ -23,21 +23,21 @@
"@angular/platform-browser": "5.2.10",
"@angular/platform-browser-dynamic": "5.2.10",
"@angular/router": "5.2.10",
"file-saver": "1.3.3",
"angular-svg-icon": "5.0.0",
"angular-tree-component": "7.1.0",
"bootstrap": "3.3.7",
"bootstrap-sass": "3.3.7",
"chart.js": "2.7.1",
"core-js": "2.5.3",
"file-saver": "1.3.3",
"jquery": "3.3.1",
"magic-string": "0.22.4",
"ng2-auto-complete": "0.12.0",
"ng2-charts": "1.6.0",
"ngx-bootstrap": "2.0.1",
"node-sass": "4.7.2",
"rxjs": "5.5.6",
"zone.js": "0.8.20",
"chart.js": "2.7.1",
"ng2-charts": "1.6.0",
"ng2-auto-complete": "0.12.0"
"rxjs": "5.5.9",
"zone.js": "0.8.20"
},
"devDependencies": {
"@angular/cli": "1.7.3",

View File

@ -7,6 +7,7 @@ import { AngularSvgIconModule } from 'angular-svg-icon';
import { AlertModule } from 'ngx-bootstrap';
import { SharedModule } from 'app/shared/shared.module';
import { AdministrationRoutingModule } from './administration-routing.module';
import { TypeaheadModule } from 'ngx-bootstrap';
/**
* Components
@ -39,7 +40,6 @@ import { ClassificationsService } from './services/classifications/classificatio
import { ClassificationTypesService } from './services/classification-types/classification-types.service';
import { ClassificationCategoriesService } from './services/classification-categories-service/classification-categories.service';
const MODULES = [
CommonModule,
FormsModule,
@ -47,7 +47,8 @@ const MODULES = [
AngularSvgIconModule,
AlertModule,
SharedModule,
AdministrationRoutingModule
AdministrationRoutingModule,
TypeaheadModule
];
const DECLARATIONS = [

View File

@ -41,13 +41,13 @@
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button>
</td>
<td class="input-group text-align text-width ">
<div [ngClass]="{
'has-warning': (accessItemsClone[index].accessId !== accessItem.accessId),
'has-error': !accessItem.accessId }">
<input type="text" required #accessItemName="ngModel" class="form-control" name="accessItem.accessId-{{index}}" [(ngModel)]="accessItem.accessId"
placeholder="{{accessItemName.invalid? 'Access id is required': ''}}">
</div>
<td class="input-group text-align text-width taskana-type-ahead" [ngClass]="{
'has-warning': (accessItemsClone[index].accessId !== accessItem.accessId),
'has-error': !accessItem.accessId } ">
<taskana-type-ahead required #accessItemName="ngModel" [(ngModel)]="accessItem.accessId" name="accessItem.accessId-{{index}}"
[(ngModel)]="accessItem.accessIdr" placeHolderMessage="Access id is required"></taskana-type-ahead>
</td>
<td [ngClass]="{'has-changes': (accessItemsClone[index].permRead !== accessItem.permRead)}">
<input type="checkbox" disabled="disabled" name="accessItem.permRead-{{index}}" [(ngModel)]="accessItem.permRead">

View File

@ -5,9 +5,14 @@ td > input[type="checkbox"] {
overflow-x: auto;
}
.text-width{
width: 100%;
min-width: 180px;
}
.required-header {
width: 200px;
}
.required-header:after {
content:" *";
color: red;
}
@ -17,3 +22,11 @@ td {
border-bottom: 1px solid #f0ad4e;;
}
}
.table > thead > tr > th {
max-width: 150px;
border-bottom: none;
}
.has-warning.taskana-type-ahead {
border-bottom: 1px solid #f0ad4e;
}

View File

@ -1,6 +1,6 @@
import { SimpleChange } from '@angular/core';
import { SimpleChange, Component, forwardRef, Input } from '@angular/core';
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { HttpModule, JsonpModule } from '@angular/http';
import { AngularSvgIconModule } from 'angular-svg-icon';
@ -26,6 +26,37 @@ import { DomainService } from 'app/services/domain/domain.service';
import { DomainServiceMock } from 'app/services/domain/domain.service.mock';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
@Component({
selector: 'taskana-type-ahead',
template: 'dummydetail',
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => TaskanaTypeAheadComponent),
}
]
})
export class TaskanaTypeAheadComponent implements ControlValueAccessor {
@Input()
placeHolderMessage;
writeValue(obj: any): void {
}
registerOnChange(fn: any): void {
}
registerOnTouched(fn: any): void {
}
setDisabledState?(isDisabled: boolean): void {
}
}
describe('AccessItemsComponent', () => {
let component: AccessItemsComponent;
let fixture: ComponentFixture<AccessItemsComponent>;
@ -33,7 +64,7 @@ describe('AccessItemsComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [SpinnerComponent, AccessItemsComponent, GeneralMessageModalComponent],
declarations: [SpinnerComponent, AccessItemsComponent, GeneralMessageModalComponent, TaskanaTypeAheadComponent],
imports: [FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule, ReactiveFormsModule],
providers: [WorkbasketService, AlertService, ErrorModalService, SavingWorkbasketService, RequestInProgressService,
{

View File

@ -15,9 +15,10 @@ import { AlertService } from 'app/services/alert/alert.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { TitlesService } from 'app/services/titles/titles.service';
import { CustomFieldsService } from '../../../../services/custom-fields/custom-fields.service';
import { Observable } from 'rxjs/Observable';
import { TypeaheadMatch } from 'ngx-bootstrap/typeahead';
declare var $: any;
@Component({
selector: 'taskana-workbasket-access-items',
templateUrl: './access-items.component.html',
@ -25,7 +26,6 @@ declare var $: any;
})
export class AccessItemsComponent implements OnChanges, OnDestroy {
@Input()
workbasket: Workbasket;
@Input()
@ -65,7 +65,11 @@ export class AccessItemsComponent implements OnChanges, OnDestroy {
private errorModalService: ErrorModalService,
private savingWorkbaskets: SavingWorkbasketService,
private requestInProgressService: RequestInProgressService,
private customFieldService: CustomFieldsService) { }
private customFieldService: CustomFieldsService) {
}
ngOnChanges(changes: SimpleChanges): void {
if (!this.initialized && changes.active && changes.active.currentValue === 'accessItems') {
@ -75,6 +79,7 @@ export class AccessItemsComponent implements OnChanges, OnDestroy {
this.setBadge();
}
}
private init() {
this.initialized = true;
if (!this.workbasket._links.accessItems) {
@ -133,6 +138,7 @@ export class AccessItemsComponent implements OnChanges, OnDestroy {
})
return false;
}
private setBadge() {
if (this.action === ACTION.COPY) {
this.badgeMessage = `Copying workbasket: ${this.workbasket.key}`;

View File

@ -6,7 +6,7 @@
<button type="button" (click)="onClear()" class="btn btn-default">Undo</button>
<button type="button" (click)="removeDistributionTargets()" data-toggle="tooltip" title="remove distribuition target" class="btn btn-default remove">
<span class="glyphicon glyphicon-remove-circle" aria-hidden="true"></span>
</button>
</button>
<button type="button" (click)="copyWorkbasket()" data-toggle="tooltip" title="copy" class="btn btn-default">
<span class="glyphicon glyphicon-copy green-blue" aria-hidden="true"></span>
</button>
@ -39,9 +39,8 @@
</div>
<div class="form-group required">
<label for="wb-owner" class="control-label">Owner</label>
<input type="text" required #owner="ngModel" class="form-control" id="wb-owner" placeholder="Owner" [(ngModel)]="workbasket.owner"
name="workbasket.owner">
<div *ngIf="!owner.valid" class="required-text">
<taskana-type-ahead required #owner="ngModel" name="owner" [(ngModel)]="workbasket.owner" placeHolderMessage="Owner is required"></taskana-type-ahead>
<div *ngIf="!owner?.valid" class="required-text">
* Owner is required
</div>
</div>
@ -98,15 +97,18 @@
</div>
<div *ngIf="custom2Field.visible" class="form-group">
<label for="wb-custom-2" class="control-label">{{custom2Field.field}}</label>
<input type="text" class="form-control" id="wb-custom-2" [placeholder]="custom2Field.field" [(ngModel)]="workbasket.custom2" name="workbasket.custom2">
<input type="text" class="form-control" id="wb-custom-2" [placeholder]="custom2Field.field" [(ngModel)]="workbasket.custom2"
name="workbasket.custom2">
</div>
<div *ngIf="custom3Field.visible" class="form-group">
<label for="wb-custom-3" class="control-label">{{custom3Field.field}}</label>
<input type="text" class="form-control" id="wb-custom-3" [placeholder]="custom3Field.field" [(ngModel)]="workbasket.custom3" name="workbasket.custom3">
<input type="text" class="form-control" id="wb-custom-3" [placeholder]="custom3Field.field" [(ngModel)]="workbasket.custom3"
name="workbasket.custom3">
</div>
<div *ngIf="custom4Field.visible" class="form-group">
<label for="wb-custom-4" class="control-label">{{custom4Field.field}}</label>
<input type="text" class="form-control" id="wb-custom-4" [placeholder]="custom4Field.field" [(ngModel)]="workbasket.custom4" name="workbasket.custom4">
<input type="text" class="form-control" id="wb-custom-4" [placeholder]="custom4Field.field" [(ngModel)]="workbasket.custom4"
name="workbasket.custom4">
</div>
</div>
</form>

View File

@ -1,13 +1,13 @@
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { WorkbasketService } from 'app/administration/services/workbasket/workbasket.service';
import { WorkbasketInformationComponent } from './workbasket-information.component';
import { FormsModule } from '@angular/forms';
import { FormsModule, ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http';
import { RouterTestingModule } from '@angular/router/testing';
import { Observable } from 'rxjs/Observable';
import { Component } from '@angular/core';
import { Component, Input, forwardRef } from '@angular/core';
import { Routes } from '@angular/router';
import { AppModule } from 'app/app.module'
@ -37,6 +37,36 @@ import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.se
export class DummyDetailComponent {
}
@Component({
selector: 'taskana-type-ahead',
template: 'dummydetail',
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => TaskanaTypeAheadComponent),
}
]
})
export class TaskanaTypeAheadComponent implements ControlValueAccessor {
@Input()
placeHolderMessage;
writeValue(obj: any): void {
}
registerOnChange(fn: any): void {
}
registerOnTouched(fn: any): void {
}
setDisabledState?(isDisabled: boolean): void {
}
}
const routes: Routes = [
{ path: ':id', component: DummyDetailComponent, outlet: 'detail' },
{ path: 'someNewId', component: DummyDetailComponent }
@ -50,7 +80,8 @@ describe('InformationComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [WorkbasketInformationComponent, IconTypeComponent, MapValuesPipe,
RemoveNoneTypePipe, SpinnerComponent, GeneralMessageModalComponent, DummyDetailComponent],
RemoveNoneTypePipe, SpinnerComponent, GeneralMessageModalComponent, DummyDetailComponent,
TaskanaTypeAheadComponent],
imports: [FormsModule,
AngularSvgIconModule,
HttpClientModule,

View File

@ -42,7 +42,6 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
custom3Field = this.customFieldsService.getCustomField('Custom 3', 'workbaskets.information.custom3');
custom4Field = this.customFieldsService.getCustomField('Custom 4', 'workbaskets.information.custom4');
private workbasketSubscription: Subscription;
private routeSubscription: Subscription;
@ -132,7 +131,6 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
});
}
private beforeRequest() {
this.requestInProgressService.setRequestInProgress(true);
}

View File

@ -1,8 +1,8 @@
import { Component, Input } from '@angular/core';
import { Component, Input, forwardRef } from '@angular/core';
import { async, ComponentFixture, TestBed, } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { Router, Routes } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { FormsModule, NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http';
@ -58,6 +58,37 @@ export class FilterComponent {
export class DummyDetailComponent {
}
@Component({
selector: 'taskana-type-ahead',
template: 'dummydetail',
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => TaskanaTypeAheadComponent),
}
]
})
export class TaskanaTypeAheadComponent implements ControlValueAccessor {
@Input()
placeHolderMessage;
writeValue(obj: any): void {
}
registerOnChange(fn: any): void {
}
registerOnTouched(fn: any): void {
}
setDisabledState?(isDisabled: boolean): void {
}
}
describe('WorkbasketDetailsComponent', () => {
let component: WorkbasketDetailsComponent;
let fixture: ComponentFixture<WorkbasketDetailsComponent>;
@ -77,7 +108,8 @@ describe('WorkbasketDetailsComponent', () => {
imports: [RouterTestingModule.withRoutes(routes), FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule],
declarations: [WorkbasketDetailsComponent, NoAccessComponent, WorkbasketInformationComponent, SpinnerComponent,
IconTypeComponent, MapValuesPipe, RemoveNoneTypePipe, AlertComponent, GeneralMessageModalComponent, AccessItemsComponent,
DistributionTargetsComponent, FilterComponent, DualListComponent, DummyDetailComponent, SelectWorkBasketPipe],
DistributionTargetsComponent, FilterComponent, DualListComponent, DummyDetailComponent,
TaskanaTypeAheadComponent, SelectWorkBasketPipe],
providers: [WorkbasketService, MasterAndDetailService, ErrorModalService, RequestInProgressService,
AlertService, SavingWorkbasketService, {
provide: DomainService,

View File

@ -50,11 +50,11 @@ import { APP_BASE_HREF } from '@angular/common';
const MODULES = [
TabsModule.forRoot(),
AlertModule.forRoot(),
BrowserModule,
FormsModule,
TabsModule.forRoot(),
AppRoutingModule,
AlertModule.forRoot(),
AngularSvgIconModule,
HttpClientModule,
BrowserAnimationsModule,
@ -66,7 +66,7 @@ const MODULES = [
const DECLARATIONS = [
AppComponent,
NavBarComponent,
UserInformationComponent
UserInformationComponent,
];
export function startupServiceFactory(startupService: StartupService): () => Promise<any> {

View File

@ -1,37 +0,0 @@
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
import { CanActivate, Router } from '@angular/router';
import { Injectable } from '@angular/core';
import { DomainService } from 'app/services/domain/domain.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { ErrorModel } from 'app/models/modal-error';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
@Injectable()
export class AdminGuard implements CanActivate {
constructor(private taskanaEngineService: TaskanaEngineService, public router: Router) { }
canActivate() {
return this.taskanaEngineService.getUserInformation().map(userInfo => {
if (userInfo.roles.length === 0) {
return this.navigateToWorplace();
}
const adminRole = userInfo.roles.find(role => {
if (role === 'ADMIN') {
return true;
}
});
if (adminRole) {
return true;
}
return this.navigateToWorplace();
}).catch(() => {
return Observable.of(this.navigateToWorplace())
});
}
navigateToWorplace(): boolean {
this.router.navigate(['workplace']);
return false
}
}

View File

@ -6,10 +6,12 @@ import { DomainService } from 'app/services/domain/domain.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { ErrorModel } from 'app/models/modal-error';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
import { WindowRefService } from 'app/services/window/window.service';
@Injectable()
export class BusinessAdminGuard implements CanActivate {
constructor(private taskanaEngineService: TaskanaEngineService, public router: Router) { }
constructor(private taskanaEngineService: TaskanaEngineService, public router: Router,
private window: WindowRefService) { }
canActivate() {
return this.taskanaEngineService.getUserInformation().map(userInfo => {
@ -17,7 +19,7 @@ export class BusinessAdminGuard implements CanActivate {
return this.navigateToWorplace();
}
const adminRole = userInfo.roles.find(role => {
if (role === 'BUSINESS_ADMIN' || role === 'ADMIN' ) {
if (role === 'BUSINESS_ADMIN' || role === 'ADMIN') {
return true;
}
});
@ -32,7 +34,9 @@ export class BusinessAdminGuard implements CanActivate {
}
navigateToWorplace(): boolean {
this.router.navigate(['workplace']);
if (this.window.nativeWindow.location.href.indexOf('administration') !== -1) {
this.router.navigate(['workplace']);
}
return false
}
}

View File

@ -6,10 +6,12 @@ import { DomainService } from 'app/services/domain/domain.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { ErrorModel } from 'app/models/modal-error';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
import { WindowRefService } from 'app/services/window/window.service';
@Injectable()
export class MonitorGuard implements CanActivate {
constructor(private taskanaEngineService: TaskanaEngineService, public router: Router) { }
constructor(private taskanaEngineService: TaskanaEngineService, public router: Router,
private window: WindowRefService) { }
canActivate() {
return this.taskanaEngineService.getUserInformation().map(userInfo => {
@ -17,7 +19,7 @@ export class MonitorGuard implements CanActivate {
return this.navigateToWorplace();
}
const adminRole = userInfo.roles.find(role => {
if (role === 'MONITOR' || role === 'ADMIN' ) {
if (role === 'MONITOR' || role === 'ADMIN') {
return true;
}
});
@ -32,7 +34,9 @@ export class MonitorGuard implements CanActivate {
}
navigateToWorplace(): boolean {
this.router.navigate(['workplace']);
if (this.window.nativeWindow.location.href.indexOf('monitor') !== -1) {
this.router.navigate(['workplace']);
}
return false
}
}

View File

@ -0,0 +1,8 @@
import { LinksClassification } from 'app/models/links-classfication';
export class AccessIdDefinition {
constructor(
public accessId: string = undefined,
public name: string = undefined) {
}
}

View File

@ -0,0 +1,18 @@
import { TestBed, inject } from '@angular/core/testing';
import { AccessIdsService } from './access-ids.service';
import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http';
describe('ValidateAccessItemsService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule, HttpModule],
providers: [AccessIdsService]
});
});
it('should be created', inject([AccessIdsService], (service: AccessIdsService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,24 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from 'environments/environment';
import { AccessIdDefinition } from 'app/models/access-id';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class AccessIdsService {
private url = environment.taskanaRestUrl + '/v1/validate-access-id';
constructor(
private httpClient: HttpClient) { }
getAccessItemsInformation(token): Observable<Array<AccessIdDefinition>> {
if (!token) {
return Observable.of([]);
}
return this.httpClient.get<Array<AccessIdDefinition>>(`${this.url}?search=${token}`);
};
}

View File

@ -6,12 +6,14 @@ import { AngularSvgIconModule } from 'angular-svg-icon';
import { AlertModule } from 'ngx-bootstrap';
import { RouterModule } from '@angular/router';
import { TreeModule } from 'angular-tree-component';
import { TypeaheadModule } from 'ngx-bootstrap';
import { GeneralMessageModalComponent } from 'app/shared/general-message-modal/general-message-modal.component';
import { SpinnerComponent } from 'app/shared/spinner/spinner.component';
import { AlertComponent } from 'app/shared/alert/alert.component';
import { MasterAndDetailComponent } from 'app/shared/master-and-detail/master-and-detail.component';
import { TaskanaTreeComponent } from 'app/shared/tree/tree.component';
import { TypeAheadComponent } from 'app/shared/type-ahead/type-ahead.component';
/**
* Pipes
@ -22,17 +24,24 @@ import { SelectWorkBasketPipe } from './pipes/selectedWorkbasket/seleted-workbas
import { SpreadNumberPipe } from './pipes/spreadNumber/spread-number';
import { OrderBy } from './pipes/orderBy/orderBy';
import { MapToIterable } from './pipes/mapToIterable/mapToIterable';
/**
* Services
*/
import { HttpClientInterceptor } from './services/httpClientInterceptor/http-client-interceptor.service';
import { AccessIdsService } from './services/access-ids/access-ids.service';
const MODULES = [
CommonModule,
FormsModule,
AlertModule.forRoot(),
TypeaheadModule.forRoot(),
AngularSvgIconModule,
HttpClientModule,
RouterModule,
TreeModule
TreeModule,
];
const DECLARATIONS = [
@ -41,6 +50,7 @@ const DECLARATIONS = [
AlertComponent,
MasterAndDetailComponent,
TaskanaTreeComponent,
TypeAheadComponent,
MapValuesPipe,
RemoveNoneTypePipe,
SelectWorkBasketPipe,
@ -58,7 +68,8 @@ const DECLARATIONS = [
provide: HTTP_INTERCEPTORS,
useClass: HttpClientInterceptor,
multi: true
}
},
AccessIdsService
]
})
export class SharedModule {

View File

@ -133,4 +133,11 @@
.no-display{
display: none;
}
}
.type-ahead-spinner {
position: relative;
width: 20px;
height: 20px;
margin-right: 15px;
}

View File

@ -0,0 +1,38 @@
<div *ngIf="dataSource" class="custom-form-control">
<ng-template class="wrapper-text" #customItemTemplate let-model="item" let-index="indexTemplate" let-query="query">
<div (mousedown)="typeaheadOnSelect({'item':model})">
<div>
<span [innerHTML]="join(model.accessId, query)">
</span>
</div>
<div>
<span [innerHTML]="join(model.name, query)">
</span>
</div>
</div>
</ng-template>
<div [ngClass]="{'hidden': !dataSource.selected || typing}" class="wrapper-text" (click)="setTyping(true)">
<span>
<label>
{{dataSource.selected?.accessId}}
</label>
</span>
<div>{{dataSource.selected?.name}}</div>
</div>
<div [ngClass]="{'hidden': dataSource.selected && !typing}">
<span class="field-label-wrapper">
<label>
{{dataSource.selected?.name}}
</label>
</span>
<div *ngIf="typeaheadLoading" class="loading">
<taskana-spinner [isRunning]="typeaheadLoading" positionClass="type-ahead-spinner"></taskana-spinner>
</div>
<input #inputTypeAhead class=" form-control input-text" (blur)="typeaheadOnSelect({'item':dataSource.selected})" name="accessItem-{{index}}"
required #accessItemName="ngModel" [(ngModel)]="value" [typeahead]="dataSource" typeaheadOptionField="name" [typeaheadItemTemplate]="customItemTemplate"
(typeaheadOnSelect)="typeaheadOnSelect($event, index)" [typeaheadScrollable]="true" [typeaheadOptionsInScrollableView]="typeaheadOptionsInScrollableView"
[typeaheadMinLength]="typeaheadMinLength" [typeaheadWaitMs]="typeaheadWaitMs" (typeaheadLoading)="changeTypeaheadLoading($event)"
placeholder="{{accessItemName.invalid? placeHolderMessage: ''}}">
</div>
</div>

View File

@ -0,0 +1,47 @@
$blue: #2e9eca;
$grey: #ddd;
.wrapper-text {
height: 47px;
& label {
width: 100%;
margin-bottom: 0px;
}
& div {
width: 100%;
border-bottom: 1px solid $grey;
margin-top:6px;
}
}
.input-text {
background: none;
box-shadow: none;
border: none;
padding: 0px;
border-radius: 0px;
//margin-top: 18px;
height: 22px;
border-bottom: 1px solid $grey;
&:focus{
border-bottom: 1px solid $blue;
}
}
.field-label-wrapper{
position: relative;
//left: 8px;
box-sizing: content-box;
overflow: hidden;
pointer-events: none;
}
.form-control:focus {
border-color: none;
box-shadow: none;
}
.loading {
position: absolute;
right: 0;
}

View File

@ -0,0 +1,130 @@
import { Component, OnInit, Input, EventEmitter, Output, ViewChild, ElementRef, forwardRef } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { TypeaheadMatch } from 'ngx-bootstrap/typeahead';
import { AccessIdsService } from 'app/shared/services/access-ids/access-ids.service';
import { AccessItemsComponent } from 'app/administration/workbasket/details/access-items/access-items.component';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
const noop = () => {
};
@Component({
selector: 'taskana-type-ahead',
templateUrl: './type-ahead.component.html',
styleUrls: ['./type-ahead.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TypeAheadComponent),
multi: true
}
]
})
export class TypeAheadComponent implements OnInit, ControlValueAccessor {
dataSource: any;
typing = false;
@Input()
placeHolderMessage;
@ViewChild('inputTypeAhead')
private inputTypeAhead;
typeaheadLoading = false;
typeaheadMinLength = 2;
typeaheadWaitMs = 500;
typeaheadOptionsInScrollableView = 6;
// The internal data model
private innerValue: any = '';
// Placeholders for the callbacks which are later provided
// by the Control Value Accessor
private onTouchedCallback: () => void = noop;
private onChangeCallback: (_: any) => void = noop;
// get accessor
get value(): any {
return this.innerValue;
};
// set accessor including call the onchange callback
set value(v: any) {
if (v !== this.innerValue) {
this.innerValue = v;
this.onChangeCallback(v);
}
}
// From ControlValueAccessor interface
writeValue(value: any) {
if (value !== this.innerValue) {
this.innerValue = value;
this.initializeDataSource();
}
}
// From ControlValueAccessor interface
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
// From ControlValueAccessor interface
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
constructor(private accessIdsService: AccessIdsService) {
}
ngOnInit() {
}
initializeDataSource() {
this.dataSource = Observable.create((observer: any) => {
observer.next(this.value);
}).mergeMap((token: string) => this.getUsersAsObservable(token));
this.accessIdsService.getAccessItemsInformation(this.value).subscribe(items => {
if (items.length > 0) {
this.dataSource.selected = items.find(item => item.accessId === this.value);
}
});
}
getUsersAsObservable(token: string): Observable<any> {
return this.accessIdsService.getAccessItemsInformation(token);
}
typeaheadOnSelect(event: TypeaheadMatch): void {
if (event && event.item) {
this.value = event.item.accessId;
this.dataSource.selected = event.item;
}
this.setTyping(false);
}
setTyping(value) {
if (value) {
setTimeout(() => {
this.inputTypeAhead.nativeElement.focus();
}, 1)
}
this.typing = value;
}
changeTypeaheadLoading(e: boolean): void {
this.typeaheadLoading = e;
}
join(text: string, str: string) {
return text.toLocaleLowerCase().split(str).join(`<strong>${str}</strong>`);
}
}

View File

@ -3,4 +3,5 @@
@import '../../node_modules/angular-tree-component/dist/angular-tree-component.css';
@import 'site';
@import 'forms';
@import 'tree';
@import 'tree';
@import 'type-ahead';

View File

@ -0,0 +1,12 @@
typeahead-container >ul.dropdown-menu{
width: 215px;
& >li.active {
&>a {
background-color: $green;
}
&>a:hover {
background-color: $green;
}
}
}

12
web/taskana-web.iml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_5">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>