2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
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";
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' }))])])]
38 export class DynamicPropertyComponent {
40 derivedPropertyTypes = DerivedPropertyType; //http://stackoverflow.com/questions/35835984/how-to-use-a-typescript-enum-value-in-an-angular2-ngswitch-statement
41 propType: DerivedPropertyType;
43 isPropertyFEModel: boolean;
45 propertyTestsId: string;
47 checkboxDisabled: boolean = false;
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() disablePropertyValue: boolean;
58 @Input() rootProperty: PropertyFEModel;
60 @Output('propertyChanged') emitter: EventEmitter<void> = new EventEmitter<void>();
61 @Output() expandChild: EventEmitter<string> = new EventEmitter<string>();
62 @Output() checkProperty: EventEmitter<string> = new EventEmitter<string>();
63 @Output() toggleTosca: EventEmitter<DerivedFEProperty> = new EventEmitter<DerivedFEProperty>();
64 @Output() deleteItem: EventEmitter<string> = new EventEmitter<string>();
65 @Output() clickOnPropertyRow: EventEmitter<PropertyFEModel | DerivedFEProperty> = new EventEmitter<PropertyFEModel | DerivedFEProperty>();
66 @Output() mapKeyChanged: EventEmitter<string> = new EventEmitter<string>();
67 @Output() addChildPropsToParent: EventEmitter<Array<DerivedFEProperty>> = new EventEmitter<Array<DerivedFEProperty>>();
69 @ViewChild('mapKeyInput') public mapKeyInput: DynamicElementComponent;
71 constructor(private propertiesUtils: PropertiesUtils, private dataTypeService: DataTypeService) {
75 this.isPropertyFEModel = this.property instanceof PropertyFEModel;
76 this.propType = this.property.derivedDataType;
77 this.propPath = (this.property instanceof PropertyFEModel) ? this.property.name : this.property.propertiesName;
78 this.nestedLevel = (this.property.propertiesName.match(/#/g) || []).length;
79 this.rootProperty = (this.rootProperty) ? this.rootProperty : <PropertyFEModel>this.property;
80 this.propertyTestsId = this.getPropertyTestsId();
82 this.initConsraintsValues();
85 initConsraintsValues(){
86 let primitiveProperties = ['string', 'integer', 'float', 'boolean', PROPERTY_TYPES.TIMESTAMP];
88 //Property has constraints
89 if(this.property.constraints && this.property.constraints[0]){
90 this.constraints = this.property.constraints[0].validValues
94 else if (primitiveProperties.indexOf(this.rootProperty.type) == -1 && primitiveProperties.indexOf(this.property.type) >= 0 ){
95 this.constraints = this.dataTypeService.getConstraintsByParentTypeAndUniqueID(this.rootProperty.type, this.property.name);
99 this.constraints = null;
105 // set custom error for mapKeyInput
106 if (this.mapKeyInput) {
107 const mapKeyInputControl = this.mapKeyInput.cmpRef.instance.control;
108 const mapKeyError = (<DerivedFEProperty>this.property).mapKeyError;
109 if (mapKeyInputControl.getError('mapKeyError') !== mapKeyError) {
110 mapKeyInputControl.setErrors({mapKeyError});
116 this.propType = this.property.derivedDataType;
117 this.propPath = (this.property instanceof PropertyFEModel) ? this.property.name : this.property.propertiesName;
118 this.rootProperty = (this.rootProperty) ? this.rootProperty : <PropertyFEModel>this.property;
119 this.propertyTestsId = this.getPropertyTestsId();
122 onClickPropertyRow = (property, event) => {
123 // Because DynamicPropertyComponent is recrusive second time the event is fire event.stopPropagation = undefined
124 event && event.stopPropagation && event.stopPropagation();
125 this.clickOnPropertyRow.emit(property);
128 expandChildById = (id: string) => {
129 this.expandedChildId = id;
130 this.expandChild.emit(id);
133 checkedChange = (propName: string) => {
134 this.checkProperty.emit(propName);
137 toggleToscaFunction = (prop: DerivedFEProperty) => {
138 this.toggleTosca.emit(prop);
141 getHasChildren = (property:DerivedFEProperty): boolean => {// enter to this function only from base property (PropertyFEModel) and check for child property if it has children
142 return _.filter((<PropertyFEModel>this.property).flattenedChildren,(prop:DerivedFEProperty)=>{
143 return _.startsWith(prop.propertiesName + '#', property.propertiesName);
147 getPropertyTestsId = () => {
148 return [this.rootProperty.name].concat(this.rootProperty.getParentNamesArray(this.property.propertiesName, [], true)).join('.');
151 onElementChanged = (event: IUiElementChangeEvent) => {
152 this.property.updateValueObj(event.value, event.isValid);
153 if (this.property.hasValueObjChanged()) {
154 this.checkboxDisabled = true;
156 if (event.value === '' || event.value === null || event.value === undefined) {
157 this.checkboxDisabled = false;
162 createNewChildProperty = (): void => {
164 let parentToscaFunction = null;
165 if (this.property.type == PROPERTY_TYPES.MAP && this.property instanceof DerivedFEProperty && this.property.mapInlist) {
166 parentToscaFunction = this.property.toscaFunction;
167 this.property.toscaFunction = null;
169 let newProps: Array<DerivedFEProperty> = this.propertiesUtils.createListOrMapChildren(this.property, '', null);
171 this.propertiesUtils.assignFlattenedChildrenValues(this.property.valueObj, [newProps[0]], this.property.propertiesName);
172 if (this.property instanceof PropertyFEModel) {
173 this.addChildProps(newProps, this.property.name);
175 this.addChildPropsToParent.emit(newProps);
177 this.property.toscaFunction = parentToscaFunction;
180 addChildProps = (newProps: Array<DerivedFEProperty>, childPropName: string) => {
182 if (this.property instanceof PropertyFEModel) {
183 let insertIndex: number = this.property.getIndexOfChild(childPropName) + this.property.getCountOfChildren(childPropName); //insert after parent prop and existing children
184 this.property.flattenedChildren.splice(insertIndex, 0, ...newProps); //using ES6 spread operator
185 this.expandChildById(newProps[0].propertiesName);
187 this.updateMapKeyValueOnMainParent(newProps);
191 updateMapKeyValueOnMainParent(childrenProps: Array<DerivedFEProperty>){
192 if (this.property instanceof PropertyFEModel) {
193 const property: PropertyFEModel = <PropertyFEModel>this.property;
194 //Update only if all this property parents has key name
195 if (property.getParentNamesArray(childrenProps[0].propertiesName, []).indexOf('') === -1){
196 angular.forEach(childrenProps, (prop:DerivedFEProperty):void => { //Update parent PropertyFEModel with value for each child, including nested props
197 property.childPropUpdated(prop);
198 if (prop.isChildOfListOrMap && prop.mapKey !== undefined) {
199 property.childPropMapKeyUpdated(prop, prop.mapKey, true);
202 //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)
203 let parentNames = (<PropertyFEModel>property).getParentNamesArray(childrenProps[0].propertiesName, []);
204 childrenProps[0].valueObj = _.get(property.valueObj, parentNames.join('.'), null);
209 childValueChanged = (property: DerivedFEProperty) => { //value of child property changed
211 if (this.property instanceof PropertyFEModel) { // will always be the case
212 if (this.property.getParentNamesArray(property.propertiesName, []).indexOf('') === -1) {//If one of the parents is empty key -don't save
213 this.property.childPropUpdated(property);
214 this.dataTypeService.checkForCustomBehavior(this.property);
220 deleteListOrMapItem = (item: DerivedFEProperty) => {
221 if (this.property instanceof PropertyFEModel) {
222 const childMapKey = item.mapKey;
223 this.removeValueFromParent(item);
224 this.property.flattenedChildren.splice(this.property.getIndexOfChild(item.propertiesName), this.property.getCountOfChildren(item.propertiesName));
225 this.expandChildById(item.propertiesName);
226 if (this.property.type == PROPERTY_TYPES.LIST && this.property.schemaType == PROPERTY_TYPES.MAP && childMapKey != null) {
227 let valueObject = JSON.parse(this.property.value);
228 let innerObject = valueObject[item.parentMapKey];
229 delete innerObject[childMapKey];
230 this.property.valueObj = valueObject;
231 this.property.value = JSON.stringify(valueObject);
232 this.property.flattenedChildren[0].valueObj = valueObject;
233 this.property.flattenedChildren[0].value = JSON.stringify(valueObject);
234 this.property.flattenedChildren[0].valueObjIsChanged = true;
239 removeValueFromParent = (item: DerivedFEProperty) => {
240 if (this.property instanceof PropertyFEModel) {
241 let itemParent = (item.parentName == this.property.name)
242 ? this.property : this.property.flattenedChildren.find(prop => prop.propertiesName == item.parentName);
246 let oldKey = item.getActualMapKey();
247 if (this.property.subPropertyToscaFunctions !== null) {
248 let deletedIndex = item.toscaPath.length > 0 ? Number(item.toscaPath[item.toscaPath.length - 1]) : null;
249 let parentIndex = item.toscaPath.length > 1 ? item.toscaPath.splice(0,item.toscaPath.length - 2) : null;
250 let tempSubToscaFunction: SubPropertyToscaFunction[] = [];
251 let toscaPathMap = new Map();
252 this.property.subPropertyToscaFunctions.forEach((subToscaItem : SubPropertyToscaFunction) => {
253 if ((subToscaItem.subPropertyPath.toString()).indexOf(item.toscaPath.toString()) == -1) {
254 tempSubToscaFunction.push(subToscaItem);
256 if (item.derivedDataType == DerivedPropertyType.LIST ) {
257 if (parentIndex != null) {
258 if ((subToscaItem.subPropertyPath.toString()).indexOf(parentIndex.toString()) != -1
259 && subToscaItem.subPropertyPath.length > parentIndex.length) {
260 let nextIndex = Number(subToscaItem.subPropertyPath[parentIndex.length]);
261 if(!isNaN(nextIndex) && !isNaN(deletedIndex) && nextIndex > deletedIndex) {
262 let revisedPAth = subToscaItem.subPropertyPath;
263 revisedPAth[parentIndex.length] = (nextIndex - 1).toString();
264 toscaPathMap.set(subToscaItem.subPropertyPath.toString(),revisedPAth.toString());
268 if (subToscaItem.subPropertyPath.length == 1 && !isNaN(deletedIndex)) {
269 let nextElementIndex = Number(subToscaItem.subPropertyPath[0]);
270 if (!isNaN(nextElementIndex) && nextElementIndex > deletedIndex) {
271 subToscaItem.subPropertyPath[0] = (nextElementIndex - 1).toString();
278 this.property.subPropertyToscaFunctions = tempSubToscaFunction;
279 if (item.derivedDataType == DerivedPropertyType.LIST && parentIndex != null && toscaPathMap.size > 0) {
280 this.property.flattenedChildren.forEach((childProperties : DerivedFEProperty) => {
281 if (toscaPathMap.has(childProperties.toscaPath.toString())) {
282 childProperties.toscaPath = toscaPathMap.get(childProperties.toscaPath.toString());
287 if (item.derivedDataType == DerivedPropertyType.MAP && !item.mapInlist) {
288 delete itemParent.valueObj[oldKey];
289 if (itemParent instanceof PropertyFEModel) {
290 delete itemParent.valueObjValidation[oldKey];
291 itemParent.valueObjIsValid = itemParent.calculateValueObjIsValid();
293 this.property.childPropMapKeyUpdated(item, null); // remove map key
295 const itemIndex: number = this.property.flattenedChildren.filter(prop => prop.parentName == item.parentName).map(prop => prop.propertiesName).indexOf(item.propertiesName);
296 itemParent.valueObj.splice(itemIndex, 1);
297 if (itemParent instanceof PropertyFEModel) {
298 itemParent.valueObjValidation.splice(itemIndex, 1);
299 itemParent.valueObjIsValid = itemParent.calculateValueObjIsValid();
302 if (itemParent instanceof PropertyFEModel) { //direct child
304 } else { //nested child - need to update parent prop by getting flattened name (recurse through parents and replace map/list keys, etc)
305 this.childValueChanged(itemParent);
310 updateChildKeyInParent(childProp: DerivedFEProperty, newMapKey: string) {
311 if (this.property instanceof PropertyFEModel) {
312 let oldKey = childProp.getActualMapKey();
313 let oldToscaPath = childProp.toscaPath;
314 this.property.childPropMapKeyUpdated(childProp, newMapKey);
315 this.updateChildMapKey(this.property.flattenedChildren, childProp.propertiesName, newMapKey);
316 if (this.property.subPropertyToscaFunctions != null) {
317 this.property.subPropertyToscaFunctions.forEach((item : SubPropertyToscaFunction) => {
318 if(item.subPropertyPath === oldToscaPath){
319 item.subPropertyPath = childProp.toscaPath;
327 updateChildMapKey(childProps: Array<DerivedFEProperty>, parentName: string, newMapKey: string) {
328 childProps.forEach(tempDervObj => {
329 if (parentName === tempDervObj.parentName) {
330 tempDervObj.mapKey = newMapKey;
331 tempDervObj.toscaPath[tempDervObj.toscaPath.length - 2] = newMapKey;
336 preventInsertItem = (property:DerivedFEProperty):boolean => {
337 if(property.type == PROPERTY_TYPES.MAP && property.valueObj != null && Object.keys(property.valueObj).indexOf('') > -1 ){