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 class="panel-heading">
<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">
<span class="glyphicon glyphicon-floppy-save" aria-hidden="true"></span>
</button>
@ -33,7 +33,7 @@
<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"
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
</div>
</div>
@ -41,7 +41,7 @@
<label for="classification-name" class="control-label">Name</label>
<input type="text" required #name="ngModel" class="form-control" id="classification-name" placeholder="Name" [(ngModel)]="classification.name"
name="classification.name">
<div *ngIf="!name.valid" class="required-text">
<div *ngIf="!name.valid" class="required-text" [@validation]="this.toogleValidationMap.get('classification.name')">
* Name is required
</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 { Subscription } from 'rxjs/Subscription';
@ -7,6 +7,7 @@ import { ACTION } from 'app/models/action';
import { ErrorModel } from 'app/models/modal-error';
import { AlertModel, AlertType } from 'app/models/alert';
import { highlight } from 'app/shared/animations/validation.animation';
import { TaskanaDate } from 'app/shared/util/taskana.date';
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 { CustomFieldsService } from '../../../services/custom-fields/custom-fields.service';
import { Pair } from 'app/models/pair';
import { NgForm } from '@angular/forms';
import { FormsValidatorService } from 'app/shared/services/forms/forms-validator.service';
@Component({
selector: 'taskana-classification-details',
templateUrl: './classification-details.component.html',
animations: [highlight],
styleUrls: ['./classification-details.component.scss']
})
export class ClassificationDetailsComponent implements OnInit, OnDestroy {
@ -62,6 +66,9 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
private categoriesSubscription: Subscription;
private domainSubscription: Subscription;
@ViewChild('ClassificationForm') classificationForm: NgForm;
toogleValidationMap = new Map<string, boolean>();
constructor(private classificationsService: ClassificationsService,
private route: ActivatedRoute,
private router: Router,
@ -74,7 +81,8 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
private categoryService: ClassificationCategoriesService,
private domainService: DomainService,
private customFieldsService: CustomFieldsService,
private removeConfirmationService: RemoveConfirmationService) { }
private removeConfirmationService: RemoveConfirmationService,
private formsValidatorService: FormsValidatorService) { }
ngOnInit() {
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?`);
}
onSave() {
onSubmit() {
if (this.formsValidatorService.validate(this.classificationForm, this.toogleValidationMap)) {
this.onSave();
}
}
private onSave() {
this.requestInProgressService.setRequestInProgress(true);
if (this.action === ACTION.CREATE) {
this.classificationSavingSubscription = this.classificationsService.postClassification(this.classification)

View File

@ -1,8 +1,8 @@
<div *ngIf="workbasket" id="wb-information" class="panel panel-default">
<div class="panel-heading">
<div class="pull-right">
<button type="button" (click)="onSave()" [disabled]="!AccessItemsForm.valid || action === 'COPY'" class="btn btn-default btn-primary"
data-toggle="tooltip" title="Save">
<button type="button" (click)="onSubmit()" [disabled]="action === 'COPY'" class="btn btn-default btn-primary" data-toggle="tooltip"
title="Save">
<span class="glyphicon glyphicon-floppy-save" aria-hidden="true"></span>
</button>
<button type="button" (click)="clear()" class="btn btn-default" data-toggle="tooltip" title="Undo Changes">
@ -49,14 +49,16 @@
</td>
<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-error': !accessItem.value.accessId } ">
<taskana-type-ahead [(ngModel)]="accessItem.value.accessId" formControlName="accessId" placeHolderMessage="Access id is required"></taskana-type-ahead>
'has-error': !accessItem.value.accessId }">
<taskana-type-ahead [(ngModel)]="accessItem.value.accessId" formControlName="accessId" placeHolderMessage="* Access id is required"
[validationValue]="toogleValidationAccessIdMap.get(index)"></taskana-type-ahead>
</td>
<ng-template #accessIdInput>
<td class="input-group text-align text-width ">
<div [ngClass]="{ 'has-warning': (accessItemsClone[index].accessId !== accessItem.value.accessId),
'has-error': !accessItem.value.accessId }">
<input type="text" class="form-control" formControlName="accessId" placeholder="{{accessItem.invalid? 'Access id is required': ''}}">
<td class="input-group text-align text-width">
<div [ngClass]="{ 'has-warning': (accessItemsClone[index].accessId !==accessItem.value.accessId), 'has-error':
!accessItem.value.accessId }">
<input type="text" class="form-control" formControlName="accessId" placeholder="{{accessItem.invalid?
'* Access id is required': ''}}" [@validation]="toogleValidationAccessIdMap.get(index)">
</div>
</td>
</ng-template>
@ -64,71 +66,71 @@
<input id="checkbox-{{index}}-00" type="checkbox" (change)="checkAll(index, $event)">
<label for="checkbox-{{index}}-00"></label>
</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">
<label for="checkbox-{{index}}-0"></label>
</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">
<label for="checkbox-{{index}}-1"></label>
</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">
<label for="checkbox-{{index}}-2"></label>
</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">
<label for="checkbox-{{index}}-3"></label>
</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">
<label for="checkbox-{{index}}-4"></label>
</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">
<label for="checkbox-{{index}}-5"></label>
</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">
<label for="checkbox-{{index}}-6"></label>
</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">
<label for="checkbox-{{index}}-7"></label>
</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">
<label for="checkbox-{{index}}-8"></label>
</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">
<label for="checkbox-{{index}}-9"></label>
</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">
<label for="checkbox-{{index}}-10"></label>
</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">
<label for="checkbox-{{index}}-11"></label>
</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">
<label for="checkbox-{{index}}-12"></label>
</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">
<label for="checkbox-{{index}}-13"></label>
</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">
<label for="checkbox-{{index}}-14"></label>
</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">
<label for="checkbox-{{index}}-15"></label>
</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">
<label for="checkbox-{{index}}-16"></label>
</td>

View File

@ -5,6 +5,7 @@ import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { Observable } from 'rxjs/Observable';
import { configureTests } from 'app/app.test.configuration';
import { Workbasket } from 'app/models/workbasket';
import { AlertModel, AlertType } from 'app/models/alert';
@ -16,46 +17,16 @@ import { ICONTYPES } from 'app/models/type';
import { AccessItemsComponent } from './access-items.component';
import { SpinnerComponent } from 'app/shared/spinner/spinner.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 { 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 { AlertService } from 'app/services/alert/alert.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({
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>;
@ -65,7 +36,7 @@ describe('AccessItemsComponent', () => {
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
declarations: [SpinnerComponent, AccessItemsComponent, GeneralMessageModalComponent, TaskanaTypeAheadComponent],
declarations: [SpinnerComponent, AccessItemsComponent, GeneralMessageModalComponent, TaskanaTypeAheadMockComponent],
imports: [FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule, ReactiveFormsModule],
providers: [WorkbasketService, AlertService, ErrorModalService, SavingWorkbasketService, RequestInProgressService,
CustomFieldsService]
@ -124,7 +95,7 @@ describe('AccessItemsComponent', () => {
});
it('should show alert successfull after saving', () => {
component.onSave();
component.onSubmit();
expect(alertService.triggerAlert).toHaveBeenCalledWith(
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 { NgForm, FormGroup, FormControl, FormBuilder, Validators, FormArray } from '@angular/forms';
import { Observable } from 'rxjs/Observable';
import { FormBuilder, Validators, FormArray } from '@angular/forms';
import { Workbasket } from 'app/models/workbasket';
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 { 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 '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({
selector: 'taskana-workbasket-access-items',
templateUrl: './access-items.component.html',
animations: [highlight],
styleUrls: ['./access-items.component.scss']
})
export class AccessItemsComponent implements OnChanges, OnDestroy {
@ -61,6 +60,7 @@ export class AccessItemsComponent implements OnChanges, OnDestroy {
accessItemsGroups: this.formBuilder.array([
])
});
toogleValidationAccessIdMap = new Map<number, boolean>();
private initialized = false;
@ -141,7 +141,33 @@ export class AccessItemsComponent implements OnChanges, OnDestroy {
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.workbasketService.updateWorkBasketAccessItem(this.accessItemsResource._links.self.href, this.AccessItemsForm.value.accessItemsGroups)
.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() {
if (this.action === ACTION.COPY) {
this.badgeMessage = `Copying workbasket: ${this.workbasket.key}`;

View File

@ -15,7 +15,7 @@
</button>
</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>
</div>
<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 { WorkbasketSummary } from 'app/models/workbasket-summary';
import { trigger, state, style, transition, animate, keyframes } from '@angular/animations';
import { FilterModel } from 'app/models/filter';
import { filter } from 'rxjs/operators';
import { Side } from '../distribution-targets.component';
import { expandDown } from 'app/shared/animations/expand.animation';
@Component({
selector: 'taskana-dual-list',
templateUrl: './dual-list.component.html',
styleUrls: ['./dual-list.component.scss'],
animations: [
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' })])))
]
)],
animations: [expandDown]
})
export class DualListComponent implements OnInit {

View File

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

View File

@ -1,3 +1,3 @@
.dropdown-menu {
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 { 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 { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http';
import { RouterTestingModule } from '@angular/router/testing';
import { Observable } from 'rxjs/Observable';
import { Component, Input, forwardRef } from '@angular/core';
import { Component } from '@angular/core';
import { Routes } from '@angular/router';
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 { SpinnerComponent } from 'app/shared/spinner/spinner.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 { RemoveNoneTypePipe } from 'app/shared/pipes/removeNoneType/remove-none-type.pipe';
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 { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.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 {
}
@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 }
@ -80,7 +52,7 @@ describe('InformationComponent', () => {
testBed.configureTestingModule({
declarations: [WorkbasketInformationComponent, IconTypeComponent, MapValuesPipe,
RemoveNoneTypePipe, SpinnerComponent, GeneralMessageModalComponent, DummyDetailComponent,
TaskanaTypeAheadComponent],
TaskanaTypeAheadMockComponent],
imports: [FormsModule,
AngularSvgIconModule,
HttpClientModule,
@ -153,7 +125,7 @@ describe('InformationComponent', () => {
'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' }));
spyOn(workbasketService, 'updateWorkbasket').and.returnValue(Observable.of(component.workbasket));
spyOn(workbasketService, 'triggerWorkBasketSaved').and.returnValue(Observable.of(component.workbasket));
component.onSave();
component.onSubmit();
expect(component.requestInProgress).toBeFalsy();
}));
@ -164,7 +136,8 @@ describe('InformationComponent', () => {
'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' }));
spyOn(workbasketService, 'updateWorkbasket').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();
});
@ -177,8 +150,8 @@ describe('InformationComponent', () => {
new Workbasket('someNewId', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' }))));
component.onSave();
fixture.detectChanges();
component.onSubmit();
expect(alertService.triggerAlert).toHaveBeenCalled();
expect(component.workbasket.workbasketId).toBe('someNewId');
});
@ -199,8 +172,8 @@ describe('InformationComponent', () => {
spyOn(savingWorkbasketService, 'triggerDistributionTargetSaving');
spyOn(savingWorkbasketService, 'triggerAccessItemsSaving');
component.onSave();
fixture.detectChanges();
component.onSubmit();
expect(alertService.triggerAlert).toHaveBeenCalled();
expect(component.workbasket.workbasketId).toBe('someNewId');
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 { Subscription } from 'rxjs/Subscription';
import { NgForm } from '@angular/forms';
import { ICONTYPES } from 'app/models/type';
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 { CustomFieldsService } from 'app/services/custom-fields/custom-fields.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({
selector: 'taskana-workbasket-information',
animations: [highlight],
templateUrl: './workbasket-information.component.html',
styleUrls: ['./workbasket-information.component.scss']
})
@ -28,6 +32,7 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
@Input()
workbasket: Workbasket;
workbasketClone: Workbasket;
workbasketErrors
@Input()
action: string;
@ -41,8 +46,11 @@ 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');
toogleValidationMap = new Map<string, boolean>();
private workbasketSubscription: Subscription;
private routeSubscription: Subscription;
@ViewChild('WorkbasketForm') workbasketForm: NgForm;
constructor(private workbasketService: WorkbasketService,
private alertService: AlertService,
@ -52,7 +60,8 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
private savingWorkbasket: SavingWorkbasketService,
private requestInProgressService: RequestInProgressService,
private customFieldsService: CustomFieldsService,
private removeConfirmationService: RemoveConfirmationService) {
private removeConfirmationService: RemoveConfirmationService,
private formsValidatorService: FormsValidatorService) {
this.allTypes = new Map([['PERSONAL', 'Personal'], ['GROUP', 'Group'],
['CLEARANCE', 'Clearance'], ['TOPIC', 'Topic']])
@ -75,7 +84,13 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
this.workbasket.type = type;
}
onSave() {
onSubmit() {
if (this.workbasketForm && this.formsValidatorService.validate(this.workbasketForm, this.toogleValidationMap)) {
this.onSave();
}
}
private onSave() {
this.beforeRequest();
if (!this.workbasket.workbasketId) {
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 { AlertService } from 'app/services/alert/alert.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 { 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 { AlertComponent } from 'app/shared/alert/alert.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 { RemoveNoneTypePipe } from 'app/shared/pipes/removeNoneType/remove-none-type.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({
selector: 'taskana-filter',
template: ''
@ -55,37 +60,6 @@ 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>;
@ -107,7 +81,7 @@ describe('WorkbasketDetailsComponent', () => {
declarations: [WorkbasketDetailsComponent, WorkbasketInformationComponent, SpinnerComponent,
IconTypeComponent, MapValuesPipe, RemoveNoneTypePipe, AlertComponent, GeneralMessageModalComponent, AccessItemsComponent,
DistributionTargetsComponent, FilterComponent, DualListComponent, DummyDetailComponent,
TaskanaTypeAheadComponent, SelectWorkBasketPipe],
TaskanaTypeAheadMockComponent, SelectWorkBasketPipe],
providers: [WorkbasketService, MasterAndDetailService, ErrorModalService, RequestInProgressService,
AlertService, SavingWorkbasketService,
CustomFieldsService]

View File

@ -17,7 +17,7 @@
</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>
</div>
</li>

View File

@ -1,34 +1,21 @@
import { Component, OnInit, Input, Output, EventEmitter, AfterViewChecked } from '@angular/core';
import { trigger, state, style, transition, animate, keyframes } from '@angular/animations';
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { SortingModel } from 'app/models/sorting';
import { FilterModel } from 'app/models/filter';
import { Subscription } from 'rxjs/Subscription';
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 { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { AlertService } from 'app/services/alert/alert.service';
import { ImportType } from 'app/models/import-type';
import { expandDown } from 'app/shared/animations/expand.animation';
@Component({
selector: 'taskana-workbasket-list-toolbar',
animations: [
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' })])))
]
)],
animations: [expandDown],
templateUrl: './workbasket-list-toolbar.component.html',
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 { OrientationService } from './services/orientation/orientation.service';
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) => {
@ -33,9 +35,10 @@ export const configureTests = (configure: (testBed: TestBed) => void) => {
configure(testBed);
testBed.configureTestingModule({
imports: [BrowserAnimationsModule],
providers: [{ provide: TaskanaEngineService, useClass: TaskanaEngineServiceMock },
{ provide: DomainService, useClass: DomainServiceMock }, CustomFieldsService, RemoveConfirmationService,
AlertService, ErrorModalService, RequestInProgressService, OrientationService, SelectedRouteService]
AlertService, ErrorModalService, RequestInProgressService, OrientationService, SelectedRouteService, FormsValidatorService]
});
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 [@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">
<div class="row">
<ul class="nav">

View File

@ -2,29 +2,18 @@ import { Component, OnInit, OnDestroy } from '@angular/core';
import { environment } from 'environments/environment';
import { SelectedRouteService } from 'app/services/selected-route/selected-route';
import { Subscription } from 'rxjs/Subscription';
import { trigger, state, style, transition, keyframes, animate } from '@angular/animations';
import { DomainService } from 'app/services/domain/domain.service';
import { BusinessAdminGuard } from 'app/guards/business-admin-guard';
import { MonitorGuard } from 'app/guards/monitor-guard';
import { WindowRefService } from 'app/services/window/window.service';
import { UserGuard } from 'app/guards/user-guard';
import { TaskanaEngineService } from '../../services/taskana-engine/taskana-engine.service';
import { expandRight } from 'app/shared/animations/expand.animation';
@Component({
selector: 'taskana-nav-bar',
templateUrl: './nav-bar.component.html',
styleUrls: ['./nav-bar.component.scss'],
animations: [
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' })])))
]
)],
animations: [expandRight],
})
export class NavBarComponent implements OnInit, OnDestroy {

View File

@ -1,10 +1,6 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { environment } from 'environments/environment';
import { UserInfoModel } from 'app/models/user-info';
import { ReplaySubject } from 'rxjs/ReplaySubject';
@Injectable()
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">
<span class="glyphicon {{alert.type === 'success'? 'glyphicon-thumbs-up': 'glyphicon-exclamation-sign' }}" aria-hidden="true"></span>
{{alert.text}}

View File

@ -1,25 +1,14 @@
import { Component, OnInit } from '@angular/core';
import { trigger, state, style, animate, transition } from '@angular/animations';
import { AlertModel } from 'app/models/alert';
import { AlertService } from 'app/services/alert/alert.service';
import { expandTop } from '../animations/expand.animation';
@Component({
selector: 'taskana-alert',
templateUrl: './alert.component.html',
styleUrls: ['./alert.component.scss'],
animations: [
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' }))
])
])
]
animations: [expandTop]
})
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 { TaskanaTreeComponent } from 'app/shared/tree/tree.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';
/**
@ -31,7 +32,7 @@ import { MapToIterable } from './pipes/mapToIterable/mapToIterable';
*/
import { HttpClientInterceptor } from './services/httpClientInterceptor/http-client-interceptor.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,
multi: true
},
AccessIdsService
AccessIdsService,
FormsValidatorService
]
})
export class SharedModule {

View File

@ -32,7 +32,6 @@
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: ''}}">
placeholder="{{accessItemName.invalid? placeHolderMessage: ''}}" [@validation]="validationValue">
</div>
</div>

View File

@ -1,20 +1,25 @@
$blue: #2e9eca;
$grey: #ddd;
$invalid: #a94442;
.wrapper-text {
height: 47px;
& label {
width: 100%;
margin-bottom: 0px;
padding-left: 12px;
font-style: italic;
}
& div {
width: 100%;
border-bottom: 1px solid $grey;
margin-top:6px;
padding-left: 12px;
}
> div{
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
padding-left: 12px;
}
}
@ -30,6 +35,7 @@ $grey: #ddd;
&:focus{
border-bottom: 1px solid $blue;
}
padding-left: 12px;
}
@ -50,3 +56,15 @@ $grey: #ddd;
position: absolute;
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 { 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';
import { highlight } from 'app/shared/animations/validation.animation';
const noop = () => {
};
@Component({
selector: 'taskana-type-ahead',
templateUrl: './type-ahead.component.html',
styleUrls: ['./type-ahead.component.scss'],
animations: [highlight],
providers: [
{
provide: NG_VALUE_ACCESSOR,
@ -29,6 +28,9 @@ export class TypeAheadComponent implements OnInit, ControlValueAccessor {
@Input()
placeHolderMessage;
@Input()
validationValue;
@ViewChild('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 { trigger, transition, keyframes, style, animate, state } from '@angular/animations';
import { opacity } from 'app/shared/animations/expand.animation';
@Component({
selector: 'taskana-code',
templateUrl: './code.component.html',
styleUrls: ['./code.component.scss'],
animations: [
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 })])))
])
]
animations: [opacity]
})
export class CodeComponent implements OnInit {

View File

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