7be44c30754ed92ffb46ec3d39f9390587dc91db
[sdc.git] /
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 import * as _ from "lodash";
22 import {Component, Input, Output, EventEmitter, ViewChild, ComponentRef} from "@angular/core";
23 import { PropertyFEModel, DerivedFEProperty, DerivedPropertyType } from "app/models";
24 import { PROPERTY_TYPES } from 'app/utils';
25 import { DataTypeService } from "../../../../services/data-type.service";
26 import { trigger, style, transition, animate } from '@angular/animations';
27 import {PropertiesUtils} from "../../../../pages/properties-assignment/services/properties.utils";
28 import {IUiElementChangeEvent} from "../../../ui/form-components/ui-element-base.component";
29 import {DynamicElementComponent} from "../../../ui/dynamic-element/dynamic-element.component";
30 import {SubPropertyToscaFunction} from "app/models/sub-property-tosca-function";
31
32 @Component({
33     selector: 'dynamic-property',
34     templateUrl: './dynamic-property.component.html',
35     styleUrls: ['./dynamic-property.component.less'],
36     animations: [trigger('fadeIn', [transition(':enter', [style({ opacity: '0' }), animate('.7s ease-out', style({ opacity: '1' }))])])]
37 })
38 export class DynamicPropertyComponent {
39
40     derivedPropertyTypes = DerivedPropertyType; //http://stackoverflow.com/questions/35835984/how-to-use-a-typescript-enum-value-in-an-angular2-ngswitch-statement
41     propType: DerivedPropertyType;
42     propPath: string;
43     isPropertyFEModel: boolean;
44     nestedLevel: number;
45     propertyTestsId: string;
46     constraints:string[];
47     checkboxDisabled: boolean = false;
48
49     @Input() canBeDeclared: boolean;
50     @Input() property: PropertyFEModel | DerivedFEProperty;
51     @Input() expandedChildId: string;
52     @Input() selectedPropertyId: string;
53     @Input() propertyNameSearchText: string;
54     @Input() readonly: boolean;
55     @Input() hasChildren: boolean;
56     @Input() hasDeclareOption:boolean;
57     @Input() rootProperty: PropertyFEModel;
58
59     @Output('propertyChanged') emitter: EventEmitter<void> = new EventEmitter<void>();
60     @Output() expandChild: EventEmitter<string> = new EventEmitter<string>();
61     @Output() checkProperty: EventEmitter<string> = new EventEmitter<string>();
62     @Output() toggleTosca: EventEmitter<DerivedFEProperty> = new EventEmitter<DerivedFEProperty>();
63     @Output() deleteItem: EventEmitter<string> = new EventEmitter<string>();
64     @Output() clickOnPropertyRow: EventEmitter<PropertyFEModel | DerivedFEProperty> = new EventEmitter<PropertyFEModel | DerivedFEProperty>();
65     @Output() mapKeyChanged: EventEmitter<string> = new EventEmitter<string>();
66     @Output() addChildPropsToParent: EventEmitter<Array<DerivedFEProperty>> = new EventEmitter<Array<DerivedFEProperty>>();
67
68     @ViewChild('mapKeyInput') public mapKeyInput: DynamicElementComponent;
69
70     constructor(private propertiesUtils: PropertiesUtils, private dataTypeService: DataTypeService) {
71     }
72
73     ngOnInit() {
74         this.isPropertyFEModel = this.property instanceof PropertyFEModel;
75         this.propType = this.property.derivedDataType;
76         this.propPath = (this.property instanceof PropertyFEModel) ? this.property.name : this.property.propertiesName;
77         this.nestedLevel = (this.property.propertiesName.match(/#/g) || []).length;
78         this.rootProperty = (this.rootProperty) ? this.rootProperty : <PropertyFEModel>this.property;
79         this.propertyTestsId = this.getPropertyTestsId();
80
81         this.initConsraintsValues();
82     }
83
84     initConsraintsValues(){
85         let primitiveProperties = ['string', 'integer', 'float', 'boolean', PROPERTY_TYPES.TIMESTAMP];
86
87         //Property has constraints
88         if(this.property.constraints && this.property.constraints[0]){
89             this.constraints = this.property.constraints[0].validValues
90         }
91
92         //Complex Type
93         else if (primitiveProperties.indexOf(this.rootProperty.type) == -1 && primitiveProperties.indexOf(this.property.type) >= 0 ){
94             this.constraints = this.dataTypeService.getConstraintsByParentTypeAndUniqueID(this.rootProperty.type, this.property.name);           
95         }
96  
97         else{
98             this.constraints = null;
99         }
100         
101     }
102
103     ngDoCheck() {
104         // set custom error for mapKeyInput
105         if (this.mapKeyInput) {
106             const mapKeyInputControl = this.mapKeyInput.cmpRef.instance.control;
107             const mapKeyError = (<DerivedFEProperty>this.property).mapKeyError;
108             if (mapKeyInputControl.getError('mapKeyError') !== mapKeyError) {
109                 mapKeyInputControl.setErrors({mapKeyError});
110             }
111         }
112     }
113
114     ngOnChanges() {
115         this.propType = this.property.derivedDataType;
116         this.propPath = (this.property instanceof PropertyFEModel) ? this.property.name : this.property.propertiesName;
117         this.rootProperty = (this.rootProperty) ? this.rootProperty : <PropertyFEModel>this.property;
118         this.propertyTestsId = this.getPropertyTestsId();
119     }
120
121     onClickPropertyRow = (property, event) => {
122         // Because DynamicPropertyComponent is recrusive second time the event is fire event.stopPropagation = undefined
123         event && event.stopPropagation && event.stopPropagation();
124         this.clickOnPropertyRow.emit(property);
125     }
126
127     expandChildById = (id: string) => {
128         this.expandedChildId = id;
129         this.expandChild.emit(id);
130     }
131
132     checkedChange = (propName: string) => {
133         this.checkProperty.emit(propName);
134     }
135
136     toggleToscaFunction = (prop: DerivedFEProperty) => {
137         this.toggleTosca.emit(prop);
138     }
139
140     getHasChildren = (property:DerivedFEProperty): boolean => {// enter to this function only from base property (PropertyFEModel) and check for child property if it has children
141         return _.filter((<PropertyFEModel>this.property).flattenedChildren,(prop:DerivedFEProperty)=>{
142             return _.startsWith(prop.propertiesName + '#', property.propertiesName);
143         }).length > 1;
144     }
145
146     getPropertyTestsId = () => {
147         return [this.rootProperty.name].concat(this.rootProperty.getParentNamesArray(this.property.propertiesName, [], true)).join('.');
148     };
149
150     onElementChanged = (event: IUiElementChangeEvent) => {
151         this.property.updateValueObj(event.value, event.isValid);
152         if (this.property.hasValueObjChanged()) {
153             this.checkboxDisabled = true;
154         }
155         if (event.value === '' || event.value === null || event.value === undefined) {
156             this.checkboxDisabled = false;
157         }
158         this.emitter.emit();
159     };
160
161     createNewChildProperty = (): void => {
162
163         let parentToscaFunction = null;
164         if (this.property.type == PROPERTY_TYPES.MAP && this.property instanceof DerivedFEProperty && this.property.mapInlist) {
165             parentToscaFunction = this.property.toscaFunction;
166             this.property.toscaFunction = null;
167         }
168         let newProps: Array<DerivedFEProperty> = this.propertiesUtils.createListOrMapChildren(this.property, '', null);
169
170         this.propertiesUtils.assignFlattenedChildrenValues(this.property.valueObj, [newProps[0]], this.property.propertiesName);
171         if (this.property instanceof PropertyFEModel) {
172             this.addChildProps(newProps, this.property.name);
173         } else {
174             this.addChildPropsToParent.emit(newProps);
175         }
176         this.property.toscaFunction = parentToscaFunction;
177     }
178
179     addChildProps = (newProps: Array<DerivedFEProperty>, childPropName: string) => {
180
181         if (this.property instanceof PropertyFEModel) {
182             let insertIndex: number = this.property.getIndexOfChild(childPropName) + this.property.getCountOfChildren(childPropName); //insert after parent prop and existing children
183             this.property.flattenedChildren.splice(insertIndex, 0, ...newProps); //using ES6 spread operator
184             this.expandChildById(newProps[0].propertiesName);
185
186             this.updateMapKeyValueOnMainParent(newProps);
187         }
188     }
189
190     updateMapKeyValueOnMainParent(childrenProps: Array<DerivedFEProperty>){
191         if (this.property instanceof PropertyFEModel) {
192             const property: PropertyFEModel = <PropertyFEModel>this.property;
193             //Update only if all this property parents has key name
194             if (property.getParentNamesArray(childrenProps[0].propertiesName, []).indexOf('') === -1){
195                 angular.forEach(childrenProps, (prop:DerivedFEProperty):void => { //Update parent PropertyFEModel with value for each child, including nested props
196                     property.childPropUpdated(prop);
197                     if (prop.isChildOfListOrMap && prop.mapKey !== undefined) {
198                         property.childPropMapKeyUpdated(prop, prop.mapKey, true);
199                     }
200                 },this);
201                 //grab the cumulative value for the new item from parent PropertyFEModel and assign that value to DerivedFEProp[0] (which is the list or map parent with UUID of the set we just added)
202                 let parentNames = (<PropertyFEModel>property).getParentNamesArray(childrenProps[0].propertiesName, []);
203                 childrenProps[0].valueObj = _.get(property.valueObj, parentNames.join('.'), null);
204             }
205         }
206     }
207
208     childValueChanged = (property: DerivedFEProperty) => { //value of child property changed
209
210         if (this.property instanceof PropertyFEModel) { // will always be the case
211             if (this.property.getParentNamesArray(property.propertiesName, []).indexOf('') === -1) {//If one of the parents is empty key -don't save
212                 this.property.childPropUpdated(property);
213                 this.dataTypeService.checkForCustomBehavior(this.property);
214                 this.emitter.emit();
215             }
216         }
217     }
218
219     deleteListOrMapItem = (item: DerivedFEProperty) => {
220         if (this.property instanceof PropertyFEModel) {
221             const childMapKey = item.mapKey;
222             this.removeValueFromParent(item);
223             this.property.flattenedChildren.splice(this.property.getIndexOfChild(item.propertiesName), this.property.getCountOfChildren(item.propertiesName));
224             this.expandChildById(item.propertiesName);
225             if (this.property.type == PROPERTY_TYPES.LIST && this.property.schemaType == PROPERTY_TYPES.MAP && childMapKey != null) {
226                 let valueObject = JSON.parse(this.property.value);
227                 let innerObject = valueObject[item.parentMapKey];
228                 delete innerObject[childMapKey];
229                 this.property.valueObj = valueObject;
230                 this.property.value = JSON.stringify(valueObject);
231                 this.property.flattenedChildren[0].valueObj = valueObject;
232                 this.property.flattenedChildren[0].value = JSON.stringify(valueObject);
233                 this.property.flattenedChildren[0].valueObjIsChanged = true;
234             }
235         }
236     }
237
238     removeValueFromParent = (item: DerivedFEProperty) => {
239         if (this.property instanceof PropertyFEModel) {
240             let itemParent = (item.parentName == this.property.name)
241                 ? this.property : this.property.flattenedChildren.find(prop => prop.propertiesName == item.parentName);
242             if (!itemParent) {
243                 return;
244             }
245             let oldKey = item.getActualMapKey();
246             if (this.property.subPropertyToscaFunctions !== null) {
247                 let deletedIndex = item.toscaPath.length > 0 ? Number(item.toscaPath[item.toscaPath.length - 1]) : null;
248                 let parentIndex = item.toscaPath.length > 1 ? item.toscaPath.splice(0,item.toscaPath.length - 2) : null;
249                 let tempSubToscaFunction: SubPropertyToscaFunction[] = [];
250                 let toscaPathMap = new Map();
251                 this.property.subPropertyToscaFunctions.forEach((subToscaItem : SubPropertyToscaFunction) => {
252                     if ((subToscaItem.subPropertyPath.toString()).indexOf(item.toscaPath.toString()) == -1) {
253                         tempSubToscaFunction.push(subToscaItem);
254                     } else {
255                         if (item.derivedDataType == DerivedPropertyType.LIST ) {
256                             if (parentIndex != null) {
257                                 if ((subToscaItem.subPropertyPath.toString()).indexOf(parentIndex.toString()) != -1
258                                     && subToscaItem.subPropertyPath.length > parentIndex.length) {
259                                     let nextIndex = Number(subToscaItem.subPropertyPath[parentIndex.length]);
260                                     if(!isNaN(nextIndex) && !isNaN(deletedIndex) && nextIndex > deletedIndex) {
261                                         let revisedPAth = subToscaItem.subPropertyPath;
262                                         revisedPAth[parentIndex.length] = (nextIndex - 1).toString();
263                                         toscaPathMap.set(subToscaItem.subPropertyPath.toString(),revisedPAth.toString());
264                                     }
265                                 }
266                             } else {
267                                 if (subToscaItem.subPropertyPath.length == 1 && !isNaN(deletedIndex)) {
268                                     let nextElementIndex = Number(subToscaItem.subPropertyPath[0]);
269                                     if (!isNaN(nextElementIndex) && nextElementIndex > deletedIndex) {
270                                         subToscaItem.subPropertyPath[0] = (nextElementIndex - 1).toString();
271                                     }
272                                 }
273                             }
274                         }
275                     }
276                 });
277                 this.property.subPropertyToscaFunctions = tempSubToscaFunction;
278                 if (item.derivedDataType == DerivedPropertyType.LIST && parentIndex != null && toscaPathMap.size > 0) {
279                     this.property.flattenedChildren.forEach((childProperties : DerivedFEProperty) => {
280                         if (toscaPathMap.has(childProperties.toscaPath.toString())) {
281                             childProperties.toscaPath = toscaPathMap.get(childProperties.toscaPath.toString());
282                         }
283                     });
284                 }
285             }
286             if (item.derivedDataType == DerivedPropertyType.MAP && !item.mapInlist) {
287                 delete itemParent.valueObj[oldKey];
288                 if (itemParent instanceof PropertyFEModel) {
289                     delete itemParent.valueObjValidation[oldKey];
290                     itemParent.valueObjIsValid = itemParent.calculateValueObjIsValid();
291                 }
292                 this.property.childPropMapKeyUpdated(item, null);  // remove map key
293             } else {
294                 const itemIndex: number = this.property.flattenedChildren.filter(prop => prop.parentName == item.parentName).map(prop => prop.propertiesName).indexOf(item.propertiesName);
295                 itemParent.valueObj.splice(itemIndex, 1);
296                 if (itemParent instanceof PropertyFEModel) {
297                     itemParent.valueObjValidation.splice(itemIndex, 1);
298                     itemParent.valueObjIsValid = itemParent.calculateValueObjIsValid();
299                 }
300             }
301             if (itemParent instanceof PropertyFEModel) { //direct child
302                 this.emitter.emit();
303             } else { //nested child - need to update parent prop by getting flattened name (recurse through parents and replace map/list keys, etc)
304                 this.childValueChanged(itemParent);
305             }
306         }
307     }
308
309     updateChildKeyInParent(childProp: DerivedFEProperty, newMapKey: string) {
310         if (this.property instanceof PropertyFEModel) {
311             let oldKey = childProp.getActualMapKey();
312             let oldToscaPath = childProp.toscaPath;
313             this.property.childPropMapKeyUpdated(childProp, newMapKey);
314             this.updateChildMapKey(this.property.flattenedChildren, childProp.propertiesName, newMapKey);
315             if (this.property.subPropertyToscaFunctions != null) {
316                 this.property.subPropertyToscaFunctions.forEach((item : SubPropertyToscaFunction) => {
317                     if(item.subPropertyPath === oldToscaPath){
318                         item.subPropertyPath = childProp.toscaPath;
319                     }
320                 });
321             }
322             this.emitter.emit();
323         }
324     }
325
326     updateChildMapKey(childProps: Array<DerivedFEProperty>, parentName: string, newMapKey: string) {
327         childProps.forEach(tempDervObj => {
328             if (parentName === tempDervObj.parentName) {
329                 tempDervObj.mapKey = newMapKey;
330                 tempDervObj.toscaPath[tempDervObj.toscaPath.length - 2] = newMapKey;
331             }
332         });
333     }
334
335     preventInsertItem = (property:DerivedFEProperty):boolean => {
336         if(property.type == PROPERTY_TYPES.MAP && property.valueObj != null && Object.keys(property.valueObj).indexOf('') > -1 ){
337             return true;
338         }
339         return false;
340     }
341
342 }