Final commit to master merge from
[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 {Component, Input, Output, EventEmitter} from "@angular/core";
22 import { PropertyFEModel, DerivedFEProperty, DerivedPropertyType } from "app/models";
23 import { PROPERTY_TYPES } from 'app/utils';
24 import { DataTypeService } from "../../../../services/data-type.service";
25 import { trigger, state, style, transition, animate } from '@angular/core';
26 import {PropertiesUtils} from "../../../../pages/properties-assignment/services/properties.utils";
27
28
29 @Component({
30     selector: 'dynamic-property',
31     templateUrl: './dynamic-property.component.html',
32     styleUrls: ['./dynamic-property.component.less'],
33     animations: [trigger('fadeIn', [transition(':enter', [style({ opacity: '0' }), animate('.7s ease-out', style({ opacity: '1' }))])])]
34 })
35 export class DynamicPropertyComponent {
36
37     derivedPropertyTypes = DerivedPropertyType; //http://stackoverflow.com/questions/35835984/how-to-use-a-typescript-enum-value-in-an-angular2-ngswitch-statement
38     propType: DerivedPropertyType;
39     propPath: string;
40     isPropertyFEModel: boolean;
41     nestedLevel: number;
42
43     @Input() canBeDeclared: boolean;
44     @Input() property: PropertyFEModel | DerivedFEProperty;
45     @Input() expandedChildId: string;
46     @Input() selectedPropertyId: string;
47     @Input() propertyNameSearchText: string;
48     @Input() readonly: boolean;
49     @Input() hasChildren: boolean;
50     @Input() hasDeclareOption:boolean;
51
52     @Output() valueChanged: EventEmitter<any> = new EventEmitter<any>();
53     @Output() expandChild: EventEmitter<string> = new EventEmitter<string>();
54     @Output() checkProperty: EventEmitter<string> = new EventEmitter<string>();
55     @Output() deleteItem: EventEmitter<string> = new EventEmitter<string>();
56     @Output() clickOnPropertyRow: EventEmitter<PropertyFEModel | DerivedFEProperty> = new EventEmitter<PropertyFEModel | DerivedFEProperty>();
57     @Output() mapKeyChanged: EventEmitter<string> = new EventEmitter<string>();
58     @Output() addChildPropsToParent: EventEmitter<Array<DerivedFEProperty>> = new EventEmitter<Array<DerivedFEProperty>>();
59
60
61     constructor(private propertiesUtils: PropertiesUtils, private dataTypeService: DataTypeService) {
62     }
63
64     ngOnInit() {
65         this.isPropertyFEModel = this.property instanceof PropertyFEModel;
66         this.propType = this.property.derivedDataType;
67         this.propPath = (this.property instanceof PropertyFEModel) ? this.property.name : this.property.propertiesName;
68         this.nestedLevel = (this.property.propertiesName.match(/#/g) || []).length;
69     }
70
71
72     onClickPropertyRow = (property, event) => {
73         // Because DynamicPropertyComponent is recrusive second time the event is fire event.stopPropagation = undefined
74         event && event.stopPropagation && event.stopPropagation();
75         this.clickOnPropertyRow.emit(property);
76     }
77
78
79     expandChildById = (id: string) => {
80         this.expandedChildId = id;
81         this.expandChild.emit(id);
82     }
83
84     checkedChange = (propName: string) => {
85         this.checkProperty.emit(propName);
86     }
87
88     getHasChildren = (property:DerivedFEProperty): boolean => {// enter to this function only from base property (PropertyFEModel) and check for child property if it has children
89         return _.filter((<PropertyFEModel>this.property).flattenedChildren,(prop:DerivedFEProperty)=>{
90             return _.startsWith(prop.propertiesName + '#', property.propertiesName);
91         }).length > 1;
92     }
93
94     createNewChildProperty = (): void => {
95
96         let newProps: Array<DerivedFEProperty> = this.propertiesUtils.createListOrMapChildren(this.property, "", undefined);
97         if (this.property instanceof PropertyFEModel) {
98             this.addChildProps(newProps, this.property.name);
99         } else {
100             this.addChildPropsToParent.emit(newProps);
101         }
102     }
103
104     addChildProps = (newProps: Array<DerivedFEProperty>, childPropName: string) => {
105
106         if (this.property instanceof PropertyFEModel) {
107             let insertIndex: number = this.property.getIndexOfChild(childPropName) + this.property.getCountOfChildren(childPropName); //insert after parent prop and existing children
108             this.property.flattenedChildren.splice(insertIndex, 0, ...newProps); //using ES6 spread operator
109             this.expandChildById(newProps[0].propertiesName);
110
111
112             if(!newProps[0].schema.property.isSimpleType){
113                 if ( newProps[0].mapKey ) {//prevent update the new item value on parent property valueObj and saving on BE if it is map item, it will be updated and saved only after user enter key (when it is list item- the map key is the es type)
114                     this.updateMapKeyValueOnMainParent(newProps);
115                     if (this.property.getParentNamesArray(newProps[0].propertiesName, []).indexOf('') === -1) {
116                         this.valueChanged.emit(this.property.name);
117                     }
118                 }
119             }
120         }
121     }
122
123     updateMapKeyValueOnMainParent(childrenProps: Array<DerivedFEProperty>){
124         if (this.property instanceof PropertyFEModel) {
125             //Update only if all this property parents has key name
126             if (this.property.getParentNamesArray(childrenProps[0].propertiesName, []).indexOf('') === -1){
127                 angular.forEach(childrenProps, (prop:DerivedFEProperty):void => { //Update parent PropertyFEModel with value for each child, including nested props
128                     (<PropertyFEModel>this.property).childPropUpdated(prop);
129                 },this);
130                 //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)
131                 let parentNames = (<PropertyFEModel>this.property).getParentNamesArray(childrenProps[0].propertiesName, []);
132                 childrenProps[0].valueObj = _.get(this.property.valueObj, parentNames.join('.'));
133             }
134         }
135     }
136
137     childValueChanged = (property: DerivedFEProperty) => { //value of child property changed
138
139         if (this.property instanceof PropertyFEModel) { // will always be the case
140             if (this.property.getParentNamesArray(property.propertiesName, []).indexOf('') === -1) {//If one of the parents is empty key -don't save
141                 this.property.childPropUpdated(property);
142                 this.dataTypeService.checkForCustomBehavior(this.property);
143                 this.valueChanged.emit(this.property.name);
144             }
145         }
146     }
147
148     deleteListOrMapItem = (item: DerivedFEProperty) => {
149         if (this.property instanceof PropertyFEModel) {
150             this.removeValueFromParent(item);
151             this.property.flattenedChildren.splice(this.property.getIndexOfChild(item.propertiesName), this.property.getCountOfChildren(item.propertiesName));
152             this.expandChildById(item.propertiesName);
153         }
154     }
155
156     removeValueFromParent = (item: DerivedFEProperty, target?: any) => {
157         if (this.property instanceof PropertyFEModel) {
158             let itemParent = (item.parentName == this.property.name) ? this.property : this.property.flattenedChildren.find(prop => prop.propertiesName == item.parentName);
159
160             if (item.derivedDataType == DerivedPropertyType.MAP) {
161                 let oldKey = item.mapKey;
162                 if (target && typeof target.value == 'string') { //allow saving empty string
163                     let replaceKey:string = target.value;
164                     if (!replaceKey) {//prevent delete map key
165                         return;
166                     }
167                     if(Object.keys(itemParent.valueObj).indexOf(replaceKey) > -1){//the key is exists
168                         target.setCustomValidity('This key is already exists.');
169                         return;
170                     }else {
171                         target.setCustomValidity('');
172                         _.set(itemParent.valueObj, replaceKey, itemParent.valueObj[oldKey]);
173                         item.mapKey = replaceKey;
174                         //If the map key was empty its valueObj was not updated on its prent property valueObj, and now we should update it.
175                         if(!oldKey && !item.schema.property.isSimpleType){
176                             //Search this map item children and update these value on parent property valueOBj
177                             let mapKeyFlattenChildren:Array<DerivedFEProperty> = _.filter(this.property.flattenedChildren, (prop:DerivedFEProperty) => {
178                                 return _.startsWith(prop.propertiesName, item.propertiesName);
179                             });
180                             this.updateMapKeyValueOnMainParent(mapKeyFlattenChildren);
181                         }
182                     }
183                 }
184                 delete itemParent.valueObj[oldKey];
185             } else {
186                 let itemIndex: number = this.property.flattenedChildren.filter(prop => prop.parentName == item.parentName).map(prop => prop.propertiesName).indexOf(item.propertiesName);
187                 itemParent.valueObj.splice(itemIndex, 1);
188             }
189             if (item.mapKey) {//prevent going to BE if user tries to delete map item without key (it was not saved in BE)
190                 if (itemParent instanceof PropertyFEModel) { //direct child
191                     this.valueChanged.emit(this.property.name);
192                 } else { //nested child - need to update parent prop by getting flattened name (recurse through parents and replace map/list keys, etc)
193                     this.childValueChanged(itemParent);
194                 }
195             }
196         }
197     }
198
199     preventInsertItem = (property:DerivedFEProperty):boolean => {
200         if(property.type == PROPERTY_TYPES.MAP && Object.keys(property.valueObj).indexOf('') > -1 ){
201             return true;
202         }
203         return false;
204     }
205
206 }