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.
24 import _includes
from 'lodash/includes'
25 import _isArray
from 'lodash/isArray'
26 import _cloneDeep
from 'lodash/cloneDeep'
27 import _debounce
from 'lodash/debounce';
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 // get a unique name for the new list item based on the current list content
142 // some lists, based on the key, may not get a uniqueName generated here
143 const uniqueName
= DescriptorModelMetaFactory
.generateItemUniqueName(container
.model
[property
.name
], property
);
144 const value
= Property
.createModelInstance(property
, uniqueName
);
145 utils
.assignPathValue(this.model
, name
, value
);
147 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
150 <Button className
="inline-hint" onClick
={onClickAddProperty
.bind(container
, property
, path
)} label
="Add" src
={imgAdd
} />
154 function buildRemovePropertyAction(container
, property
, path
) {
155 function onClickRemoveProperty(property
, path
, event
) {
156 event
.preventDefault();
157 const name
= path
.join('.');
158 const removeMethod
= Property
.getContainerMethod(property
, this, 'remove');
160 removeMethod(utils
.resolvePath(this.model
, name
));
162 utils
.removePathValue(this.model
, name
);
164 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
167 <Button className
="remove-property-action inline-hint" title
="Remove" onClick
={onClickRemoveProperty
.bind(container
, property
, path
)} label
="Remove" src
={imgRemove
}/>
171 function buildField(container
, property
, path
, value
, fieldKey
) {
172 let cds
= CatalogDataStore
;
173 let catalogs
= cds
.getTransientCatalogs();
175 const pathToProperty
= path
.join('.');
176 const isEditable
= true;
177 const isGuid
= Property
.isGuid(property
);
178 const isBoolean
= Property
.isBoolean(property
);
179 const isEnumeration
= Property
.isEnumeration(property
);
180 const isLeafRef
= Property
.isLeafRef(property
);
181 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
182 const placeholder
= changeCase
.title(property
.name
);
183 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
184 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : (isNaN(value
) ? undefined : value
);
186 // process the named field value change
187 function processFieldValueChange(name
, value
) {
188 console
.debug('processed change for -- ' + name
+ ' -- with value -- ' + value
);
189 // this = the container being edited
190 if (DescriptorModelFactory
.isContainer(this)) {
191 utils
.assignPathValue(this.model
, name
, value
);
192 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
196 // change handler used for onChange event
197 const changeHandler
= (handleValueChange
, event
) => {
198 event
.preventDefault();
199 console
.debug(event
.target
.value
);
200 handleValueChange(event
.target
.value
);
202 // create an onChange event handler for a text field for the specified field path (debounced to accumulate chars)
203 const onTextChange
= changeHandler
.bind(null, _debounce(
204 processFieldValueChange
.bind(container
, pathToProperty
), 2000, {maxWait
: 5000})); // max wait for short-name
205 // create an onChange event handler for a select field for the specified field path
206 const onSelectChange
= changeHandler
.bind(null, processFieldValueChange
.bind(container
, pathToProperty
));
209 const enumeration
= Property
.getEnumeration(property
, value
);
210 const options
= enumeration
.map((d
, i
) => {
211 // note yangforge generates values for enums but the system does not use them
212 // so we categorically ignore them
213 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
214 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
215 return <option key
={':' + i
} value
={d
.name
}>{d
.name
}</option
>;
217 const isValueSet
= enumeration
.filter(d
=> d
.isSelected
).length
> 0;
218 if (!isValueSet
|| property
.cardinality
=== '0..1') {
219 const noValueDisplayText
= changeCase
.title(property
.name
);
220 options
.unshift(<option key
={'(value-not-in-enum)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
226 className
={ClassNames({'-value-not-set': !isValueSet
})}
228 title
={pathToProperty
}
229 onChange
={onSelectChange
}
232 onMouseDown
={startEditing
}
233 onMouseOver
={startEditing
}
234 readOnly
={!isEditable
}>
241 let fullPathString
= container
.key
+ ':' + path
.join(':');
242 let containerRef
= container
;
243 while (containerRef
.parent
) {
244 fullPathString
= containerRef
.parent
.key
+ ':' + fullPathString
;
245 containerRef
= containerRef
.parent
;
247 const leafRefPathValues
= Property
.getLeafRef(property
, path
, value
, fullPathString
, catalogs
, container
);
249 const options
= leafRefPathValues
&& leafRefPathValues
.map((d
, i
) => {
250 return <option key
={':' + i
} value
={d
.value
}>{d
.value
}</option
>;
252 const isValueSet
= leafRefPathValues
.filter(d
=> d
.isSelected
).length
> 0;
253 if (!isValueSet
|| property
.cardinality
=== '0..1') {
254 const noValueDisplayText
= changeCase
.title(property
.name
);
255 options
.unshift(<option key
={'(value-not-in-leafref)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
261 className
={ClassNames({'-value-not-set': !isValueSet
})}
263 title
={pathToProperty
}
264 onChange
={onSelectChange
}
267 onMouseDown
={startEditing
}
268 onMouseOver
={startEditing
}
269 readOnly
={!isEditable
}>
277 <option key
={'true'} value
="TRUE">TRUE
</option
>,
278 <option key
={'false'} value
="FALSE">FALSE
</option
>
281 // if (!isValueSet) {
282 const noValueDisplayText
= changeCase
.title(property
.name
);
283 options
.unshift(<option key
={'(value-not-in-leafref)'} value
="" placeholder
={placeholder
}></option
>);
286 if(typeof(val
) == 'number') {
287 val
= value
? "TRUE" : "FALSE"
289 const isValueSet
= (val
!= '' && val
)
294 className
={ClassNames({'-value-not-set': !isValueSet
})}
295 defaultValue
={val
&& val
.toUpperCase()} title
={pathToProperty
}
296 onChange
={onSelectChange
}
299 onMouseDown
={startEditing
}
300 onMouseOver
={startEditing
}
301 readOnly
={!isEditable
}>
307 if (property
['preserve-line-breaks']) {
314 placeholder
={placeholder
}
315 onChange
={onTextChange
}
318 onMouseDown
={startEditing
}
319 onMouseOver
={startEditing
}
320 onMouseOut
={endEditing
}
321 onMouseLeave
={endEditing
}
322 readOnly
={!isEditable
} />
331 defaultValue
={fieldValue
}
332 className
={className
}
333 placeholder
={placeholder
}
334 onChange
={onTextChange
}
337 onMouseDown
={startEditing
}
338 onMouseOver
={startEditing
}
339 onMouseOut
={endEditing
}
340 onMouseLeave
={endEditing
}
341 readOnly
={!isEditable
}
347 function buildElement(container
, property
, valuePath
, value
) {
348 return property
.properties
.map((property
, index
) => {
350 const childPath
= valuePath
.slice();
351 if (typeof value
=== 'object') {
352 childValue
= value
[property
.name
];
354 if(property
.type
!= 'choice'){
355 childPath
.push(property
.name
);
357 return build(container
, property
, childPath
, childValue
);
362 function buildChoice(container
, property
, path
, value
, key
) {
364 function processChoiceChange(name
, value
) {
365 if (DescriptorModelFactory
.isContainer(this)) {
368 Transient State is stored for convenience in the uiState field.
369 The choice yang type uses case elements to describe the "options".
370 A choice can only ever have one option selected which allows
371 the system to determine which type is selected by the name of
372 the element contained within the field.
375 const stateExample = {
390 const statePath
= ['uiState.choice'].concat(name
);
391 const stateObject
= utils
.resolvePath(this.model
, statePath
.join('.')) || {};
392 const selected
= stateObject
.selected
? stateObject
.selected
.split('.')[1] : undefined;
393 // write state back to the model so the new state objects are captured
394 utils
.assignPathValue(this.model
, statePath
.join('.'), stateObject
);
396 // write the current choice value into the state
397 let choiceObject
= utils
.resolvePath(this.model
, [name
, selected
].join('.'));
398 let isTopCase
= false;
401 choiceObject
= utils
.resolvePath(this.model
, [selected
].join('.'));
403 utils
.assignPathValue(stateObject
, [selected
].join('.'), _cloneDeep(choiceObject
));
406 if(this.model
.uiState
.choice
.hasOwnProperty(name
)) {
407 delete this.model
[selected
];
408 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
410 // remove the current choice value from the model
411 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
415 // get any state for the new selected choice
416 const newChoiceObject
= utils
.resolvePath(stateObject
, [value
].join('.')) || {};
418 // assign new choice value to the model
420 utils
.assignPathValue(this.model
, [name
, value
].join('.'), newChoiceObject
);
422 utils
.assignPathValue(this.model
, [value
].join('.'), newChoiceObject
)
425 // update the selected name
426 utils
.assignPathValue(this.model
, statePath
.concat('selected').join('.'), value
);
428 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
432 const pathToChoice
= path
.join('.');
433 const caseByNameMap
= {};
435 const choiceChangeHandler
= processChoiceChange
.bind(container
, pathToChoice
);
436 const onChange
= ((handleChoiceChange
, event
) => {
437 event
.preventDefault();
438 handleChoiceChange(event
.target
.value
);
439 }).bind(null, choiceChangeHandler
);
442 const cases
= property
.properties
.map(d
=> {
443 if (d
.type
=== 'case') {
444 //Previous it was assumed that a case choice would have only one property. Now we pass on either the only item or the
445 caseByNameMap
[d
.name
] = d
.properties
&& (d
.properties
.length
== 1 ? d
.properties
[0] : d
.properties
);
448 optionTitle
: d
.description
,
449 //represents case name and case element name
450 optionValue
: [d
.name
, d
.properties
[0].name
].join('.')
453 caseByNameMap
[d
.name
] = d
;
454 return {optionName
: d
.name
};
457 const options
= [{optionName
: '', optionValue
: false}].concat(cases
).map((d
, i
) => {
459 <option key
={i
} value
={d
.optionValue
} title
={d
.optionTitle
}>
461 {i
? null : changeCase
.title(property
.name
)}
466 let selectedOptionPath
= ['uiState.choice', pathToChoice
, 'selected'].join('.');
467 //Currently selected choice/case statement on UI model
468 let selectedOptionValue
= utils
.resolvePath(container
.model
, selectedOptionPath
);
469 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
470 if(!selectedOptionValue
) {
471 //get field properties for choice on container model
472 let fieldProperties
= utils
.resolvePath(container
.model
, pathToChoice
);
473 if(fieldProperties
) {
474 //Check each case statement in model and see if it is present in container model.
475 cases
.map(function(c
){
476 if(fieldProperties
.hasOwnProperty(c
.optionValue
.split('.')[1])) {
477 utils
.assignPathValue(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'), c
.optionValue
);
480 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'));
482 property
.properties
.map(function(p
) {
483 let pname
= p
.properties
[0].name
;
484 if(container
.model
.hasOwnProperty(pname
)) {
485 utils
.assignPathValue(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'), [p
.name
, pname
].join('.'));
488 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'));
491 //If selectedOptionValue is present, take first item in string which represents the case name.
492 const valueProperty
= caseByNameMap
[selectedOptionValue
? selectedOptionValue
.split('.')[0] : undefined] || {properties
: []};
493 const isLeaf
= Property
.isLeaf(valueProperty
);
494 const hasProperties
= _isArray(valueProperty
.properties
) && valueProperty
.properties
.length
;
495 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(valueProperty
);
496 //Some magic that prevents errors for arising
497 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
]) :
498 valueProperty
.map
&& valueProperty
.map(valuePropertyFn
);
499 function valuePropertyFn(d
, i
) {
500 const childPath
= path
.concat(valueProperty
.name
, d
.name
);
501 const childValue
= utils
.resolvePath(container
.model
, childPath
.join('.'));
503 <div key
={childPath
.concat('info', i
).join(':')}>
504 {build(container
, d
, childPath
, childValue
, props
)}
509 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
512 <div key
={key
} className
="choice">
515 className
={ClassNames({'-value-not-set': !selectedOptionValue
})}
516 defaultValue
={selectedOptionValue
}
520 onMouseDown
={startEditing
}
521 onMouseOver
={startEditing
}
522 onMouseOut
={endEditing
}
523 onMouseLeave
={endEditing
}
533 function buildSimpleListItem(container
, property
, path
, value
, uniqueId
, index
) {
534 // todo need to abstract this better
535 const title
= getTitle(value
);
536 var req
= require
.context("../", true, /\.svg
/);
538 <div key
={uniqueId
} >
539 <a href
="#select-list-item" className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
540 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
543 {buildRemovePropertyAction(container
, property
, path
)}
548 function buildRemoveListItem(container
, property
, valuePath
, index
) {
549 const className
= ClassNames(property
.name
+ '-remove actions');
551 <div className
={className
}>
553 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
554 <span className
="info">{index
+ 1}</span
>
555 {buildRemovePropertyAction(container
, property
, valuePath
)}
561 function buildLeafListItem(container
, property
, valuePath
, value
, uniqueId
, index
) {
562 // look at the type to determine how to parse the value
565 {buildRemoveListItem(container
, property
, valuePath
, index
)}
566 {buildField(container
, property
, valuePath
, value
, uniqueId
)}
572 function build(container
, property
, path
, value
, props
= {}) {
575 const isLeaf
= Property
.isLeaf(property
);
576 const isArray
= Property
.isArray(property
);
577 const isObject
= Property
.isObject(property
);
578 const isLeafList
= Property
.isLeafList(property
);
579 const isRequired
= Property
.isRequired(property
);
580 const title
= changeCase
.titleCase(property
.name
);
581 const columnCount
= property
.properties
.length
|| 1;
582 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
583 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
585 // create a unique Id for use as react component keys and html element ids
586 // use uid (from ui info) instead of id property (which is not stable)
587 let uniqueId
= container
.uid
;
588 let containerRef
= container
;
589 while (containerRef
.parent
) {
590 uniqueId
= containerRef
.parent
.uid
+ ':' + uniqueId
;
591 containerRef
= containerRef
.parent
;
593 uniqueId
+= ':' + path
.join(':')
595 if (!property
.properties
&& isObject
) {
596 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
597 property
.properties
= uiState
.properties
;
600 const hasProperties
= _isArray(property
.properties
) && property
.properties
.length
;
601 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
603 // ensure value is not undefined for non-leaf property types
605 if (typeof value
!== 'object') {
606 value
= isArray
? [] : {};
609 const valueAsArray
= _isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
611 const isMetaField
= property
.name
=== 'meta';
612 const isCVNFD
= property
.name
=== 'constituent-vnfd';
613 const isSimpleListView
= Property
.isSimpleList(property
);
615 valueAsArray
.forEach((value
, index
) => {
618 const valuePath
= path
.slice();
619 // create a unique field Id for use as react component keys and html element ids
621 // keys only need to be unique on components in the same array
622 // html element ids should be unique with the document (or form)
623 let fieldId
= uniqueId
;
626 valuePath
.push(index
);
631 if (typeof value
=== 'object') {
632 value
= JSON
.stringify(value
, undefined, 12);
633 } else if (typeof value
!== 'string') {
638 if (isMissingDescriptorMeta
) {
639 field
= <span key
={fieldId
} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
640 } else if (property
.type
=== 'choice') {
641 field
= buildChoice(container
, property
, valuePath
, value
, fieldId
);
642 } else if (isSimpleListView
) {
643 field
= buildSimpleListItem(container
, property
, valuePath
, value
, fieldId
, index
);
644 } else if (isLeafList
) {
645 field
= buildLeafListItem(container
, property
, valuePath
, value
, fieldId
, index
);
646 } else if (hasProperties
) {
647 field
= buildElement(container
, property
, valuePath
, value
, fieldId
)
649 field
= buildField(container
, property
, valuePath
, value
, fieldId
);
652 function onClickLeaf(property
, path
, value
, event
) {
653 if (event
.isDefaultPrevented()) {
656 event
.preventDefault();
657 event
.stopPropagation();
658 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
659 console
.debug('property selected', path
.join('.'));
660 ComposerAppActions
.propertySelected([path
.join('.')]);
663 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
664 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
668 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
669 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
670 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, index
) : null}
677 classNames
['-is-leaf'] = isLeaf
;
678 classNames
['-is-array'] = isArray
;
679 classNames
['cols-' + columnCount
] = isColumnar
;
681 if (property
.type
=== 'choice') {
682 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
684 property
.properties
.map(function(p
) {
685 let pname
= p
.properties
[0].name
;
686 if(container
.model
.hasOwnProperty(pname
)) {
687 value
= container
.model
[pname
];
693 let displayValue
= typeof value
=== 'object' ? '' : value
;
694 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
696 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
699 <div key
={uniqueId
} className
={ClassNames(property
.type
+ '-property property', classNames
)} onFocus
={onFocus
}>
700 <h3 className
="property-label">
701 <label htmlFor
={uniqueId
}>
702 <span className
={property
.type
+ '-name name'}>{title
}</span
>
704 <span className
={property
.type
+ '-info info'}>{displayValueInfo
}</span
>
705 <span className
={property
.type
+ '-value value'}>{displayValue
}</span
>
707 {isArray
? buildAddPropertyAction(container
, property
, path
.concat(valueAsArray
.length
)) : null}
710 <span className
={property
.type
+ '-description description'}>{property
.description
}</span
>
711 <val className
="property-value">
712 {isCVNFD
? <span className
={property
.type
+ '-tip tip'}>Drag a VNFD
from the Catalog to add more
.</span
> : null}
720 const containerType
= container
.uiState
['qualified-type'] || container
.uiState
.type
;
721 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
723 function buildBasicGroup() {
724 if (basicProperties
.length
=== 0) {
728 <div className
="basic-properties-group">
731 {basicProperties
.map(property
=> {
732 const path
= [property
.name
];
733 const value
= container
.model
[property
.name
];
734 return build(container
, property
, path
, value
);
741 function buildAdvancedGroup() {
742 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
743 if (properties
.length
=== 0) {
746 const hasBasicFields
= basicProperties
.length
> 0;
747 const closeGroup
= basicProperties
.length
> 0;
749 <div className
="advanced-properties-group">
750 <h1 data
-toggle
={closeGroup
? 'true' : 'false'} className
={ClassNames({'-is-toggled': closeGroup
})} onClick
={toggle
} style
={{display
: hasBasicFields
? 'block' : 'none'}}>
751 <a className
="toggle-show-more" href
="#show-more-properties">more
&hellip
;</a
>
752 <a className
="toggle-show-less" href
="#show-more-properties">less
&hellip
;</a
>
754 <div className
="toggleable">
755 {properties
.map(property
=> {
756 const path
= [property
.name
];
757 const value
= container
.model
[property
.name
];
758 return build(container
, property
, path
, value
, {toggle
: true, width
: props
.width
});
761 <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>
766 function buildMoreLess(d, i) {
768 <span key={'bread
-crumb
-part
-' + i}>
769 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
776 if (container.parent) {
777 path.push(container.parent);
779 path.push(container);
782 <div className="EditDescriptorModelProperties -is-tree-view">
783 <h1>{path.map(buildMoreLess)}</h1>
785 {buildAdvancedGroup()}