Add validation for int and float constraints 78/132878/11
authoreschcam <cameron.scholes@est.tech>
Fri, 6 Jan 2023 12:17:56 +0000 (12:17 +0000)
committerMichael Morris <michael.morris@est.tech>
Tue, 28 Feb 2023 12:24:57 +0000 (12:24 +0000)
Issue-ID: SDC-4316
Signed-off-by: eschcam <cameron.scholes@est.tech>
Change-Id: I6d6172743779291597305583f2a7f4f2145f57fb

catalog-ui/src/app/ng2/pages/properties-assignment/constraints/constraints.component.html
catalog-ui/src/app/ng2/pages/properties-assignment/constraints/constraints.component.spec.ts
catalog-ui/src/app/ng2/pages/properties-assignment/constraints/constraints.component.ts
catalog-ui/src/app/ng2/pages/properties-assignment/constraints/constraints.module.ts
catalog-ui/src/app/view-models/forms/property-forms/base-property-form/property-form-base.less
catalog-ui/src/app/view-models/forms/property-forms/component-property-form/property-form-view-model.ts
catalog-ui/src/app/view-models/forms/property-forms/select-datatype-modal/select-datatype-modal.less

index 46d4114..3bbdaff 100644 (file)
   -->
 
 <div class="app-constraints">
-  <form novalidate class="w-sdc-form two-columns">
-    <div class="w-sdc-form-columns-wrapper" *ngFor="let constraint of constraints; let constraintIndex = index; trackBy:trackByFn">
-        <div class="w-sdc-form-column-small">
-            <select class="i-sdc-form-select"
-                    data-tests-id="constraints"
-                    [disabled]="isViewOnly"
-                    (change)="onChangeConstraintType(constraintIndex, $event.target.value)">
-              <option *ngIf="constraint" [value]="constraint.type"
-                      hidden selected>
-                {{ConstraintTypesMapping[constraint.type]}}
-              </option>
-              <option *ngFor="let constraintType of constraintTypes"
-                      [value]="constraintType"
-                      [disabled]="disableConstraint(constraintType, constraint.type)">
-                {{ConstraintTypesMapping[constraintType]}}
-              </option>
-            </select>
-        </div>
+  <form novalidate class="w-sdc-form two-columns" [formGroup]="constraintForm">
+    <div *ngFor="let constraint of constraintsArray.controls; let constraintIndex = index; trackBy:trackByFn">
 
-        <div class="w-sdc-form-columns-wrapper">
+      <div formArrayName="constraint">
+        <div class="w-sdc-form-columns-wrapper" [formGroupName]="constraintIndex">
+          <div class="w-sdc-form-column-small">
+              <select class="i-sdc-form-select"
+                      data-tests-id="constraints"
+                      formControlName="type"
+                      [value]="constraintsArray.at(constraintIndex).get('type').value"
+                      (change)="onChangeConstraintType(constraintIndex, $event.target.value)">
+                <option *ngIf="constraint" [value]="constraint.value.type"
+                        hidden selected>
+                  {{ConstraintTypesMapping[constraint.value.type]}}
+                </option>
+                <option *ngFor="let constraintType of constraintTypes"
+                        [value]="constraintType"
+                        [disabled]="disableConstraint(constraintType, constraint.value.type)">
+                  {{ConstraintTypesMapping[constraintType]}}
+                </option>
+              </select>
 
-          <div class="w-sdc-form-column">
-            <!-- ConstraintTypes.in_range-->
-            <div class="w-sdc-form-columns-wrapper" *ngIf="constraint.type == 'inRange'">
-              <div class="w-sdc-form-column">
-                <input type="text" class="i-sdc-form-input myClass"
-                      (input)="onChangeConstrainValueIndex(constraintIndex, $event.target.value, 0)"
-                      [disabled]="isViewOnly"
-                      [value]="getInRangeValue(constraintIndex, 0)"/>
-              </div>
-              <div class="w-sdc-form-column">
-                <input type="text" class="i-sdc-form-input myClass"
-                      (input)="onChangeConstrainValueIndex(constraintIndex, $event.target.value, 1)"
-                      [disabled]="isViewOnly"
-                      [value]="getInRangeValue(constraintIndex, 1)"/>
-              </div>
+            <div class="validation-errors">
+              <ng-container *ngFor="let validation of validationMessages.type">
+                <div class="input-error" *ngIf="constraintsArray.at(constraintIndex).get('type').hasError(validation.type);">
+                  {{ validation.message }}
+                </div>
+              </ng-container>
             </div>
+          </div>
 
-            <!-- ConstraintTypes.valid_values-->
-            <div *ngIf="constraint.type == 'validValues'">
-              <div class="w-sdc-form-columns-wrapper-block">
-                <div class="add-btn add-list-item w-sdc-form-column-block"
-                    [ngClass]="{'disabled': isViewOnly}"
-                    (click)="addToList(constraintIndex)">Add to List</div>
-              </div>
-              <div class="w-sdc-form-columns-wrapper" *ngFor="let value of constraint.value; let valueIndex = index; trackBy:trackByFn">
+
+          <div class="w-sdc-form-columns-wrapper">
+
+            <div class="w-sdc-form-column">
+              <div class="w-sdc-form-columns-wrapper" *ngIf="constraint.value.type == 'inRange'">
                 <div class="w-sdc-form-column">
-                  <input type="text" class="i-sdc-form-input" *ngIf="propertyType !== 'boolean'"
-                         [disabled]="isViewOnly"
-                         [value]="value"
-                        (input)="onChangeConstrainValueIndex(constraintIndex, $event.target.value, valueIndex)"/>
-                  <select class="i-sdc-form-select" *ngIf="propertyType == 'boolean'"
-                          [disabled]="isViewOnly"
-                          [value]="value"
-                          (input)="onChangeConstrainValueIndex(constraintIndex, $event.target.value, valueIndex)">
-                    <option ngValue="true">true</option>
-                    <option ngValue="false">false</option>
-                  </select>
+                  <input type="text" class="i-sdc-form-input myClass" required
+                        (input)="onChangeConstrainValueIndex(constraintIndex, $event.target.value, 0)"
+                        [value]="getInRangeValue(constraintIndex, 0)"/>
+
+                  <ng-container *ngFor="let validation of validationMessages.constraint">
+                    <div class="input-error" *ngIf="constraintValuesArray(constraintIndex).controls[0].hasError(validation.type);">
+                      {{ validation.message }}
+                    </div>
+                  </ng-container>
                 </div>
                 <div class="w-sdc-form-column">
-                  <span class="sprite-new delete-btn" [ngClass]="{'disabled': isViewOnly}" (click)="removeFromList(constraintIndex, valueIndex)"></span>
+                  <input type="text" class="i-sdc-form-input myClass" required
+                        (input)="onChangeConstrainValueIndex(constraintIndex, $event.target.value, 1)"
+                        [value]="getInRangeValue(constraintIndex, 1)"/>
+
+                  <ng-container *ngFor="let validation of validationMessages.constraint">
+                    <div class="input-error" *ngIf="constraintValuesArray(constraintIndex).controls[1].hasError(validation.type);">
+                      {{ validation.message }}
+                    </div>
+                  </ng-container>
                 </div>
               </div>
-            </div>
 
-            <!-- ConstraintTypes.equal-->
-            <div *ngIf="constraint.type == 'equal'">
-              <input type="text" class="i-sdc-form-input" *ngIf="propertyType !== 'boolean'"
-                     [disabled]="isViewOnly"
-                     (input)="onChangeConstraintValue(constraintIndex, $event.target.value)"
-                     [value]="constraint.value"/>
-              <select class="i-sdc-form-select" *ngIf="propertyType == 'boolean'"
-                      [disabled]="isViewOnly"
-                      [value]="constraint.value"
-                      (input)="onChangeConstraintValue(constraintIndex, $event.target.value)">
-                <option ngValue="true">true</option>
-                <option ngValue="false">false</option>
-              </select>
-            </div>
+              <div *ngIf="constraint.value.type == 'validValues'">
+                <div class="w-sdc-form-columns-wrapper-block">
+                  <div class="add-btn add-list-item w-sdc-form-column-block"
+                      [ngClass]="{'disabled': isViewOnly}"
+                      (click)="addToList(constraintIndex)">Add to List</div>
+                </div>
+                <div class="w-sdc-form-columns-wrapper" *ngFor="let value of constraintValuesArray(constraintIndex).controls; let valueIndex = index; trackBy:trackByFn">
+                  <div class="w-sdc-form-column">
+                    <input type="text" class="i-sdc-form-input" required
+                      [value]="value.value"
+                      (input)="onChangeConstrainValueIndex(constraintIndex, $event.target.value, valueIndex)"/>
+                  </div>
+
+                  <ng-container *ngFor="let validation of validationMessages.constraint">
+                    <div class="input-error" *ngIf="constraintValuesArray(constraintIndex).controls[valueIndex].hasError(validation.type);">
+                      {{ validation.message }}
+                    </div>
+                  </ng-container>
+
+                  <div class="w-sdc-form-column">
+                    <span class="sprite-new delete-btn" [ngClass]="{'disabled': isViewOnly}" (click)="removeFromList(constraintIndex, valueIndex)"></span>
+                  </div>
+                </div>
+              </div>
+
+              <div *ngIf="constraint.get('type').value != 'inRange' && constraint.get('type').value != 'validValues'">
+                <input type="text" class="i-sdc-form-input myClass required" required
+                       formControlName="value"
+                       [value]="constraintsArray.at(constraintIndex).get('value').value"
+                       (input)="onChangeConstraintValue(constraintIndex, $event.target.value)"/>
 
-            <!-- all other ConstraintTypes-->
-            <div *ngIf="constraint.type != 'inRange' && constraint.type != 'validValues' && constraint.type != 'equal'">
-              <input type="text" class="i-sdc-form-input myClass"
-                     [disabled]="isViewOnly"
-                     (input)="onChangeConstraintValue(constraintIndex, $event.target.value)"
-                     [value]="constraint.value"/>
+                <div class="validation-errors">
+                  <ng-container *ngFor="let validation of validationMessages.constraint">
+                    <div class="input-error" *ngIf="constraintsArray.at(constraintIndex).get('value').hasError(validation.type);">
+                      {{ validation.message }}
+                    </div>
+                  </ng-container>
+                </div>
+              </div>
             </div>
-          </div>
 
-          <div class="w-sdc-form-column-vsmall">
-              <span class="sprite-new delete-btn" [ngClass]="{'disabled': isViewOnly}" (click)="removeConstraint(constraintIndex)"></span>
+            <div class="w-sdc-form-column-vsmall" *ngIf="!isViewOnly">
+                <span class="sprite-new delete-btn" [ngClass]="{'disabled': isViewOnly}" (click)="removeConstraint(constraintIndex)"></span>
+            </div>
           </div>
         </div>
+      </div>
 
     </div>
     <div class="w-sdc-form-columns-wrapper-small" *ngIf="!isViewOnly">
index bfee769..06112e5 100644 (file)
@@ -19,6 +19,7 @@
 
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { ConstraintsComponent } from './constraints.component';
 
 describe('ConstraintsComponent', () => {
@@ -27,7 +28,8 @@ describe('ConstraintsComponent', () => {
 
   beforeEach(async(() => {
     TestBed.configureTestingModule({
-      declarations: [ ConstraintsComponent ]
+      declarations: [ ConstraintsComponent ],
+      imports: [FormsModule, ReactiveFormsModule]
     })
     .compileComponents();
   }));
index 2fb8b64..31dbead 100644 (file)
  */
 
 import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
-import { PROPERTY_DATA, PROPERTY_TYPES } from "app/utils/constants"
+import {
+  AbstractControl, FormArray,
+  FormBuilder,
+  FormControl,
+  FormGroup, ValidationErrors,
+  ValidatorFn,
+  Validators
+} from '@angular/forms';
+import { PROPERTY_DATA, PROPERTY_TYPES } from 'app/utils/constants';
 
 @Component({
   selector: 'app-constraints',
@@ -32,88 +40,358 @@ export class ConstraintsComponent implements OnInit {
   @Input() isViewOnly: boolean = false;
   @Output() onConstraintChange: EventEmitter<any> = new EventEmitter<any>();
 
-  constraints: Constraint[] = new Array();
   constraintTypes: string[];
   ConstraintTypesMapping = ConstraintTypesMapping;
-  valid: boolean = true;
+  valid: boolean = false;
+  constraintForm: FormGroup;
+  validationMessages;
+  init: boolean = true;
+
+  constructor(private formBuilder: FormBuilder) {}
+
+  get constraintsArray() {
+    return this.constraintForm.get('constraint') as FormArray;
+  }
+
+  get constraintValidators(): ValidatorFn {
+    switch (this.propertyType) {
+      case PROPERTY_TYPES.INTEGER:
+        console.warn('Add int validator');
+        return Validators.compose([
+          Validators.required,
+          intValidator()
+        ]);
+      case PROPERTY_TYPES.FLOAT:
+        console.warn('Add float validator');
+        return Validators.compose([
+          Validators.required,
+          floatValidator()
+        ]);
+      default:
+        console.warn('Only required validator');
+        return Validators.compose([
+          Validators.required
+        ]);
+    }
+}
+
+  public constraintValuesArray(index: number): FormArray {
+    return this.constraintsArray.at(index).get('value') as FormArray;
+  }
 
   ngOnInit() {
-    this.constraintTypes = Object.keys(ConstraintTypes).map(key => ConstraintTypes[key]);
+    console.groupEnd();
+    this.constraintTypes = Object.keys(ConstraintTypes).map((key) => ConstraintTypes[key]);
+
+    // This is only used by the spec test
+    if (!this.constraintForm) {
+      this.constraintForm = this.formBuilder.group({
+        constraint: this.formBuilder.array([])
+      });
+    }
+
+    this.validationMessages = {
+      constraint: [
+        { type: 'required', message: 'Constraint value is required'},
+        { type: 'invalidInt', message: 'Constraint value is not a valid integer'},
+        { type: 'invalidFloat', message: 'Constraint value is not a valid floating point value'}
+      ],
+      type : [
+        { type: 'required', message: 'Constraint type is required'}
+      ]
+    };
+
+    this.init = false;
   }
 
   ngOnChanges(changes): void {
+    console.groupEnd();
+
+    // Changes fires before init so form has to be initialised here
+    if (this.init) {
+      this.constraintForm = this.formBuilder.group({
+        constraint: this.formBuilder.array([])
+      });
+
+      if (changes.propertyConstraints && changes.propertyConstraints.currentValue) {
+        changes.propertyConstraints.currentValue.forEach((constraint: any) => {
+          const prop = this.getConstraintFromPropertyBEModel(constraint);
+          console.log('constraint from BE model', prop);
+          this.constraintsArray.push(prop);
+        });
+      }
+    }
+
     if (changes.propertyType) {
+      if (!this.init) {
+        // Reset constraints on property type change
+        console.warn('Property type changed. Resetting constraints');
+        this.constraintForm = this.formBuilder.group({
+          constraint: this.formBuilder.array([])
+        });
+      }
+
       if (!this.propertyType || changes.propertyType.currentValue == this.propertyType) {
         this.propertyType = changes.propertyType.currentValue;
       } else {
-        this.constraints = new Array();
         this.propertyType = changes.propertyType;
         this.emitOnConstraintChange();
       }
+
+      this.constraintsArray.controls.forEach((control: AbstractControl) => {
+        control.get('value').setValidators(this.constraintValidators);
+      });
     }
-    this.constraints = new Array();
-    if(changes.propertyConstraints) {
-      if (changes.propertyConstraints.currentValue) {
-        changes.propertyConstraints.currentValue.forEach((constraint: any) => {
-            this.constraints.push(this.getConstraintFromPropertyBEModel(constraint));
-        });
-      }
+
+    console.log('constraints', this.constraintsArray);
+  }
+
+  removeFromList(constraintIndex: number, valueIndex: number) {
+    this.constraintsArray.at(constraintIndex).get('value').value.splice(valueIndex, 1);
+    this.emitOnConstraintChange();
+  }
+
+  addToList(constraintIndex: number) {
+    const newConstraint = new FormControl('', this.constraintValidators);
+
+    this.constraintValuesArray(constraintIndex).push(newConstraint);
+    console.log('constraintsArray', this.constraintsArray);
+    console.log('constraintValuesArray', this.constraintValuesArray(constraintIndex));
+    this.emitOnConstraintChange();
+  }
+
+  onChangeConstraintType(constraintIndex: number, newType: ConstraintTypes) {
+    if ((newType == ConstraintTypes.valid_values)) {
+      const newConstraint = this.formBuilder.group({
+        type: new FormControl({
+          value: newType,
+          disabled: this.isViewOnly
+        }, Validators.required),
+        value: this.formBuilder.array([])});
+
+      this.constraintsArray.removeAt(constraintIndex);
+      this.constraintsArray.push(newConstraint);
+    } else if (newType == ConstraintTypes.in_range) {
+      const newConstraint = this.formBuilder.group({
+        type: new FormControl({
+          value: newType,
+          disabled: this.isViewOnly
+        }, Validators.required),
+        value: this.formBuilder.array([])});
+
+      const valRef = newConstraint.get('value') as FormArray;
+      valRef.push(new FormControl('', this.constraintValidators));
+      valRef.push(new FormControl('', this.constraintValidators));
+
+      this.constraintsArray.removeAt(constraintIndex);
+      this.constraintsArray.push(newConstraint);
+    } else {
+      this.constraintsArray.at(constraintIndex).value.type = newType;
     }
+    this.emitOnConstraintChange();
+  }
+
+  onChangeConstraintValue(constraintIndex: number, newValue: any) {
+    this.constraintsArray.at(constraintIndex).get('value').setValue(newValue);
+    this.emitOnConstraintChange();
+  }
+
+  onChangeConstrainValueIndex(constraintIndex: number, newValue: any, valueIndex: number) {
+    this.constraintValuesArray(constraintIndex).controls[valueIndex].setValue(newValue);
+    this.emitOnConstraintChange();
   }
 
-  private getConstraintFromPropertyBEModel(constraint: any):Constraint {
+  removeConstraint(constraintIndex: number) {
+    this.constraintsArray.removeAt(constraintIndex);
+    this.emitOnConstraintChange();
+}
+
+  addConstraint() {
+    const newConstraint = this.formBuilder.group({
+      type: new FormControl({
+        value: ConstraintTypes.null,
+        disabled: this.isViewOnly
+      }, Validators.required),
+      value: new FormControl({
+        value: '',
+        disabled: this.isViewOnly
+      }, this.constraintValidators)
+    });
+    this.constraintsArray.push(newConstraint);
+    this.valid = false;
+    this.emitOnConstraintChange();
+  }
+
+  getInRangeValue(constraintIndex: number, valueIndex: number): string {
+    const value = this.constraintsArray.at(constraintIndex).get('value').value;
+
+    if (!value || !value[valueIndex]) {
+      return '';
+    }
+
+    return value[valueIndex];
+  }
+
+  disableConstraint(optionConstraintType: ConstraintTypes): boolean {
+    const invalid = this.notAllowedConstraint(optionConstraintType);
+    return invalid ? invalid : this.getConstraintTypeIfPresent(optionConstraintType) ? true : false;
+  }
+
+  notAllowedConstraint(optionConstraintType: ConstraintTypes): boolean {
+    switch (optionConstraintType) {
+      case ConstraintTypes.less_or_equal:
+      case ConstraintTypes.less_than:
+      case ConstraintTypes.greater_or_equal:
+      case ConstraintTypes.greater_than:
+      case ConstraintTypes.in_range:
+        if (this.isComparable(this.propertyType)) {
+          return false;
+        }
+        break;
+      case ConstraintTypes.length:
+      case ConstraintTypes.max_length:
+      case ConstraintTypes.min_length:
+        if (this.propertyType == PROPERTY_TYPES.STRING || this.propertyType == PROPERTY_TYPES.MAP || this.propertyType == PROPERTY_TYPES.LIST) {
+          return false;
+        }
+        break;
+      case ConstraintTypes.pattern:
+        if (this.propertyType == PROPERTY_TYPES.STRING) {
+          return false;
+        }
+        break;
+      case ConstraintTypes.valid_values:
+      case ConstraintTypes.equal:
+        return false;
+    }
+    return true;
+  }
+
+  getConstraintTypeIfPresent(constraintType: ConstraintTypes): AbstractControl {
+    return this.constraintsArray.controls.find((control: AbstractControl) => {
+      const type = control.get('type').value;
+      return type == constraintType;
+    });
+  }
+
+  trackByFn(index) {
+    return index;
+  }
+
+  isComparable(propType: string): boolean {
+    if (PROPERTY_DATA.COMPARABLE_TYPES.indexOf(propType) >= 0) {
+      return true;
+    }
+    return false;
+  }
+
+  private getConstraintFromPropertyBEModel(constraint: any): AbstractControl {
+    console.log('be model constraints', constraint);
     let constraintType: ConstraintTypes;
     let constraintValue: any;
     if (!constraint) {
       constraintType = ConstraintTypes.null;
-      constraintValue = "";
-    } else if(constraint.hasOwnProperty(ConstraintTypes.valid_values)){
+      constraintValue = '';
+    } else if (constraint.hasOwnProperty(ConstraintTypes.valid_values)) {
       constraintType = ConstraintTypes.valid_values;
-      constraintValue = constraint.validValues;
-    } else if(constraint.hasOwnProperty(ConstraintTypes.equal)) {
+    } else if (constraint.hasOwnProperty(ConstraintTypes.equal)) {
       constraintType = ConstraintTypes.equal;
       constraintValue = constraint.equal;
-    } else if(constraint.hasOwnProperty(ConstraintTypes.greater_than)) {
+    } else if (constraint.hasOwnProperty(ConstraintTypes.greater_than)) {
       constraintType = ConstraintTypes.greater_than;
       constraintValue = constraint.greaterThan;
-    } else if(constraint.hasOwnProperty(ConstraintTypes.greater_or_equal)) {
+    } else if (constraint.hasOwnProperty(ConstraintTypes.greater_or_equal)) {
       constraintType = ConstraintTypes.greater_or_equal;
       constraintValue = constraint.greaterOrEqual;
-    } else if(constraint.hasOwnProperty(ConstraintTypes.less_than)) {
+    } else if (constraint.hasOwnProperty(ConstraintTypes.less_than)) {
       constraintType = ConstraintTypes.less_than;
       constraintValue = constraint.lessThan;
-    } else if(constraint.hasOwnProperty(ConstraintTypes.less_or_equal)) {
+    } else if (constraint.hasOwnProperty(ConstraintTypes.less_or_equal)) {
       constraintType = ConstraintTypes.less_or_equal;
       constraintValue = constraint.lessOrEqual;
-    } else if(constraint.hasOwnProperty(ConstraintTypes.in_range)) {
+    } else if (constraint.hasOwnProperty(ConstraintTypes.in_range)) {
       constraintType = ConstraintTypes.in_range;
       constraintValue = new Array(constraint.inRange[0], constraint.inRange[1]);
-    } else if(constraint.rangeMaxValue || constraint.rangeMinValue) {
+    } else if (constraint.rangeMaxValue || constraint.rangeMinValue) {
       constraintType = ConstraintTypes.in_range;
       constraintValue = new Array(constraint.rangeMinValue, constraint.rangeMaxValue);
-    } else if(constraint.hasOwnProperty(ConstraintTypes.length)) {
+    } else if (constraint.hasOwnProperty(ConstraintTypes.length)) {
       constraintType = ConstraintTypes.length;
       constraintValue = constraint.length;
-    } else if(constraint.hasOwnProperty(ConstraintTypes.min_length)) {
+    } else if (constraint.hasOwnProperty(ConstraintTypes.min_length)) {
       constraintType = ConstraintTypes.min_length;
       constraintValue = constraint.minLength;
-    } else if(constraint.hasOwnProperty(ConstraintTypes.max_length)) {
+    } else if (constraint.hasOwnProperty(ConstraintTypes.max_length)) {
       constraintType = ConstraintTypes.max_length;
       constraintValue = constraint.maxLength;
-    } else if(constraint.hasOwnProperty(ConstraintTypes.pattern)) {
+    } else if (constraint.hasOwnProperty(ConstraintTypes.pattern)) {
       constraintType = ConstraintTypes.pattern;
       constraintValue = constraint.pattern;
     }
-    return {
-      type:constraintType,
-      value:constraintValue
+
+    if (!constraint.hasOwnProperty(ConstraintTypes.valid_values) && !constraint.hasOwnProperty(ConstraintTypes.in_range)) {
+      return this.formBuilder.group({
+        type: new FormControl({
+          value: constraintType,
+          disabled: this.isViewOnly
+        }, Validators.required),
+        value: new FormControl({
+          value: constraintValue,
+          disabled: this.isViewOnly
+        }, this.constraintValidators)
+      });
+    } else {
+      const newForm = this.formBuilder.group({
+        type: new FormControl({
+          value: constraintType,
+          disabled: this.isViewOnly
+        }, Validators.required),
+        value: this.formBuilder.array([])
+      });
+
+      const valRef = newForm.get('value') as FormArray;
+
+      if (constraint.hasOwnProperty(ConstraintTypes.valid_values)) {
+        constraint.validValues.forEach((val) => {
+          valRef.push(new FormControl(val, this.constraintValidators));
+        });
+      } else {
+        constraint.inRange.forEach((val) => {
+          valRef.push(new FormControl(val, this.constraintValidators));
+        });
+      }
+
+      console.log('new form', newForm);
+      return newForm;
     }
   }
 
   private getConstraintsFormat(): any[] {
-    let constraintArray = new Array();
-    this.constraints.forEach((constraint: Constraint) => {
-      constraintArray.push(this.getConstraintFormat(constraint))
+    const constraintArray = new Array();
+    this.constraintsArray.controls.forEach((control: AbstractControl) => {
+      const type = control.get('type').value;
+      let constraint: Constraint;
+
+      if (type != ConstraintTypes.valid_values && type != ConstraintTypes.in_range) {
+        constraint = {
+          type,
+          value: control.get('value').value
+        };
+      } else {
+        const valArray = [];
+
+        control.get('value').value.forEach((val) => {
+          valArray.push(val);
+        });
+
+        constraint = {
+          type,
+          value: valArray
+        };
+      }
+
+      console.log('New constraint object', constraint);
+      constraintArray.push(this.getConstraintFormat(constraint));
     });
     return constraintArray;
   }
@@ -123,224 +401,153 @@ export class ConstraintsComponent implements OnInit {
       case ConstraintTypes.equal:
         return {
           [ConstraintTypes.equal]: constraint.value
-        }
+        };
       case ConstraintTypes.less_or_equal:
         return {
           [ConstraintTypes.less_or_equal]: constraint.value
-        }
+        };
       case ConstraintTypes.less_than:
         return {
           [ConstraintTypes.less_than]: constraint.value
-        }
+        };
       case ConstraintTypes.greater_or_equal:
         return {
           [ConstraintTypes.greater_or_equal]: constraint.value
-        }
+        };
       case ConstraintTypes.greater_than:
         return {
           [ConstraintTypes.greater_than]: constraint.value
-        }
+        };
       case ConstraintTypes.in_range:
         return {
           [ConstraintTypes.in_range]: constraint.value
-        }
+        };
       case ConstraintTypes.length:
         return {
           [ConstraintTypes.length]: constraint.value
-        }
+        };
       case ConstraintTypes.max_length:
         return {
           [ConstraintTypes.max_length]: constraint.value
-        }
+        };
       case ConstraintTypes.min_length:
         return {
           [ConstraintTypes.min_length]: constraint.value
-        }
+        };
       case ConstraintTypes.pattern:
         return {
           [ConstraintTypes.pattern]: constraint.value
-        }
+        };
       case ConstraintTypes.valid_values:
         return {
           [ConstraintTypes.valid_values]: constraint.value
-        }
+        };
       default:
         return;
     }
   }
 
   private validateConstraints(): void {
-    this.valid = this.constraints.every((constraint: Constraint) => {
-      if (Array.isArray(constraint.value)) {
-        return !(constraint.value.length == 0 || this.doesArrayContaintEmptyValues(constraint.value));
+    this.valid = this.constraintsArray.controls.every((control: AbstractControl) => {
+      const value = control.get('value').value;
+      const type = control.get('type').value;
+      control.updateValueAndValidity();
+
+      if (Array.isArray(value)) {
+        return !(value.length == 0 || this.doesArrayContaintEmptyValues(value));
       }
-      if (constraint.type == ConstraintTypes.pattern) {
+      if (type == ConstraintTypes.pattern) {
         try {
-          new RegExp(constraint.value);
+          new RegExp(value);
           this.valid = true;
-        } catch(e) {
+        } catch (e) {
           this.valid = false;
         }
+      } else {
+        this.valid = this.constraintForm.valid;
       }
-      return constraint.value && constraint.type != ConstraintTypes.null
+
+      return value && type != ConstraintTypes.null;
     });
   }
 
   private doesArrayContaintEmptyValues(arr) {
-    for(const element of arr) {
-      if(element === "") return true;
+    for (const element of arr) {
+      if (element === '') { return true; }
     }
     return false;
   }
 
   private emitOnConstraintChange(): void {
+    console.log('constraints', this.constraintsArray);
+
     this.validateConstraints();
     const newConstraints = this.getConstraintsFormat();
+
+    this.valid = this.constraintForm.valid;
+    console.log('emitOnConstraintChange.valid', this.valid);
+
     this.onConstraintChange.emit({
       constraints: newConstraints,
       valid: this.valid
     });
   }
 
-  removeFromList(constraintIndex: number, valueIndex: number){
-    this.constraints[constraintIndex].value.splice(valueIndex, 1);
-    this.emitOnConstraintChange()
-  }
-
-  addToList(constraintIndex: number){
-    if (!this.constraints[constraintIndex].value) {
-      this.constraints[constraintIndex].value = new Array();
-    }
-    this.constraints[constraintIndex].value.push("");
-    this.emitOnConstraintChange()
-  }
-
-  onChangeConstraintType(constraintIndex: number, newType: ConstraintTypes) {
-    this.constraints[constraintIndex].type = newType;
-    if ((newType == ConstraintTypes.in_range || newType == ConstraintTypes.valid_values) && !Array.isArray(this.constraints[constraintIndex].value)) {
-      this.constraints[constraintIndex].value = new Array()
-    }
-    this.emitOnConstraintChange();
-  }
-
-  onChangeConstraintValue(constraintIndex: number, newValue: any) {
-    this.constraints[constraintIndex].value = newValue;
-    this.emitOnConstraintChange();
-  }
-
-  onChangeConstrainValueIndex(constraintIndex: number, newValue: any, valueIndex: number) {
-    if(!this.constraints[constraintIndex].value) {
-      this.constraints[constraintIndex].value = new Array();
-    }
-    this.constraints[constraintIndex].value[valueIndex] = newValue;
-    this.emitOnConstraintChange();
-  }
-
-  removeConstraint(constraintIndex: number) {
-    this.constraints.splice(constraintIndex, 1);
-    this.emitOnConstraintChange();
 }
 
-  addConstraint() {
-    let newConstraint: Constraint = {
-      type: ConstraintTypes.null,
-      value: ""
-    }
-    this.constraints.push(newConstraint);
-    this.emitOnConstraintChange();
-  }
-
-  getInRangeValue(constraintIndex: number, valueIndex: number): string {
-    if(!this.constraints[constraintIndex].value || !this.constraints[constraintIndex].value[valueIndex]) {
-      return "";
-    }
-    return this.constraints[constraintIndex].value[valueIndex];
-  }
-
-  disableConstraint(optionConstraintType: ConstraintTypes): boolean {
-    const invalid = this.notAllowedConstraint(optionConstraintType);
-    return invalid ? invalid : this.getConstraintTypeIfPresent(optionConstraintType) ? true : false;
-  }
-
-  notAllowedConstraint(optionConstraintType: ConstraintTypes): boolean {
-    switch (optionConstraintType) {
-      case ConstraintTypes.less_or_equal:
-      case ConstraintTypes.less_than:
-      case ConstraintTypes.greater_or_equal:
-      case ConstraintTypes.greater_than:
-      case ConstraintTypes.in_range:
-        if (this.isComparable(this.propertyType)){
-          return false;
-        }
-        break;
-      case ConstraintTypes.length:
-      case ConstraintTypes.max_length:
-      case ConstraintTypes.min_length:
-        if (this.propertyType == PROPERTY_TYPES.STRING || this.propertyType == PROPERTY_TYPES.MAP || this.propertyType == PROPERTY_TYPES.LIST){
-          return false;
-        }
-        break;
-      case ConstraintTypes.pattern:
-        if (this.propertyType == PROPERTY_TYPES.STRING){
-          return false;
-        }
-        break;
-      case ConstraintTypes.valid_values:
-      case ConstraintTypes.equal:
-        return false;
-    }
-    return true;
-  }
+export enum ConstraintTypes {
+  null = '',
+  equal= 'equal',
+  greater_than = 'greaterThan',
+  greater_or_equal = 'greaterOrEqual',
+  less_than = 'lessThan',
+  less_or_equal = 'lessOrEqual',
+  in_range = 'inRange',
+  valid_values = 'validValues',
+  length = 'length',
+  min_length = 'minLength',
+  max_length = 'maxLength',
+  pattern = 'pattern'
+}
 
-  getConstraintTypeIfPresent(constraintType: ConstraintTypes): Constraint {
-    return this.constraints.find((constraint) => {
-      return constraint.type == constraintType ? true : false;
-    })
-  }
+export const ConstraintTypesMapping = {
+  [ConstraintTypes.equal]: 'equal',
+  [ConstraintTypes.greater_than]: 'greater_than',
+  [ConstraintTypes.greater_or_equal]: 'greater_or_equal',
+  [ConstraintTypes.less_than]: 'less_than',
+  [ConstraintTypes.less_or_equal]: 'less_or_equal',
+  [ConstraintTypes.in_range]: 'in_range',
+  [ConstraintTypes.valid_values]: 'valid_values',
+  [ConstraintTypes.length]: 'length',
+  [ConstraintTypes.min_length]: 'min_length',
+  [ConstraintTypes.max_length]: 'max_length',
+  [ConstraintTypes.pattern]: 'pattern'
+};
 
-  trackByFn(index) {
-    return index;
-  }
+export interface Constraint {
+  type: ConstraintTypes;
+  value: any;
+}
 
-  isComparable(propType: string): boolean {
-    if (PROPERTY_DATA.COMPARABLE_TYPES.indexOf(propType) >= 0) {
-      return true;
+export function intValidator(): ValidatorFn {
+  const intRegex = /^[-+]?\d+$/;
+  return (control: AbstractControl): ValidationErrors | null => {
+    if (control.value && !intRegex.test(control.value)) {
+      return {invalidInt: true};
     }
-    return false;
-  }
 
+    return null;
+  };
 }
 
-export enum ConstraintTypes {
-  null = "",
-  equal= "equal",
-  greater_than = "greaterThan",
-  greater_or_equal = "greaterOrEqual",
-  less_than = "lessThan",
-  less_or_equal = "lessOrEqual",
-  in_range = "inRange",
-  valid_values = "validValues",
-  length = "length",
-  min_length = "minLength",
-  max_length = "maxLength",
-  pattern = "pattern"
-}
+export function floatValidator(): ValidatorFn {
+  const floatRegex = /^[-+]?\d+(\.\d+)?$/;
 
-export const ConstraintTypesMapping = {
-  [ConstraintTypes.equal]: "equal",
-  [ConstraintTypes.greater_than]: "greater_than",
-  [ConstraintTypes.greater_or_equal]: "greater_or_equal",
-  [ConstraintTypes.less_than]: "less_than",
-  [ConstraintTypes.less_or_equal]: "less_or_equal",
-  [ConstraintTypes.in_range]: "in_range",
-  [ConstraintTypes.valid_values]: "valid_values",
-  [ConstraintTypes.length]: "length",
-  [ConstraintTypes.min_length]: "min_length",
-  [ConstraintTypes.max_length]: "max_length",
-  [ConstraintTypes.pattern]: "pattern"
-};
+  return (control: AbstractControl): ValidationErrors | null => {
+    if (control.value && !floatRegex.test(control.value)) {
+      return {invalidFloat: true};
+    }
 
-export interface Constraint {
-  type:ConstraintTypes,
-  value:any
+    return null;
+  };
 }
index 4f14e1f..48ad24e 100644 (file)
 import { NgModule } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { ConstraintsComponent } from './constraints.component';
-import { FormsModule } from '@angular/forms';
+import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 
 @NgModule({
-    imports: [
-        CommonModule,
-        FormsModule
-    ],
+  imports: [
+    CommonModule,
+    FormsModule,
+    ReactiveFormsModule
+  ],
     declarations: [ConstraintsComponent],
     exports: [ConstraintsComponent],
     entryComponents: [
index 05045c1..52e8c00 100644 (file)
@@ -507,6 +507,8 @@ export class PropertyFormViewModel {
         }
 
         this.$scope.onConstraintChange = (constraints: any): void => {
+            console.log('$scope.onConstraintChange', constraints);
+
             if (!this.$scope.invalidMandatoryFields) {
                 this.$scope.footerButtons[0].disabled = !constraints.valid;
             } else {