Fix handling of default values for VFC properties
[sdc.git] / catalog-ui / src / app / directives / property-types / type-map / type-map-directive.ts
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 /**
22  * Created by rcohen on 9/15/2016.
23  */
24 'use strict';
25 import { DataTypesMap, PropertyModel, SchemaProperty } from 'app/models';
26 import { InstanceFeDetails } from 'app/models/instance-fe-details';
27 import { SubPropertyToscaFunction } from 'app/models/sub-property-tosca-function';
28 import { ToscaGetFunction } from 'app/models/tosca-get-function';
29 import { DataTypesService } from 'app/services';
30 import { PROPERTY_DATA, PROPERTY_TYPES, ValidationUtils } from 'app/utils';
31
32 export interface ITypeMapScope extends ng.IScope {
33     parentFormObj: ng.IFormController;
34     schemaProperty: SchemaProperty;
35     parentProperty: PropertyModel;
36     componentInstanceMap: Map<string, InstanceFeDetails>;
37     isMapKeysUnique: boolean;
38     isSchemaTypeDataType: boolean;
39     valueObjRef: any;
40     mapKeys: string[]; // array of map keys
41     mapKeysStatic: string[];
42     MapKeyValidationPattern: RegExp;
43     fieldsPrefixName: string;
44     readOnly: boolean;
45     mapDefaultValue: any;
46     maxLength: number;
47     constraints: string[];
48     showAddBtn: boolean;
49     showToscaFunction: boolean[];
50     types: DataTypesMap;
51
52     getValidationPattern(type: string): RegExp;
53     validateIntRange(value: string): boolean;
54     changeKeyOfMap(newKey: string, index: number, fieldName: string): void;
55     deleteMapItem(index: number): void;
56     addMapItemFields(): void;
57     parseToCorrectType(objectOfValues: any, locationInObj: string, type: string): void;
58     getNumber(num: number): any[];
59     validateSubToscaFunction(key: string): boolean;
60     onEnableTosca(toscaFlag: boolean, index: number);
61     onGetToscaFunction(toscaGetFunction: ToscaGetFunction, key: string);
62 }
63
64 export class TypeMapDirective implements ng.IDirective {
65
66     scope = {
67         valueObjRef: '=', // ref to map object in the parent value object
68         componentInstanceMap: '=',
69         schemaProperty: '=', // get the schema.property object
70         parentFormObj: '=', // ref to parent form (get angular form object)
71         fieldsPrefixName: '=', // prefix for form fields names
72         readOnly: '=', // is form read only
73         defaultValue: '@', // this map default value
74         maxLength: '=',
75         constraints: '=',
76         showAddBtn: '=?',
77         parentProperty: '=',
78         types: '='
79     };
80
81     restrict = 'E';
82     replace = true;
83
84     constructor(private DataTypesService: DataTypesService,
85                 private MapKeyValidationPattern: RegExp,
86                 private ValidationUtils: ValidationUtils,
87                 private $timeout: ng.ITimeoutService) {
88     }
89
90     public static factory = (DataTypesService: DataTypesService,
91                              MapKeyValidationPattern: RegExp,
92                              ValidationUtils: ValidationUtils,
93                              $timeout: ng.ITimeoutService) => {
94         return new TypeMapDirective(DataTypesService, MapKeyValidationPattern, ValidationUtils, $timeout);
95     }
96     template = (): string => {
97         return require('./type-map-directive.html');
98     }
99
100     link = (scope: ITypeMapScope, element: any, $attr: any) => {
101         scope.showAddBtn = angular.isDefined(scope.showAddBtn) ? scope.showAddBtn : true;
102         scope.MapKeyValidationPattern = this.MapKeyValidationPattern;
103         scope.isMapKeysUnique = true;
104
105         if (scope.mapKeys === undefined) {
106             if (scope.valueObjRef) {
107                 scope.mapKeys = Object.keys(scope.valueObjRef);
108             } else if (scope.defaultValue) {
109                 const defaultValue = JSON.parse(scope.defaultValue);
110                 scope.valueObjRef = defaultValue;
111                 scope.mapKeys = Object.keys(defaultValue);
112             } else {
113                 console.warn('Missing value keys');
114             }
115         }
116
117         if (scope.mapKeys) {
118             scope.showToscaFunction = new Array(scope.mapKeys.length);
119             scope.mapKeys.forEach((key, index) => {
120                 scope.showToscaFunction[index] = false;
121                 if (scope.parentProperty && scope.parentProperty.subPropertyToscaFunctions != null) {
122                     scope.parentProperty.subPropertyToscaFunctions.forEach((SubPropertyToscaFunction) => {
123                         if (SubPropertyToscaFunction.subPropertyPath.indexOf(key) != -1) {
124                             scope.showToscaFunction[index] = true;
125                         }
126                     });
127                 }
128             });
129         } else {
130             console.warn('Missing map keys');
131         }
132
133         // reset valueObjRef and mapKeys when schema type is changed
134         scope.$watchCollection('schemaProperty.type', (newData: any): void => {
135             scope.isSchemaTypeDataType = this.isDataTypeForSchemaType(scope.schemaProperty, scope.types);
136             if (scope.valueObjRef) {
137                 scope.mapKeys = Object.keys(scope.valueObjRef);
138                 // keeping another copy of the keys, as the mapKeys gets overridden sometimes
139                 scope.mapKeysStatic = Object.keys(scope.valueObjRef);
140             }
141         });
142
143         scope.$watchCollection('valueObjRef', (newData: any): void => {
144             if (scope.valueObjRef) {
145                 scope.mapKeys = Object.keys(scope.valueObjRef);
146                 scope.mapKeysStatic = Object.keys(scope.valueObjRef);
147             } else {
148                 console.warn('valueObjRef missing', scope.valueObjRef);
149             }
150         });
151
152         // when user brows between properties in "edit property form"
153         scope.$watchCollection('fieldsPrefixName', (newData: any): void => {
154             if (scope.valueObjRef) {
155                 scope.mapKeys = Object.keys(scope.valueObjRef);
156                 // keeping another copy of the keys, as the mapKeys gets overridden sometimes
157                 scope.mapKeysStatic = Object.keys(scope.valueObjRef);
158             }
159
160             if ($attr.defaultValue) {
161                 scope.mapDefaultValue = JSON.parse($attr.defaultValue);
162             }
163         });
164
165         // return dummy array in order to prevent rendering map-keys ng-repeat again when a map key is changed
166         scope.getNumber = (num: number): any[] => {
167             return new Array(num);
168         };
169
170         scope.getValidationPattern = (type: string): RegExp => {
171             return this.ValidationUtils.getValidationPattern(type);
172         };
173
174         scope.validateIntRange = (value: string): boolean => {
175             return !value || this.ValidationUtils.validateIntRange(value);
176         };
177
178         scope.changeKeyOfMap = (newKey: string, index: number, fieldName: string): void => {
179             const currentKeySet = Object.keys(scope.valueObjRef);
180             const currentKey = currentKeySet[index];
181             const existingKeyIndex = currentKeySet.indexOf(newKey);
182             if (existingKeyIndex > -1 && existingKeyIndex != index) {
183                 scope.parentFormObj[fieldName].$setValidity('keyExist', false);
184                 scope.isMapKeysUnique = false;
185                 return;
186             }
187
188             scope.parentFormObj[fieldName].$setValidity('keyExist', true);
189             scope.isMapKeysUnique = true;
190             if (!scope.parentFormObj[fieldName].$invalid) {
191                 // To preserve the order of the keys, delete each one and recreate
192                 const newObj = {};
193                 angular.copy(scope.valueObjRef, newObj);
194                 angular.forEach(newObj, function(value: any, key: string) {
195                     delete scope.valueObjRef[key];
196                     if (key == currentKey) {
197                         scope.valueObjRef[newKey] = value;
198                     } else {
199                         scope.valueObjRef[key] = value;
200                     }
201                 });
202             }
203         };
204
205         scope.deleteMapItem = (index: number): void => {
206             const keyToChange = scope.mapKeys[index];
207             delete scope.valueObjRef[scope.mapKeys[index]];
208             scope.mapKeys.splice(index, 1);
209             scope.showToscaFunction.splice(index, 1);
210             if (scope.parentProperty && scope.parentProperty.subPropertyToscaFunctions != null) {
211                 const subToscaFunctionList: SubPropertyToscaFunction[] = [];
212                 scope.parentProperty.subPropertyToscaFunctions.forEach((SubPropertyToscaFunction, index) => {
213                     if (SubPropertyToscaFunction.subPropertyPath.indexOf(keyToChange) == -1) {
214                         subToscaFunctionList.push(SubPropertyToscaFunction);
215                     }
216                 });
217                 scope.parentProperty.subPropertyToscaFunctions = subToscaFunctionList;
218             }
219             if (!scope.mapKeys.length) {// only when user removes all pairs of key-value fields - put the default
220                 if (scope.mapDefaultValue) {
221                     angular.copy(scope.mapDefaultValue, scope.valueObjRef);
222                     scope.mapKeys = Object.keys(scope.valueObjRef);
223                 }
224             }
225         };
226
227         scope.onEnableTosca = (toscaFlag: boolean, flagIndex: number): void => {
228             scope.showToscaFunction[flagIndex] = toscaFlag;
229             scope.valueObjRef[scope.mapKeys[flagIndex]] = null;
230             if (!toscaFlag) {
231                 if (scope.parentProperty.subPropertyToscaFunctions != null) {
232                     const subToscaFunctionList: SubPropertyToscaFunction[] = [];
233                     scope.parentProperty.subPropertyToscaFunctions.forEach((SubPropertyToscaFunction, index) => {
234                         if (SubPropertyToscaFunction.subPropertyPath.indexOf(scope.mapKeys[flagIndex]) == -1) {
235                             subToscaFunctionList.push(SubPropertyToscaFunction);
236                         }
237                     });
238                     scope.parentProperty.subPropertyToscaFunctions = subToscaFunctionList;
239                 }
240             }
241         };
242
243         scope.onGetToscaFunction = (toscaGetFunction: ToscaGetFunction, key: string): void => {
244             if (scope.parentProperty.subPropertyToscaFunctions != null) {
245                 scope.parentProperty.subPropertyToscaFunctions.forEach((SubPropertyToscaFunction) => {
246                     if (SubPropertyToscaFunction.subPropertyPath.indexOf(key) != -1) {
247                         SubPropertyToscaFunction.toscaFunction = toscaGetFunction;
248                         return;
249                     }
250                 });
251
252             }
253             if (scope.parentProperty.subPropertyToscaFunctions == null) {
254                 scope.parentProperty.subPropertyToscaFunctions = [];
255             }
256             const subPropertyToscaFunction = new SubPropertyToscaFunction();
257             subPropertyToscaFunction.toscaFunction = toscaGetFunction;
258             subPropertyToscaFunction.subPropertyPath = [key];
259             scope.parentProperty.subPropertyToscaFunctions.push(subPropertyToscaFunction);
260         };
261
262         scope.addMapItemFields = (): void => {
263             if (!scope.valueObjRef) {
264                 scope.valueObjRef = {};
265                 scope.showToscaFunction = [];
266             }
267
268             scope.valueObjRef[''] = null;
269             scope.mapKeys = Object.keys(scope.valueObjRef);
270             scope.showToscaFunction.push(false);
271         };
272
273         scope.parseToCorrectType = (objectOfValues: any, locationInObj: string, type: string): void => {
274             if (objectOfValues[locationInObj] && type != PROPERTY_TYPES.STRING) {
275                 objectOfValues[locationInObj] = JSON.parse(objectOfValues[locationInObj]);
276             }
277         };
278
279         scope.validateSubToscaFunction = (key: string): boolean => {
280             if (scope.parentProperty.subPropertyToscaFunctions != null) {
281                 scope.parentProperty.subPropertyToscaFunctions.forEach((SubPropertyToscaFunction) => {
282                     if (SubPropertyToscaFunction.subPropertyPath.indexOf(key) != -1) {
283                         return true;
284                     }
285                 });
286             }
287             return false;
288         };
289     }
290
291     private isDataTypeForSchemaType = (property: SchemaProperty, types: DataTypesMap): boolean => {
292         property.simpleType = '';
293         if (property.type && PROPERTY_DATA.TYPES.indexOf(property.type) > -1) {
294             return false;
295         }
296         const simpleType = this.getTypeForDataTypeDerivedFromSimple(property.type, types);
297         if (simpleType) {
298             property.simpleType = simpleType;
299             return false;
300         }
301         return true;
302     }
303
304     private getTypeForDataTypeDerivedFromSimple = (dataTypeName: string, types: DataTypesMap): string => {
305         if (!types[dataTypeName]) {
306             return 'string';
307         }
308         if (types[dataTypeName].derivedFromName == 'tosca.datatypes.Root' || types[dataTypeName].properties) {
309             return null;
310         }
311         if (PROPERTY_DATA.SIMPLE_TYPES.indexOf(types[dataTypeName].derivedFromName) > -1) {
312             return types[dataTypeName].derivedFromName;
313         }
314         return this.getTypeForDataTypeDerivedFromSimple(types[dataTypeName].derivedFromName, types);
315     }
316 }
317
318 TypeMapDirective.factory.$inject = ['Sdc.Services.DataTypesService', 'MapKeyValidationPattern', 'ValidationUtils', '$timeout'];