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 {SchemaPropertyGroupModel, SchemaProperty} from '../aschema-property';
23 import { PROPERTY_DATA, PROPERTY_TYPES } from 'app/utils';
24 import { FilterPropertiesAssignmentData, PropertyBEModel, DerivedPropertyType, DerivedFEPropertyMap, DerivedFEProperty } from 'app/models';
27 export class PropertyFEModel extends PropertyBEModel {
29 expandedChildPropertyId: string;
30 flattenedChildren: Array<DerivedFEProperty>;
34 isSimpleType: boolean; //for convenience only - we can really just check if derivedDataType == derivedPropertyTypes.SIMPLE to know if the prop is simple
35 propertiesName: string;
37 valueObj: any; //this is the only value we relate to in the html templates
38 valueObjValidation: any;
39 valueObjIsValid: boolean;
40 valueObjOrig: any; //this is valueObj representation as saved in server
41 valueObjIsChanged: boolean;
42 derivedDataType: DerivedPropertyType;
44 constructor(property: PropertyBEModel){
46 this.value = property.value ? property.value : property.defaultValue;//In FE if a property doesn't have value - display the default value
47 this.isSimpleType = PROPERTY_DATA.SIMPLE_TYPES.indexOf(this.type) > -1;
48 this.setNonDeclared();
49 this.derivedDataType = this.getDerivedPropertyType();
50 this.flattenedChildren = [];
51 this.propertiesName = this.name;
53 this.updateValueObjOrig();
54 this.resetValueObjValidation();
58 public updateValueObj(valueObj:any, isValid:boolean) {
59 this.valueObj = PropertyFEModel.cleanValueObj(valueObj);
60 this.valueObjValidation = this.valueObjIsValid = isValid;
61 this.valueObjIsChanged = this.hasValueObjChanged();
64 public updateValueObjOrig() {
65 this.valueObjOrig = _.cloneDeep(this.valueObj);
66 this.valueObjIsChanged = false;
69 public calculateValueObjIsValid(valueObjValidation?: any) {
70 valueObjValidation = (valueObjValidation !== undefined) ? valueObjValidation : this.valueObjValidation;
71 if (valueObjValidation instanceof Array) {
72 return valueObjValidation.every((v) => this.calculateValueObjIsValid(v));
73 } else if (valueObjValidation instanceof Object) {
74 return Object.keys(valueObjValidation).every((k) => this.calculateValueObjIsValid(valueObjValidation[k]));
76 return Boolean(valueObjValidation);
79 public resetValueObjValidation() {
80 if (this.derivedDataType === DerivedPropertyType.SIMPLE) {
81 this.valueObjValidation = null;
82 } else if (this.derivedDataType === DerivedPropertyType.LIST) {
83 this.valueObjValidation = [];
85 this.valueObjValidation = {};
87 this.valueObjIsValid = true;
90 public getJSONValue = (): string => {
91 return PropertyFEModel.stringifyValueObj(this.valueObj, this.schema.property.type, this.derivedDataType);
94 public getValueObj = (): any => {
95 return PropertyFEModel.parseValueObj(this.value, this.type, this.derivedDataType, this.defaultValue);
98 public setNonDeclared = (childPath?: string): void => {
99 if (!childPath) { //un-declaring a child prop
100 this.isDeclared = false;
102 let childProp: DerivedFEProperty = this.flattenedChildren.find(child => child.propertiesName == childPath);
103 childProp.isDeclared = false;
107 public setAsDeclared = (childNameToDeclare?:string): void => {
108 if (!childNameToDeclare) { //declaring a child prop
109 this.isSelected = false;
110 this.isDeclared = true;
112 let childProp: DerivedFEProperty = this.flattenedChildren.find(child => child.propertiesName == childNameToDeclare);
113 if (!childProp) { console.log("ERROR: Unabled to find child: " + childNameToDeclare, this); return; }
114 childProp.isSelected = false;
115 childProp.isDeclared = true;
119 //For expand-collapse functionality - used within HTML template
120 public updateExpandedChildPropertyId = (childPropertyId: string): void => {
121 if (childPropertyId.lastIndexOf('#') > -1) {
122 this.expandedChildPropertyId = (this.expandedChildPropertyId == childPropertyId) ? (childPropertyId.substring(0, childPropertyId.lastIndexOf('#'))) : childPropertyId;
124 this.expandedChildPropertyId = this.name;
128 public getIndexOfChild = (childPropName: string): number => {
129 return this.flattenedChildren.findIndex(prop => prop.propertiesName.indexOf(childPropName) === 0);
132 public getCountOfChildren = (childPropName: string):number => {
133 let matchingChildren:Array<DerivedFEProperty> = this.flattenedChildren.filter(prop => prop.propertiesName.indexOf(childPropName) === 0) || [];
134 return matchingChildren.length;
137 // public getListIndexOfChild = (childPropName: string): number => { //gets list of siblings and then the index within that list
138 // this.flattenedChildren.filter(prop => prop.parentName == item.parentName).map(prop => prop.propertiesName).indexOf(item.propertiesName)
141 /* Updates parent valueObj when a child prop's value has changed */
142 public childPropUpdated = (childProp: DerivedFEProperty): void => {
143 let parentNames = this.getParentNamesArray(childProp.propertiesName, []);
144 if (parentNames.length) {
145 const childPropName = parentNames.join('.');
146 // unset value only if is null and valid, and not in a list
147 if (childProp.valueObj === null && childProp.valueObjIsValid) {
148 const parentChildProp = this.flattenedChildren.find((ch) => ch.propertiesName === childProp.parentName) || this;
149 if (parentChildProp.derivedDataType !== DerivedPropertyType.LIST) {
150 _.unset(this.valueObj, childPropName);
151 this.valueObj = PropertyFEModel.cleanValueObj(this.valueObj);
153 _.set(this.valueObj, childPropName, null);
156 _.set(this.valueObj, childPropName, childProp.valueObj);
158 if (childProp.valueObjIsChanged) {
159 _.set(this.valueObjValidation, childPropName, childProp.valueObjIsValid);
160 this.valueObjIsValid = childProp.valueObjIsValid && this.calculateValueObjIsValid();
161 this.valueObjIsChanged = true;
163 _.unset(this.valueObjValidation, childPropName);
164 this.valueObjIsValid = this.calculateValueObjIsValid();
165 this.valueObjIsChanged = this.hasValueObjChanged();
170 childPropMapKeyUpdated = (childProp: DerivedFEProperty, newMapKey: string, forceValidate: boolean = false) => {
171 if (!childProp.isChildOfListOrMap || childProp.derivedDataType !== DerivedPropertyType.MAP) {
175 const childParentNames = this.getParentNamesArray(childProp.parentName);
176 const oldActualMapKey = childProp.getActualMapKey();
178 childProp.mapKey = newMapKey;
179 if (childProp.mapKey === null) { // null -> remove map key
180 childProp.mapKeyError = null;
181 } else if (!childProp.mapKey) {
182 childProp.mapKeyError = 'Key cannot be empty.';
183 } else if (this.flattenedChildren
184 .filter((fch) => fch !== childProp && fch.parentName === childProp.parentName) // filter sibling child props
185 .map((fch) => fch.mapKey)
186 .indexOf(childProp.mapKey) !== -1) {
187 childProp.mapKeyError = 'This key already exists.';
189 childProp.mapKeyError = null;
191 const newActualMapKey = childProp.getActualMapKey();
192 const newMapKeyIsValid = !childProp.mapKeyError;
194 // if mapKey was changed, then replace the old key with the new one
195 if (newActualMapKey !== oldActualMapKey) {
196 const oldChildPropNames = childParentNames.concat([oldActualMapKey]);
197 const newChildPropNames = (newActualMapKey) ? childParentNames.concat([newActualMapKey]) : null;
199 // add map key to valueObj and valueObjValidation
200 if (newChildPropNames) {
201 const newChildVal = _.get(this.valueObj, oldChildPropNames);
202 if (newChildVal !== undefined) {
203 _.set(this.valueObj, newChildPropNames, newChildVal);
204 _.set(this.valueObjValidation, newChildPropNames, _.get(this.valueObjValidation, oldChildPropNames, childProp.valueObjIsValid));
208 // remove map key from valueObj and valueObjValidation
209 _.unset(this.valueObj, oldChildPropNames);
210 _.unset(this.valueObjValidation, oldChildPropNames);
212 // force validate after map key change
213 forceValidate = true;
217 // add custom entry for map key validation:
218 const childMapKeyNames = childParentNames.concat(`%%KEY:${childProp.name}%%`);
219 if (newActualMapKey) {
220 _.set(this.valueObjValidation, childMapKeyNames, newMapKeyIsValid);
222 _.unset(this.valueObjValidation, childMapKeyNames);
225 this.valueObjIsValid = newMapKeyIsValid && this.calculateValueObjIsValid();
226 this.valueObjIsChanged = this.hasValueObjChanged();
230 /* Returns array of individual parents for given prop path, with list/map UUIDs replaced with index/mapkey */
231 public getParentNamesArray = (parentPropName: string, parentNames?: Array<string>): Array<string> => {
232 parentNames = parentNames || [];
233 if (parentPropName.indexOf("#") == -1) { return parentNames; } //finished recursing parents. return
235 let parentProp: DerivedFEProperty = this.flattenedChildren.find(prop => prop.propertiesName === parentPropName);
236 let nameToInsert: string = parentProp.name;
238 if (parentProp.isChildOfListOrMap) {
239 if (parentProp.derivedDataType == DerivedPropertyType.MAP) {
240 nameToInsert = parentProp.getActualMapKey();
242 let siblingProps = this.flattenedChildren.filter(prop => prop.parentName == parentProp.parentName).map(prop => prop.propertiesName);
243 nameToInsert = siblingProps.indexOf(parentProp.propertiesName).toString();
247 parentNames.splice(0, 0, nameToInsert); //add prop name to array
248 return this.getParentNamesArray(parentProp.parentName, parentNames); //continue recursing
251 public hasValueObjChanged() {
252 return !_.isEqual(this.valueObj, this.valueObjOrig);
255 static stringifyValueObj(valueObj: any, propertyType: PROPERTY_TYPES, propertyDerivedType: DerivedPropertyType): string {
256 // if valueObj is null, return null
257 if (valueObj === null || valueObj === undefined) {
261 //If type is JSON, need to try parsing it before we stringify it so that it appears property in TOSCA - change per Bracha due to AMDOCS
262 //TODO: handle this.derivedDataType == DerivedPropertyType.MAP
263 if (propertyDerivedType == DerivedPropertyType.LIST && propertyType == PROPERTY_TYPES.JSON) {
265 return JSON.stringify(valueObj.map(item => (typeof item == 'string') ? JSON.parse(item) : item));
269 // if type is anything but string, then stringify valueObj
270 if ((typeof valueObj) !== 'string') {
271 return JSON.stringify(valueObj);
274 // return string value as is
278 static parseValueObj(value: string, propertyType: PROPERTY_TYPES, propertyDerivedType: DerivedPropertyType, defaultValue?: string): any {
280 if (propertyDerivedType === DerivedPropertyType.SIMPLE) {
281 valueObj = value || defaultValue || null; // use null for empty value object
283 propertyType !== PROPERTY_TYPES.STRING &&
284 propertyType !== PROPERTY_TYPES.JSON &&
285 PROPERTY_DATA.SCALAR_TYPES.indexOf(<string>propertyType) == -1) {
286 valueObj = JSON.parse(value); // the value object contains the real value ans not the value as string
288 } else if (propertyDerivedType == DerivedPropertyType.LIST) {
289 valueObj = _.merge([], JSON.parse(defaultValue || '[]'), JSON.parse(value || '[]')); // value object should be merged value and default value. Value takes higher precedence. Set value object to empty obj if undefined.
291 valueObj = _.merge({}, JSON.parse(defaultValue || '{}'), JSON.parse(value || '{}')); // value object should be merged value and default value. Value takes higher precedence. Set value object to empty obj if undefined.
296 static cleanValueObj(valueObj: any, unsetEmpty?: boolean): any {
297 // By default - unsetEmpty undefined - will make valueObj cleaned (no null or empty objects, but array will keep null or empty objects).
298 if (valueObj === undefined || valueObj === null || valueObj === '') {
301 if (valueObj instanceof Array) {
302 const cleanArr = valueObj.map((v) => PropertyFEModel.cleanValueObj(v)).filter((v) => v !== null);
303 valueObj.splice(0, valueObj.length, ...cleanArr)
304 } else if (valueObj instanceof Object) {
305 Object.keys(valueObj).forEach((k) => {
306 // clean each item in the valueObj (by default, unset empty objects)
307 valueObj[k] = PropertyFEModel.cleanValueObj(valueObj[k], unsetEmpty !== undefined ? unsetEmpty : true);
308 if (valueObj[k] === null) {
312 // if unsetEmpty flag is true and valueObj is empty
313 if (unsetEmpty && !Object.keys(valueObj).length) {