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