TSK-611 Add validation form errors on save

- Add custom form validation  on save with highlighting animation and snack bar warning
- Added validation for classifications, workbaskets information and access items.
- Enable save button always
- Refactored animations
- Test configuration cleanup
This commit is contained in:
Martin Rojas Miguel Angel 2018-07-09 14:44:12 +02:00 committed by Holger Hagen
parent cc62283437
commit 42197bee6c
31 changed files with 322 additions and 260 deletions

View File

@ -11,7 +11,7 @@
<div *ngIf="classification" id="classification" class="panel panel-default classification"> <div *ngIf="classification" id="classification" class="panel panel-default classification">
<div class="panel-heading"> <div class="panel-heading">
<div class="pull-right"> <div class="pull-right">
<button type="button" [disabled]="!ClassificationForm.form.valid" (click)="onSave()" class="btn btn-default btn-primary" <button type="button" (click)="onSubmit()" class="btn btn-default btn-primary"
data-toggle="tooltip" title="Save"> data-toggle="tooltip" title="Save">
<span class="glyphicon glyphicon-floppy-save" aria-hidden="true"></span> <span class="glyphicon glyphicon-floppy-save" aria-hidden="true"></span>
</button> </button>
@ -33,7 +33,7 @@
<label for="classification-key" class="control-label">Key</label> <label for="classification-key" class="control-label">Key</label>
<input type="text" required #key="ngModel" [disabled]="action!== 'CREATE'? true : false" class="form-control" id="classification-key" <input type="text" required #key="ngModel" [disabled]="action!== 'CREATE'? true : false" class="form-control" id="classification-key"
placeholder="Key" [(ngModel)]="classification.key" name="classification.key"> placeholder="Key" [(ngModel)]="classification.key" name="classification.key">
<div *ngIf="!key.valid && action === 'CREATE'" class="required-text"> <div *ngIf="!key.valid && action === 'CREATE'" class="required-text" [@validation]="this.toogleValidationMap.get('classification.key')">
* Key is required * Key is required
</div> </div>
</div> </div>
@ -41,7 +41,7 @@
<label for="classification-name" class="control-label">Name</label> <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" <input type="text" required #name="ngModel" class="form-control" id="classification-name" placeholder="Name" [(ngModel)]="classification.name"
name="classification.name"> name="classification.name">
<div *ngIf="!name.valid" class="required-text"> <div *ngIf="!name.valid" class="required-text" [@validation]="this.toogleValidationMap.get('classification.name')">
* Name is required * Name is required
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
@ -7,6 +7,7 @@ import { ACTION } from 'app/models/action';
import { ErrorModel } from 'app/models/modal-error'; import { ErrorModel } from 'app/models/modal-error';
import { AlertModel, AlertType } from 'app/models/alert'; import { AlertModel, AlertType } from 'app/models/alert';
import { highlight } from 'app/shared/animations/validation.animation';
import { TaskanaDate } from 'app/shared/util/taskana.date'; import { TaskanaDate } from 'app/shared/util/taskana.date';
import { ClassificationsService } from 'app/administration/services/classifications/classifications.service'; import { ClassificationsService } from 'app/administration/services/classifications/classifications.service';
@ -24,10 +25,13 @@ import { ClassificationCategoriesService } from 'app/administration/services/cla
import { DomainService } from 'app/services/domain/domain.service'; import { DomainService } from 'app/services/domain/domain.service';
import { CustomFieldsService } from '../../../services/custom-fields/custom-fields.service'; import { CustomFieldsService } from '../../../services/custom-fields/custom-fields.service';
import { Pair } from 'app/models/pair'; import { Pair } from 'app/models/pair';
import { NgForm } from '@angular/forms';
import { FormsValidatorService } from 'app/shared/services/forms/forms-validator.service';
@Component({ @Component({
selector: 'taskana-classification-details', selector: 'taskana-classification-details',
templateUrl: './classification-details.component.html', templateUrl: './classification-details.component.html',
animations: [highlight],
styleUrls: ['./classification-details.component.scss'] styleUrls: ['./classification-details.component.scss']
}) })
export class ClassificationDetailsComponent implements OnInit, OnDestroy { export class ClassificationDetailsComponent implements OnInit, OnDestroy {
@ -62,6 +66,9 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
private categoriesSubscription: Subscription; private categoriesSubscription: Subscription;
private domainSubscription: Subscription; private domainSubscription: Subscription;
@ViewChild('ClassificationForm') classificationForm: NgForm;
toogleValidationMap = new Map<string, boolean>();
constructor(private classificationsService: ClassificationsService, constructor(private classificationsService: ClassificationsService,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
@ -74,7 +81,8 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
private categoryService: ClassificationCategoriesService, private categoryService: ClassificationCategoriesService,
private domainService: DomainService, private domainService: DomainService,
private customFieldsService: CustomFieldsService, private customFieldsService: CustomFieldsService,
private removeConfirmationService: RemoveConfirmationService) { } private removeConfirmationService: RemoveConfirmationService,
private formsValidatorService: FormsValidatorService) { }
ngOnInit() { ngOnInit() {
this.classificationTypeService.getClassificationTypes().subscribe((classificationTypes: Array<string>) => { this.classificationTypeService.getClassificationTypes().subscribe((classificationTypes: Array<string>) => {
@ -133,7 +141,13 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
`You are going to delete classification: ${this.classification.key}. Can you confirm this action?`); `You are going to delete classification: ${this.classification.key}. Can you confirm this action?`);
} }
onSave() { onSubmit() {
if (this.formsValidatorService.validate(this.classificationForm, this.toogleValidationMap)) {
this.onSave();
}
}
private onSave() {
this.requestInProgressService.setRequestInProgress(true); this.requestInProgressService.setRequestInProgress(true);
if (this.action === ACTION.CREATE) { if (this.action === ACTION.CREATE) {
this.classificationSavingSubscription = this.classificationsService.postClassification(this.classification) this.classificationSavingSubscription = this.classificationsService.postClassification(this.classification)

View File

@ -1,8 +1,8 @@
<div *ngIf="workbasket" id="wb-information" class="panel panel-default"> <div *ngIf="workbasket" id="wb-information" class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<div class="pull-right"> <div class="pull-right">
<button type="button" (click)="onSave()" [disabled]="!AccessItemsForm.valid || action === 'COPY'" class="btn btn-default btn-primary" <button type="button" (click)="onSubmit()" [disabled]="action === 'COPY'" class="btn btn-default btn-primary" data-toggle="tooltip"
data-toggle="tooltip" title="Save"> title="Save">
<span class="glyphicon glyphicon-floppy-save" aria-hidden="true"></span> <span class="glyphicon glyphicon-floppy-save" aria-hidden="true"></span>
</button> </button>
<button type="button" (click)="clear()" class="btn btn-default" data-toggle="tooltip" title="Undo Changes"> <button type="button" (click)="clear()" class="btn btn-default" data-toggle="tooltip" title="Undo Changes">
@ -49,14 +49,16 @@
</td> </td>
<td *ngIf="accessIdField.lookupField else accessIdInput" class="input-group text-align text-width taskana-type-ahead" [ngClass]="{ <td *ngIf="accessIdField.lookupField else accessIdInput" class="input-group text-align text-width taskana-type-ahead" [ngClass]="{
'has-warning': (accessItemsClone[index].accessId !== accessItem.value.accessId), 'has-warning': (accessItemsClone[index].accessId !== accessItem.value.accessId),
'has-error': !accessItem.value.accessId } "> 'has-error': !accessItem.value.accessId }">
<taskana-type-ahead [(ngModel)]="accessItem.value.accessId" formControlName="accessId" placeHolderMessage="Access id is required"></taskana-type-ahead> <taskana-type-ahead [(ngModel)]="accessItem.value.accessId" formControlName="accessId" placeHolderMessage="* Access id is required"
[validationValue]="toogleValidationAccessIdMap.get(index)"></taskana-type-ahead>
</td> </td>
<ng-template #accessIdInput> <ng-template #accessIdInput>
<td class="input-group text-align text-width "> <td class="input-group text-align text-width">
<div [ngClass]="{ 'has-warning': (accessItemsClone[index].accessId !== accessItem.value.accessId), <div [ngClass]="{ 'has-warning': (accessItemsClone[index].accessId !==accessItem.value.accessId), 'has-error':
'has-error': !accessItem.value.accessId }"> !accessItem.value.accessId }">
<input type="text" class="form-control" formControlName="accessId" placeholder="{{accessItem.invalid? 'Access id is required': ''}}"> <input type="text" class="form-control" formControlName="accessId" placeholder="{{accessItem.invalid?
'* Access id is required': ''}}" [@validation]="toogleValidationAccessIdMap.get(index)">
</div> </div>
</td> </td>
</ng-template> </ng-template>
@ -64,71 +66,71 @@
<input id="checkbox-{{index}}-00" type="checkbox" (change)="checkAll(index, $event)"> <input id="checkbox-{{index}}-00" type="checkbox" (change)="checkAll(index, $event)">
<label for="checkbox-{{index}}-00"></label> <label for="checkbox-{{index}}-00"></label>
</td> </td>
<td [ngClass]="{'has-changes': (accessItemsClone[index].permRead !== accessItem.value.permRead)}"> <td [ngClass]="{ 'has-changes': (accessItemsClone[index].permRead !== accessItem.value.permRead)}">
<input id="checkbox-{{index}}-0" type="checkbox" formControlName="permRead" class="regular-checkbox"> <input id="checkbox-{{index}}-0" type="checkbox" formControlName="permRead" class="regular-checkbox">
<label for="checkbox-{{index}}-0"></label> <label for="checkbox-{{index}}-0"></label>
</td> </td>
<td [ngClass]="{'has-changes': (accessItemsClone[index].permOpen !== accessItem.value.permOpen)}"> <td [ngClass]="{ 'has-changes': (accessItemsClone[index].permOpen !== accessItem.value.permOpen)}">
<input id="checkbox-{{index}}-1" type="checkbox" formControlName="permOpen"> <input id="checkbox-{{index}}-1" type="checkbox" formControlName="permOpen">
<label for="checkbox-{{index}}-1"></label> <label for="checkbox-{{index}}-1"></label>
</td> </td>
<td [ngClass]="{'has-changes': (accessItemsClone[index].permAppend !== accessItem.value.permAppend)}"> <td [ngClass]="{ 'has-changes': (accessItemsClone[index].permAppend !== accessItem.value.permAppend)}">
<input id="checkbox-{{index}}-2" type="checkbox" formControlName="permAppend"> <input id="checkbox-{{index}}-2" type="checkbox" formControlName="permAppend">
<label for="checkbox-{{index}}-2"></label> <label for="checkbox-{{index}}-2"></label>
</td> </td>
<td [ngClass]="{'has-changes': (accessItemsClone[index].permTransfer !== accessItem.value.permTransfer)}"> <td [ngClass]="{ 'has-changes': (accessItemsClone[index].permTransfer !== accessItem.value.permTransfer)}">
<input id="checkbox-{{index}}-3" type="checkbox" formControlName="permTransfer"> <input id="checkbox-{{index}}-3" type="checkbox" formControlName="permTransfer">
<label for="checkbox-{{index}}-3"></label> <label for="checkbox-{{index}}-3"></label>
</td> </td>
<td [ngClass]="{'has-changes': (accessItemsClone[index].permDistribute !== accessItem.value.permDistribute)}"> <td [ngClass]="{ 'has-changes': (accessItemsClone[index].permDistribute !== accessItem.value.permDistribute)}">
<input id="checkbox-{{index}}-4" type="checkbox" formControlName="permDistribute"> <input id="checkbox-{{index}}-4" type="checkbox" formControlName="permDistribute">
<label for="checkbox-{{index}}-4"></label> <label for="checkbox-{{index}}-4"></label>
</td> </td>
<td *ngIf="custom1Field.visible" [ngClass]="{'has-changes': (accessItemsClone[index].permCustom1 !== accessItem.value.permCustom1)}"> <td *ngIf="custom1Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom1 !== accessItem.value.permCustom1)}">
<input id="checkbox-{{index}}-5" type="checkbox" formControlName="permCustom1"> <input id="checkbox-{{index}}-5" type="checkbox" formControlName="permCustom1">
<label for="checkbox-{{index}}-5"></label> <label for="checkbox-{{index}}-5"></label>
</td> </td>
<td *ngIf="custom2Field.visible" [ngClass]="{'has-changes': (accessItemsClone[index].permCustom2 !== accessItem.value.permCustom2)}"> <td *ngIf="custom2Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom2 !== accessItem.value.permCustom2)}">
<input id="checkbox-{{index}}-6" type="checkbox" formControlName="permCustom2"> <input id="checkbox-{{index}}-6" type="checkbox" formControlName="permCustom2">
<label for="checkbox-{{index}}-6"></label> <label for="checkbox-{{index}}-6"></label>
</td> </td>
<td *ngIf="custom3Field.visible" [ngClass]="{'has-changes': (accessItemsClone[index].permCustom3 !== accessItem.value.permCustom3)}"> <td *ngIf="custom3Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom3 !== accessItem.value.permCustom3)}">
<input id="checkbox-{{index}}-7" type="checkbox" formControlName="permCustom3"> <input id="checkbox-{{index}}-7" type="checkbox" formControlName="permCustom3">
<label for="checkbox-{{index}}-7"></label> <label for="checkbox-{{index}}-7"></label>
</td> </td>
<td *ngIf="custom4Field.visible" [ngClass]="{'has-changes': (accessItemsClone[index].permCustom4 !== accessItem.value.permCustom4)}"> <td *ngIf="custom4Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom4 !== accessItem.value.permCustom4)}">
<input id="checkbox-{{index}}-8" type="checkbox" formControlName="permCustom4"> <input id="checkbox-{{index}}-8" type="checkbox" formControlName="permCustom4">
<label for="checkbox-{{index}}-8"></label> <label for="checkbox-{{index}}-8"></label>
</td> </td>
<td *ngIf="custom5Field.visible" [ngClass]="{'has-changes': (accessItemsClone[index].permCustom5 !== accessItem.value.permCustom5)}"> <td *ngIf="custom5Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom5 !== accessItem.value.permCustom5)}">
<input id="checkbox-{{index}}-9" type="checkbox" formControlName="permCustom5"> <input id="checkbox-{{index}}-9" type="checkbox" formControlName="permCustom5">
<label for="checkbox-{{index}}-9"></label> <label for="checkbox-{{index}}-9"></label>
</td> </td>
<td *ngIf="custom6Field.visible" [ngClass]="{'has-changes': (accessItemsClone[index].permCustom6 !== accessItem.value.permCustom6)}"> <td *ngIf="custom6Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom6 !== accessItem.value.permCustom6)}">
<input id="checkbox-{{index}}-10" type="checkbox" formControlName="permCustom6"> <input id="checkbox-{{index}}-10" type="checkbox" formControlName="permCustom6">
<label for="checkbox-{{index}}-10"></label> <label for="checkbox-{{index}}-10"></label>
</td> </td>
<td *ngIf="custom7Field.visible" [ngClass]="{'has-changes': (accessItemsClone[index].permCustom7 !== accessItem.value.permCustom7)}"> <td *ngIf="custom7Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom7 !== accessItem.value.permCustom7)}">
<input id="checkbox-{{index}}-11" type="checkbox" formControlName="permCustom7"> <input id="checkbox-{{index}}-11" type="checkbox" formControlName="permCustom7">
<label for="checkbox-{{index}}-11"></label> <label for="checkbox-{{index}}-11"></label>
</td> </td>
<td *ngIf="custom8Field.visible" [ngClass]="{'has-changes': (accessItemsClone[index].permCustom8 !== accessItem.value.permCustom8)}"> <td *ngIf="custom8Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom8 !== accessItem.value.permCustom8)}">
<input id="checkbox-{{index}}-12" type="checkbox" formControlName="permCustom8"> <input id="checkbox-{{index}}-12" type="checkbox" formControlName="permCustom8">
<label for="checkbox-{{index}}-12"></label> <label for="checkbox-{{index}}-12"></label>
</td> </td>
<td *ngIf="custom9Field.visible" [ngClass]="{'has-changes': (accessItemsClone[index].permCustom9 !== accessItem.value.permCustom9)}"> <td *ngIf="custom9Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom9 !== accessItem.value.permCustom9)}">
<input id="checkbox-{{index}}-13" type="checkbox" formControlName="permCustom9"> <input id="checkbox-{{index}}-13" type="checkbox" formControlName="permCustom9">
<label for="checkbox-{{index}}-13"></label> <label for="checkbox-{{index}}-13"></label>
</td> </td>
<td *ngIf="custom10Field.visible" [ngClass]="{'has-changes': (accessItemsClone[index].permCustom10 !== accessItem.value.permCustom10)}"> <td *ngIf="custom10Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom10 !== accessItem.value.permCustom10)}">
<input id="checkbox-{{index}}-14" type="checkbox" formControlName="permCustom10"> <input id="checkbox-{{index}}-14" type="checkbox" formControlName="permCustom10">
<label for="checkbox-{{index}}-14"></label> <label for="checkbox-{{index}}-14"></label>
</td> </td>
<td *ngIf="custom11Field.visible" [ngClass]="{'has-changes': (accessItemsClone[index].permCustom11 !== accessItem.value.permCustom11)}"> <td *ngIf="custom11Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom11 !== accessItem.value.permCustom11)}">
<input id="checkbox-{{index}}-15" type="checkbox" formControlName="permCustom11"> <input id="checkbox-{{index}}-15" type="checkbox" formControlName="permCustom11">
<label for="checkbox-{{index}}-15"></label> <label for="checkbox-{{index}}-15"></label>
</td> </td>
<td *ngIf="custom12Field.visible" [ngClass]="{'has-changes': (accessItemsClone[index].permCustom12 !== accessItem.value.permCustom12)}"> <td *ngIf="custom12Field.visible" [ngClass]="{ 'has-changes': (accessItemsClone[index].permCustom12 !== accessItem.value.permCustom12)}">
<input id="checkbox-{{index}}-16" type="checkbox" formControlName="permCustom12"> <input id="checkbox-{{index}}-16" type="checkbox" formControlName="permCustom12">
<label for="checkbox-{{index}}-16"></label> <label for="checkbox-{{index}}-16"></label>
</td> </td>

View File

@ -5,6 +5,7 @@ import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http'; import { HttpModule } from '@angular/http';
import { AngularSvgIconModule } from 'angular-svg-icon'; import { AngularSvgIconModule } from 'angular-svg-icon';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { configureTests } from 'app/app.test.configuration';
import { Workbasket } from 'app/models/workbasket'; import { Workbasket } from 'app/models/workbasket';
import { AlertModel, AlertType } from 'app/models/alert'; import { AlertModel, AlertType } from 'app/models/alert';
@ -16,46 +17,16 @@ import { ICONTYPES } from 'app/models/type';
import { AccessItemsComponent } from './access-items.component'; import { AccessItemsComponent } from './access-items.component';
import { SpinnerComponent } from 'app/shared/spinner/spinner.component'; import { SpinnerComponent } from 'app/shared/spinner/spinner.component';
import { GeneralMessageModalComponent } from 'app/shared/general-message-modal/general-message-modal.component'; import { GeneralMessageModalComponent } from 'app/shared/general-message-modal/general-message-modal.component';
import { TaskanaTypeAheadMockComponent } from 'app/shared/type-ahead/type-ahead.mock.component';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service'; import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { SavingWorkbasketService, SavingInformation } from 'app/administration/services/saving-workbaskets/saving-workbaskets.service'; import { SavingWorkbasketService } from 'app/administration/services/saving-workbaskets/saving-workbaskets.service';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service'; import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { AlertService } from 'app/services/alert/alert.service'; import { AlertService } from 'app/services/alert/alert.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
import { configureTests } from 'app/app.test.configuration';
@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', () => { describe('AccessItemsComponent', () => {
let component: AccessItemsComponent; let component: AccessItemsComponent;
let fixture: ComponentFixture<AccessItemsComponent>; let fixture: ComponentFixture<AccessItemsComponent>;
@ -65,7 +36,7 @@ describe('AccessItemsComponent', () => {
beforeEach(done => { beforeEach(done => {
const configure = (testBed: TestBed) => { const configure = (testBed: TestBed) => {
testBed.configureTestingModule({ testBed.configureTestingModule({
declarations: [SpinnerComponent, AccessItemsComponent, GeneralMessageModalComponent, TaskanaTypeAheadComponent], declarations: [SpinnerComponent, AccessItemsComponent, GeneralMessageModalComponent, TaskanaTypeAheadMockComponent],
imports: [FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule, ReactiveFormsModule], imports: [FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule, ReactiveFormsModule],
providers: [WorkbasketService, AlertService, ErrorModalService, SavingWorkbasketService, RequestInProgressService, providers: [WorkbasketService, AlertService, ErrorModalService, SavingWorkbasketService, RequestInProgressService,
CustomFieldsService] CustomFieldsService]
@ -124,7 +95,7 @@ describe('AccessItemsComponent', () => {
}); });
it('should show alert successfull after saving', () => { it('should show alert successfull after saving', () => {
component.onSave(); component.onSubmit();
expect(alertService.triggerAlert).toHaveBeenCalledWith( expect(alertService.triggerAlert).toHaveBeenCalledWith(
new AlertModel(AlertType.SUCCESS, `Workbasket ${component.workbasket.key} Access items were saved successfully`)); new AlertModel(AlertType.SUCCESS, `Workbasket ${component.workbasket.key} Access items were saved successfully`));
}); });

View File

@ -1,7 +1,6 @@
import { Component, OnInit, Input, AfterViewInit, OnDestroy, OnChanges, SimpleChanges, ViewChild } from '@angular/core'; import { Component, Input, OnDestroy, OnChanges, SimpleChanges } from '@angular/core';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { NgForm, FormGroup, FormControl, FormBuilder, Validators, FormArray } from '@angular/forms'; import { FormBuilder, Validators, FormArray } from '@angular/forms';
import { Observable } from 'rxjs/Observable';
import { Workbasket } from 'app/models/workbasket'; import { Workbasket } from 'app/models/workbasket';
import { WorkbasketAccessItems } from 'app/models/workbasket-access-items'; import { WorkbasketAccessItems } from 'app/models/workbasket-access-items';
@ -15,14 +14,14 @@ import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service'; import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { AlertService } from 'app/services/alert/alert.service'; import { AlertService } from 'app/services/alert/alert.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { TitlesService } from 'app/services/titles/titles.service';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
import { TypeaheadMatch } from 'ngx-bootstrap/typeahead'; import { highlight } from 'app/shared/animations/validation.animation';
declare var $: any; declare const $: any;
@Component({ @Component({
selector: 'taskana-workbasket-access-items', selector: 'taskana-workbasket-access-items',
templateUrl: './access-items.component.html', templateUrl: './access-items.component.html',
animations: [highlight],
styleUrls: ['./access-items.component.scss'] styleUrls: ['./access-items.component.scss']
}) })
export class AccessItemsComponent implements OnChanges, OnDestroy { export class AccessItemsComponent implements OnChanges, OnDestroy {
@ -61,6 +60,7 @@ export class AccessItemsComponent implements OnChanges, OnDestroy {
accessItemsGroups: this.formBuilder.array([ accessItemsGroups: this.formBuilder.array([
]) ])
}); });
toogleValidationAccessIdMap = new Map<number, boolean>();
private initialized = false; private initialized = false;
@ -141,7 +141,33 @@ export class AccessItemsComponent implements OnChanges, OnDestroy {
this.accessItemsClone.splice(index, 1); this.accessItemsClone.splice(index, 1);
} }
onSave() { onSubmit() {
let valid = true;
for (let i = 0; i < this.accessItemsGroups.length; i++) {
if (this.accessItemsGroups.controls[i].invalid) {
const validationState = this.toogleValidationAccessIdMap.get(i);
validationState ? this.toogleValidationAccessIdMap.set(i, !validationState) : this.toogleValidationAccessIdMap.set(i, true);
valid = false;
}
}
if (!valid) {
this.alertService.triggerAlert(new AlertModel(AlertType.WARNING, `There are some empty fields which are required.`))
return false;
}
this.onSave();
}
checkAll(row: number, value: any) {
const checkAll = value.target.checked;
const workbasketAccessItemsObj = new WorkbasketAccessItems();
for (const property in workbasketAccessItemsObj) {
if (property !== 'accessId' && property !== '_links' && property !== 'workbasketId' && property !== 'accessItemId') {
this.accessItemsGroups.controls[row].get(property).setValue(checkAll);
}
}
}
private onSave() {
this.requestInProgressService.setRequestInProgress(true); this.requestInProgressService.setRequestInProgress(true);
this.workbasketService.updateWorkBasketAccessItem(this.accessItemsResource._links.self.href, this.AccessItemsForm.value.accessItemsGroups) this.workbasketService.updateWorkBasketAccessItem(this.accessItemsResource._links.self.href, this.AccessItemsForm.value.accessItemsGroups)
.subscribe(response => { .subscribe(response => {
@ -156,16 +182,6 @@ export class AccessItemsComponent implements OnChanges, OnDestroy {
}) })
} }
checkAll(row: number, value: any) {
const checkAll = value.target.checked;
const workbasketAccessItemsObj = new WorkbasketAccessItems();
for (const property in workbasketAccessItemsObj) {
if (property !== 'accessId' && property !== '_links' && property !== 'workbasketId' && property !== 'accessItemId') {
this.accessItemsGroups.controls[row].get(property).setValue(checkAll);
}
}
}
private setBadge() { private setBadge() {
if (this.action === ACTION.COPY) { if (this.action === ACTION.COPY) {
this.badgeMessage = `Copying workbasket: ${this.workbasket.key}`; this.badgeMessage = `Copying workbasket: ${this.workbasket.key}`;

View File

@ -15,7 +15,7 @@
</button> </button>
</div> </div>
</div> </div>
<div [@toggle]="toolbarState" *ngIf="toolbarState" class="row"> <div [@toggleDown]="toolbarState" *ngIf="toolbarState" class="row">
<taskana-filter class="col-xs-12" (performFilter)="performAvailableFilter($event)"></taskana-filter> <taskana-filter class="col-xs-12" (performFilter)="performAvailableFilter($event)"></taskana-filter>
</div> </div>
<taskana-spinner [isRunning]="requestInProgress" positionClass="centered-spinner" class="floating"></taskana-spinner> <taskana-spinner [isRunning]="requestInProgress" positionClass="centered-spinner" class="floating"></taskana-spinner>

View File

@ -1,28 +1,14 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { WorkbasketSummary } from 'app/models/workbasket-summary'; import { WorkbasketSummary } from 'app/models/workbasket-summary';
import { trigger, state, style, transition, animate, keyframes } from '@angular/animations';
import { FilterModel } from 'app/models/filter'; import { FilterModel } from 'app/models/filter';
import { filter } from 'rxjs/operators';
import { Side } from '../distribution-targets.component'; import { Side } from '../distribution-targets.component';
import { expandDown } from 'app/shared/animations/expand.animation';
@Component({ @Component({
selector: 'taskana-dual-list', selector: 'taskana-dual-list',
templateUrl: './dual-list.component.html', templateUrl: './dual-list.component.html',
styleUrls: ['./dual-list.component.scss'], styleUrls: ['./dual-list.component.scss'],
animations: [ animations: [expandDown]
trigger('toggle', [
state('*', style({ opacity: '1' })),
state('void', style({ opacity: '0' })),
transition('void => *', animate('300ms ease-in', keyframes([
style({ opacity: 0, height: '0px' }),
style({ opacity: 0.5, height: '50px' }),
style({ opacity: 1, height: '*' })]))),
transition('* => void', animate('300ms ease-out', keyframes([
style({ opacity: 1, height: '*' }),
style({ opacity: 0.5, height: '50px' }),
style({ opacity: 0, height: '0px' })])))
]
)],
}) })
export class DualListComponent implements OnInit { export class DualListComponent implements OnInit {

View File

@ -2,8 +2,7 @@
<div *ngIf="workbasket" id="wb-information" class="panel panel-default"> <div *ngIf="workbasket" id="wb-information" class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<div class="pull-right"> <div class="pull-right">
<button type="button" [disabled]="!WorkbasketForm.form.valid" (click)="onSave()" class="btn btn-default btn-primary" data-toggle="tooltip" <button type="button" (click)="onSubmit()" class="btn btn-default btn-primary" data-toggle="tooltip" title="Save">
title="Save">
<span class="glyphicon glyphicon-floppy-save" aria-hidden="true"></span> <span class="glyphicon glyphicon-floppy-save" aria-hidden="true"></span>
</button> </button>
<button type="button" (click)="onClear()" class="btn btn-default" data-toggle="tooltip" title="Undo Changes"> <button type="button" (click)="onClear()" class="btn btn-default" data-toggle="tooltip" title="Undo Changes">
@ -30,7 +29,7 @@
<label for="wb-key" class="control-label">Key</label> <label for="wb-key" class="control-label">Key</label>
<input type="text" required #key="ngModel" class="form-control" id="wb-key" placeholder="Key" [(ngModel)]="workbasket.key" <input type="text" required #key="ngModel" class="form-control" id="wb-key" placeholder="Key" [(ngModel)]="workbasket.key"
name="workbasket.key"> name="workbasket.key">
<div *ngIf="!key.valid" class="required-text"> <div *ngIf="!key.valid" class="required-text" [@validation]="this.toogleValidationMap.get('workbasket.key')">
* Key is required * Key is required
</div> </div>
</div> </div>
@ -38,18 +37,18 @@
<label for="wb-name" class="control-label">Name</label> <label for="wb-name" class="control-label">Name</label>
<input type="text" required #name="ngModel" class="form-control" id="wb-name" placeholder="Name" [(ngModel)]="workbasket.name" <input type="text" required #name="ngModel" class="form-control" id="wb-name" placeholder="Name" [(ngModel)]="workbasket.name"
name="workbasket.name"> name="workbasket.name">
<div *ngIf="!name.valid" class="required-text"> <div *ngIf="!name.valid" class="required-text" [@validation]="this.toogleValidationMap.get('workbasket.name')">
* Name is required * Name is required
</div> </div>
</div> </div>
<div class="form-group required"> <div class="form-group required">
<label for="wb-owner" class="control-label">Owner</label> <label for="wb-owner" class="control-label">Owner</label>
<taskana-type-ahead *ngIf="ownerField.lookupField else ownerInput" required #owner="ngModel" name="owner" [(ngModel)]="workbasket.owner" <taskana-type-ahead *ngIf="ownerField.lookupField else ownerInput" required #owner="ngModel" name="workbasket.owner" [(ngModel)]="workbasket.owner"
placeHolderMessage="Owner is required"></taskana-type-ahead> placeHolderMessage="* Owner is required" [validationValue]="this.toogleValidationMap.get('workbasket.owner')"></taskana-type-ahead>
<ng-template #ownerInput> <ng-template #ownerInput>
<input type="text" required #owner="ngModel" class="form-control" id="wb-owner" placeholder="Owner" [(ngModel)]="workbasket.owner" <input type="text" required #owner="ngModel" class="form-control" id="wb-owner" placeholder="Owner" [(ngModel)]="workbasket.owner"
name="workbasket.owner"> name="workbasket.owner">
<div *ngIf="!owner?.valid" class="required-text"> <div *ngIf="!owner?.valid" class="required-text" [@validation]="this.toogleValidationMap.get('workbasket.owner')">
* Owner is required * Owner is required
</div> </div>
</ng-template> </ng-template>

View File

@ -1,3 +1,3 @@
.dropdown-menu { .dropdown-menu {
min-width: auto; min-width: auto;
} }

View File

@ -1,13 +1,13 @@
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { async, ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service'; import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { WorkbasketInformationComponent } from './workbasket-information.component'; import { WorkbasketInformationComponent } from './workbasket-information.component';
import { FormsModule, ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { AngularSvgIconModule } from 'angular-svg-icon'; import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http'; import { HttpModule } from '@angular/http';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { Component, Input, forwardRef } from '@angular/core'; import { Component } from '@angular/core';
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
import { Workbasket } from 'app/models/workbasket'; import { Workbasket } from 'app/models/workbasket';
@ -18,11 +18,13 @@ import { Links } from 'app/models/links';
import { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component'; import { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component';
import { SpinnerComponent } from 'app/shared/spinner/spinner.component'; import { SpinnerComponent } from 'app/shared/spinner/spinner.component';
import { GeneralMessageModalComponent } from 'app/shared/general-message-modal/general-message-modal.component'; import { GeneralMessageModalComponent } from 'app/shared/general-message-modal/general-message-modal.component';
import { TaskanaTypeAheadMockComponent } from 'app/shared/type-ahead/type-ahead.mock.component';
import { MapValuesPipe } from 'app/shared/pipes/mapValues/map-values.pipe'; import { MapValuesPipe } from 'app/shared/pipes/mapValues/map-values.pipe';
import { RemoveNoneTypePipe } from 'app/shared/pipes/removeNoneType/remove-none-type.pipe'; import { RemoveNoneTypePipe } from 'app/shared/pipes/removeNoneType/remove-none-type.pipe';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service'; import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { SavingWorkbasketService, SavingInformation } from 'app/administration/services/saving-workbaskets/saving-workbaskets.service'; import { SavingWorkbasketService } from 'app/administration/services/saving-workbaskets/saving-workbaskets.service';
import { AlertService } from 'app/services/alert/alert.service'; import { AlertService } from 'app/services/alert/alert.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
@ -35,36 +37,6 @@ import { configureTests } from 'app/app.test.configuration';
export class DummyDetailComponent { 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 = [ const routes: Routes = [
{ path: ':id', component: DummyDetailComponent, outlet: 'detail' }, { path: ':id', component: DummyDetailComponent, outlet: 'detail' },
{ path: 'someNewId', component: DummyDetailComponent } { path: 'someNewId', component: DummyDetailComponent }
@ -80,7 +52,7 @@ describe('InformationComponent', () => {
testBed.configureTestingModule({ testBed.configureTestingModule({
declarations: [WorkbasketInformationComponent, IconTypeComponent, MapValuesPipe, declarations: [WorkbasketInformationComponent, IconTypeComponent, MapValuesPipe,
RemoveNoneTypePipe, SpinnerComponent, GeneralMessageModalComponent, DummyDetailComponent, RemoveNoneTypePipe, SpinnerComponent, GeneralMessageModalComponent, DummyDetailComponent,
TaskanaTypeAheadComponent], TaskanaTypeAheadMockComponent],
imports: [FormsModule, imports: [FormsModule,
AngularSvgIconModule, AngularSvgIconModule,
HttpClientModule, HttpClientModule,
@ -153,7 +125,7 @@ describe('InformationComponent', () => {
'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' })); 'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' }));
spyOn(workbasketService, 'updateWorkbasket').and.returnValue(Observable.of(component.workbasket)); spyOn(workbasketService, 'updateWorkbasket').and.returnValue(Observable.of(component.workbasket));
spyOn(workbasketService, 'triggerWorkBasketSaved').and.returnValue(Observable.of(component.workbasket)); spyOn(workbasketService, 'triggerWorkBasketSaved').and.returnValue(Observable.of(component.workbasket));
component.onSave(); component.onSubmit();
expect(component.requestInProgress).toBeFalsy(); expect(component.requestInProgress).toBeFalsy();
})); }));
@ -164,7 +136,8 @@ describe('InformationComponent', () => {
'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' })); 'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' }));
spyOn(workbasketService, 'updateWorkbasket').and.returnValue(Observable.of(component.workbasket)); spyOn(workbasketService, 'updateWorkbasket').and.returnValue(Observable.of(component.workbasket));
spyOn(workbasketService, 'triggerWorkBasketSaved').and.returnValue(Observable.of(component.workbasket)); spyOn(workbasketService, 'triggerWorkBasketSaved').and.returnValue(Observable.of(component.workbasket));
component.onSave(); fixture.detectChanges();
component.onSubmit();
expect(workbasketService.triggerWorkBasketSaved).toHaveBeenCalled(); expect(workbasketService.triggerWorkBasketSaved).toHaveBeenCalled();
}); });
@ -177,8 +150,8 @@ describe('InformationComponent', () => {
new Workbasket('someNewId', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description', new Workbasket('someNewId', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2', 'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' })))); 'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' }))));
fixture.detectChanges();
component.onSave(); component.onSubmit();
expect(alertService.triggerAlert).toHaveBeenCalled(); expect(alertService.triggerAlert).toHaveBeenCalled();
expect(component.workbasket.workbasketId).toBe('someNewId'); expect(component.workbasket.workbasketId).toBe('someNewId');
}); });
@ -199,8 +172,8 @@ describe('InformationComponent', () => {
spyOn(savingWorkbasketService, 'triggerDistributionTargetSaving'); spyOn(savingWorkbasketService, 'triggerDistributionTargetSaving');
spyOn(savingWorkbasketService, 'triggerAccessItemsSaving'); spyOn(savingWorkbasketService, 'triggerAccessItemsSaving');
fixture.detectChanges();
component.onSave(); component.onSubmit();
expect(alertService.triggerAlert).toHaveBeenCalled(); expect(alertService.triggerAlert).toHaveBeenCalled();
expect(component.workbasket.workbasketId).toBe('someNewId'); expect(component.workbasket.workbasketId).toBe('someNewId');
expect(savingWorkbasketService.triggerDistributionTargetSaving).toHaveBeenCalled(); expect(savingWorkbasketService.triggerDistributionTargetSaving).toHaveBeenCalled();

View File

@ -1,6 +1,7 @@
import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges } from '@angular/core'; import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { NgForm } from '@angular/forms';
import { ICONTYPES } from 'app/models/type'; import { ICONTYPES } from 'app/models/type';
import { ErrorModel } from 'app/models/modal-error'; import { ErrorModel } from 'app/models/modal-error';
@ -16,9 +17,12 @@ import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
import { RemoveConfirmationService } from 'app/services/remove-confirmation/remove-confirmation.service'; import { RemoveConfirmationService } from 'app/services/remove-confirmation/remove-confirmation.service';
import { highlight } from 'app/shared/animations/validation.animation';
import { FormsValidatorService } from 'app/shared/services/forms/forms-validator.service';
@Component({ @Component({
selector: 'taskana-workbasket-information', selector: 'taskana-workbasket-information',
animations: [highlight],
templateUrl: './workbasket-information.component.html', templateUrl: './workbasket-information.component.html',
styleUrls: ['./workbasket-information.component.scss'] styleUrls: ['./workbasket-information.component.scss']
}) })
@ -28,6 +32,7 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
@Input() @Input()
workbasket: Workbasket; workbasket: Workbasket;
workbasketClone: Workbasket; workbasketClone: Workbasket;
workbasketErrors
@Input() @Input()
action: string; action: string;
@ -41,8 +46,11 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
custom3Field = this.customFieldsService.getCustomField('Custom 3', 'workbaskets.information.custom3'); custom3Field = this.customFieldsService.getCustomField('Custom 3', 'workbaskets.information.custom3');
custom4Field = this.customFieldsService.getCustomField('Custom 4', 'workbaskets.information.custom4'); custom4Field = this.customFieldsService.getCustomField('Custom 4', 'workbaskets.information.custom4');
toogleValidationMap = new Map<string, boolean>();
private workbasketSubscription: Subscription; private workbasketSubscription: Subscription;
private routeSubscription: Subscription; private routeSubscription: Subscription;
@ViewChild('WorkbasketForm') workbasketForm: NgForm;
constructor(private workbasketService: WorkbasketService, constructor(private workbasketService: WorkbasketService,
private alertService: AlertService, private alertService: AlertService,
@ -52,7 +60,8 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
private savingWorkbasket: SavingWorkbasketService, private savingWorkbasket: SavingWorkbasketService,
private requestInProgressService: RequestInProgressService, private requestInProgressService: RequestInProgressService,
private customFieldsService: CustomFieldsService, private customFieldsService: CustomFieldsService,
private removeConfirmationService: RemoveConfirmationService) { private removeConfirmationService: RemoveConfirmationService,
private formsValidatorService: FormsValidatorService) {
this.allTypes = new Map([['PERSONAL', 'Personal'], ['GROUP', 'Group'], this.allTypes = new Map([['PERSONAL', 'Personal'], ['GROUP', 'Group'],
['CLEARANCE', 'Clearance'], ['TOPIC', 'Topic']]) ['CLEARANCE', 'Clearance'], ['TOPIC', 'Topic']])
@ -75,7 +84,13 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
this.workbasket.type = type; this.workbasket.type = type;
} }
onSave() { onSubmit() {
if (this.workbasketForm && this.formsValidatorService.validate(this.workbasketForm, this.toogleValidationMap)) {
this.onSave();
}
}
private onSave() {
this.beforeRequest(); this.beforeRequest();
if (!this.workbasket.workbasketId) { if (!this.workbasket.workbasketId) {
this.postNewWorkbasket(); this.postNewWorkbasket();

View File

@ -21,6 +21,10 @@ import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-detail.service'; import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-detail.service';
import { AlertService } from 'app/services/alert/alert.service'; import { AlertService } from 'app/services/alert/alert.service';
import { SavingWorkbasketService } from 'app/administration/services/saving-workbaskets/saving-workbaskets.service'; import { SavingWorkbasketService } from 'app/administration/services/saving-workbaskets/saving-workbaskets.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
import { configureTests } from 'app/app.test.configuration';
import { WorkbasketDetailsComponent } from './workbasket-details.component'; import { WorkbasketDetailsComponent } from './workbasket-details.component';
import { WorkbasketInformationComponent } from './information/workbasket-information.component'; import { WorkbasketInformationComponent } from './information/workbasket-information.component';
@ -31,13 +35,14 @@ import { SpinnerComponent } from 'app/shared/spinner/spinner.component';
import { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component'; import { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component';
import { AlertComponent } from 'app/shared/alert/alert.component'; import { AlertComponent } from 'app/shared/alert/alert.component';
import { GeneralMessageModalComponent } from 'app/shared/general-message-modal/general-message-modal.component'; import { GeneralMessageModalComponent } from 'app/shared/general-message-modal/general-message-modal.component';
import { TaskanaTypeAheadMockComponent } from 'app/shared/type-ahead/type-ahead.mock.component';
import { MapValuesPipe } from 'app/shared/pipes/mapValues/map-values.pipe'; import { MapValuesPipe } from 'app/shared/pipes/mapValues/map-values.pipe';
import { RemoveNoneTypePipe } from 'app/shared/pipes/removeNoneType/remove-none-type.pipe'; import { RemoveNoneTypePipe } from 'app/shared/pipes/removeNoneType/remove-none-type.pipe';
import { SelectWorkBasketPipe } from 'app/shared/pipes/selectedWorkbasket/seleted-workbasket.pipe'; import { SelectWorkBasketPipe } from 'app/shared/pipes/selectedWorkbasket/seleted-workbasket.pipe';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
import { configureTests } from 'app/app.test.configuration';
@Component({ @Component({
selector: 'taskana-filter', selector: 'taskana-filter',
template: '' template: ''
@ -55,37 +60,6 @@ export class FilterComponent {
export class DummyDetailComponent { 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', () => { describe('WorkbasketDetailsComponent', () => {
let component: WorkbasketDetailsComponent; let component: WorkbasketDetailsComponent;
let fixture: ComponentFixture<WorkbasketDetailsComponent>; let fixture: ComponentFixture<WorkbasketDetailsComponent>;
@ -107,7 +81,7 @@ describe('WorkbasketDetailsComponent', () => {
declarations: [WorkbasketDetailsComponent, WorkbasketInformationComponent, SpinnerComponent, declarations: [WorkbasketDetailsComponent, WorkbasketInformationComponent, SpinnerComponent,
IconTypeComponent, MapValuesPipe, RemoveNoneTypePipe, AlertComponent, GeneralMessageModalComponent, AccessItemsComponent, IconTypeComponent, MapValuesPipe, RemoveNoneTypePipe, AlertComponent, GeneralMessageModalComponent, AccessItemsComponent,
DistributionTargetsComponent, FilterComponent, DualListComponent, DummyDetailComponent, DistributionTargetsComponent, FilterComponent, DualListComponent, DummyDetailComponent,
TaskanaTypeAheadComponent, SelectWorkBasketPipe], TaskanaTypeAheadMockComponent, SelectWorkBasketPipe],
providers: [WorkbasketService, MasterAndDetailService, ErrorModalService, RequestInProgressService, providers: [WorkbasketService, MasterAndDetailService, ErrorModalService, RequestInProgressService,
AlertService, SavingWorkbasketService, AlertService, SavingWorkbasketService,
CustomFieldsService] CustomFieldsService]

View File

@ -17,7 +17,7 @@
</div> </div>
</div> </div>
<div [@toggle]="toolbarState" *ngIf="toolbarState" class="row no-overflow"> <div [@toggleDown]="toolbarState" *ngIf="toolbarState" class="row no-overflow">
<taskana-filter (performFilter)="filtering($event)"></taskana-filter> <taskana-filter (performFilter)="filtering($event)"></taskana-filter>
</div> </div>
</li> </li>

View File

@ -1,34 +1,21 @@
import { Component, OnInit, Input, Output, EventEmitter, AfterViewChecked } from '@angular/core'; import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { trigger, state, style, transition, animate, keyframes } from '@angular/animations';
import { Router, ActivatedRoute } from '@angular/router'; import { Router, ActivatedRoute } from '@angular/router';
import { SortingModel } from 'app/models/sorting'; import { SortingModel } from 'app/models/sorting';
import { FilterModel } from 'app/models/filter'; import { FilterModel } from 'app/models/filter';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { WorkbasketSummary } from 'app/models/workbasket-summary'; import { WorkbasketSummary } from 'app/models/workbasket-summary';
import { ErrorModel } from 'app/models/modal-error';
import { AlertModel, AlertType } from 'app/models/alert';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service'; import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service'; import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { AlertService } from 'app/services/alert/alert.service'; import { AlertService } from 'app/services/alert/alert.service';
import { ImportType } from 'app/models/import-type'; import { ImportType } from 'app/models/import-type';
import { expandDown } from 'app/shared/animations/expand.animation';
@Component({ @Component({
selector: 'taskana-workbasket-list-toolbar', selector: 'taskana-workbasket-list-toolbar',
animations: [ animations: [expandDown],
trigger('toggle', [
transition('void => *', animate('300ms ease-in', keyframes([
style({ height: '0px' }),
style({ height: '50px' }),
style({ height: '*' })]))),
transition('* => void', animate('300ms ease-out', keyframes([
style({ height: '*' }),
style({ height: '50px' }),
style({ height: '0px' })])))
]
)],
templateUrl: './workbasket-list-toolbar.component.html', templateUrl: './workbasket-list-toolbar.component.html',
styleUrls: ['./workbasket-list-toolbar.component.scss'] styleUrls: ['./workbasket-list-toolbar.component.scss']
}) })

View File

@ -20,6 +20,8 @@ import { ErrorModalService } from './services/errorModal/error-modal.service';
import { RequestInProgressService } from './services/requestInProgress/request-in-progress.service'; import { RequestInProgressService } from './services/requestInProgress/request-in-progress.service';
import { OrientationService } from './services/orientation/orientation.service'; import { OrientationService } from './services/orientation/orientation.service';
import { SelectedRouteService } from './services/selected-route/selected-route'; import { SelectedRouteService } from './services/selected-route/selected-route';
import { FormsValidatorService } from './shared/services/forms/forms-validator.service';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
export const configureTests = (configure: (testBed: TestBed) => void) => { export const configureTests = (configure: (testBed: TestBed) => void) => {
@ -33,9 +35,10 @@ export const configureTests = (configure: (testBed: TestBed) => void) => {
configure(testBed); configure(testBed);
testBed.configureTestingModule({ testBed.configureTestingModule({
imports: [BrowserAnimationsModule],
providers: [{ provide: TaskanaEngineService, useClass: TaskanaEngineServiceMock }, providers: [{ provide: TaskanaEngineService, useClass: TaskanaEngineServiceMock },
{ provide: DomainService, useClass: DomainServiceMock }, CustomFieldsService, RemoveConfirmationService, { provide: DomainService, useClass: DomainServiceMock }, CustomFieldsService, RemoveConfirmationService,
AlertService, ErrorModalService, RequestInProgressService, OrientationService, SelectedRouteService] AlertService, ErrorModalService, RequestInProgressService, OrientationService, SelectedRouteService, FormsValidatorService]
}); });
return testBed.compileComponents().then(() => testBed); return testBed.compileComponents().then(() => testBed);

View File

@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { TaskanaTypeAheadMockComponent } from 'app/shared/type-ahead/type-ahead.mock.component';
const MODULES = [
];
const DECLARATIONS = [
TaskanaTypeAheadMockComponent
];
@NgModule({
declarations: DECLARATIONS,
imports: MODULES,
providers: []
})
export class AppTestModule {
}

View File

@ -30,7 +30,7 @@
</div> </div>
</div> </div>
</div> </div>
<div [@toggle]="showNavbar" *ngIf="showNavbar" class="navbar-inverse sidenav full-height col-xs-9 col-sm-3" data-html="false" <div [@toggleRight]="showNavbar" *ngIf="showNavbar" class="navbar-inverse sidenav full-height col-xs-9 col-sm-3" data-html="false"
aria-expanded="true"> aria-expanded="true">
<div class="row"> <div class="row">
<ul class="nav"> <ul class="nav">

View File

@ -2,29 +2,18 @@ import { Component, OnInit, OnDestroy } from '@angular/core';
import { environment } from 'environments/environment'; import { environment } from 'environments/environment';
import { SelectedRouteService } from 'app/services/selected-route/selected-route'; import { SelectedRouteService } from 'app/services/selected-route/selected-route';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { trigger, state, style, transition, keyframes, animate } from '@angular/animations';
import { DomainService } from 'app/services/domain/domain.service'; import { DomainService } from 'app/services/domain/domain.service';
import { BusinessAdminGuard } from 'app/guards/business-admin-guard'; import { BusinessAdminGuard } from 'app/guards/business-admin-guard';
import { MonitorGuard } from 'app/guards/monitor-guard'; import { MonitorGuard } from 'app/guards/monitor-guard';
import { WindowRefService } from 'app/services/window/window.service'; import { WindowRefService } from 'app/services/window/window.service';
import { UserGuard } from 'app/guards/user-guard'; import { UserGuard } from 'app/guards/user-guard';
import { TaskanaEngineService } from '../../services/taskana-engine/taskana-engine.service'; import { TaskanaEngineService } from '../../services/taskana-engine/taskana-engine.service';
import { expandRight } from 'app/shared/animations/expand.animation';
@Component({ @Component({
selector: 'taskana-nav-bar', selector: 'taskana-nav-bar',
templateUrl: './nav-bar.component.html', templateUrl: './nav-bar.component.html',
styleUrls: ['./nav-bar.component.scss'], styleUrls: ['./nav-bar.component.scss'],
animations: [ animations: [expandRight],
trigger('toggle', [
transition('void => *', animate('300ms ease-in', keyframes([
style({ opacity: 0, width: '0px' }),
style({ opacity: 1, width: '150px' }),
style({ opacity: 1, width: '*' })]))),
transition('* => void', animate('300ms ease-out', keyframes([
style({ opacity: 1, width: '*' }),
style({ opacity: 0, width: '150px' }),
style({ opacity: 0, width: '0px' })])))
]
)],
}) })
export class NavBarComponent implements OnInit, OnDestroy { export class NavBarComponent implements OnInit, OnDestroy {

View File

@ -1,10 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { environment } from 'environments/environment';
import { UserInfoModel } from 'app/models/user-info'; import { UserInfoModel } from 'app/models/user-info';
import { ReplaySubject } from 'rxjs/ReplaySubject';
@Injectable() @Injectable()
export class TaskanaEngineServiceMock { export class TaskanaEngineServiceMock {

View File

@ -1,4 +1,4 @@
<div *ngIf="alert" [@alertState]="alert" class="alert alert-{{alert.type}} {{alert.autoClosing? '':'alert-dismissible'}} footer" <div *ngIf="alert" [@toggleTop]="alert" class="alert alert-{{alert.type}} {{alert.autoClosing? '':'alert-dismissible'}} footer"
role="alert"> role="alert">
<span class="glyphicon {{alert.type === 'success'? 'glyphicon-thumbs-up': 'glyphicon-exclamation-sign' }}" aria-hidden="true"></span> <span class="glyphicon {{alert.type === 'success'? 'glyphicon-thumbs-up': 'glyphicon-exclamation-sign' }}" aria-hidden="true"></span>
{{alert.text}} {{alert.text}}

View File

@ -1,25 +1,14 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { trigger, state, style, animate, transition } from '@angular/animations';
import { AlertModel } from 'app/models/alert'; import { AlertModel } from 'app/models/alert';
import { AlertService } from 'app/services/alert/alert.service'; import { AlertService } from 'app/services/alert/alert.service';
import { expandTop } from '../animations/expand.animation';
@Component({ @Component({
selector: 'taskana-alert', selector: 'taskana-alert',
templateUrl: './alert.component.html', templateUrl: './alert.component.html',
styleUrls: ['./alert.component.scss'], styleUrls: ['./alert.component.scss'],
animations: [ animations: [expandTop]
trigger('alertState', [
state('in', style({ transform: 'translateY(0)', overflow: 'hidden' })),
transition('void => *', [
style({ transform: 'translateY(100%)', overflow: 'hidden' }),
animate(100)
]),
transition('* => void', [
animate(100, style({ transform: 'translateY(100%)', overflow: 'hidden' }))
])
])
]
}) })
export class AlertComponent implements OnInit { export class AlertComponent implements OnInit {

View File

@ -0,0 +1,51 @@
import { trigger, style, transition, animate, keyframes, state } from '@angular/core';
export const expandDown =
trigger('toggleDown', [
state('*', style({ opacity: '1' })),
state('void', style({ opacity: '0' })),
transition('void => *', animate('300ms ease-in', keyframes([
style({ opacity: 0, height: '0px' }),
style({ opacity: 0.5, height: '50px' }),
style({ opacity: 1, height: '*' })]))),
transition('* => void', animate('300ms ease-out', keyframes([
style({ opacity: 1, height: '*' }),
style({ opacity: 0.5, height: '50px' }),
style({ opacity: 0, height: '0px' })])))
]);
export const expandRight = trigger('toggleRight', [
transition('void => *', animate('300ms ease-in', keyframes([
style({ opacity: 0, width: '0px' }),
style({ opacity: 1, width: '150px' }),
style({ opacity: 1, width: '*' })]))),
transition('* => void', animate('300ms ease-out', keyframes([
style({ opacity: 1, width: '*' }),
style({ opacity: 0, width: '150px' }),
style({ opacity: 0, width: '0px' })])))
]);
export const expandTop = trigger('toggleTop', [
state('in', style({ transform: 'translateY(0)', overflow: 'hidden' })),
transition('void => *', [
style({ transform: 'translateY(100%)', overflow: 'hidden' }),
animate(100)
]),
transition('* => void', [
animate(100, style({ transform: 'translateY(100%)', overflow: 'hidden' }))
])
])
export const opacity = trigger('toggleOpacity', [
state('*', style({ opacity: '1' })),
state('void', style({ opacity: '0' })),
transition('void => *', animate('300ms ease-in', keyframes([
style({ opacity: 0 }),
style({ opacity: 0.5 }),
style({ opacity: 1 })]))),
transition('* => void', animate('300ms ease-out', keyframes([
style({ opacity: 1 }),
style({ opacity: 0.5 }),
style({ opacity: 0 })])))
])

View File

@ -0,0 +1,12 @@
import { trigger, style, transition, animate, keyframes } from '@angular/core';
export const highlight = trigger('validation', [
transition('true => false, false => true, * => true', animate('1500ms', keyframes([
style({ opacity: '1', }),
style({ opacity: '0.3' }),
style({ opacity: '1' }),
style({ opacity: '0.3' }),
style({ opacity: '1' }),
style({ opacity: '0.3' }),
style({ opacity: '1' })])))
]);

View File

@ -0,0 +1,25 @@
import { NgForm } from '@angular/forms';
import { Injectable } from '@angular/core';
import { AlertService } from 'app/services/alert/alert.service';
import { AlertModel, AlertType } from 'app/models/alert';
@Injectable()
export class FormsValidatorService {
constructor(
private alertService: AlertService) { }
public validate(form: NgForm, toogleValidationMap: Map<any, boolean>): boolean {
let valid = true;
for (const control in form.form.controls) {
if (form.form.controls[control].invalid) {
const validationState = toogleValidationMap.get(control);
validationState ? toogleValidationMap.set(control, !validationState) : toogleValidationMap.set(control, true);
valid = false;
}
}
if (!valid) {
this.alertService.triggerAlert(new AlertModel(AlertType.WARNING, `There are some empty fields which are required.`))
}
return valid;
}
}

View File

@ -14,6 +14,7 @@ import { AlertComponent } from 'app/shared/alert/alert.component';
import { MasterAndDetailComponent } from 'app/shared/master-and-detail/master-and-detail.component'; import { MasterAndDetailComponent } from 'app/shared/master-and-detail/master-and-detail.component';
import { TaskanaTreeComponent } from 'app/shared/tree/tree.component'; import { TaskanaTreeComponent } from 'app/shared/tree/tree.component';
import { TypeAheadComponent } from 'app/shared/type-ahead/type-ahead.component'; import { TypeAheadComponent } from 'app/shared/type-ahead/type-ahead.component';
import { SortComponent } from './sort/sort.component';
import { RemoveConfirmationComponent } from 'app/shared/remove-confirmation/remove-confirmation.component'; import { RemoveConfirmationComponent } from 'app/shared/remove-confirmation/remove-confirmation.component';
/** /**
@ -31,7 +32,7 @@ import { MapToIterable } from './pipes/mapToIterable/mapToIterable';
*/ */
import { HttpClientInterceptor } from './services/httpClientInterceptor/http-client-interceptor.service'; import { HttpClientInterceptor } from './services/httpClientInterceptor/http-client-interceptor.service';
import { AccessIdsService } from './services/access-ids/access-ids.service'; import { AccessIdsService } from './services/access-ids/access-ids.service';
import { SortComponent } from './sort/sort.component'; import { FormsValidatorService } from './services/forms/forms-validator.service';
@ -73,7 +74,8 @@ const DECLARATIONS = [
useClass: HttpClientInterceptor, useClass: HttpClientInterceptor,
multi: true multi: true
}, },
AccessIdsService AccessIdsService,
FormsValidatorService
] ]
}) })
export class SharedModule { export class SharedModule {

View File

@ -32,7 +32,6 @@
required #accessItemName="ngModel" [(ngModel)]="value" [typeahead]="dataSource" typeaheadOptionField="name" [typeaheadItemTemplate]="customItemTemplate" required #accessItemName="ngModel" [(ngModel)]="value" [typeahead]="dataSource" typeaheadOptionField="name" [typeaheadItemTemplate]="customItemTemplate"
(typeaheadOnSelect)="typeaheadOnSelect($event, index)" [typeaheadScrollable]="true" [typeaheadOptionsInScrollableView]="typeaheadOptionsInScrollableView" (typeaheadOnSelect)="typeaheadOnSelect($event, index)" [typeaheadScrollable]="true" [typeaheadOptionsInScrollableView]="typeaheadOptionsInScrollableView"
[typeaheadMinLength]="typeaheadMinLength" [typeaheadWaitMs]="typeaheadWaitMs" (typeaheadLoading)="changeTypeaheadLoading($event)" [typeaheadMinLength]="typeaheadMinLength" [typeaheadWaitMs]="typeaheadWaitMs" (typeaheadLoading)="changeTypeaheadLoading($event)"
placeholder="{{accessItemName.invalid? placeHolderMessage: ''}}"> placeholder="{{accessItemName.invalid? placeHolderMessage: ''}}" [@validation]="validationValue">
</div> </div>
</div> </div>

View File

@ -1,20 +1,25 @@
$blue: #2e9eca; $blue: #2e9eca;
$grey: #ddd; $grey: #ddd;
$invalid: #a94442;
.wrapper-text { .wrapper-text {
height: 47px; height: 47px;
& label { & label {
width: 100%; width: 100%;
margin-bottom: 0px; margin-bottom: 0px;
padding-left: 12px;
font-style: italic;
} }
& div { & div {
width: 100%; width: 100%;
border-bottom: 1px solid $grey; border-bottom: 1px solid $grey;
margin-top:6px; margin-top:6px;
padding-left: 12px;
} }
> div{ > div{
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
padding-left: 12px;
} }
} }
@ -30,6 +35,7 @@ $grey: #ddd;
&:focus{ &:focus{
border-bottom: 1px solid $blue; border-bottom: 1px solid $blue;
} }
padding-left: 12px;
} }
@ -50,3 +56,15 @@ $grey: #ddd;
position: absolute; position: absolute;
right: 0; right: 0;
} }
::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
color: $invalid;
opacity: 1; /* Firefox */
}
:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: $invalid;
}
::-ms-input-placeholder { /* Microsoft Edge */
color: $invalid;
}

View File

@ -1,18 +1,17 @@
import { Component, OnInit, Input, EventEmitter, Output, ViewChild, ElementRef, forwardRef } from '@angular/core'; import { Component, OnInit, Input, ViewChild, forwardRef } from '@angular/core';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { TypeaheadMatch } from 'ngx-bootstrap/typeahead'; import { TypeaheadMatch } from 'ngx-bootstrap/typeahead';
import { AccessIdsService } from 'app/shared/services/access-ids/access-ids.service'; 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'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { highlight } from 'app/shared/animations/validation.animation';
const noop = () => {
};
@Component({ @Component({
selector: 'taskana-type-ahead', selector: 'taskana-type-ahead',
templateUrl: './type-ahead.component.html', templateUrl: './type-ahead.component.html',
styleUrls: ['./type-ahead.component.scss'], styleUrls: ['./type-ahead.component.scss'],
animations: [highlight],
providers: [ providers: [
{ {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
@ -29,6 +28,9 @@ export class TypeAheadComponent implements OnInit, ControlValueAccessor {
@Input() @Input()
placeHolderMessage; placeHolderMessage;
@Input()
validationValue;
@ViewChild('inputTypeAhead') @ViewChild('inputTypeAhead')
private inputTypeAhead; private inputTypeAhead;

View File

@ -0,0 +1,34 @@
import { Component, forwardRef, Input } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
@Component({
selector: 'taskana-type-ahead',
template: 'dummydetail',
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => TaskanaTypeAheadMockComponent),
}
]
})
export class TaskanaTypeAheadMockComponent implements ControlValueAccessor {
@Input()
placeHolderMessage;
@Input()
validationValue;
writeValue(obj: any): void {
}
registerOnChange(fn: any): void {
}
registerOnTouched(fn: any): void {
}
setDisabledState?(isDisabled: boolean): void {
}
}

View File

@ -1,24 +1,12 @@
import { Component, OnInit, HostListener } from '@angular/core'; import { Component, OnInit, HostListener } from '@angular/core';
import { trigger, transition, keyframes, style, animate, state } from '@angular/animations'; import { trigger, transition, keyframes, style, animate, state } from '@angular/animations';
import { opacity } from 'app/shared/animations/expand.animation';
@Component({ @Component({
selector: 'taskana-code', selector: 'taskana-code',
templateUrl: './code.component.html', templateUrl: './code.component.html',
styleUrls: ['./code.component.scss'], styleUrls: ['./code.component.scss'],
animations: [ animations: [opacity]
trigger('toggle', [
state('*', style({ opacity: '1' })),
state('void', style({ opacity: '0' })),
transition('void => *', animate('300ms ease-in', keyframes([
style({ opacity: 0 }),
style({ opacity: 0.5 }),
style({ opacity: 1 })]))),
transition('* => void', animate('300ms ease-out', keyframes([
style({ opacity: 1 }),
style({ opacity: 0.5 }),
style({ opacity: 0 })])))
])
]
}) })
export class CodeComponent implements OnInit { export class CodeComponent implements OnInit {

View File

@ -5,7 +5,7 @@
} }
.required-text { .required-text {
padding-left: 15px; padding-left: 3px;
color: $invalid; color: $invalid;
} }