f8fa6dd359ff14d2ae0a6e0e0c5618944f81f1fa
[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     isService: boolean;
52
53     getValidationPattern(type: string): RegExp;
54     validateIntRange(value: string): boolean;
55     changeKeyOfMap(newKey: string, index: number, fieldName: string): void;
56     deleteMapItem(index: number): void;
57     addMapItemFields(): void;
58     parseToCorrectType(objectOfValues: any, locationInObj: string, type: string): void;
59     getNumber(num: number): any[];
60     validateSubToscaFunction(key: string): boolean;
61     onEnableTosca(toscaFlag: boolean, index: number);
62     onGetToscaFunction(toscaGetFunction: ToscaGetFunction, key: string);
63 }
64
65 export class TypeMapDirective implements ng.IDirective {
66
67     scope = {
68         valueObjRef: '=', // ref to map object in the parent value object
69         componentInstanceMap: '=',
70         schemaProperty: '=', // get the schema.property object
71         parentFormObj: '=', // ref to parent form (get angular form object)
72         fieldsPrefixName: '=', // prefix for form fields names
73         readOnly: '=', // is form read only
74         defaultValue: '@', // this map default value
75         maxLength: '=',
76         constraints: '=',
77         showAddBtn: '=?',
78         parentProperty: '=',
79         types: '=',
80         isService: '='
81     };
82
83     restrict = 'E';
84     replace = true;
85
86     constructor(private DataTypesService: DataTypesService,
87                 private MapKeyValidationPattern: RegExp,
88                 private ValidationUtils: ValidationUtils,
89                 private $timeout: ng.ITimeoutService) {
90     }
91
92     public static factory = (DataTypesService: DataTypesService,
93                              MapKeyValidationPattern: RegExp,
94                              ValidationUtils: ValidationUtils,
95                              $timeout: ng.ITimeoutService) => {
96         return new TypeMapDirective(DataTypesService, MapKeyValidationPattern, ValidationUtils, $timeout);
97     }
98     template = (): string => {
99         return require('./type-map-directive.html');
100     }
101
102     link = (scope: ITypeMapScope, element: any, $attr: any) => {
103         scope.showAddBtn = angular.isDefined(scope.showAddBtn) ? scope.showAddBtn : true;
104         scope.MapKeyValidationPattern = this.MapKeyValidationPattern;
105         scope.isMapKeysUnique = true;
106
107         if (scope.mapKeys === undefined) {
108             if (scope.valueObjRef) {
109                 scope.mapKeys = Object.keys(scope.valueObjRef);
110             } else if (scope.defaultValue) {
111                 const defaultValue = JSON.parse(scope.defaultValue);
112                 scope.valueObjRef = defaultValue;
113                 scope.mapKeys = Object.keys(defaultValue);
114             } else {
115                 console.warn('Missing value keys');
116             }
117         }
118
119         if (scope.mapKeys) {
120             scope.showToscaFunction = new Array(scope.mapKeys.length);
121             scope.mapKeys.forEach((key, index) => {
122                 scope.showToscaFunction[index] = false;
123                 if (scope.parentProperty && scope.parentProperty.subPropertyToscaFunctions != null) {
124                     scope.parentProperty.subPropertyToscaFunctions.forEach((SubPropertyToscaFunction) => {
125                         if (SubPropertyToscaFunction.subPropertyPath.indexOf(key) != -1) {
126                             scope.showToscaFunction[index] = true;
127                         }
128                     });
129                 }
130             });
131         } else {
132             console.warn('Missing map keys');
133         }
134
135         // reset valueObjRef and mapKeys when schema type is changed
136         scope.$watchCollection('schemaProperty.type', (newData: any): void => {
137             scope.isSchemaTypeDataType = this.isDataTypeForSchemaType(scope.schemaProperty, scope.types);
138             if (scope.valueObjRef) {
139                 scope.mapKeys = Object.keys(scope.valueObjRef);
140                 // keeping another copy of the keys, as the mapKeys gets overridden sometimes
141                 scope.mapKeysStatic = Object.keys(scope.valueObjRef);
142             }
143         });
144
145         scope.$watchCollection('valueObjRef', (newData: any): void => {
146             if (scope.valueObjRef) {
147                 scope.mapKeys = Object.keys(scope.valueObjRef);
148                 scope.mapKeysStatic = Object.keys(scope.valueObjRef);
149             } else {
150                 console.warn('valueObjRef missing', scope.valueObjRef);
151             }
152         });
153
154         // when user brows between properties in "edit property form"
155         scope.$watchCollection('fieldsPrefixName', (newData: any): void => {
156             if (scope.valueObjRef) {
157                 scope.mapKeys = Object.keys(scope.valueObjRef);
158                 // keeping another copy of the keys, as the mapKeys gets overridden sometimes
159                 scope.mapKeysStatic = Object.keys(scope.valueObjRef);
160             }
161
162             if ($attr.defaultValue) {
163                 scope.mapDefaultValue = JSON.parse($attr.defaultValue);
164             }
165         });
166
167         // return dummy array in order to prevent rendering map-keys ng-repeat again when a map key is changed
168         scope.getNumber = (num: number): any[] => {
169             return new Array(num);
170         };
171
172         scope.getValidationPattern = (type: string): RegExp => {
173             return this.ValidationUtils.getValidationPattern(type);
174         };
175
176         scope.validateIntRange = (value: string): boolean => {
177             return !value || this.ValidationUtils.validateIntRange(value);
178         };
179
180         scope.changeKeyOfMap = (newKey: string, index: number, fieldName: string): void => {
181             const currentKeySet = Object.keys(scope.valueObjRef);
182             const currentKey = currentKeySet[index];
183             const existingKeyIndex = currentKeySet.indexOf(newKey);
184             if (existingKeyIndex > -1 && existingKeyIndex != index) {
185                 scope.parentFormObj[fieldName].$setValidity('keyExist', false);
186                 scope.isMapKeysUnique = false;
187                 return;
188             }
189
190             scope.parentFormObj[fieldName].$setValidity('keyExist', true);
191             scope.isMapKeysUnique = true;
192             if (!scope.parentFormObj[fieldName].$invalid) {
193                 // To preserve the order of the keys, delete each one and recreate
194                 const newObj = {};
195                 angular.copy(scope.valueObjRef, newObj);
196                 angular.forEach(newObj, function(value: any, key: string) {
197                     delete scope.valueObjRef[key];
198                     if (key == currentKey) {
199                         scope.valueObjRef[newKey] = value;
200                     } else {
201                         scope.valueObjRef[key] = value;
202                     }
203                 });
204             }
205         };
206
207         scope.deleteMapItem = (index: number): void => {
208             const keyToChange = scope.mapKeys[index];
209             delete scope.valueObjRef[scope.mapKeys[index]];
210             scope.mapKeys.splice(index, 1);
211             scope.showToscaFunction.splice(index, 1);
212             if (scope.parentProperty && scope.parentProperty.subPropertyToscaFunctions != null) {
213                 const subToscaFunctionList: SubPropertyToscaFunction[] = [];
214                 scope.parentProperty.subPropertyToscaFunctions.forEach((SubPropertyToscaFunction, index) => {
215                     if (SubPropertyToscaFunction.subPropertyPath.indexOf(keyToChange) == -1) {
216                         subToscaFunctionList.push(SubPropertyToscaFunction);
217                     }
218                 });
219                 scope.parentProperty.subPropertyToscaFunctions = subToscaFunctionList;
220             }
221             if (!scope.mapKeys.length) {// only when user removes all pairs of key-value fields - put the default
222                 if (scope.mapDefaultValue) {
223                     angular.copy(scope.mapDefaultValue, scope.valueObjRef);
224                     scope.mapKeys = Object.keys(scope.valueObjRef);
225                 }
226             }
227         };
228
229         scope.onEnableTosca = (toscaFlag: boolean, flagIndex: number): void => {
230             scope.showToscaFunction[flagIndex] = toscaFlag;
231             scope.valueObjRef[scope.mapKeys[flagIndex]] = null;
232             if (!toscaFlag) {
233                 if (scope.parentProperty.subPropertyToscaFunctions != null) {
234                     const subToscaFunctionList: SubPropertyToscaFunction[] = [];
235                     scope.parentProperty.subPropertyToscaFunctions.forEach((SubPropertyToscaFunction, index) => {
236                         if (SubPropertyToscaFunction.subPropertyPath.indexOf(scope.mapKeys[flagIndex]) == -1) {
237                             subToscaFunctionList.push(SubPropertyToscaFunction);
238                         }
239                     });
240                     scope.parentProperty.subPropertyToscaFunctions = subToscaFunctionList;
241                 }
242             }
243         };
244
245         scope.onGetToscaFunction = (toscaGetFunction: ToscaGetFunction, key: string): void => {
246             if (scope.parentProperty.subPropertyToscaFunctions != null) {
247                 scope.parentProperty.subPropertyToscaFunctions.forEach((SubPropertyToscaFunction) => {
248                     if (SubPropertyToscaFunction.subPropertyPath.indexOf(key) != -1) {
249                         SubPropertyToscaFunction.toscaFunction = toscaGetFunction;
250                         return;
251                     }
252                 });
253
254             }
255             if (scope.parentProperty.subPropertyToscaFunctions == null) {
256                 scope.parentProperty.subPropertyToscaFunctions = [];
257             }
258             const subPropertyToscaFunction = new SubPropertyToscaFunction();
259             subPropertyToscaFunction.toscaFunction = toscaGetFunction;
260             subPropertyToscaFunction.subPropertyPath = [key];
261             scope.parentProperty.subPropertyToscaFunctions.push(subPropertyToscaFunction);
262         };
263
264         scope.addMapItemFields = (): void => {
265             if (!scope.valueObjRef) {
266                 scope.valueObjRef = {};
267                 scope.showToscaFunction = [];
268             }
269
270             scope.valueObjRef[''] = null;
271             scope.mapKeys = Object.keys(scope.valueObjRef);
272             scope.showToscaFunction.push(false);
273         };
274
275         scope.parseToCorrectType = (objectOfValues: any, locationInObj: string, type: string): void => {
276             if (objectOfValues[locationInObj] && type != PROPERTY_TYPES.STRING) {
277                 objectOfValues[locationInObj] = JSON.parse(objectOfValues[locationInObj]);
278             }
279         };
280
281         scope.validateSubToscaFunction = (key: string): boolean => {
282             if (scope.parentProperty.subPropertyToscaFunctions != null) {
283                 scope.parentProperty.subPropertyToscaFunctions.forEach((SubPropertyToscaFunction) => {
284                     if (SubPropertyToscaFunction.subPropertyPath.indexOf(key) != -1) {
285                         return true;
286                     }
287                 });
288             }
289             return false;
290         };
291     }
292
293     private isDataTypeForSchemaType = (property: SchemaProperty, types: DataTypesMap): boolean => {
294         property.simpleType = '';
295         if (property.type && PROPERTY_DATA.TYPES.indexOf(property.type) > -1) {
296             return false;
297         }
298         const simpleType = this.getTypeForDataTypeDerivedFromSimple(property.type, types);
299         if (simpleType) {
300             property.simpleType = simpleType;
301             return false;
302         }
303         return true;
304     }
305
306     private getTypeForDataTypeDerivedFromSimple = (dataTypeName: string, types: DataTypesMap): string => {
307         if (!types[dataTypeName]) {
308             return 'string';
309         }
310         if (types[dataTypeName].derivedFromName == 'tosca.datatypes.Root' || types[dataTypeName].properties) {
311             return null;
312         }
313         if (PROPERTY_DATA.SIMPLE_TYPES.indexOf(types[dataTypeName].derivedFromName) > -1) {
314             return types[dataTypeName].derivedFromName;
315         }
316         return this.getTypeForDataTypeDerivedFromSimple(types[dataTypeName].derivedFromName, types);
317     }
318 }
319
320 TypeMapDirective.factory.$inject = ['Sdc.Services.DataTypesService', 'MapKeyValidationPattern', 'ValidationUtils', '$timeout'];