Provide tosca function to list of map values
[sdc.git] / catalog-ui / src / app / ng2 / components / logic / properties-table / dynamic-property / dynamic-property.component.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 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, state, 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
48     @Input() canBeDeclared: boolean;
49     @Input() property: PropertyFEModel | DerivedFEProperty;
50     @Input() expandedChildId: string;
51     @Input() selectedPropertyId: string;
52     @Input() propertyNameSearchText: string;
53     @Input() readonly: boolean;
54     @Input() hasChildren: boolean;
55     @Input() hasDeclareOption:boolean;
56     @Input() rootProperty: PropertyFEModel;
57
58     @Output('propertyChanged') emitter: EventEmitter<void> = new EventEmitter<void>();
59     @Output() expandChild: EventEmitter<string> = new EventEmitter<string>();
60     @Output() checkProperty: EventEmitter<string> = new EventEmitter<string>();
61     @Output() toggleTosca: EventEmitter<DerivedFEProperty> = new EventEmitter<DerivedFEProperty>();
62     @Output() deleteItem: EventEmitter<string> = new EventEmitter<string>();
63     @Output() clickOnPropertyRow: EventEmitter<PropertyFEModel | DerivedFEProperty> = new EventEmitter<PropertyFEModel | DerivedFEProperty>();
64     @Output() mapKeyChanged: EventEmitter<string> = new EventEmitter<string>();
65     @Output() addChildPropsToParent: EventEmitter<Array<DerivedFEProperty>> = new EventEmitter<Array<DerivedFEProperty>>();
66
67     @ViewChild('mapKeyInput') public mapKeyInput: DynamicElementComponent;
68
69     constructor(private propertiesUtils: PropertiesUtils, private dataTypeService: DataTypeService) {
70     }
71
72     ngOnInit() {
73         this.isPropertyFEModel = this.property instanceof PropertyFEModel;
74         this.propType = this.property.derivedDataType;
75         this.propPath = (this.property instanceof PropertyFEModel) ? this.property.name : this.property.propertiesName;
76         this.nestedLevel = (this.property.propertiesName.match(/#/g) || []).length;
77         this.rootProperty = (this.rootProperty) ? this.rootProperty : <PropertyFEModel>this.property;
78         this.propertyTestsId = this.getPropertyTestsId();
79
80         this.initConsraintsValues();
81     }
82
83     initConsraintsValues(){
84         let primitiveProperties = ['string', 'integer', 'float', 'boolean', PROPERTY_TYPES.TIMESTAMP];
85
86         //Property has constraints
87         if(this.property.constraints && this.property.constraints[0]){
88             this.constraints = this.property.constraints[0].validValues
89         }
90
91         //Complex Type
92         else if (primitiveProperties.indexOf(this.rootProperty.type) == -1 && primitiveProperties.indexOf(this.property.type) >= 0 ){
93             this.constraints = this.dataTypeService.getConstraintsByParentTypeAndUniqueID(this.rootProperty.type, this.property.name);           
94         }
95  
96         else{
97             this.constraints = null;
98         }
99         
100     }
101
102     ngDoCheck() {
103         // set custom error for mapKeyInput
104         if (this.mapKeyInput) {
105             const mapKeyInputControl = this.mapKeyInput.cmpRef.instance.control;
106             const mapKeyError = (<DerivedFEProperty>this.property).mapKeyError;
107             if (mapKeyInputControl.getError('mapKeyError') !== mapKeyError) {
108                 mapKeyInputControl.setErrors({mapKeyError});
109             }
110         }
111     }
112
113     ngOnChanges() {
114         this.propType = this.property.derivedDataType;
115         this.propPath = (this.property instanceof PropertyFEModel) ? this.property.name : this.property.propertiesName;
116         this.rootProperty = (this.rootProperty) ? this.rootProperty : <PropertyFEModel>this.property;
117         this.propertyTestsId = this.getPropertyTestsId();
118     }
119
120     onClickPropertyRow = (property, event) => {
121         // Because DynamicPropertyComponent is recrusive second time the event is fire event.stopPropagation = undefined
122         event && event.stopPropagation && event.stopPropagation();
123         this.clickOnPropertyRow.emit(property);
124     }
125
126     expandChildById = (id: string) => {
127         this.expandedChildId = id;
128         this.expandChild.emit(id);
129     }
130
131     checkedChange = (propName: string) => {
132         this.checkProperty.emit(propName);
133     }
134
135     toggleToscaFunction = (prop: DerivedFEProperty) => {
136         this.toggleTosca.emit(prop);
137     }
138
139     getHasChildren = (property:DerivedFEProperty): boolean => {// enter to this function only from base property (PropertyFEModel) and check for child property if it has children
140         return _.filter((<PropertyFEModel>this.property).flattenedChildren,(prop:DerivedFEProperty)=>{
141             return _.startsWith(prop.propertiesName + '#', property.propertiesName);
142         }).length > 1;
143     }
144
145     getPropertyTestsId = () => {
146         return [this.rootProperty.name].concat(this.rootProperty.getParentNamesArray(this.property.propertiesName, [], true)).join('.');
147     };
148
149     onElementChanged = (event: IUiElementChangeEvent) => {
150         this.property.updateValueObj(event.value, event.isValid);
151         this.emitter.emit();
152     };
153
154     createNewChildProperty = (): void => {
155
156         let mapKeyValue = this.property instanceof DerivedFEProperty ? this.property.mapKey : "";
157         let parentToscaFunction = null;
158         if (this.property.type == PROPERTY_TYPES.LIST && mapKeyValue === "") {
159             if (this.property.value != null) {
160                 const valueJson = JSON.parse(this.property.value);
161                 if (this.property instanceof PropertyFEModel && this.property.expandedChildPropertyId != null) {
162                     let indexNumber = Number(Object.keys(valueJson).sort().reverse()[0]) + 1;
163                     mapKeyValue = indexNumber.toString();
164                 }else{
165                     mapKeyValue = Object.keys(valueJson).sort().reverse()[0];
166                 }
167             }else {
168                 mapKeyValue = "0";
169             }
170         }
171         if (this.property.type == PROPERTY_TYPES.MAP && this.property instanceof DerivedFEProperty && this.property.mapInlist) {
172             parentToscaFunction = this.property.toscaFunction;
173             this.property.toscaFunction = null;
174         }
175         let newProps: Array<DerivedFEProperty> = this.propertiesUtils.createListOrMapChildren(this.property, mapKeyValue, null);
176
177         this.propertiesUtils.assignFlattenedChildrenValues(this.property.valueObj, [newProps[0]], this.property.propertiesName);
178         if (this.property instanceof PropertyFEModel) {
179             this.addChildProps(newProps, this.property.name);
180         } else {
181             this.addChildPropsToParent.emit(newProps);
182         }
183         this.property.toscaFunction = parentToscaFunction;
184     }
185
186     addChildProps = (newProps: Array<DerivedFEProperty>, childPropName: string) => {
187
188         if (this.property instanceof PropertyFEModel) {
189             let insertIndex: number = this.property.getIndexOfChild(childPropName) + this.property.getCountOfChildren(childPropName); //insert after parent prop and existing children
190             this.property.flattenedChildren.splice(insertIndex, 0, ...newProps); //using ES6 spread operator
191             this.expandChildById(newProps[0].propertiesName);
192
193             this.updateMapKeyValueOnMainParent(newProps);
194         }
195     }
196
197     updateMapKeyValueOnMainParent(childrenProps: Array<DerivedFEProperty>){
198         if (this.property instanceof PropertyFEModel) {
199             const property: PropertyFEModel = <PropertyFEModel>this.property;
200             //Update only if all this property parents has key name
201             if (property.getParentNamesArray(childrenProps[0].propertiesName, []).indexOf('') === -1){
202                 angular.forEach(childrenProps, (prop:DerivedFEProperty):void => { //Update parent PropertyFEModel with value for each child, including nested props
203                     property.childPropUpdated(prop);
204                     if (prop.isChildOfListOrMap && prop.mapKey !== undefined) {
205                         property.childPropMapKeyUpdated(prop, prop.mapKey, true);
206                     }
207                 },this);
208                 //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)
209                 let parentNames = (<PropertyFEModel>property).getParentNamesArray(childrenProps[0].propertiesName, []);
210                 childrenProps[0].valueObj = _.get(property.valueObj, parentNames.join('.'), null);
211             }
212         }
213     }
214
215     childValueChanged = (property: DerivedFEProperty) => { //value of child property changed
216
217         if (this.property instanceof PropertyFEModel) { // will always be the case
218             if (this.property.getParentNamesArray(property.propertiesName, []).indexOf('') === -1) {//If one of the parents is empty key -don't save
219                 this.property.childPropUpdated(property);
220                 this.dataTypeService.checkForCustomBehavior(this.property);
221                 this.emitter.emit();
222             }
223         }
224     }
225
226     deleteListOrMapItem = (item: DerivedFEProperty) => {
227         if (this.property instanceof PropertyFEModel) {
228             const childMapKey = item.mapKey;
229             this.removeValueFromParent(item);
230             this.property.flattenedChildren.splice(this.property.getIndexOfChild(item.propertiesName), this.property.getCountOfChildren(item.propertiesName));
231             this.expandChildById(item.propertiesName);
232             if (this.property.type == PROPERTY_TYPES.LIST && this.property.schemaType == PROPERTY_TYPES.MAP && childMapKey != null) {
233                 let valueObject = JSON.parse(this.property.value);
234                 let innerObject = valueObject[item.parentMapKey];
235                 delete innerObject[childMapKey];
236                 this.property.valueObj = valueObject;
237                 this.property.value = JSON.stringify(valueObject);
238                 this.property.flattenedChildren[0].valueObj = valueObject;
239                 this.property.flattenedChildren[0].value = JSON.stringify(valueObject);
240                 this.property.flattenedChildren[0].valueObjIsChanged = true;
241             }
242         }
243     }
244
245     removeValueFromParent = (item: DerivedFEProperty) => {
246         if (this.property instanceof PropertyFEModel) {
247             let itemParent = (item.parentName == this.property.name)
248                 ? this.property : this.property.flattenedChildren.find(prop => prop.propertiesName == item.parentName);
249             if (!itemParent) {
250                 return;
251             }
252             let oldKey = item.getActualMapKey();
253             let keyIndex : number = 0;
254                 if(item.parentMapKey != null && oldKey != null) {
255                     keyIndex = 1;
256                 }
257                 if(item.parentMapKey != null && oldKey == null) {
258                     oldKey = item.parentMapKey;
259                 }
260             if (this.property.subPropertyToscaFunctions !== null) {
261                 let tempSubToscaFunction: SubPropertyToscaFunction[] = [];
262                 this.property.subPropertyToscaFunctions.forEach((subToscaItem : SubPropertyToscaFunction) => {
263                     if(subToscaItem.subPropertyPath[keyIndex] != oldKey){
264                         tempSubToscaFunction.push(subToscaItem);
265                     }
266                 });
267                 this.property.subPropertyToscaFunctions = tempSubToscaFunction;
268             }
269             if (item.derivedDataType == DerivedPropertyType.MAP && !item.mapInlist) {
270                 delete itemParent.valueObj[oldKey];
271                 if (itemParent instanceof PropertyFEModel) {
272                     delete itemParent.valueObjValidation[oldKey];
273                     itemParent.valueObjIsValid = itemParent.calculateValueObjIsValid();
274                 }
275                 this.property.childPropMapKeyUpdated(item, null);  // remove map key
276             } else {
277                 const itemIndex: number = this.property.flattenedChildren.filter(prop => prop.parentName == item.parentName).map(prop => prop.propertiesName).indexOf(item.propertiesName);
278                 itemParent.valueObj.splice(itemIndex, 1);
279                 if (itemParent instanceof PropertyFEModel) {
280                     itemParent.valueObjValidation.splice(itemIndex, 1);
281                     itemParent.valueObjIsValid = itemParent.calculateValueObjIsValid();
282                 }
283             }
284             if (itemParent instanceof PropertyFEModel) { //direct child
285                 this.emitter.emit();
286             } else { //nested child - need to update parent prop by getting flattened name (recurse through parents and replace map/list keys, etc)
287                 this.childValueChanged(itemParent);
288             }
289         }
290     }
291
292     updateChildKeyInParent(childProp: DerivedFEProperty, newMapKey: string) {
293         if (this.property instanceof PropertyFEModel) {
294             let oldKey = childProp.getActualMapKey();
295             this.property.childPropMapKeyUpdated(childProp, newMapKey);
296             this.property.flattenedChildren.forEach(tempDervObj => {
297                 if (childProp.propertiesName === tempDervObj.parentName) {
298                     tempDervObj.mapKey = newMapKey;
299                 }
300             });
301             if (this.property.subPropertyToscaFunctions != null) {
302                 this.property.subPropertyToscaFunctions.forEach((item : SubPropertyToscaFunction) => {
303                     if(item.subPropertyPath[0] === oldKey){
304                         item.subPropertyPath = [newMapKey];
305                     }
306                 });
307             }
308             this.emitter.emit();
309         }
310     }
311
312     preventInsertItem = (property:DerivedFEProperty):boolean => {
313         if(property.type == PROPERTY_TYPES.MAP && property.valueObj != null && Object.keys(property.valueObj).indexOf('') > -1 ){
314             return true;
315         }
316         return false;
317     }
318
319 }