Sync Integ to Master
[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/core';
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
31 @Component({
32     selector: 'dynamic-property',
33     templateUrl: './dynamic-property.component.html',
34     styleUrls: ['./dynamic-property.component.less'],
35     animations: [trigger('fadeIn', [transition(':enter', [style({ opacity: '0' }), animate('.7s ease-out', style({ opacity: '1' }))])])]
36 })
37 export class DynamicPropertyComponent {
38
39     derivedPropertyTypes = DerivedPropertyType; //http://stackoverflow.com/questions/35835984/how-to-use-a-typescript-enum-value-in-an-angular2-ngswitch-statement
40     propType: DerivedPropertyType;
41     propPath: string;
42     isPropertyFEModel: boolean;
43     nestedLevel: number;
44
45     @Input() canBeDeclared: boolean;
46     @Input() property: PropertyFEModel | DerivedFEProperty;
47     @Input() expandedChildId: string;
48     @Input() selectedPropertyId: string;
49     @Input() propertyNameSearchText: string;
50     @Input() readonly: boolean;
51     @Input() hasChildren: boolean;
52     @Input() hasDeclareOption:boolean;
53
54     @Output('propertyChanged') emitter: EventEmitter<void> = new EventEmitter<void>();
55     @Output() expandChild: EventEmitter<string> = new EventEmitter<string>();
56     @Output() checkProperty: EventEmitter<string> = new EventEmitter<string>();
57     @Output() deleteItem: EventEmitter<string> = new EventEmitter<string>();
58     @Output() clickOnPropertyRow: EventEmitter<PropertyFEModel | DerivedFEProperty> = new EventEmitter<PropertyFEModel | DerivedFEProperty>();
59     @Output() mapKeyChanged: EventEmitter<string> = new EventEmitter<string>();
60     @Output() addChildPropsToParent: EventEmitter<Array<DerivedFEProperty>> = new EventEmitter<Array<DerivedFEProperty>>();
61
62     @ViewChild('mapKeyInput') public mapKeyInput: DynamicElementComponent;
63
64     constructor(private propertiesUtils: PropertiesUtils, private dataTypeService: DataTypeService) {
65     }
66
67     ngOnInit() {
68         this.isPropertyFEModel = this.property instanceof PropertyFEModel;
69         this.propType = this.property.derivedDataType;
70         this.propPath = (this.property instanceof PropertyFEModel) ? this.property.name : this.property.propertiesName;
71         this.nestedLevel = (this.property.propertiesName.match(/#/g) || []).length;
72     }
73
74     ngDoCheck() {
75         // set custom error for mapKeyInput
76         if (this.mapKeyInput) {
77             const mapKeyInputControl = this.mapKeyInput.cmpRef.instance.control;
78             const mapKeyError = (<DerivedFEProperty>this.property).mapKeyError;
79             if (mapKeyInputControl.getError('mapKeyError') !== mapKeyError) {
80                 mapKeyInputControl.setErrors({mapKeyError});
81             }
82         }
83     }
84
85
86     onClickPropertyRow = (property, event) => {
87         // Because DynamicPropertyComponent is recrusive second time the event is fire event.stopPropagation = undefined
88         event && event.stopPropagation && event.stopPropagation();
89         this.clickOnPropertyRow.emit(property);
90     }
91
92
93     expandChildById = (id: string) => {
94         this.expandedChildId = id;
95         this.expandChild.emit(id);
96     }
97
98     checkedChange = (propName: string) => {
99         this.checkProperty.emit(propName);
100     }
101
102     getHasChildren = (property:DerivedFEProperty): boolean => {// enter to this function only from base property (PropertyFEModel) and check for child property if it has children
103         return _.filter((<PropertyFEModel>this.property).flattenedChildren,(prop:DerivedFEProperty)=>{
104             return _.startsWith(prop.propertiesName + '#', property.propertiesName);
105         }).length > 1;
106     }
107
108     onElementChanged = (event: IUiElementChangeEvent) => {
109         this.property.updateValueObj(event.value, event.isValid);
110         this.emitter.emit();
111     };
112
113     createNewChildProperty = (): void => {
114
115         let newProps: Array<DerivedFEProperty> = this.propertiesUtils.createListOrMapChildren(this.property, "", null);
116         this.propertiesUtils.assignFlattenedChildrenValues(this.property.valueObj, [newProps[0]], this.property.propertiesName);
117         if (this.property instanceof PropertyFEModel) {
118             this.addChildProps(newProps, this.property.name);
119         } else {
120             this.addChildPropsToParent.emit(newProps);
121         }
122     }
123
124     addChildProps = (newProps: Array<DerivedFEProperty>, childPropName: string) => {
125
126         if (this.property instanceof PropertyFEModel) {
127             let insertIndex: number = this.property.getIndexOfChild(childPropName) + this.property.getCountOfChildren(childPropName); //insert after parent prop and existing children
128             this.property.flattenedChildren.splice(insertIndex, 0, ...newProps); //using ES6 spread operator
129             this.expandChildById(newProps[0].propertiesName);
130
131             this.updateMapKeyValueOnMainParent(newProps);
132             this.emitter.emit();
133         }
134     }
135
136     updateMapKeyValueOnMainParent(childrenProps: Array<DerivedFEProperty>){
137         if (this.property instanceof PropertyFEModel) {
138             const property: PropertyFEModel = <PropertyFEModel>this.property;
139             //Update only if all this property parents has key name
140             if (property.getParentNamesArray(childrenProps[0].propertiesName, []).indexOf('') === -1){
141                 angular.forEach(childrenProps, (prop:DerivedFEProperty):void => { //Update parent PropertyFEModel with value for each child, including nested props
142                     property.childPropUpdated(prop);
143                     if (prop.isChildOfListOrMap && prop.mapKey !== undefined) {
144                         property.childPropMapKeyUpdated(prop, prop.mapKey, true);
145                     }
146                 },this);
147                 //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)
148                 let parentNames = (<PropertyFEModel>property).getParentNamesArray(childrenProps[0].propertiesName, []);
149                 childrenProps[0].valueObj = _.get(property.valueObj, parentNames.join('.'), null);
150             }
151         }
152     }
153
154     childValueChanged = (property: DerivedFEProperty) => { //value of child property changed
155
156         if (this.property instanceof PropertyFEModel) { // will always be the case
157             if (this.property.getParentNamesArray(property.propertiesName, []).indexOf('') === -1) {//If one of the parents is empty key -don't save
158                 this.property.childPropUpdated(property);
159                 this.dataTypeService.checkForCustomBehavior(this.property);
160                 this.emitter.emit();
161             }
162         }
163     }
164
165     deleteListOrMapItem = (item: DerivedFEProperty) => {
166         if (this.property instanceof PropertyFEModel) {
167             this.removeValueFromParent(item);
168             this.property.flattenedChildren.splice(this.property.getIndexOfChild(item.propertiesName), this.property.getCountOfChildren(item.propertiesName));
169             this.expandChildById(item.propertiesName);
170         }
171     }
172
173     removeValueFromParent = (item: DerivedFEProperty) => {
174         if (this.property instanceof PropertyFEModel) {
175             let itemParent = (item.parentName == this.property.name)
176                 ? this.property : this.property.flattenedChildren.find(prop => prop.propertiesName == item.parentName);
177             if (!itemParent) {
178                 return;
179             }
180
181             if (item.derivedDataType == DerivedPropertyType.MAP) {
182                 const oldKey = item.getActualMapKey();
183                 delete itemParent.valueObj[oldKey];
184                 if (itemParent instanceof PropertyFEModel) {
185                     delete itemParent.valueObjValidation[oldKey];
186                     itemParent.valueObjIsValid = itemParent.calculateValueObjIsValid();
187                 }
188                 this.property.childPropMapKeyUpdated(item, null);  // remove map key
189             } else {
190                 const itemIndex: number = this.property.flattenedChildren.filter(prop => prop.parentName == item.parentName).map(prop => prop.propertiesName).indexOf(item.propertiesName);
191                 itemParent.valueObj.splice(itemIndex, 1);
192                 if (itemParent instanceof PropertyFEModel) {
193                     itemParent.valueObjValidation.splice(itemIndex, 1);
194                     itemParent.valueObjIsValid = itemParent.calculateValueObjIsValid();
195                 }
196             }
197             if (itemParent instanceof PropertyFEModel) { //direct child
198                 this.emitter.emit();
199             } else { //nested child - need to update parent prop by getting flattened name (recurse through parents and replace map/list keys, etc)
200                 this.childValueChanged(itemParent);
201             }
202         }
203     }
204
205     updateChildKeyInParent(childProp: DerivedFEProperty, newMapKey: string) {
206         if (this.property instanceof PropertyFEModel) {
207             this.property.childPropMapKeyUpdated(childProp, newMapKey);
208             this.emitter.emit();
209         }
210     }
211
212     preventInsertItem = (property:DerivedFEProperty):boolean => {
213         if(property.type == PROPERTY_TYPES.MAP && Object.keys(property.valueObj).indexOf('') > -1 ){
214             return true;
215         }
216         return false;
217     }
218
219 }