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