TSK-1315: Added maxlength validation in workbasket

Also small fixes and formating
This commit is contained in:
Tristan Eisermann 2020-07-22 17:48:20 +02:00 committed by Tristan2357
parent 33845db647
commit 78ba08fd77
5 changed files with 214 additions and 145 deletions

View File

@ -101,7 +101,9 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
onSubmit() { onSubmit() {
this.formsValidatorService.formSubmitAttempt = true; this.formsValidatorService.formSubmitAttempt = true;
this.formsValidatorService.validateFormInformation(this.classificationForm, this.toggleValidationMap).then(value => { this.formsValidatorService
.validateFormInformation(this.classificationForm, this.toggleValidationMap)
.then((value) => {
if (value) { if (value) {
this.onSave(); this.onSave();
} }
@ -244,7 +246,10 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
} }
if (model.value.length >= max && !event.altKey && !event.ctrlKey) { if (model.value.length >= max && !event.altKey && !event.ctrlKey) {
this.tooLongMap.set(model.name, true); this.tooLongMap.set(model.name, true);
this.timeout.set(model.name, timer(3000).subscribe(() => this.tooLongMap.set(model.name, false))); this.timeout.set(
model.name,
timer(3000).subscribe(() => this.tooLongMap.set(model.name, false))
);
} }
} }
} }

View File

@ -2,20 +2,25 @@
<div *ngIf="workbasket" id="wb-information" class="panel panel-default"> <div *ngIf="workbasket" id="wb-information" class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<div class="pull-right btn-group"> <div class="pull-right btn-group">
<button type="button" (click)="onSubmit()" data-toggle="tooltip" title="Save" class="btn btn-default btn-primary"> <button type="button" (click)="onSubmit()" data-toggle="tooltip" title="Save"
class="btn btn-default btn-primary">
<span class="material-icons md-20">save</span> <span class="material-icons md-20">save</span>
</button> </button>
<button type="button" (click)="onUndo()" data-toggle="tooltip" title="Undo Changes" class="btn btn-default"> <button type="button" (click)="onUndo()" data-toggle="tooltip" title="Undo Changes"
class="btn btn-default">
<span class="material-icons md-20 blue">undo</span> <span class="material-icons md-20 blue">undo</span>
</button> </button>
<button type="button" (click)="removeDistributionTargets()" data-toggle="tooltip" title="Remove workbasket as distribution target" <button type="button" (click)="removeDistributionTargets()" data-toggle="tooltip"
title="Remove workbasket as distribution target"
class="btn btn-default"> class="btn btn-default">
<span class="material-icons md-20 red">remove_circle_outline</span> <span class="material-icons md-20 red">remove_circle_outline</span>
</button> </button>
<button type="button" (click)="copyWorkbasket()" data-toggle="tooltip" title="Copy" class="btn btn-default"> <button type="button" (click)="copyWorkbasket()" data-toggle="tooltip" title="Copy"
class="btn btn-default">
<span class="material-icons md-20 green-blue">content_copy</span> <span class="material-icons md-20 green-blue">content_copy</span>
</button> </button>
<button type="button" (click)="removeWorkbasket()" data-toggle="tooltip" title="Remove" class="btn btn-default"> <button type="button" (click)="removeWorkbasket()" data-toggle="tooltip" title="Remove"
class="btn btn-default">
<span class="material-icons md-20 red">delete</span> <span class="material-icons md-20 red">delete</span>
</button> </button>
</div> </div>
@ -30,9 +35,12 @@
<!-- KEY --> <!-- KEY -->
<div class="form-group required"> <div class="form-group required">
<label for="wb-key" class="control-label">Key</label> <label for="wb-key" class="control-label">Key</label>
<input type="text" required #key="ngModel" class="form-control" id="wb-key" placeholder="Key" <input type="text" required maxlength="64" #key="ngModel" class="form-control" id="wb-key"
[(ngModel)]="workbasket.key" name="workbasket.key"> placeholder="Key"
<taskana-shared-field-error-display [displayError]="!isFieldValid('workbasket.key')" [validationTrigger]="this.toogleValidationMap.get('workbasket.key')" [(ngModel)]="workbasket.key" name="workbasket.key" (keypress)="onKeyPressed(key, 64)">
<div *ngIf="tooLongMap.get(key.name)" class="error">{{lengthError}}</div>
<taskana-shared-field-error-display [displayError]="!isFieldValid('workbasket.key')"
[validationTrigger]="this.toogleValidationMap.get('workbasket.key')"
errorMessage="* Key is required"> errorMessage="* Key is required">
</taskana-shared-field-error-display> </taskana-shared-field-error-display>
</div> </div>
@ -40,9 +48,12 @@
<!-- NAME --> <!-- NAME -->
<div class="form-group required"> <div class="form-group required">
<label for="wb-name" class="control-label">Name</label> <label for="wb-name" class="control-label">Name</label>
<input type="text" required #name="ngModel" class="form-control" id="wb-name" placeholder="Name" <input type="text" required maxlength="255" #name="ngModel" class="form-control" id="wb-name"
[(ngModel)]="workbasket.name" name="workbasket.name"> placeholder="Name"
<taskana-shared-field-error-display [displayError]="!isFieldValid('workbasket.name')" [validationTrigger]="this.toogleValidationMap.get('workbasket.name')" [(ngModel)]="workbasket.name" name="workbasket.name" (keypress)="onKeyPressed(name, 255)">
<div *ngIf="tooLongMap.get(name.name)" class="error">{{lengthError}}</div>
<taskana-shared-field-error-display [displayError]="!isFieldValid('workbasket.name')"
[validationTrigger]="this.toogleValidationMap.get('workbasket.name')"
errorMessage="* Name is required"> errorMessage="* Name is required">
</taskana-shared-field-error-display> </taskana-shared-field-error-display>
</div> </div>
@ -50,14 +61,23 @@
<!-- OWNER --> <!-- OWNER -->
<div class="input-group form-group col-xs-12 required"> <div class="input-group form-group col-xs-12 required">
<label for="wb-owner" class="control-label ">Owner</label> <label for="wb-owner" class="control-label ">Owner</label>
<taskana-shared-type-ahead *ngIf="lookupField else ownerInput" required #owner="ngModel" name="workbasket.owner" <taskana-shared-type-ahead *ngIf="lookupField else ownerInput" required maxlength="128" #owner="ngModel"
[(ngModel)]="workbasket.owner" placeHolderMessage="* Owner is required" [validationValue]="this.toogleValidationMap.get('workbasket.owner')" name="workbasket.owner"
[displayError]="!isFieldValid('workbasket.owner')" width="100%"></taskana-shared-type-ahead> [(ngModel)]="workbasket.owner"
placeHolderMessage="* Owner is required"
[validationValue]="this.toogleValidationMap.get('workbasket.owner')"
[displayError]="!isFieldValid('workbasket.owner')"
width="100%" (keypress)="onKeyPressed(owner, 128)">
<div *ngIf="tooLongMap.get(owner.name)" class="error">{{lengthError}}</div>
</taskana-shared-type-ahead>
<ng-template #ownerInput> <ng-template #ownerInput>
<input type="text" required #owner="ngModel" class="form-control" id="wb-owner" placeholder="Owner" <input type="text" required maxlength="128" #owner="ngModel" class="form-control" id="wb-owner"
[(ngModel)]="workbasket.owner" name="workbasket.owner"> placeholder="Owner"
[(ngModel)]="workbasket.owner" name="workbasket.owner" (keypress)="onKeyPressed(owner, 128)">
<div *ngIf="tooLongMap.get(owner.name)" class="error">{{lengthError}}</div>
<taskana-shared-field-error-display [displayError]="!isFieldValid('workbasket.owner')" <taskana-shared-field-error-display [displayError]="!isFieldValid('workbasket.owner')"
[validationTrigger]="this.toogleValidationMap.get('workbasket.owner')" errorMessage="* Owner is required"> [validationTrigger]="this.toogleValidationMap.get('workbasket.owner')"
errorMessage="* Owner is required">
</taskana-shared-field-error-display> </taskana-shared-field-error-display>
</ng-template> </ng-template>
</div> </div>
@ -65,7 +85,8 @@
<!-- DOMAIN --> <!-- DOMAIN -->
<div class="form-group "> <div class="form-group ">
<label for="wb-domain" class="control-label">Domain</label> <label for="wb-domain" class="control-label">Domain</label>
<input type="text" #domain="ngModel" class="form-control" disabled id="wb-domain" placeholder="Domain" <input type="text" #domain="ngModel" class="form-control" disabled id="wb-domain"
placeholder="Domain"
[(ngModel)]="workbasket.domain" name="workbasket.domain"> [(ngModel)]="workbasket.domain" name="workbasket.domain">
</div> </div>
@ -74,16 +95,20 @@
<div class="form-group col-xs-4"> <div class="form-group col-xs-4">
<label class="control-label">Type</label> <label class="control-label">Type</label>
<div class="dropdown"> <div class="dropdown">
<button class="btn btn-default" type="button" id="dropdownMenu24" data-toggle="dropdown" <button class="btn btn-default" type="button" id="dropdownMenu24"
data-toggle="dropdown"
aria-haspopup="true" aria-expanded="true"> aria-haspopup="true" aria-expanded="true">
<taskana-administration-icon-type [type]='workbasket.type'></taskana-administration-icon-type> <taskana-administration-icon-type
[type]='workbasket.type'></taskana-administration-icon-type>
{{allTypes.get(workbasket.type)}} {{allTypes.get(workbasket.type)}}
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu dropdown-menu" aria-labelledby="dropdownMenu"> <ul class="dropdown-menu dropdown-menu" aria-labelledby="dropdownMenu">
<li> <li>
<a *ngFor="let type of allTypes | mapValues | removeEmptyType" (click)="selectType(type.key)"> <a *ngFor="let type of allTypes | mapValues | removeEmptyType"
<taskana-administration-icon-type [type]='type.key' [text]="type.value"></taskana-administration-icon-type> (click)="selectType(type.key)">
<taskana-administration-icon-type [type]='type.key'
[text]="type.value"></taskana-administration-icon-type>
</a> </a>
</li> </li>
</ul> </ul>
@ -91,37 +116,50 @@
</div> </div>
<div class="form-group col-xs-8"> <div class="form-group col-xs-8">
<label for="wb-description" class="control-label">Description</label> <label for="wb-description" class="control-label">Description</label>
<textarea 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"></textarea> [(ngModel)]="workbasket.description" name="workbasket.description"
(keypress)="onKeyPressed(description, 255)"></textarea>
<div *ngIf="tooLongMap.get(description.name)" class="error">{{lengthError}}</div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="form-group"> <div class="form-group">
<label for="wb-org-level-1" class="control-label">OrgLevel 1</label> <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" <input type="text" class="form-control" id="wb-org-level-1" placeholder="OrgLevel 1"
name="workbasket.orgLevel1"> [(ngModel)]="workbasket.orgLevel1"
name="workbasket.orgLevel1" maxlength="255" #orgLevel1="ngModel" (keypress)="onKeyPressed(orgLevel1, 255)">
<div *ngIf="tooLongMap.get(orgLevel1.name)" class="error">{{lengthError}}</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="wb-org-level-2" class="control-label">OrgLevel 2</label> <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" <input type="text" class="form-control" id="wb-org-level-2" placeholder="OrgLevel 2"
name="workbasket.orgLevel2"> [(ngModel)]="workbasket.orgLevel2"
name="workbasket.orgLevel2" maxlength="255" #orgLevel2="ngModel" (keypress)="onKeyPressed(orgLevel2, 255)">
<div *ngIf="tooLongMap.get(orgLevel2.name)" class="error">{{lengthError}}</div>
</div> </div>
<div class="form-group" style="padding-top: 18px;"> <div class="form-group" style="padding-top: 18px;">
<label for="wb-org-level-3" class="control-label">OrgLevel 3</label> <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" <input type="text" class="form-control" id="wb-org-level-3" placeholder="OrgLevel 3"
name="workbasket.orgLevel3"> [(ngModel)]="workbasket.orgLevel3"
name="workbasket.orgLevel3" maxlength="255" #orgLevel3="ngModel" (keypress)="onKeyPressed(orgLevel3, 255)">
<div *ngIf="tooLongMap.get(orgLevel3.name)" class="error">{{lengthError}}</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="wb-org-level-4" class="control-label">OrgLevel 4</label> <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" <input type="text" class="form-control" id="wb-org-level-4" placeholder="OrgLevel 4"
name="workbasket.orgLevel4"> [(ngModel)]="workbasket.orgLevel4"
name="workbasket.orgLevel4" maxlength="255" #orgLevel4="ngModel" (keypress)="onKeyPressed(orgLevel4, 255)">
<div *ngIf="tooLongMap.get(orgLevel4.name)" class="error">{{lengthError}}</div>
</div> </div>
<ng-container *ngFor="let customField of customFields$ | async; let index = index"> <ng-container *ngFor="let customField of customFields$ | async; let index = index">
<div *ngIf="customField.visible" class="form-group"> <div *ngIf="customField.visible" class="form-group">
<label for='wb-custom-{{index+1}}' class="control-label">{{customField.field}}</label> <label for='wb-custom-{{index+1}}' class="control-label">{{customField.field}}</label>
<input type="text" class="form-control" id="wb-custom-{{index+1}}" [placeholder]="customField.field" <input type="text" class="form-control" id="wb-custom-{{index+1}}"
[(ngModel)]="workbasket[getWorkbasketCustomProperty(index + 1)]" name="workbasket[{{getWorkbasketCustomProperty(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>
</div> </div>
</ng-container> </ng-container>
</div> </div>

View File

@ -1,7 +1,7 @@
import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild } from '@angular/core'; import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Observable, Subject, Subscription } from 'rxjs'; import { Observable, Subject, Subscription, timer } from 'rxjs';
import { NgForm } from '@angular/forms'; import { NgForm, NgModel } from '@angular/forms';
import { Select, Store } from '@ngxs/store'; import { Select, Store } from '@ngxs/store';
import { ICONTYPES } from 'app/shared/models/icon-types'; import { ICONTYPES } from 'app/shared/models/icon-types';
@ -53,6 +53,9 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
customFields$: Observable<CustomField[]>; customFields$: Observable<CustomField[]>;
destroy$ = new Subject<void>(); destroy$ = new Subject<void>();
readonly lengthError = 'You have reached the maximum length';
tooLongMap = new Map<string, boolean>();
private timeout = new Map<string, Subscription>();
constructor( constructor(
private workbasketService: WorkbasketService, private workbasketService: WorkbasketService,
@ -187,4 +190,18 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
this.destroy$.next(); this.destroy$.next();
this.destroy$.complete(); 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

@ -2,7 +2,7 @@ import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Task } from 'app/workplace/models/task'; import { Task } from 'app/workplace/models/task';
@Component({ @Component({
selector: 'taskana-general-fields-extension', selector: 'taskana-task-details-general-fields-extension',
templateUrl: './general-fields-extension.component.html', templateUrl: './general-fields-extension.component.html',
styleUrls: ['./general-fields-extension.component.scss'] styleUrls: ['./general-fields-extension.component.scss']
}) })

View File

@ -1,5 +1,6 @@
<taskana-shared-spinner [isRunning]="requestInProgress"></taskana-shared-spinner> <taskana-shared-spinner [isRunning]="requestInProgress"></taskana-shared-spinner>
<div class="panel panel-default" *ngIf="task && !requestInProgress"> <div class="panel panel-default" *ngIf="task && !requestInProgress">
<!--Buttonbar-->
<div class="panel-heading"> <div class="panel-heading">
<div *ngIf="showDetail" class="pull-left btn-group align-header"> <div *ngIf="showDetail" class="pull-left btn-group align-header">
<button (click)="backClicked()" class="btn btn-default no-style blue visible-xs visible-sm hidden"> <button (click)="backClicked()" class="btn btn-default no-style blue visible-xs visible-sm hidden">
@ -28,24 +29,29 @@
<span *ngIf="!task.taskId" class="badge warning"> {{'Creating Task'}}</span> <span *ngIf="!task.taskId" class="badge warning"> {{'Creating Task'}}</span>
</h4> </h4>
</div> </div>
<!--Accordion Fields for Sectioned View of Data-->
<div class="panel-body"> <div class="panel-body">
<accordion *ngIf="task && !requestInProgress"> <accordion *ngIf="task && !requestInProgress">
<!--Information-->
<accordion-group panelClass="customClass" isOpen="true" (isOpenChange)="accordion1State = $event"> <accordion-group panelClass="customClass" isOpen="true" (isOpenChange)="accordion1State = $event">
<button class="btn btn-block clearfix" accordion-heading> <button class="btn btn-block clearfix" accordion-heading>
<div class="pull-left float-left">1 - Information</div> <div class="pull-left float-left">1 - Information</div>
<span class="float-right pull-right material-icons md-20 blue">{{accordion1State ? <span class="float-right pull-right material-icons md-20 blue">{{accordion1State ?
'expand_more' : 'expand_less'}}</span> 'expand_more' : 'expand_less'}}</span>
</button> </button>
<taskana-task-details-general-fields [task]="task" [saveToggleTriggered]="toggleFormValidation" (formValid)="onSubmit()"></taskana-task-details-general-fields> <taskana-task-details-general-fields [task]="task" [saveToggleTriggered]="toggleFormValidation"
(formValid)="onSubmit()"></taskana-task-details-general-fields>
</accordion-group> </accordion-group>
<!--Status Details-->
<accordion-group panelClass="customClass" (isOpenChange)="accordion2State = $event"> <accordion-group panelClass="customClass" (isOpenChange)="accordion2State = $event">
<button class="btn btn-block clearfix" accordion-heading> <button class="btn btn-block clearfix" accordion-heading>
<div class="pull-left float-left">2 - Status details</div> <div class="pull-left float-left">2 - Status details</div>
<span class="float-right pull-right material-icons md-20 blue">{{accordion2State ? <span class="float-right pull-right material-icons md-20 blue">{{accordion2State ?
'expand_more' : 'expand_less'}}</span> 'expand_more' : 'expand_less'}}</span>
</button> </button>
<taskana-general-fields-extension [task]="taskClone"></taskana-general-fields-extension> <taskana-task-details-general-fields-extension [task]="task"></taskana-task-details-general-fields-extension>
</accordion-group> </accordion-group>
<!--Custom Fields-->
<accordion-group panelClass="customClass" (isOpenChange)="accordion3State = $event"> <accordion-group panelClass="customClass" (isOpenChange)="accordion3State = $event">
<button class="btn btn-block clearfix" accordion-heading> <button class="btn btn-block clearfix" accordion-heading>
<div class="pull-left float-left">3 - Custom fields</div> <div class="pull-left float-left">3 - Custom fields</div>
@ -54,6 +60,7 @@
</button> </button>
<taskana-task-details-custom-fields [task]="task"></taskana-task-details-custom-fields> <taskana-task-details-custom-fields [task]="task"></taskana-task-details-custom-fields>
</accordion-group> </accordion-group>
<!--Custom Attributes-->
<accordion-group panelClass="customClass" (isOpenChange)="accordion4State = $event"> <accordion-group panelClass="customClass" (isOpenChange)="accordion4State = $event">
<button class="btn btn-block clearfix" accordion-heading> <button class="btn btn-block clearfix" accordion-heading>
<div class="pull-left float-left">4 - Custom attributes</div> <div class="pull-left float-left">4 - Custom attributes</div>
@ -62,13 +69,15 @@
</button> </button>
<taskana-task-details-attributes [attributes]="task.customAttributes"></taskana-task-details-attributes> <taskana-task-details-attributes [attributes]="task.customAttributes"></taskana-task-details-attributes>
</accordion-group> </accordion-group>
<!--Callback Information-->
<accordion-group panelClass="customClass" (isOpenChange)="accordion5State = $event"> <accordion-group panelClass="customClass" (isOpenChange)="accordion5State = $event">
<button class="btn btn-block clearfix" accordion-heading> <button class="btn btn-block clearfix" accordion-heading>
<div class="pull-left float-left">5 - Callback information</div> <div class="pull-left float-left">5 - Callback information</div>
<span class="float-right pull-right material-icons md-20 blue">{{accordion5State ? <span class="float-right pull-right material-icons md-20 blue">{{accordion5State ?
'expand_more' : 'expand_less'}}</span> 'expand_more' : 'expand_less'}}</span>
</button> </button>
<taskana-task-details-attributes [attributes]="task.callbackInfo" [callbackInfo]="true"></taskana-task-details-attributes> <taskana-task-details-attributes [attributes]="task.callbackInfo"
[callbackInfo]="true"></taskana-task-details-attributes>
</accordion-group> </accordion-group>
</accordion> </accordion>
</div> </div>