TSK-636 Validate fields after submit form

Only validate fields after form submission.
This commit is contained in:
Martin Rojas Miguel Angel 2018-07-16 13:00:41 +02:00 committed by Holger Hagen
parent d44e88b6cc
commit abb7ae818f
30 changed files with 182 additions and 168 deletions

View File

@ -10,8 +10,7 @@
<div id="classification" class="panel panel-default classification">
<div class="panel-heading">
<div class="pull-right">
<button type="button" (click)="onSubmit()" 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">
@ -32,17 +31,17 @@
<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" [@validation]="this.toogleValidationMap.get('classification.key')">
* Key is required
</div>
<taskana-field-error-display *ngIf="action === 'CREATE'" [displayError]="!isFieldValid('classification.key')" [validationTrigger]="this.toogleValidationMap.get('classification.key')"
errorMessage="* Key is required">
</taskana-field-error-display>
</div>
<div class="form-group required">
<label for="classification-name" class="control-label">Name</label>
<input type="text" required #name="ngModel" class="form-control" id="classification-name" placeholder="Name" [(ngModel)]="classification.name"
name="classification.name">
<div *ngIf="!name.valid" class="required-text" [@validation]="this.toogleValidationMap.get('classification.name')">
* Name is required
</div>
<taskana-field-error-display [displayError]="!isFieldValid('classification.name')" [validationTrigger]="this.toogleValidationMap.get('classification.name')"
errorMessage="* Name is required">
</taskana-field-error-display>
</div>
<div class="form-group">
<label for="classification-domain" class="control-label">Domain</label>

View File

@ -10,7 +10,6 @@ import { configureTests } from 'app/app.test.configuration';
import { ClassificationDetailsComponent } from './classification-details.component';
import { SpinnerComponent } from 'app/shared/spinner/spinner.component';
import { ClassificationDefinition } from 'app/models/classification-definition';
import { LinksClassification } from 'app/models/links-classfication';
import { Pair } from 'app/models/pair';
@ -53,7 +52,7 @@ describe('ClassificationDetailsComponent', () => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
imports: [FormsModule, HttpClientModule, RouterTestingModule.withRoutes(routes), AngularSvgIconModule],
declarations: [ClassificationDetailsComponent, SpinnerComponent, DummyDetailComponent],
declarations: [ClassificationDetailsComponent, DummyDetailComponent],
providers: [MasterAndDetailService, RequestInProgressService, ClassificationsService, HttpClient, ErrorModalService, AlertService,
TreeService, ClassificationTypesService, ClassificationCategoriesService,
CustomFieldsService]

View File

@ -83,7 +83,8 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
private domainService: DomainService,
private customFieldsService: CustomFieldsService,
private removeConfirmationService: RemoveConfirmationService,
private formsValidatorService: FormsValidatorService) { }
private formsValidatorService: FormsValidatorService) {
}
ngOnInit() {
this.classificationTypeService.getClassificationTypes().subscribe((classificationTypes: Array<string>) => {
@ -91,7 +92,7 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
})
this.classificationSelectedSubscription = this.classificationsService.getSelectedClassification()
.subscribe(classificationSelected => {
this.classification = undefined;
this.initProperties();
if (classificationSelected) {
this.fillClassificationInformation(classificationSelected);
}
@ -142,12 +143,22 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
`You are going to delete classification: ${this.classification.key}. Can you confirm this action?`);
}
isFieldValid(field: string): boolean {
return this.formsValidatorService.isFieldValid(this.classificationForm, field);
}
onSubmit() {
this.formsValidatorService.formSubmitAttempt = true;
if (this.formsValidatorService.validate(this.classificationForm, this.toogleValidationMap)) {
this.onSave();
this.onSave();
}
}
private initProperties() {
this.classification = undefined;
this.action = undefined
}
private onSave() {
this.requestInProgressService.setRequestInProgress(true);
if (this.action === ACTION.CREATE) {
@ -177,6 +188,7 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
}
onClear() {
this.formsValidatorService.formSubmitAttempt = false;
this.alertService.triggerAlert(new AlertModel(AlertType.INFO, 'Reset edited fields'))
this.classification = { ...this.classificationClone };
}

View File

@ -1,6 +1,6 @@
import { Component, Input } from '@angular/core';
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { Routes } from '@angular/router';
@ -12,10 +12,7 @@ import { TreeNodeModel } from 'app/models/tree-node';
import { ClassificationListComponent } from './classification-list.component';
import { ImportExportComponent } from 'app/administration/components/import-export/import-export.component';
import { SpinnerComponent } from 'app/shared/spinner/spinner.component';
import { ClassificationTypesSelectorComponent } from 'app/shared/classification-types-selector/classification-types-selector.component';
import { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component';
import { MapValuesPipe } from 'app/shared/pipes/mapValues/map-values.pipe';
import { WorkbasketDefinitionService } from 'app/administration/services/workbasket-definition/workbasket-definition.service';
import { AlertService } from 'app/services/alert/alert.service';
@ -30,17 +27,7 @@ import {
ClassificationCategoriesService
} from 'app/administration/services/classification-categories-service/classification-categories.service';
import { Pair } from 'app/models/pair';
@Component({
selector: 'taskana-tree',
template: ''
})
class TaskanaTreeComponent {
@Input() treeNodes;
@Input() selectNodeId;
@Input() filterText;
@Input() filterIcon;
}
import { TreeService } from 'app/services/tree/tree.service';
@Component({
selector: 'taskana-dummy-detail',
@ -50,7 +37,6 @@ class DummyDetailComponent {
}
const routes: Routes = [
{ path: ':id', component: DummyDetailComponent }
];
@ -65,12 +51,12 @@ describe('ClassificationListComponent', () => {
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
declarations: [ClassificationListComponent, ImportExportComponent, SpinnerComponent, ClassificationTypesSelectorComponent,
TaskanaTreeComponent, DummyDetailComponent, IconTypeComponent, MapValuesPipe],
declarations: [ClassificationListComponent, ImportExportComponent, ClassificationTypesSelectorComponent,
DummyDetailComponent],
imports: [HttpClientModule, RouterTestingModule.withRoutes(routes), FormsModule, AngularSvgIconModule, HttpModule],
providers: [
HttpClient, WorkbasketDefinitionService, AlertService, ClassificationsService, DomainService, ClassificationDefinitionService,
ErrorModalService, ClassificationTypesService, RequestInProgressService, ClassificationCategoriesService
ErrorModalService, ClassificationTypesService, RequestInProgressService, ClassificationCategoriesService, TreeService
]
})
};

View File

@ -13,8 +13,7 @@ describe('IconTypeComponent', () => {
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
imports: [AngularSvgIconModule, HttpClientModule, HttpModule],
declarations: [IconTypeComponent]
imports: [AngularSvgIconModule, HttpClientModule, HttpModule]
})
};
configureTests(configure).then(testBed => {

View File

@ -51,12 +51,12 @@
'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"
[validationValue]="toogleValidationAccessIdMap.get(index)"></taskana-type-ahead>
[validationValue]="toogleValidationAccessIdMap.get(index)" [displayError]="!isFieldValid('accessItem.value.accessId', 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 }">
!accessItem.value.accessId && formSubmitAttempt}">
<input type="text" class="form-control" formControlName="accessId" placeholder="{{accessItem.invalid?
'* Access id is required': ''}}" [@validation]="toogleValidationAccessIdMap.get(index)">
</div>

View File

@ -1,6 +1,6 @@
import { SimpleChange, Component, forwardRef, Input } from '@angular/core';
import { SimpleChange } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http';
import { AngularSvgIconModule } from 'angular-svg-icon';
@ -15,9 +15,6 @@ import { WorkbasketAccessItemsResource } from 'app/models/workbasket-access-item
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 } from 'app/administration/services/saving-workbaskets/saving-workbaskets.service';
@ -36,7 +33,7 @@ describe('AccessItemsComponent', () => {
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
declarations: [SpinnerComponent, AccessItemsComponent, GeneralMessageModalComponent, TaskanaTypeAheadMockComponent],
declarations: [AccessItemsComponent],
imports: [FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule, ReactiveFormsModule],
providers: [WorkbasketService, AlertService, ErrorModalService, SavingWorkbasketService, RequestInProgressService,
CustomFieldsService]

View File

@ -16,6 +16,7 @@ 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 { highlight } from 'app/shared/animations/validation.animation';
import { FormsValidatorService } from 'app/shared/services/forms/forms-validator.service';
declare const $: any;
@Component({
@ -61,7 +62,6 @@ export class AccessItemsComponent implements OnChanges, OnDestroy {
])
});
toogleValidationAccessIdMap = new Map<number, boolean>();
private initialized = false;
setAccessItemsGroups(accessItems: Array<WorkbasketAccessItems>) {
@ -84,7 +84,8 @@ export class AccessItemsComponent implements OnChanges, OnDestroy {
private savingWorkbaskets: SavingWorkbasketService,
private requestInProgressService: RequestInProgressService,
private customFieldService: CustomFieldsService,
private formBuilder: FormBuilder) {
private formBuilder: FormBuilder,
private formsValidatorService: FormsValidatorService) {
}
ngOnChanges(changes: SimpleChanges): void {
@ -130,6 +131,7 @@ export class AccessItemsComponent implements OnChanges, OnDestroy {
}
clear() {
this.formsValidatorService.formSubmitAttempt = false;
this.AccessItemsForm.reset();
this.setAccessItemsGroups(this.accessItemsResetClone);
this.accessItemsClone = this.cloneAccessItems(this.accessItemsResetClone);
@ -141,8 +143,13 @@ export class AccessItemsComponent implements OnChanges, OnDestroy {
this.accessItemsClone.splice(index, 1);
}
isFieldValid(field: string, index: number): boolean {
return this.formsValidatorService.isFieldValid(this.accessItemsGroups[index], field);
}
onSubmit() {
let valid = true;
this.formsValidatorService.formSubmitAttempt = true;
for (let i = 0; i < this.accessItemsGroups.length; i++) {
if (this.accessItemsGroups.controls[i].invalid) {
const validationState = this.toogleValidationAccessIdMap.get(i);

View File

@ -1,5 +1,5 @@
import { Input, Component, SimpleChange } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Observable } from 'rxjs/Observable';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
@ -21,31 +21,9 @@ import { RequestInProgressService } from 'app/services/requestInProgress/request
import { DualListComponent } from './dual-list/dual-list.component';
import { DistributionTargetsComponent, Side } from './distribution-targets.component';
import { SpinnerComponent } from 'app/shared/spinner/spinner.component';
import { GeneralMessageModalComponent } from 'app/shared/general-message-modal/general-message-modal.component';
import { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component';
import { SelectWorkBasketPipe } from 'app/shared/pipes/selectedWorkbasket/seleted-workbasket.pipe';
import { LinksWorkbasketSummary } from 'app/models/links-workbasket-summary';
import { configureTests } from 'app/app.test.configuration';
const workbasketSummaryResource: WorkbasketSummaryResource = new WorkbasketSummaryResource({
'workbaskets': new Array<WorkbasketSummary>(
new WorkbasketSummary('1', 'key1', 'NAME1', 'description 1', 'owner 1', '', '', 'PERSONAL', '', '', '', ''),
new WorkbasketSummary('2', 'key2', 'NAME2', 'description 2', 'owner 2', '', '', 'GROUP', '', '', '', ''))
}, new LinksWorkbasketSummary({ 'href': 'url' }));
@Component({
selector: 'taskana-filter',
template: ''
})
export class FilterComponent {
@Input()
target: string;
}
describe('DistributionTargetsComponent', () => {
let component: DistributionTargetsComponent;
let fixture: ComponentFixture<DistributionTargetsComponent>;
@ -57,8 +35,7 @@ describe('DistributionTargetsComponent', () => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
imports: [AngularSvgIconModule, HttpClientModule, HttpModule, JsonpModule],
declarations: [DistributionTargetsComponent, SpinnerComponent, GeneralMessageModalComponent,
FilterComponent, SelectWorkBasketPipe, IconTypeComponent, DualListComponent],
declarations: [DistributionTargetsComponent, DualListComponent],
providers: [WorkbasketService, AlertService, SavingWorkbasketService, ErrorModalService, RequestInProgressService,
]
})

View File

@ -29,28 +29,29 @@
<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" [@validation]="this.toogleValidationMap.get('workbasket.key')">
* Key is required
</div>
<taskana-field-error-display [displayError]="!isFieldValid('workbasket.key')" [validationTrigger]="this.toogleValidationMap.get('workbasket.key')"
errorMessage="* Key is required">
</taskana-field-error-display>
</div>
<div class="form-group required">
<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" [@validation]="this.toogleValidationMap.get('workbasket.name')">
* Name is required
</div>
<taskana-field-error-display [displayError]="!isFieldValid('workbasket.name')" [validationTrigger]="this.toogleValidationMap.get('workbasket.name')"
errorMessage="* Name is required">
</taskana-field-error-display>
</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="workbasket.owner" [(ngModel)]="workbasket.owner"
placeHolderMessage="* Owner is required" [validationValue]="this.toogleValidationMap.get('workbasket.owner')"></taskana-type-ahead>
placeHolderMessage="* Owner is required" [validationValue]="this.toogleValidationMap.get('workbasket.owner')"
[displayError]="!isFieldValid('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" [@validation]="this.toogleValidationMap.get('workbasket.owner')">
* Owner is required
</div>
<taskana-field-error-display [displayError]="!isFieldValid('workbasket.owner')" [validationTrigger]="this.toogleValidationMap.get('workbasket.owner')"
errorMessage="* Owner is required">
</taskana-field-error-display>
</ng-template>
</div>
<div class="form-group ">

View File

@ -15,14 +15,6 @@ import { ICONTYPES } from 'app/models/type';
import { ACTION } from 'app/models/action';
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 } from 'app/administration/services/saving-workbaskets/saving-workbaskets.service';
import { AlertService } from 'app/services/alert/alert.service';
@ -42,7 +34,7 @@ const routes: Routes = [
{ path: 'someNewId', component: DummyDetailComponent }
];
describe('InformationComponent', () => {
describe('WorkbasketInformationComponent', () => {
let component: WorkbasketInformationComponent;
let fixture: ComponentFixture<WorkbasketInformationComponent>;
let debugElement, workbasketService, alertService, savingWorkbasketService, requestInProgressService;
@ -50,9 +42,7 @@ describe('InformationComponent', () => {
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
declarations: [WorkbasketInformationComponent, IconTypeComponent, MapValuesPipe,
RemoveNoneTypePipe, SpinnerComponent, GeneralMessageModalComponent, DummyDetailComponent,
TaskanaTypeAheadMockComponent],
declarations: [WorkbasketInformationComponent, DummyDetailComponent],
imports: [FormsModule,
AngularSvgIconModule,
HttpClientModule,

View File

@ -17,12 +17,10 @@ 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']
})
@ -68,7 +66,6 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
}
ngOnInit(): void {
}
ngOnChanges(changes: SimpleChanges): void {
@ -85,33 +82,18 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
}
onSubmit() {
this.formsValidatorService.formSubmitAttempt = true;
if (this.workbasketForm && this.formsValidatorService.validate(this.workbasketForm, this.toogleValidationMap)) {
this.onSave();
}
}
private onSave() {
this.beforeRequest();
if (!this.workbasket.workbasketId) {
this.postNewWorkbasket();
return true;
}
this.workbasketSubscription = (this.workbasketService.updateWorkbasket(this.workbasket._links.self.href, this.workbasket).subscribe(
workbasketUpdated => {
this.afterRequest();
this.workbasket = workbasketUpdated;
this.workbasketClone = { ...this.workbasket };
this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, `Workbasket ${workbasketUpdated.key} was saved successfully`))
},
error => {
this.afterRequest();
this.errorModalService.triggerError(new ErrorModel('There was error while saving your workbasket', error))
}
));
isFieldValid(field: string): boolean {
return this.formsValidatorService.isFieldValid(this.workbasketForm, field);
}
onClear() {
this.formsValidatorService.formSubmitAttempt = false;
this.alertService.triggerAlert(new AlertModel(AlertType.INFO, 'Reset edited fields'))
this.workbasket = { ...this.workbasketClone };
}
@ -138,6 +120,27 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
});
}
private onSave() {
this.beforeRequest();
if (!this.workbasket.workbasketId) {
this.postNewWorkbasket();
return true;
}
this.workbasketSubscription = (this.workbasketService.updateWorkbasket(this.workbasket._links.self.href, this.workbasket).subscribe(
workbasketUpdated => {
this.afterRequest();
this.workbasket = workbasketUpdated;
this.workbasketClone = { ...this.workbasket };
this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, `Workbasket ${workbasketUpdated.key} was saved successfully`))
},
error => {
this.afterRequest();
this.errorModalService.triggerError(new ErrorModel('There was error while saving your workbasket', error))
}
));
}
private beforeRequest() {
this.requestInProgressService.setRequestInProgress(true);
}

View File

@ -1,8 +1,8 @@
import { Component, Input, forwardRef } from '@angular/core';
import { Component } from '@angular/core';
import { ComponentFixture, TestBed, } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { Router, Routes } from '@angular/router';
import { FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http';
@ -31,27 +31,6 @@ import { WorkbasketInformationComponent } from './information/workbasket-informa
import { AccessItemsComponent } from './access-items/access-items.component';
import { DistributionTargetsComponent } from './distribution-targets/distribution-targets.component';
import { DualListComponent } from './distribution-targets//dual-list/dual-list.component';
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';
@Component({
selector: 'taskana-filter',
template: ''
})
export class FilterComponent {
@Input()
target: string;
}
@Component({
selector: 'taskana-dummy-detail',
@ -78,10 +57,9 @@ describe('WorkbasketDetailsComponent', () => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
imports: [RouterTestingModule.withRoutes(routes), FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule, ReactiveFormsModule],
declarations: [WorkbasketDetailsComponent, WorkbasketInformationComponent, SpinnerComponent,
IconTypeComponent, MapValuesPipe, RemoveNoneTypePipe, AlertComponent, GeneralMessageModalComponent, AccessItemsComponent,
DistributionTargetsComponent, FilterComponent, DualListComponent, DummyDetailComponent,
TaskanaTypeAheadMockComponent, SelectWorkBasketPipe],
declarations: [WorkbasketDetailsComponent, WorkbasketInformationComponent,
AccessItemsComponent,
DistributionTargetsComponent, DualListComponent, DummyDetailComponent],
providers: [WorkbasketService, MasterAndDetailService, ErrorModalService, RequestInProgressService,
AlertService, SavingWorkbasketService,
CustomFieldsService]

View File

@ -1,15 +1,10 @@
import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '@angular/core';
import { ActivatedRoute, Params, Router, NavigationStart } from '@angular/router';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';
import { Workbasket } from 'app/models/workbasket';
import { WorkbasketSummary } from 'app/models/workbasket-summary';
import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resource';
import { ICONTYPES } from 'app/models/type';
import { ErrorModel } from 'app/models/modal-error';
import { ACTION } from 'app/models/action';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service'
import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-detail.service'
import { DomainService } from 'app/services/domain/domain.service';

View File

@ -8,6 +8,7 @@ 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 'app/shared/services/forms/forms-validator.service';
@Component({
selector: 'taskana-root',
@ -39,13 +40,15 @@ export class AppComponent implements OnInit, OnDestroy {
private errorModalService: ErrorModalService,
private requestInProgressService: RequestInProgressService,
private orientationService: OrientationService,
private selectedRouteService: SelectedRouteService) {
private selectedRouteService: SelectedRouteService,
private formsValidatorService: FormsValidatorService) {
}
ngOnInit() {
this.routerSubscription = this.router.events.subscribe(event => {
if (event instanceof NavigationStart) {
this.selectedRouteService.selectRoute(event);
this.formsValidatorService.formSubmitAttempt = false;
}
});
this.errorModalSubscription = this.errorModalService.getError().subscribe((error: ErrorModel) => {

View File

@ -33,6 +33,9 @@ import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.se
import { WindowRefService } from 'app/services/window/window.service';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
import { RemoveConfirmationService } from './services/remove-confirmation/remove-confirmation.service';
import { FormsValidatorService } from './shared/services/forms/forms-validator.service';
/**
* Components
@ -108,7 +111,8 @@ export function startupServiceFactory(startupService: StartupService): () => Pro
TitlesService,
CustomFieldsService,
TaskanaEngineService,
RemoveConfirmationService
RemoveConfirmationService,
FormsValidatorService
],
bootstrap: [AppComponent]
})

View File

@ -22,6 +22,7 @@ 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';
import { SharedModule } from './shared/shared.module';
export const configureTests = (configure: (testBed: TestBed) => void) => {
@ -35,7 +36,7 @@ export const configureTests = (configure: (testBed: TestBed) => void) => {
configure(testBed);
testBed.configureTestingModule({
imports: [BrowserAnimationsModule],
imports: [BrowserAnimationsModule, SharedModule],
providers: [{ provide: TaskanaEngineService, useClass: TaskanaEngineServiceMock },
{ provide: DomainService, useClass: DomainServiceMock }, CustomFieldsService, RemoveConfirmationService,
AlertService, ErrorModalService, RequestInProgressService, OrientationService, SelectedRouteService, FormsValidatorService]

View File

@ -1,12 +1,10 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { HttpModule } from '@angular/http';
import { UserInformationComponent } from './user-information.component';
import { UserInfoModel } from 'app/models/user-info';
import { configureTests } from 'app/app.test.configuration';
describe('UserInformationComponent', () => {

View File

@ -0,0 +1,3 @@
<div *ngIf="displayError" class="required-text" [@validation]="validationTrigger">
{{errorMessage}}
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FieldErrorDisplayComponent } from './field-error-display.component';
describe('FieldErrorDisplayComponent', () => {
let component: FieldErrorDisplayComponent;
let fixture: ComponentFixture<FieldErrorDisplayComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FieldErrorDisplayComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FieldErrorDisplayComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,24 @@
import { Component, OnInit, Input } from '@angular/core';
import { highlight } from 'app/shared/animations/validation.animation';
@Component({
selector: 'taskana-field-error-display',
templateUrl: './field-error-display.component.html',
animations: [highlight],
styleUrls: ['./field-error-display.component.scss']
})
export class FieldErrorDisplayComponent implements OnInit {
@Input()
displayError: boolean;
@Input()
errorMessage: string;
@Input()
validationTrigger: boolean;
constructor() { }
ngOnInit() {
}
}

View File

@ -4,9 +4,7 @@ import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http';
import { IconTypeComponent } from '../../administration/components/type-icon/icon-type.component';
import { FilterComponent } from './filter.component';
import { MapValuesPipe } from 'app/shared/pipes/mapValues/map-values.pipe';
import { configureTests } from 'app/app.test.configuration';
describe('FilterComponent', () => {
@ -18,7 +16,7 @@ describe('FilterComponent', () => {
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
declarations: [FilterComponent, IconTypeComponent, MapValuesPipe],
declarations: [],
imports: [AngularSvgIconModule, FormsModule, HttpClientModule, HttpModule]
})
};

View File

@ -10,7 +10,7 @@ describe('RemoveConfirmationComponent', () => {
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
declarations: [RemoveConfirmationComponent],
declarations: [],
providers: []
})
};

View File

@ -5,8 +5,12 @@ import { AlertModel, AlertType } from 'app/models/alert';
@Injectable()
export class FormsValidatorService {
public formSubmitAttempt = false;
constructor(
private alertService: AlertService) { }
private alertService: AlertService) {
}
public validate(form: NgForm, toogleValidationMap: Map<any, boolean>): boolean {
let valid = true;
@ -22,4 +26,15 @@ export class FormsValidatorService {
}
return valid;
}
public isFieldValid(ngForm: NgForm, field: string) {
if (!ngForm || !ngForm.form.controls || !ngForm.form.controls[field]) {
return false;
}
if (!this.formSubmitAttempt) {
return true;
}
return (this.formSubmitAttempt && ngForm.form.controls[field].valid) ||
(ngForm.form.controls[field].touched && ngForm.form.controls[field].valid);
}
}

View File

@ -18,6 +18,7 @@ import { SortComponent } from './sort/sort.component';
import { RemoveConfirmationComponent } from 'app/shared/remove-confirmation/remove-confirmation.component';
import { FilterComponent } from 'app/shared/filter/filter.component';
import { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component';
import { FieldErrorDisplayComponent } from 'app/shared/field-error-display/field-error-display.component';
/**
* Pipes
@ -34,9 +35,6 @@ 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 { FormsValidatorService } from './services/forms/forms-validator.service';
const MODULES = [
CommonModule,
@ -65,7 +63,8 @@ const DECLARATIONS = [
SortComponent,
FilterComponent,
IconTypeComponent,
RemoveConfirmationComponent
RemoveConfirmationComponent,
FieldErrorDisplayComponent
];
@NgModule({
@ -79,7 +78,6 @@ const DECLARATIONS = [
multi: true
},
AccessIdsService,
FormsValidatorService
]
})
export class SharedModule {

View File

@ -1,5 +1,4 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {MapValuesPipe} from 'app/shared/pipes/mapValues/map-values.pipe';
import {SortComponent} from './sort.component';
import {configureTests} from 'app/app.test.configuration';
@ -13,7 +12,7 @@ describe('SortComponent', () => {
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
declarations: [SortComponent, MapValuesPipe]
declarations: []
});
};
configureTests(configure).then(testBed => {

View File

@ -38,7 +38,7 @@ describe('TaskanaTreeComponent', () => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
imports: [AngularSvgIconModule, HttpClientModule, HttpModule],
declarations: [TaskanaTreeComponent, TreeVendorComponent],
declarations: [TreeVendorComponent],
providers: [TreeService, ClassificationCategoriesService]
})

View File

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

View File

@ -31,6 +31,9 @@ export class TypeAheadComponent implements OnInit, ControlValueAccessor {
@Input()
validationValue;
@Input()
displayError;
@ViewChild('inputTypeAhead')
private inputTypeAhead;