3 * Copyright 2016 RIFT.IO Inc
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
19 * Created by onvelocity on 1/18/16.
21 * This class generates the form fields used to edit the CONFD JSON model.
25 import _includes
from 'lodash/includes'
26 import _isArray
from 'lodash/isArray'
27 import _cloneDeep
from 'lodash/cloneDeep'
28 import utils
from '../libraries/utils'
29 import React
from 'react'
30 import ClassNames
from 'classnames'
31 import changeCase
from 'change-case'
32 import toggle
from '../libraries/ToggleElementHandler'
33 import Button
from './Button'
34 import Property
from '../libraries/model/DescriptorModelMetaProperty'
35 import ComposerAppActions
from '../actions/ComposerAppActions'
36 import CatalogItemsActions
from '../actions/CatalogItemsActions'
37 import DESCRIPTOR_MODEL_FIELDS
from '../libraries/model/DescriptorModelFields'
38 import DescriptorModelFactory
from '../libraries/model/DescriptorModelFactory'
39 import DescriptorModelMetaFactory
from '../libraries/model/DescriptorModelMetaFactory'
40 import SelectionManager
from '../libraries/SelectionManager'
41 import DeletionManager
from '../libraries/DeletionManager'
42 import DescriptorModelIconFactory
from '../libraries/model/IconFactory'
43 import getEventPath
from '../libraries/getEventPath'
44 import CatalogDataStore
from '../stores/CatalogDataStore'
46 import imgAdd
from '../../../node_modules/open-iconic/svg/plus.svg'
47 import imgRemove
from '../../../node_modules/open-iconic/svg/trash.svg'
49 import '../styles/EditDescriptorModelProperties.scss'
51 function getDescriptorMetaBasicForType(type
) {
52 const basicPropertiesFilter
= d
=> _includes(DESCRIPTOR_MODEL_FIELDS
[type
], d
.name
);
53 return DescriptorModelMetaFactory
.getModelMetaForType(type
, basicPropertiesFilter
) || {properties
: []};
56 function getDescriptorMetaAdvancedForType(type
) {
57 const advPropertiesFilter
= d
=> !_includes(DESCRIPTOR_MODEL_FIELDS
[type
], d
.name
);
58 return DescriptorModelMetaFactory
.getModelMetaForType(type
, advPropertiesFilter
) || {properties
: []};
61 function getTitle(model
= {}) {
62 if (typeof model
['short-name'] === 'string' && model
['short-name']) {
63 return model
['short-name'];
65 if (typeof model
.name
=== 'string' && model
.name
) {
68 if (model
.uiState
&& typeof model
.uiState
.displayName
=== 'string' && model
.uiState
.displayName
) {
69 return model
.uiState
.displayName
71 if (typeof model
.id
=== 'string') {
76 export default function EditDescriptorModelProperties(props
) {
78 const container
= props
.container
;
80 if (!(DescriptorModelFactory
.isContainer(container
))) {
84 function startEditing() {
85 DeletionManager
.removeEventListeners();
88 function endEditing() {
89 DeletionManager
.addEventListeners();
92 function onClickSelectItem(property
, path
, value
, event
) {
93 event
.preventDefault();
94 const root
= this.getRoot();
95 if (SelectionManager
.select(value
)) {
96 CatalogItemsActions
.catalogItemMetaDataChanged(root
.model
);
100 function onFocusPropertyFormInputElement(property
, path
, value
, event
) {
102 event
.preventDefault();
105 function removeIsFocusedClass(event
) {
106 event
.target
.removeEventListener('blur', removeIsFocusedClass
);
107 Array
.from(document
.querySelectorAll('.-is-focused')).forEach(d
=> d
.classList
.remove('-is-focused'));
110 removeIsFocusedClass(event
);
112 const propertyWrapper
= getEventPath(event
).reduce((parent
, element
) => {
116 if (!element
.classList
) {
119 if (element
.classList
.contains('property')) {
124 if (propertyWrapper
) {
125 propertyWrapper
.classList
.add('-is-focused');
126 event
.target
.addEventListener('blur', removeIsFocusedClass
);
131 function buildAddPropertyAction(container
, property
, path
) {
132 function onClickAddProperty(property
, path
, event
) {
133 event
.preventDefault();
134 //SelectionManager.resume();
135 const create
= Property
.getContainerCreateMethod(property
, this);
138 create(model
, path
, property
);
140 const name
= path
.join('.');
141 const value
= Property
.createModelInstance(property
);
142 utils
.assignPathValue(this.model
, name
, value
);
144 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
147 <Button className
="inline-hint" onClick
={onClickAddProperty
.bind(container
, property
, path
)} label
="Add" src
={imgAdd
} />
151 function buildRemovePropertyAction(container
, property
, path
) {
152 function onClickRemoveProperty(property
, path
, event
) {
153 event
.preventDefault();
154 const name
= path
.join('.');
155 const removeMethod
= Property
.getContainerMethod(property
, this, 'remove');
157 removeMethod(utils
.resolvePath(this.model
, name
));
159 utils
.removePathValue(this.model
, name
);
161 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
164 <Button className
="remove-property-action inline-hint" title
="Remove" onClick
={onClickRemoveProperty
.bind(container
, property
, path
)} label
="Remove" src
={imgRemove
}/>
168 function onFormFieldValueChanged(event
) {
169 if (DescriptorModelFactory
.isContainer(this)) {
170 event
.preventDefault();
171 const name
= event
.target
.name
;
172 const value
= event
.target
.value
;
173 utils
.assignPathValue(this.model
, name
, value
);
174 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
178 function buildField(container
, property
, path
, value
, fieldKey
) {
179 let cds
= CatalogDataStore
;
180 let catalogs
= cds
.getTransientCatalogs();
182 const name
= path
.join('.');
183 const isEditable
= true;
184 const isGuid
= Property
.isGuid(property
);
185 const isBoolean
= Property
.isBoolean(property
);
186 const onChange
= onFormFieldValueChanged
.bind(container
);
187 const isEnumeration
= Property
.isEnumeration(property
);
188 const isLeafRef
= Property
.isLeafRef(property
);
189 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
190 const placeholder
= changeCase
.title(property
.name
);
191 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
192 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : (isNaN(value
) ? undefined : value
);
194 const enumeration
= Property
.getEnumeration(property
, value
);
195 const options
= enumeration
.map((d
, i
) => {
196 // note yangforge generates values for enums but the system does not use them
197 // so we categorically ignore them
198 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
199 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
200 return <option key
={fieldKey
.toString() + ':' + i
} value
={d
.name
}>{d
.name
}</option
>;
202 const isValueSet
= enumeration
.filter(d
=> d
.isSelected
).length
> 0;
203 if (!isValueSet
|| property
.cardinality
=== '0..1') {
204 const noValueDisplayText
= changeCase
.title(property
.name
);
205 options
.unshift(<option key
={'(value-not-in-enum)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
207 return <select key
={fieldKey
.toString()} id
={fieldKey
.toString()} className
={ClassNames({'-value-not-set': !isValueSet
})} name
={name
} value
={value
} title
={name
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} readOnly
={!isEditable
}>{options
}</select
>;
211 let fullFieldKey
= _isArray(fieldKey
) ? fieldKey
.join(':') : fieldKey
;
212 let containerRef
= container
;
213 while (containerRef
.parent
) {
214 fullFieldKey
= containerRef
.parent
.key
+ ':' + fullFieldKey
;
215 containerRef
= containerRef
.parent
;
217 const leafRefPathValues
= Property
.getLeafRef(property
, path
, value
, fullFieldKey
, catalogs
, container
);
219 const options
= leafRefPathValues
&& leafRefPathValues
.map((d
, i
) => {
220 return <option key
={fieldKey
.toString() + ':' + i
} value
={d
.value
}>{d
.value
}</option
>;
222 const isValueSet
= leafRefPathValues
.filter(d
=> d
.isSelected
).length
> 0;
223 if (!isValueSet
|| property
.cardinality
=== '0..1') {
224 const noValueDisplayText
= changeCase
.title(property
.name
);
225 options
.unshift(<option key
={'(value-not-in-leafref)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
227 return <select key
={fieldKey
.toString()} id
={fieldKey
.toString()} className
={ClassNames({'-value-not-set': !isValueSet
})} name
={name
} value
={value
} title
={name
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} readOnly
={!isEditable
}>{options
}</select
>;
231 let fullFieldKey
= _isArray(fieldKey
) ? fieldKey
.join(':') : fieldKey
;
232 let containerRef
= container
;
233 while (containerRef
.parent
) {
234 fullFieldKey
= containerRef
.parent
.key
+ ':' + fullFieldKey
;
235 containerRef
= containerRef
.parent
;
239 <option key
={fieldKey
.toString() + '-true'} value
="TRUE">TRUE
</option
>,
240 <option key
={fieldKey
.toString() + '-false'} value
="FALSE">FALSE
</option
>
243 // if (!isValueSet) {
244 const noValueDisplayText
= changeCase
.title(property
.name
);
245 options
.unshift(<option key
={'(value-not-in-leafref)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}></option
>);
248 if(typeof(val
) == 'number') {
249 val
= value
? "TRUE" : "FALSE"
251 const isValueSet
= (val
!= '' && val
)
252 return <select key
={fieldKey
.toString()} id
={fieldKey
.toString()} className
={ClassNames({'-value-not-set': !isValueSet
})} name
={name
} value
={val
&& val
.toUpperCase()} title
={name
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} readOnly
={!isEditable
}>{options
}</select
>;
255 if (property
['preserve-line-breaks']) {
256 return <textarea key
={fieldKey
.toString()} cols
="5" id
={fieldKey
.toString()} name
={name
} value
={value
} placeholder
={placeholder
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} onMouseOut
={endEditing
} onMouseLeave
={endEditing
} readOnly
={!isEditable
} />;
259 return <input key
={fieldKey
.toString()}
260 id
={fieldKey
.toString()}
264 className
={className
}
265 placeholder
={placeholder
}
269 onMouseDown
={startEditing
}
270 onMouseOver
={startEditing
}
271 onMouseOut
={endEditing
}
272 onMouseLeave
={endEditing
}
273 readOnly
={!isEditable
}
278 function buildElement(container
, property
, valuePath
, value
) {
279 return property
.properties
.map((property
, index
) => {
281 const childPath
= valuePath
.slice();
282 if (typeof value
=== 'object') {
283 childValue
= value
[property
.name
];
285 if(property
.type
!= 'choice'){
286 childPath
.push(property
.name
);
288 return build(container
, property
, childPath
, childValue
);
293 function buildChoice(container
, property
, path
, value
, key
) {
295 function onFormFieldValueChanged(event
) {
296 if (DescriptorModelFactory
.isContainer(this)) {
298 event
.preventDefault();
300 let name
= event
.target
.name
;
301 const value
= event
.target
.value
;
305 Transient State is stored for convenience in the uiState field.
306 The choice yang type uses case elements to describe the "options".
307 A choice can only ever have one option selected which allows
308 the system to determine which type is selected by the name of
309 the element contained within the field.
312 const stateExample = {
327 const statePath
= ['uiState.choice'].concat(name
);
328 const stateObject
= utils
.resolvePath(this.model
, statePath
.join('.')) || {};
329 const selected
= stateObject
.selected
? stateObject
.selected
.split('.')[1] : undefined;
330 // write state back to the model so the new state objects are captured
331 utils
.assignPathValue(this.model
, statePath
.join('.'), stateObject
);
333 // write the current choice value into the state
334 let choiceObject
= utils
.resolvePath(this.model
, [name
, selected
].join('.'));
335 let isTopCase
= false;
338 choiceObject
= utils
.resolvePath(this.model
, [selected
].join('.'));
340 utils
.assignPathValue(stateObject
, [selected
].join('.'), _cloneDeep(choiceObject
));
343 if(this.model
.uiState
.choice
.hasOwnProperty(name
)) {
344 delete this.model
[selected
];
345 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
347 // remove the current choice value from the model
348 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
352 // get any state for the new selected choice
353 const newChoiceObject
= utils
.resolvePath(stateObject
, [value
].join('.')) || {};
355 // assign new choice value to the model
357 utils
.assignPathValue(this.model
, [name
, value
].join('.'), newChoiceObject
);
359 utils
.assignPathValue(this.model
, [value
].join('.'), newChoiceObject
)
363 // update the selected name
364 utils
.assignPathValue(this.model
, statePath
.concat('selected').join('.'), value
);
366 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
370 const caseByNameMap
= {};
372 const onChange
= onFormFieldValueChanged
.bind(container
);
374 const cases
= property
.properties
.map(d
=> {
375 if (d
.type
=== 'case') {
376 //Previous it was assumed that a case choice would have only one property. Now we pass on either the only item or the
377 caseByNameMap
[d
.name
] = d
.properties
&& (d
.properties
.length
== 1 ? d
.properties
[0] : d
.properties
);
380 optionTitle
: d
.description
,
381 //represents case name and case element name
382 optionValue
: [d
.name
, d
.properties
[0].name
].join('.')
385 caseByNameMap
[d
.name
] = d
;
386 return {optionName
: d
.name
};
389 const options
= [{optionName
: '', optionValue
: false}].concat(cases
).map((d
, i
) => {
391 <option key
={i
} value
={d
.optionValue
} title
={d
.optionTitle
}>
393 {i
? null : changeCase
.title(property
.name
)}
398 const selectName
= path
.join('.');
399 let selectedOptionPath
= ['uiState.choice', selectName
, 'selected'].join('.');
400 //Currently selected choice/case statement on UI model
401 let selectedOptionValue
= utils
.resolvePath(container
.model
, selectedOptionPath
);
402 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
403 if(!selectedOptionValue
) {
404 //get field properties for choice on container model
405 let fieldProperties
= utils
.resolvePath(container
.model
, selectName
);
406 if(fieldProperties
) {
407 //Check each case statement in model and see if it is present in container model.
408 cases
.map(function(c
){
409 if(fieldProperties
.hasOwnProperty(c
.optionValue
.split('.')[1])) {
410 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), c
.optionValue
);
413 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
415 property
.properties
.map(function(p
) {
416 let pname
= p
.properties
[0].name
;
417 if(container
.model
.hasOwnProperty(pname
)) {
418 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), [p
.name
, pname
].join('.'));
421 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
424 //If selectedOptionValue is present, take first item in string which represents the case name.
425 const valueProperty
= caseByNameMap
[selectedOptionValue
? selectedOptionValue
.split('.')[0] : undefined] || {properties
: []};
426 const isLeaf
= Property
.isLeaf(valueProperty
);
427 const hasProperties
= _isArray(valueProperty
.properties
) && valueProperty
.properties
.length
;
428 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(valueProperty
);
429 //Some magic that prevents errors for arising
430 const valueResponse
= valueProperty
.properties
&& valueProperty
.properties
.length
? valueProperty
.properties
.map(valuePropertyFn
) : (!isMissingDescriptorMeta
) ? build(container
, valueProperty
, path
.concat(valueProperty
.name
), utils
.resolvePath(container
.model
, path
.concat(valueProperty
.name
).join('.')) || container
.model
[valueProperty
.name
]) :
431 valueProperty
.map
&& valueProperty
.map(valuePropertyFn
);
432 function valuePropertyFn(d
, i
) {
433 const childPath
= path
.concat(valueProperty
.name
, d
.name
);
434 const childValue
= utils
.resolvePath(container
.model
, childPath
.join('.'));
436 <div key
={childPath
.concat('info', i
).join(':')}>
437 {build(container
, d
, childPath
, childValue
, props
)}
442 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
445 <div key
={key
} className
="choice">
446 <select key
={Date
.now()} className
={ClassNames({'-value-not-set': !selectedOptionValue
})} name
={selectName
} value
={selectedOptionValue
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} onMouseOut
={endEditing
} onMouseLeave
={endEditing
}>
455 function buildSimpleListItem(container
, property
, path
, value
, key
, index
) {
456 // todo need to abstract this better
457 const title
= getTitle(value
);
458 var req
= require
.context("../", true, /\.svg
/);
461 <a href
="#select-list-item" key
={Date
.now()} className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
462 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
465 {buildRemovePropertyAction(container
, property
, path
)}
470 function buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) {
471 const className
= ClassNames(property
.name
+ '-remove actions');
473 <div key
={fieldKey
.concat(index
).join(':')} className
={className
}>
475 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
476 <span className
="info">{index
+ 1}</span
>
477 {buildRemovePropertyAction(container
, property
, valuePath
)}
483 function buildLeafListItem(container
, property
, valuePath
, value
, key
, index
) {
484 // look at the type to determine how to parse the value
487 {buildRemoveListItem(container
, property
, valuePath
, key
, index
)}
488 {buildField(container
, property
, valuePath
, value
, key
)}
494 function build(container
, property
, path
, value
, props
= {}) {
497 const isLeaf
= Property
.isLeaf(property
);
498 const isArray
= Property
.isArray(property
);
499 const isObject
= Property
.isObject(property
);
500 const isLeafList
= Property
.isLeafList(property
);
501 const fieldKey
= [container
.id
].concat(path
);
502 const isRequired
= Property
.isRequired(property
);
503 const title
= changeCase
.titleCase(property
.name
);
504 const columnCount
= property
.properties
.length
|| 1;
505 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
506 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
508 if (!property
.properties
&& isObject
) {
509 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
510 property
.properties
= uiState
.properties
;
513 const hasProperties
= _isArray(property
.properties
) && property
.properties
.length
;
514 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
516 // ensure value is not undefined for non-leaf property types
518 if (typeof value
!== 'object') {
519 value
= isArray
? [] : {};
522 const valueAsArray
= _isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
524 const isMetaField
= property
.name
=== 'meta';
525 const isCVNFD
= property
.name
=== 'constituent-vnfd';
526 const isSimpleListView
= Property
.isSimpleList(property
);
528 valueAsArray
.forEach((value
, index
) => {
531 const key
= fieldKey
.slice();
532 const valuePath
= path
.slice();
535 valuePath
.push(index
);
540 if (typeof value
=== 'object') {
541 value
= JSON
.stringify(value
, undefined, 12);
542 } else if (typeof value
!== 'string') {
547 if (isMissingDescriptorMeta
) {
548 field
= <span key
={key
.concat('warning').join(':')} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
549 } else if (property
.type
=== 'choice') {
550 field
= buildChoice(container
, property
, valuePath
, value
, key
.join(':'));
551 } else if (isSimpleListView
) {
552 field
= buildSimpleListItem(container
, property
, valuePath
, value
, key
, index
);
553 } else if (isLeafList
) {
554 field
= buildLeafListItem(container
, property
, valuePath
, value
, key
, index
);
555 } else if (hasProperties
) {
556 field
= buildElement(container
, property
, valuePath
, value
, key
.join(':'))
558 field
= buildField(container
, property
, valuePath
, value
, key
.join(':'));
561 function onClickLeaf(property
, path
, value
, event
) {
562 if (event
.isDefaultPrevented()) {
565 event
.preventDefault();
566 event
.stopPropagation();
567 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
568 console
.log('property selected', path
.join('.'));
569 ComposerAppActions
.propertySelected([path
.join('.')]);
572 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
573 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
576 <div key
={fieldKey
.concat(['property-content', index
]).join(':')}
577 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
578 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
579 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) : null}
586 classNames
['-is-leaf'] = isLeaf
;
587 classNames
['-is-array'] = isArray
;
588 classNames
['cols-' + columnCount
] = isColumnar
;
590 if (property
.type
=== 'choice') {
591 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
593 property
.properties
.map(function(p
) {
594 let pname
= p
.properties
[0].name
;
595 if(container
.model
.hasOwnProperty(pname
)) {
596 value
= container
.model
[pname
];
602 let displayValue
= typeof value
=== 'object' ? '' : value
;
603 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
605 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
608 <div key
={fieldKey
.join(':')} className
={ClassNames(property
.type
+ '-property property', classNames
)} onFocus
={onFocus
}>
609 <h3 className
="property-label">
610 <label htmlFor
={fieldKey
.join(':')}>
611 <span className
={property
.type
+ '-name name'}>{title
}</span
>
613 <span className
={property
.type
+ '-info info'}>{displayValueInfo
}</span
>
614 <span className
={property
.type
+ '-value value'}>{displayValue
}</span
>
616 {isArray
? buildAddPropertyAction(container
, property
, path
.concat(valueAsArray
.length
)) : null}
619 <span className
={property
.type
+ '-description description'}>{property
.description
}</span
>
620 <val className
="property-value">
621 {isCVNFD
? <span className
={property
.type
+ '-tip tip'}>Drag a VNFD
from the Catalog to add more
.</span
> : null}
629 const containerType
= container
.uiState
['qualified-type'] || container
.uiState
.type
;
630 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
632 function buildBasicGroup() {
633 if (basicProperties
.length
=== 0) {
637 <div className
="basic-properties-group">
640 {basicProperties
.map(property
=> {
641 const path
= [property
.name
];
642 const value
= container
.model
[property
.name
];
643 return build(container
, property
, path
, value
);
650 function buildAdvancedGroup() {
651 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
652 if (properties
.length
=== 0) {
655 const hasBasicFields
= basicProperties
.length
> 0;
656 const closeGroup
= basicProperties
.length
> 0;
658 <div className
="advanced-properties-group">
659 <h1 data
-toggle
={closeGroup
? 'true' : 'false'} className
={ClassNames({'-is-toggled': closeGroup
})} onClick
={toggle
} style
={{display
: hasBasicFields
? 'block' : 'none'}}>
660 <a className
="toggle-show-more" href
="#show-more-properties">more
&hellip
;</a
>
661 <a className
="toggle-show-less" href
="#show-more-properties">less
&hellip
;</a
>
663 <div className
="toggleable">
664 {properties
.map(property
=> {
665 const path
= [property
.name
];
666 const value
= container
.model
[property
.name
];
667 return build(container
, property
, path
, value
, {toggle
: true, width
: props
.width
});
670 <div className
="toggle-bottom-spacer" style
={{visibility
: 'hidden', 'height': '50%', position
: 'absolute'}}>We need
this so when the user closes the panel it won
't shift away and scare the bj out of them!</div>
675 function buildMoreLess(d, i) {
677 <span key={'bread
-crumb
-part
-' + i}>
678 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
685 if (container.parent) {
686 path.push(container.parent);
688 path.push(container);
691 <div className="EditDescriptorModelProperties -is-tree-view">
692 <h1>{path.map(buildMoreLess)}</h1>
694 {buildAdvancedGroup()}