2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2022 Nordix Foundation
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 * SPDX-License-Identifier: Apache-2.0
17 * ============LICENSE_END=========================================================
20 import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
22 AbstractControl, FormArray,
25 FormGroup, ValidationErrors,
28 } from '@angular/forms';
29 import { PROPERTY_DATA, PROPERTY_TYPES } from 'app/utils/constants';
32 selector: 'app-constraints',
33 templateUrl: './constraints.component.html',
34 styleUrls: ['./constraints.component.less']
36 export class ConstraintsComponent implements OnInit {
38 @Input() propertyConstraints: any[];
39 @Input() propertyType: string;
40 @Input() isViewOnly: boolean = false;
41 @Output() onConstraintChange: EventEmitter<any> = new EventEmitter<any>();
43 constraintTypes: string[];
44 ConstraintTypesMapping = ConstraintTypesMapping;
45 valid: boolean = false;
46 constraintForm: FormGroup;
50 constructor(private formBuilder: FormBuilder) {}
52 get constraintsArray() {
53 return this.constraintForm.get('constraint') as FormArray;
56 get constraintValidators(): ValidatorFn {
57 switch (this.propertyType) {
58 case PROPERTY_TYPES.INTEGER:
59 console.warn('Add int validator');
60 return Validators.compose([
64 case PROPERTY_TYPES.FLOAT:
65 console.warn('Add float validator');
66 return Validators.compose([
71 console.warn('Only required validator');
72 return Validators.compose([
78 public constraintValuesArray(index: number): FormArray {
79 return this.constraintsArray.at(index).get('value') as FormArray;
84 this.constraintTypes = Object.keys(ConstraintTypes).map((key) => ConstraintTypes[key]);
86 // This is only used by the spec test
87 if (!this.constraintForm) {
88 this.constraintForm = this.formBuilder.group({
89 constraint: this.formBuilder.array([])
93 this.validationMessages = {
95 { type: 'required', message: 'Constraint value is required'},
96 { type: 'invalidInt', message: 'Constraint value is not a valid integer'},
97 { type: 'invalidFloat', message: 'Constraint value is not a valid floating point value'}
100 { type: 'required', message: 'Constraint type is required'}
107 ngOnChanges(changes): void {
110 // Changes fires before init so form has to be initialised here
112 this.constraintForm = this.formBuilder.group({
113 constraint: this.formBuilder.array([])
116 if (changes.propertyConstraints && changes.propertyConstraints.currentValue) {
117 changes.propertyConstraints.currentValue.forEach((constraint: any) => {
118 const prop = this.getConstraintFromPropertyBEModel(constraint);
119 console.log('constraint from BE model', prop);
120 this.constraintsArray.push(prop);
125 if (changes.propertyType) {
127 // Reset constraints on property type change
128 console.warn('Property type changed. Resetting constraints');
129 this.constraintForm = this.formBuilder.group({
130 constraint: this.formBuilder.array([])
134 if (!this.propertyType || changes.propertyType.currentValue == this.propertyType) {
135 this.propertyType = changes.propertyType.currentValue;
137 this.propertyType = changes.propertyType;
138 this.emitOnConstraintChange();
141 this.constraintsArray.controls.forEach((control: AbstractControl) => {
142 control.get('value').setValidators(this.constraintValidators);
146 console.log('constraints', this.constraintsArray);
149 removeFromList(constraintIndex: number, valueIndex: number) {
150 this.constraintsArray.at(constraintIndex).get('value').value.splice(valueIndex, 1);
151 this.emitOnConstraintChange();
154 addToList(constraintIndex: number) {
155 const newConstraint = new FormControl('', this.constraintValidators);
157 this.constraintValuesArray(constraintIndex).push(newConstraint);
158 console.log('constraintsArray', this.constraintsArray);
159 console.log('constraintValuesArray', this.constraintValuesArray(constraintIndex));
160 this.emitOnConstraintChange();
163 onChangeConstraintType(constraintIndex: number, newType: ConstraintTypes) {
164 if ((newType == ConstraintTypes.valid_values)) {
165 const newConstraint = this.formBuilder.group({
166 type: new FormControl({
168 disabled: this.isViewOnly
169 }, Validators.required),
170 value: this.formBuilder.array([])});
172 this.constraintsArray.removeAt(constraintIndex);
173 this.constraintsArray.push(newConstraint);
174 } else if (newType == ConstraintTypes.in_range) {
175 const newConstraint = this.formBuilder.group({
176 type: new FormControl({
178 disabled: this.isViewOnly
179 }, Validators.required),
180 value: this.formBuilder.array([])});
182 const valRef = newConstraint.get('value') as FormArray;
183 valRef.push(new FormControl('', this.constraintValidators));
184 valRef.push(new FormControl('', this.constraintValidators));
186 this.constraintsArray.removeAt(constraintIndex);
187 this.constraintsArray.push(newConstraint);
189 this.constraintsArray.at(constraintIndex).value.type = newType;
191 this.emitOnConstraintChange();
194 onChangeConstraintValue(constraintIndex: number, newValue: any) {
195 this.constraintsArray.at(constraintIndex).get('value').setValue(newValue);
196 this.emitOnConstraintChange();
199 onChangeConstrainValueIndex(constraintIndex: number, newValue: any, valueIndex: number) {
200 this.constraintValuesArray(constraintIndex).controls[valueIndex].setValue(newValue);
201 this.emitOnConstraintChange();
204 removeConstraint(constraintIndex: number) {
205 this.constraintsArray.removeAt(constraintIndex);
206 this.emitOnConstraintChange();
210 const newConstraint = this.formBuilder.group({
211 type: new FormControl({
212 value: ConstraintTypes.null,
213 disabled: this.isViewOnly
214 }, Validators.required),
215 value: new FormControl({
217 disabled: this.isViewOnly
218 }, this.constraintValidators)
220 this.constraintsArray.push(newConstraint);
222 this.emitOnConstraintChange();
225 getInRangeValue(constraintIndex: number, valueIndex: number): string {
226 const value = this.constraintsArray.at(constraintIndex).get('value').value;
228 if (!value || !value[valueIndex]) {
232 return value[valueIndex];
235 disableConstraint(optionConstraintType: ConstraintTypes): boolean {
236 const invalid = this.notAllowedConstraint(optionConstraintType);
237 return invalid ? invalid : this.getConstraintTypeIfPresent(optionConstraintType) ? true : false;
240 notAllowedConstraint(optionConstraintType: ConstraintTypes): boolean {
241 switch (optionConstraintType) {
242 case ConstraintTypes.less_or_equal:
243 case ConstraintTypes.less_than:
244 case ConstraintTypes.greater_or_equal:
245 case ConstraintTypes.greater_than:
246 case ConstraintTypes.in_range:
247 if (this.isComparable(this.propertyType)) {
251 case ConstraintTypes.length:
252 case ConstraintTypes.max_length:
253 case ConstraintTypes.min_length:
254 if (this.propertyType == PROPERTY_TYPES.STRING || this.propertyType == PROPERTY_TYPES.MAP || this.propertyType == PROPERTY_TYPES.LIST) {
258 case ConstraintTypes.pattern:
259 if (this.propertyType == PROPERTY_TYPES.STRING) {
263 case ConstraintTypes.valid_values:
264 case ConstraintTypes.equal:
270 getConstraintTypeIfPresent(constraintType: ConstraintTypes): AbstractControl {
271 return this.constraintsArray.controls.find((control: AbstractControl) => {
272 const type = control.get('type').value;
273 return type == constraintType;
281 isComparable(propType: string): boolean {
282 if (PROPERTY_DATA.COMPARABLE_TYPES.indexOf(propType) >= 0) {
288 private getConstraintFromPropertyBEModel(constraint: any): AbstractControl {
289 console.log('be model constraints', constraint);
290 let constraintType: ConstraintTypes;
291 let constraintValue: any;
293 constraintType = ConstraintTypes.null;
294 constraintValue = '';
295 } else if (constraint.hasOwnProperty(ConstraintTypes.valid_values)) {
296 constraintType = ConstraintTypes.valid_values;
297 } else if (constraint.hasOwnProperty(ConstraintTypes.equal)) {
298 constraintType = ConstraintTypes.equal;
299 constraintValue = constraint.equal;
300 } else if (constraint.hasOwnProperty(ConstraintTypes.greater_than)) {
301 constraintType = ConstraintTypes.greater_than;
302 constraintValue = constraint.greaterThan;
303 } else if (constraint.hasOwnProperty(ConstraintTypes.greater_or_equal)) {
304 constraintType = ConstraintTypes.greater_or_equal;
305 constraintValue = constraint.greaterOrEqual;
306 } else if (constraint.hasOwnProperty(ConstraintTypes.less_than)) {
307 constraintType = ConstraintTypes.less_than;
308 constraintValue = constraint.lessThan;
309 } else if (constraint.hasOwnProperty(ConstraintTypes.less_or_equal)) {
310 constraintType = ConstraintTypes.less_or_equal;
311 constraintValue = constraint.lessOrEqual;
312 } else if (constraint.hasOwnProperty(ConstraintTypes.in_range)) {
313 constraintType = ConstraintTypes.in_range;
314 constraintValue = new Array(constraint.inRange[0], constraint.inRange[1]);
315 } else if (constraint.rangeMaxValue || constraint.rangeMinValue) {
316 constraintType = ConstraintTypes.in_range;
317 constraintValue = new Array(constraint.rangeMinValue, constraint.rangeMaxValue);
318 } else if (constraint.hasOwnProperty(ConstraintTypes.length)) {
319 constraintType = ConstraintTypes.length;
320 constraintValue = constraint.length;
321 } else if (constraint.hasOwnProperty(ConstraintTypes.min_length)) {
322 constraintType = ConstraintTypes.min_length;
323 constraintValue = constraint.minLength;
324 } else if (constraint.hasOwnProperty(ConstraintTypes.max_length)) {
325 constraintType = ConstraintTypes.max_length;
326 constraintValue = constraint.maxLength;
327 } else if (constraint.hasOwnProperty(ConstraintTypes.pattern)) {
328 constraintType = ConstraintTypes.pattern;
329 constraintValue = constraint.pattern;
332 if (!constraint.hasOwnProperty(ConstraintTypes.valid_values) && !constraint.hasOwnProperty(ConstraintTypes.in_range)) {
333 return this.formBuilder.group({
334 type: new FormControl({
335 value: constraintType,
336 disabled: this.isViewOnly
337 }, Validators.required),
338 value: new FormControl({
339 value: constraintValue,
340 disabled: this.isViewOnly
341 }, this.constraintValidators)
344 const newForm = this.formBuilder.group({
345 type: new FormControl({
346 value: constraintType,
347 disabled: this.isViewOnly
348 }, Validators.required),
349 value: this.formBuilder.array([])
352 const valRef = newForm.get('value') as FormArray;
353 if (constraint.hasOwnProperty(ConstraintTypes.valid_values)) {
354 constraint.validValues.forEach((val) => {
355 valRef.push(new FormControl({value: val, disabled: this.isViewOnly}, this.constraintValidators));
358 constraint.inRange.forEach((val) => {
359 valRef.push(new FormControl({value: val, disabled: this.isViewOnly}, this.constraintValidators));
363 console.log('new form', newForm);
368 private getConstraintsFormat(): any[] {
369 const constraintArray = new Array();
370 this.constraintsArray.controls.forEach((control: AbstractControl) => {
371 const type = control.get('type').value;
372 let constraint: Constraint;
374 if (type != ConstraintTypes.valid_values && type != ConstraintTypes.in_range) {
377 value: control.get('value').value
382 control.get('value').value.forEach((val) => {
392 console.log('New constraint object', constraint);
393 constraintArray.push(this.getConstraintFormat(constraint));
395 return constraintArray;
398 private getConstraintFormat(constraint: Constraint): any {
399 switch (constraint.type) {
400 case ConstraintTypes.equal:
402 [ConstraintTypes.equal]: constraint.value
404 case ConstraintTypes.less_or_equal:
406 [ConstraintTypes.less_or_equal]: constraint.value
408 case ConstraintTypes.less_than:
410 [ConstraintTypes.less_than]: constraint.value
412 case ConstraintTypes.greater_or_equal:
414 [ConstraintTypes.greater_or_equal]: constraint.value
416 case ConstraintTypes.greater_than:
418 [ConstraintTypes.greater_than]: constraint.value
420 case ConstraintTypes.in_range:
422 [ConstraintTypes.in_range]: constraint.value
424 case ConstraintTypes.length:
426 [ConstraintTypes.length]: constraint.value
428 case ConstraintTypes.max_length:
430 [ConstraintTypes.max_length]: constraint.value
432 case ConstraintTypes.min_length:
434 [ConstraintTypes.min_length]: constraint.value
436 case ConstraintTypes.pattern:
438 [ConstraintTypes.pattern]: constraint.value
440 case ConstraintTypes.valid_values:
442 [ConstraintTypes.valid_values]: constraint.value
449 private validateConstraints(): void {
450 this.valid = this.constraintsArray.controls.every((control: AbstractControl) => {
451 const value = control.get('value').value;
452 const type = control.get('type').value;
453 control.updateValueAndValidity();
455 if (Array.isArray(value)) {
456 return !(value.length == 0 || this.doesArrayContaintEmptyValues(value));
458 if (type == ConstraintTypes.pattern) {
466 this.valid = this.constraintForm.valid;
469 return value && type != ConstraintTypes.null;
473 private doesArrayContaintEmptyValues(arr) {
474 for (const element of arr) {
475 if (element === '') { return true; }
480 private emitOnConstraintChange(): void {
481 console.log('constraints', this.constraintsArray);
483 this.validateConstraints();
484 const newConstraints = this.getConstraintsFormat();
486 this.valid = this.constraintForm.valid;
487 console.log('emitOnConstraintChange.valid', this.valid);
489 this.onConstraintChange.emit({
490 constraints: newConstraints,
497 export enum ConstraintTypes {
500 greater_than = 'greaterThan',
501 greater_or_equal = 'greaterOrEqual',
502 less_than = 'lessThan',
503 less_or_equal = 'lessOrEqual',
504 in_range = 'inRange',
505 valid_values = 'validValues',
507 min_length = 'minLength',
508 max_length = 'maxLength',
512 export const ConstraintTypesMapping = {
513 [ConstraintTypes.equal]: 'equal',
514 [ConstraintTypes.greater_than]: 'greater_than',
515 [ConstraintTypes.greater_or_equal]: 'greater_or_equal',
516 [ConstraintTypes.less_than]: 'less_than',
517 [ConstraintTypes.less_or_equal]: 'less_or_equal',
518 [ConstraintTypes.in_range]: 'in_range',
519 [ConstraintTypes.valid_values]: 'valid_values',
520 [ConstraintTypes.length]: 'length',
521 [ConstraintTypes.min_length]: 'min_length',
522 [ConstraintTypes.max_length]: 'max_length',
523 [ConstraintTypes.pattern]: 'pattern'
526 export interface Constraint {
527 type: ConstraintTypes;
531 export function intValidator(): ValidatorFn {
532 const intRegex = /^[-+]?\d+$/;
533 return (control: AbstractControl): ValidationErrors | null => {
534 if (control.value && !intRegex.test(control.value)) {
535 return {invalidInt: true};
542 export function floatValidator(): ValidatorFn {
543 const floatRegex = /^[-+]?\d+(\.\d+)?$/;
545 return (control: AbstractControl): ValidationErrors | null => {
546 if (control.value && !floatRegex.test(control.value)) {
547 return {invalidFloat: true};