TSK-1315: Moved validation from components to service

This commit is contained in:
Tristan Eisermann 2020-07-23 10:34:25 +02:00 committed by Tristan2357
parent 78ba08fd77
commit 153f783a8a
7 changed files with 120 additions and 97 deletions

View File

@ -39,9 +39,11 @@
<!-- KEY -->
<div class="form-group required">
<label for="classification-key" class="control-label">Key</label>
<input type="text" required maxlength="32" #key="ngModel" [disabled]="!isCreatingNewClassification" class="form-control"
id="classification-key" placeholder="Key" [(ngModel)]="classification.key" name="classification.key" (keypress)="onKeyPressed($event, key, 32)">
<div *ngIf="tooLongMap.get(key.name)" class="error">{{lengthError}}</div>
<input type="text" required maxlength="32" #key="ngModel" [disabled]="!isCreatingNewClassification"
class="form-control"
id="classification-key" placeholder="Key" [(ngModel)]="classification.key"
name="classification.key" (keypress)="validateKeypress(key, 32)">
<div *ngIf="inputOverflowMap.get(key.name)" class="error">{{lengthError}}</div>
<taskana-shared-field-error-display [displayError]="key.invalid && key.dirty"
errorMessage="* Key is required">
</taskana-shared-field-error-display>
@ -53,8 +55,9 @@
<input type="text" required maxlength="255" #name="ngModel"
class="form-control"
id="classification-name" placeholder="Name"
[(ngModel)]="classification.name" name="classification.name" (keypress)="onKeyPressed($event, name, 255)">
<div *ngIf="tooLongMap.get(name.name)" class="error">{{lengthError}}</div>
[(ngModel)]="classification.name" name="classification.name"
(keypress)="validateKeypress(name, 255)">
<div *ngIf="inputOverflowMap.get(name.name)" class="error">{{lengthError}}</div>
<taskana-shared-field-error-display [displayError]="name.invalid && name.dirty"
errorMessage="* Name is required">
</taskana-shared-field-error-display>
@ -131,20 +134,22 @@
Level</label>
<input type="text" maxlength="255" class="form-control"
id="classification-service-level" placeholder="Service Level"
[(ngModel)]="classification.serviceLevel" name="classification.serviceLevel" #serviceLevel="ngModel" (keypress)="onKeyPressed($event, serviceLevel, 255)">
<div *ngIf="tooLongMap.get(serviceLevel.name)" class="error">{{lengthError}}</div>
[(ngModel)]="classification.serviceLevel" name="classification.serviceLevel"
#serviceLevel="ngModel" (keypress)="validateKeypress(serviceLevel, 255)">
<div *ngIf="inputOverflowMap.get(serviceLevel.name)" class="error">{{lengthError}}</div>
</div>
<!-- APPLICATION ENTRY POINT -->
<div class="form-group">
<label for="classification-application-entry-point" class="control-label">
Application entry point</label>
<input type="text" maxlength="255" class="form-control"
<input type="text" maxlength="255" class="form-control"
id="classification-application-entry-point"
placeholder="Application entry point"
[(ngModel)]="classification.applicationEntryPoint"
name="classification.applicationEntryPoint" #appEntryPoint="ngModel" (keypress)="onKeyPressed($event, appEntryPoint, 255)">
<div *ngIf="tooLongMap.get(appEntryPoint.name)" class="error">{{lengthError}}</div>
name="classification.applicationEntryPoint" #appEntryPoint="ngModel"
(keypress)="validateKeypress(appEntryPoint, 255)">
<div *ngIf="inputOverflowMap.get(appEntryPoint.name)" class="error">{{lengthError}}</div>
</div>
<!-- DESCRIPTION -->
@ -153,8 +158,9 @@
<textarea class="form-control" maxlength="255" rows="5"
id="classification-description" placeholder="Description"
[(ngModel)]="classification.description"
name="classification.description" #description="ngModel" (keypress)="onKeyPressed($event, description, 255)"></textarea>
<div *ngIf="tooLongMap.get(description.name)" class="error">{{lengthError}}</div>
name="classification.description" #description="ngModel"
(keypress)="validateKeypress(description, 255)"></textarea>
<div *ngIf="inputOverflowMap.get(description.name)" class="error">{{lengthError}}</div>
</div>
</div>
</div>
@ -170,8 +176,9 @@
<input type="text" maxlength="255" class="form-control"
id="classification-custom-{{i + 1}}" placeholder="{{customField.field}}"
[(ngModel)]="classification[getClassificationCustom(i + 1)]"
name="classification.custom{{i + 1}}" #custom="ngModel" (keypress)="onKeyPressed($event, custom, 255)">
<div *ngIf="tooLongMap.get(custom.name)" class="error">{{lengthError}}</div>
name="classification.custom{{i + 1}}" #custom="ngModel"
(keypress)="validateKeypress(custom, 255)">
<div *ngIf="inputOverflowMap.get(custom.name)" class="error">{{lengthError}}</div>
</div>
</div>
</div>

View File

@ -1,4 +1,4 @@
@import "src/theme/_colors.scss";
@import 'src/theme/_colors.scss';
.custom-field-row {
display: flex;
@ -20,7 +20,3 @@
input:invalid.dirty {
border-color: $invalid;
}
div.error {
color: $invalid;
}

View File

@ -50,15 +50,14 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
spinnerIsRunning = false;
customFields$: Observable<CustomField[]>;
isCreatingNewClassification: boolean = false;
readonly lengthError = 'You have reached the maximum length for this field';
inputOverflowMap = new Map<string, boolean>();
validateKeypress: Function;
@ViewChild('ClassificationForm', { static: false }) classificationForm: NgForm;
toggleValidationMap = new Map<string, boolean>();
destroy$ = new Subject<void>();
private timeout = new Map<string, Subscription>();
readonly lengthError = 'You have reached the maximal length';
tooLongMap = new Map<string, boolean>();
constructor(
private location: Location,
private requestInProgressService: RequestInProgressService,
@ -86,6 +85,13 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
.subscribe(() => {
this.store.dispatch(new SelectClassification(this.classification.classificationId));
});
this.formsValidatorService.inputOverflowObservable.subscribe((inputOverflowMap) => {
this.inputOverflowMap = inputOverflowMap;
});
this.validateKeypress = (inputFieldModel, maxLength) => {
this.formsValidatorService.validateKeypress(inputFieldModel, maxLength);
};
}
removeClassification() {
@ -239,17 +245,4 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
});
this.location.go(this.location.path().replace(/(classifications).*/g, 'classifications'));
}
onKeyPressed(event: KeyboardEvent, model: NgModel, max: Number): void {
if (this.timeout.has(model.name)) {
this.timeout.get(model.name).unsubscribe();
}
if (model.value.length >= max && !event.altKey && !event.ctrlKey) {
this.tooLongMap.set(model.name, true);
this.timeout.set(
model.name,
timer(3000).subscribe(() => this.tooLongMap.set(model.name, false))
);
}
}
}

View File

@ -37,10 +37,10 @@
<label for="wb-key" class="control-label">Key</label>
<input type="text" required maxlength="64" #key="ngModel" class="form-control" id="wb-key"
placeholder="Key"
[(ngModel)]="workbasket.key" name="workbasket.key" (keypress)="onKeyPressed(key, 64)">
<div *ngIf="tooLongMap.get(key.name)" class="error">{{lengthError}}</div>
[(ngModel)]="workbasket.key" name="workbasket.key" (keypress)="validateKeypress(key, 64)">
<div *ngIf="inputOverflowMap.get(key.name)" class="error">{{lengthError}}</div>
<taskana-shared-field-error-display [displayError]="!isFieldValid('workbasket.key')"
[validationTrigger]="this.toogleValidationMap.get('workbasket.key')"
[validationTrigger]="this.toggleValidationMap.get('workbasket.key')"
errorMessage="* Key is required">
</taskana-shared-field-error-display>
</div>
@ -50,10 +50,10 @@
<label for="wb-name" class="control-label">Name</label>
<input type="text" required maxlength="255" #name="ngModel" class="form-control" id="wb-name"
placeholder="Name"
[(ngModel)]="workbasket.name" name="workbasket.name" (keypress)="onKeyPressed(name, 255)">
<div *ngIf="tooLongMap.get(name.name)" class="error">{{lengthError}}</div>
[(ngModel)]="workbasket.name" name="workbasket.name" (keypress)="validateKeypress(name, 255)">
<div *ngIf="inputOverflowMap.get(name.name)" class="error">{{lengthError}}</div>
<taskana-shared-field-error-display [displayError]="!isFieldValid('workbasket.name')"
[validationTrigger]="this.toogleValidationMap.get('workbasket.name')"
[validationTrigger]="this.toggleValidationMap.get('workbasket.name')"
errorMessage="* Name is required">
</taskana-shared-field-error-display>
</div>
@ -65,18 +65,18 @@
name="workbasket.owner"
[(ngModel)]="workbasket.owner"
placeHolderMessage="* Owner is required"
[validationValue]="this.toogleValidationMap.get('workbasket.owner')"
[validationValue]="this.toggleValidationMap.get('workbasket.owner')"
[displayError]="!isFieldValid('workbasket.owner')"
width="100%" (keypress)="onKeyPressed(owner, 128)">
<div *ngIf="tooLongMap.get(owner.name)" class="error">{{lengthError}}</div>
width="100%" (keypress)="validateKeypress(owner, 128)">
<div *ngIf="inputOverflowMap.get(owner.name)" class="error">{{lengthError}}</div>
</taskana-shared-type-ahead>
<ng-template #ownerInput>
<input type="text" required maxlength="128" #owner="ngModel" class="form-control" id="wb-owner"
placeholder="Owner"
[(ngModel)]="workbasket.owner" name="workbasket.owner" (keypress)="onKeyPressed(owner, 128)">
<div *ngIf="tooLongMap.get(owner.name)" class="error">{{lengthError}}</div>
[(ngModel)]="workbasket.owner" name="workbasket.owner" (keypress)="validateKeypress(owner, 128)">
<div *ngIf="inputOverflowMap.get(owner.name)" class="error">{{lengthError}}</div>
<taskana-shared-field-error-display [displayError]="!isFieldValid('workbasket.owner')"
[validationTrigger]="this.toogleValidationMap.get('workbasket.owner')"
[validationTrigger]="this.toggleValidationMap.get('workbasket.owner')"
errorMessage="* Owner is required">
</taskana-shared-field-error-display>
</ng-template>
@ -116,10 +116,11 @@
</div>
<div class="form-group col-xs-8">
<label for="wb-description" class="control-label">Description</label>
<textarea #description="ngModel" maxlength="255" class="form-control" rows="7" id="wb-description" placeholder="Description"
<textarea #description="ngModel" maxlength="255" class="form-control" rows="7" id="wb-description"
placeholder="Description"
[(ngModel)]="workbasket.description" name="workbasket.description"
(keypress)="onKeyPressed(description, 255)"></textarea>
<div *ngIf="tooLongMap.get(description.name)" class="error">{{lengthError}}</div>
(keypress)="validateKeypress(description, 255)"></textarea>
<div *ngIf="inputOverflowMap.get(description.name)" class="error">{{lengthError}}</div>
</div>
</div>
</div>
@ -128,29 +129,33 @@
<label for="wb-org-level-1" class="control-label">OrgLevel 1</label>
<input type="text" class="form-control" id="wb-org-level-1" placeholder="OrgLevel 1"
[(ngModel)]="workbasket.orgLevel1"
name="workbasket.orgLevel1" maxlength="255" #orgLevel1="ngModel" (keypress)="onKeyPressed(orgLevel1, 255)">
<div *ngIf="tooLongMap.get(orgLevel1.name)" class="error">{{lengthError}}</div>
name="workbasket.orgLevel1" maxlength="255" #orgLevel1="ngModel"
(keypress)="validateKeypress(orgLevel1, 255)">
<div *ngIf="inputOverflowMap.get(orgLevel1.name)" class="error">{{lengthError}}</div>
</div>
<div class="form-group">
<label for="wb-org-level-2" class="control-label">OrgLevel 2</label>
<input type="text" class="form-control" id="wb-org-level-2" placeholder="OrgLevel 2"
[(ngModel)]="workbasket.orgLevel2"
name="workbasket.orgLevel2" maxlength="255" #orgLevel2="ngModel" (keypress)="onKeyPressed(orgLevel2, 255)">
<div *ngIf="tooLongMap.get(orgLevel2.name)" class="error">{{lengthError}}</div>
name="workbasket.orgLevel2" maxlength="255" #orgLevel2="ngModel"
(keypress)="validateKeypress(orgLevel2, 255)">
<div *ngIf="inputOverflowMap.get(orgLevel2.name)" class="error">{{lengthError}}</div>
</div>
<div class="form-group" style="padding-top: 18px;">
<label for="wb-org-level-3" class="control-label">OrgLevel 3</label>
<input type="text" class="form-control" id="wb-org-level-3" placeholder="OrgLevel 3"
[(ngModel)]="workbasket.orgLevel3"
name="workbasket.orgLevel3" maxlength="255" #orgLevel3="ngModel" (keypress)="onKeyPressed(orgLevel3, 255)">
<div *ngIf="tooLongMap.get(orgLevel3.name)" class="error">{{lengthError}}</div>
name="workbasket.orgLevel3" maxlength="255" #orgLevel3="ngModel"
(keypress)="validateKeypress(orgLevel3, 255)">
<div *ngIf="inputOverflowMap.get(orgLevel3.name)" class="error">{{lengthError}}</div>
</div>
<div class="form-group">
<label for="wb-org-level-4" class="control-label">OrgLevel 4</label>
<input type="text" class="form-control" id="wb-org-level-4" placeholder="OrgLevel 4"
[(ngModel)]="workbasket.orgLevel4"
name="workbasket.orgLevel4" maxlength="255" #orgLevel4="ngModel" (keypress)="onKeyPressed(orgLevel4, 255)">
<div *ngIf="tooLongMap.get(orgLevel4.name)" class="error">{{lengthError}}</div>
name="workbasket.orgLevel4" maxlength="255" #orgLevel4="ngModel"
(keypress)="validateKeypress(orgLevel4, 255)">
<div *ngIf="inputOverflowMap.get(orgLevel4.name)" class="error">{{lengthError}}</div>
</div>
<ng-container *ngFor="let customField of customFields$ | async; let index = index">
<div *ngIf="customField.visible" class="form-group">
@ -158,8 +163,9 @@
<input type="text" class="form-control" id="wb-custom-{{index+1}}"
[placeholder]="customField.field"
[(ngModel)]="workbasket[getWorkbasketCustomProperty(index + 1)]"
name="workbasket[{{getWorkbasketCustomProperty(index + 1)}}]" maxlength="255" #custom="ngModel" (keypress)="onKeyPressed(custom, 255)">
<div *ngIf="tooLongMap.get(custom.name)" class="error">{{lengthError}}</div>
name="workbasket[{{getWorkbasketCustomProperty(index + 1)}}]" maxlength="255" #custom="ngModel"
(keypress)="validateKeypress(custom, 255)">
<div *ngIf="inputOverflowMap.get(custom.name)" class="error">{{lengthError}}</div>
</div>
</ng-container>
</div>

View File

@ -1,7 +1,7 @@
import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable, Subject, Subscription, timer } from 'rxjs';
import { NgForm, NgModel } from '@angular/forms';
import { Observable, Subject } from 'rxjs';
import { NgForm } from '@angular/forms';
import { Select, Store } from '@ngxs/store';
import { ICONTYPES } from 'app/shared/models/icon-types';
@ -9,7 +9,7 @@ import { ACTION } from 'app/shared/models/action';
import { customFieldCount, Workbasket } from 'app/shared/models/workbasket';
import { TaskanaDate } from 'app/shared/util/taskana.date';
import { SavingWorkbasketService, SavingInformation } from 'app/administration/services/saving-workbaskets.service';
import { SavingInformation, SavingWorkbasketService } from 'app/administration/services/saving-workbaskets.service';
import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service';
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
import { FormsValidatorService } from 'app/shared/services/forms-validator/forms-validator.service';
@ -45,17 +45,18 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
allTypes: Map<string, string>;
requestInProgress = false;
badgeMessage = '';
toogleValidationMap = new Map<string, boolean>();
toggleValidationMap = new Map<string, boolean>();
lookupField = false;
readonly lengthError = 'You have reached the maximum length for this field';
inputOverflowMap = new Map<string, boolean>();
validateKeypress: Function;
@Select(EngineConfigurationSelectors.workbasketsCustomisation)
workbasketsCustomisation$: Observable<WorkbasketsCustomisation>;
customFields$: Observable<CustomField[]>;
destroy$ = new Subject<void>();
readonly lengthError = 'You have reached the maximum length';
tooLongMap = new Map<string, boolean>();
private timeout = new Map<string, Subscription>();
constructor(
private workbasketService: WorkbasketService,
@ -84,6 +85,12 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
this.lookupField = workbasketsCustomization.information.owner.lookupField;
}
});
this.formsValidatorService.inputOverflowObservable.subscribe((inputOverflowMap) => {
this.inputOverflowMap = inputOverflowMap;
});
this.validateKeypress = (inputFieldModel, maxLength) => {
this.formsValidatorService.validateKeypress(inputFieldModel, maxLength);
};
}
ngOnChanges(changes: SimpleChanges) {
@ -101,7 +108,7 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
onSubmit() {
this.formsValidatorService.formSubmitAttempt = true;
this.formsValidatorService.validateFormInformation(this.workbasketForm, this.toogleValidationMap).then((value) => {
this.formsValidatorService.validateFormInformation(this.workbasketForm, this.toggleValidationMap).then((value) => {
if (value) {
this.onSave();
}
@ -190,18 +197,4 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
this.destroy$.next();
this.destroy$.complete();
}
onKeyPressed(model: NgModel, max: Number): void {
if (this.timeout.has(model.name)) {
this.timeout.get(model.name).unsubscribe();
}
console.log(model.name);
if (model.value.length >= max) {
this.tooLongMap.set(model.name, true);
this.timeout.set(
model.name,
timer(3000).subscribe(() => this.tooLongMap.set(model.name, false))
);
}
}
}

View File

@ -1,17 +1,24 @@
import { FormArray, NgForm } from '@angular/forms';
import { FormArray, NgForm, NgModel } from '@angular/forms';
import { Injectable } from '@angular/core';
import { AccessIdsService } from 'app/shared/services/access-ids/access-ids.service';
import { NOTIFICATION_TYPES } from '../../models/notifications';
import { NotificationService } from '../notifications/notification.service';
import { Observable, Subject, Subscription, timer } from 'rxjs';
@Injectable()
export class FormsValidatorService {
public formSubmitAttempt = false;
get inputOverflowObservable(): Observable<Map<string, boolean>> {
return this.inputOverflow.asObservable();
}
formSubmitAttempt = false;
private workbasketOwner = 'workbasket.owner';
private inputOverflowInternalMap = new Map<string, boolean>();
private inputOverflow = new Subject<Map<string, boolean>>();
private overflowErrorSubscriptionMap = new Map<string, Subscription>();
constructor(private notificationsService: NotificationService, private accessIdsService: AccessIdsService) {}
public async validateFormInformation(form: NgForm, toogleValidationMap: Map<any, boolean>): Promise<any> {
async validateFormInformation(form: NgForm, toggleValidationMap: Map<any, boolean>): Promise<any> {
let validSync = true;
if (!form) {
return false;
@ -19,8 +26,8 @@ export class FormsValidatorService {
const forFieldsPromise = new Promise((resolve) => {
Object.keys(form.form.controls).forEach((control) => {
if (control.indexOf('owner') === -1 && form.form.controls[control].invalid) {
const validationState = toogleValidationMap.get(control);
toogleValidationMap.set(this.workbasketOwner, !validationState);
const validationState = toggleValidationMap.get(control);
toggleValidationMap.set(this.workbasketOwner, !validationState);
validSync = false;
}
});
@ -31,14 +38,14 @@ export class FormsValidatorService {
const ownerString = 'owner';
if (form.form.controls[this.workbasketOwner]) {
this.accessIdsService.searchForAccessId(form.form.controls[this.workbasketOwner].value).subscribe((items) => {
const validationState = toogleValidationMap.get(this.workbasketOwner);
toogleValidationMap.set(this.workbasketOwner, !validationState);
const validationState = toggleValidationMap.get(this.workbasketOwner);
toggleValidationMap.set(this.workbasketOwner, !validationState);
const valid = items.find((item) => item.accessId === form.form.controls[this.workbasketOwner].value);
resolve(new ResponseOwner({ valid, field: ownerString }));
});
} else {
const validationState = toogleValidationMap.get(form.form.controls[this.workbasketOwner]);
toogleValidationMap.set(this.workbasketOwner, !validationState);
const validationState = toggleValidationMap.get(form.form.controls[this.workbasketOwner]);
toggleValidationMap.set(this.workbasketOwner, !validationState);
resolve(new ResponseOwner({ valid: true, field: ownerString }));
}
});
@ -58,14 +65,14 @@ export class FormsValidatorService {
return values[0] && responseOwner.valid;
}
public async validateFormAccess(form: FormArray, toogleValidationAccessIdMap: Map<any, boolean>): Promise<boolean> {
async validateFormAccess(form: FormArray, toggleValidationAccessIdMap: Map<any, boolean>): Promise<boolean> {
const ownerPromise: Array<Promise<boolean>> = new Array<Promise<boolean>>();
for (let i = 0; i < form.length; i++) {
ownerPromise.push(
new Promise((resolve) => {
const validationState = toogleValidationAccessIdMap.get(i);
toogleValidationAccessIdMap.set(i, !validationState);
const validationState = toggleValidationAccessIdMap.get(i);
toggleValidationAccessIdMap.set(i, !validationState);
this.accessIdsService.searchForAccessId(form.controls[i].value.accessId).subscribe((items) => {
resolve(new ResponseOwner({ valid: items.length > 0, field: 'access id' }));
});
@ -89,7 +96,7 @@ export class FormsValidatorService {
return result;
}
public isFieldValid(ngForm: NgForm, field: string) {
isFieldValid(ngForm: NgForm, field: string) {
if (!ngForm || !ngForm.form.controls || !ngForm.form.controls[field]) {
return false;
}
@ -101,6 +108,23 @@ export class FormsValidatorService {
(ngForm.form.controls[field].touched && ngForm.form.controls[field].valid)
);
}
validateKeypress(inputFieldModel: NgModel, maxLength: Number): void {
if (this.overflowErrorSubscriptionMap.has(inputFieldModel.name)) {
this.overflowErrorSubscriptionMap.get(inputFieldModel.name).unsubscribe();
}
if (inputFieldModel.value.length >= maxLength) {
this.inputOverflowInternalMap.set(inputFieldModel.name, true);
this.inputOverflow.next(this.inputOverflowInternalMap);
this.overflowErrorSubscriptionMap.set(
inputFieldModel.name,
timer(3000).subscribe(() => {
this.inputOverflowInternalMap.set(inputFieldModel.name, false);
this.inputOverflow.next(this.inputOverflowInternalMap);
})
);
}
}
}
function ResponseOwner(owner) {

View File

@ -156,6 +156,10 @@ svg-icon {
background-color: #e1e1e1;
}
div.error {
color: $invalid;
}
/*
*Remove bootstrap cols padding for master and detail component
*/
@ -196,7 +200,7 @@ svg-icon {
.form-group.required .control-label:after {
content: ' *';
color: red;
color: $invalid;
}
.user-select {