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 const EMPTY_LEAF_PRESENT
= '--empty-leaf-set--';
53 function getDescriptorMetaBasicForType(type
) {
54 const basicPropertiesFilter
= d
=> _includes(DESCRIPTOR_MODEL_FIELDS
[type
], d
.name
);
55 return DescriptorModelMetaFactory
.getModelMetaForType(type
, basicPropertiesFilter
) || {properties
: []};
58 function getDescriptorMetaAdvancedForType(type
) {
59 const advPropertiesFilter
= d
=> !_includes(DESCRIPTOR_MODEL_FIELDS
[type
], d
.name
);
60 return DescriptorModelMetaFactory
.getModelMetaForType(type
, advPropertiesFilter
) || {properties
: []};
63 function getTitle(model
= {}) {
64 if (typeof model
['short-name'] === 'string' && model
['short-name']) {
65 return model
['short-name'];
67 if (typeof model
.name
=== 'string' && model
.name
) {
70 if (model
.uiState
&& typeof model
.uiState
.displayName
=== 'string' && model
.uiState
.displayName
) {
71 return model
.uiState
.displayName
73 if (typeof model
.id
=== 'string') {
78 export default function EditDescriptorModelProperties(props
) {
80 const container
= props
.container
;
81 const readonly
= props
.readonly
;
82 const isEditable
= !readonly
; //true
84 if (!(DescriptorModelFactory
.isContainer(container
))) {
88 function startEditing() {
89 DeletionManager
.removeEventListeners();
92 function endEditing() {
93 DeletionManager
.addEventListeners();
96 function onClickSelectItem(property
, path
, value
, event
) {
97 event
.preventDefault();
98 const root
= this.getRoot();
99 if (SelectionManager
.select(value
)) {
100 CatalogItemsActions
.catalogItemMetaDataChanged(root
.model
);
104 function onFocusPropertyFormInputElement(property
, path
, value
, event
) {
106 event
.preventDefault();
109 function removeIsFocusedClass(event
) {
110 event
.target
.removeEventListener('blur', removeIsFocusedClass
);
111 Array
.from(document
.querySelectorAll('.-is-focused')).forEach(d
=> d
.classList
.remove('-is-focused'));
114 removeIsFocusedClass(event
);
116 const propertyWrapper
= getEventPath(event
).reduce((parent
, element
) => {
120 if (!element
.classList
) {
123 if (element
.classList
.contains('property')) {
128 if (propertyWrapper
) {
129 propertyWrapper
.classList
.add('-is-focused');
130 event
.target
.addEventListener('blur', removeIsFocusedClass
);
135 function buildAddPropertyAction(container
, property
, path
) {
136 function onClickAddProperty(property
, path
, event
) {
137 event
.preventDefault();
138 //SelectionManager.resume();
139 const create
= Property
.getContainerCreateMethod(property
, this);
142 create(model
, path
, property
);
144 const name
= path
.join('.');
145 // get a unique name for the new list item based on the current list content
146 // some lists, based on the key, may not get a uniqueName generated here
147 const uniqueName
= DescriptorModelMetaFactory
.generateItemUniqueName(container
.model
[property
.name
], property
);
148 const value
= Property
.createModelInstance(property
, uniqueName
);
149 utils
.assignPathValue(this.model
, name
, value
);
151 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
157 <Button className
="inline-hint" onClick
={onClickAddProperty
.bind(container
, property
, path
)} label
="Add" src
={imgAdd
} />
161 function buildRemovePropertyAction(container
, property
, path
) {
162 function onClickRemoveProperty(property
, path
, event
) {
163 event
.preventDefault();
164 const name
= path
.join('.');
165 const removeMethod
= Property
.getContainerMethod(property
, this, 'remove');
167 removeMethod(utils
.resolvePath(this.model
, name
));
169 utils
.removePathValue(this.model
, name
);
171 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
177 <Button className
="remove-property-action inline-hint" title
="Remove" onClick
={onClickRemoveProperty
.bind(container
, property
, path
)} label
="Remove" src
={imgRemove
}/>
181 function buildField(container
, property
, path
, value
, fieldKey
) {
182 let cds
= CatalogDataStore
;
183 let catalogs
= cds
.getTransientCatalogs();
185 const pathToProperty
= path
.join('.');
186 const isEditable
= true;
187 const isGuid
= Property
.isGuid(property
);
188 const isBoolean
= Property
.isBoolean(property
);
189 const isEnumeration
= Property
.isEnumeration(property
);
190 const isLeafRef
= Property
.isLeafRef(property
);
191 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
192 const placeholder
= changeCase
.title(property
.name
);
193 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
194 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : (isNaN(value
) ? undefined : value
);
196 // process the named field value change
197 function processFieldValueChange(name
, value
) {
198 console
.debug('processed change for -- ' + name
+ ' -- with value -- ' + value
);
199 // this = the container being edited
200 if (DescriptorModelFactory
.isContainer(this)) {
201 utils
.assignPathValue(this.model
, name
, value
);
202 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
206 // change handler used for onChange event
207 const changeHandler
= (handleValueChange
, event
) => {
208 event
.preventDefault();
209 console
.debug(event
.target
.value
);
210 handleValueChange(event
.target
.value
);
212 // create an onChange event handler for a text field for the specified field path (debounced to accumulate chars)
213 const onTextChange
= changeHandler
.bind(null, _debounce(
214 processFieldValueChange
.bind(container
, pathToProperty
), 2000, {maxWait
: 5000})); // max wait for short-name
215 // create an onChange event handler for a select field for the specified field path
216 const onSelectChange
= changeHandler
.bind(null, processFieldValueChange
.bind(container
, pathToProperty
));
219 const enumeration
= Property
.getEnumeration(property
, value
);
220 const options
= enumeration
.map((d
, i
) => {
221 // note yangforge generates values for enums but the system does not use them
222 // so we categorically ignore them
223 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
224 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
225 return <option key
={':' + i
} value
={d
.name
}>{d
.name
}</option
>;
227 const isValueSet
= enumeration
.filter(d
=> d
.isSelected
).length
> 0;
228 if (!isValueSet
|| property
.cardinality
=== '0..1') {
229 const noValueDisplayText
= changeCase
.title(property
.name
);
230 options
.unshift(<option key
={'(value-not-in-enum)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
236 className
={ClassNames({'-value-not-set': !isValueSet
})}
238 title
={pathToProperty
}
239 onChange
={onSelectChange
}
242 onMouseDown
={startEditing
}
243 onMouseOver
={startEditing
}
244 disabled
={!isEditable
}>
251 let fullPathString
= container
.key
+ ':' + path
.join(':');
252 let containerRef
= container
;
253 while (containerRef
.parent
) {
254 fullPathString
= containerRef
.parent
.key
+ ':' + fullPathString
;
255 containerRef
= containerRef
.parent
;
257 const leafRefPathValues
= Property
.getLeafRef(property
, path
, value
, fullPathString
, catalogs
, container
);
259 const options
= leafRefPathValues
&& leafRefPathValues
.map((d
, i
) => {
260 return <option key
={':' + i
} value
={d
.value
}>{d
.value
}</option
>;
262 const isValueSet
= leafRefPathValues
.filter(d
=> d
.isSelected
).length
> 0;
263 if (!isValueSet
|| property
.cardinality
=== '0..1') {
264 const noValueDisplayText
= changeCase
.title(property
.name
);
265 options
.unshift(<option key
={'(value-not-in-leafref)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
271 className
={ClassNames({'-value-not-set': !isValueSet
})}
273 title
={pathToProperty
}
274 onChange
={onSelectChange
}
277 onMouseDown
={startEditing
}
278 onMouseOver
={startEditing
}
279 disabled
={!isEditable
}>
287 <option key
={'true'} value
="TRUE">TRUE
</option
>,
288 <option key
={'false'} value
="FALSE">FALSE
</option
>
291 // if (!isValueSet) {
292 const noValueDisplayText
= changeCase
.title(property
.name
);
293 options
.unshift(<option key
={'(value-not-in-leafref)'} value
="" placeholder
={placeholder
}></option
>);
296 if(typeof(val
) == 'number') {
297 val
= value
? "TRUE" : "FALSE"
299 const isValueSet
= (val
!= '' && val
)
304 className
={ClassNames({'-value-not-set': !isValueSet
})}
305 defaultValue
={val
&& val
.toUpperCase()}
306 title
={pathToProperty
}
307 onChange
={onSelectChange
}
310 onMouseDown
={startEditing
}
311 onMouseOver
={startEditing
}
312 disabled
={!isEditable
}>
318 if (Property
.isLeafEmpty(property
)) {
319 // A null value indicates the leaf exists (as opposed to undefined).
320 // We stick in a string when the user actually sets it to simplify things
321 // but the correct thing happens when we serialize to user data
322 let isEmptyLeafPresent
= (value
=== EMPTY_LEAF_PRESENT
|| value
=== null);
323 let present
= isEmptyLeafPresent
? EMPTY_LEAF_PRESENT
: "";
325 <option key
={'true'} value
={EMPTY_LEAF_PRESENT
}>Enabled
</option
>,
326 <option key
={'false'} value
="">Not Enabled
</option
>
333 className
={ClassNames({'-value-not-set': !isEmptyLeafPresent
})}
334 defaultValue
={present
}
335 title
={pathToProperty
}
336 onChange
={onSelectChange
}
339 onMouseDown
={startEditing
}
340 onMouseOver
={startEditing
}
341 disabled
={!isEditable
}>
347 if (property
['preserve-line-breaks']) {
354 placeholder
={placeholder
}
355 onChange
={onTextChange
}
358 onMouseDown
={startEditing
}
359 onMouseOver
={startEditing
}
360 onMouseOut
={endEditing
}
361 onMouseLeave
={endEditing
}
362 disabled
={!isEditable
} />
371 defaultValue
={fieldValue
}
372 className
={className
}
373 placeholder
={placeholder
}
374 onChange
={onTextChange
}
377 onMouseDown
={startEditing
}
378 onMouseOver
={startEditing
}
379 onMouseOut
={endEditing
}
380 onMouseLeave
={endEditing
}
381 disabled
={!isEditable
}
387 function buildElement(container
, property
, valuePath
, value
) {
388 return property
.properties
.map((property
, index
) => {
390 const childPath
= valuePath
.slice();
391 if (typeof value
=== 'object') {
392 childValue
= value
[property
.name
];
394 if(property
.type
!= 'choice'){
395 childPath
.push(property
.name
);
397 return build(container
, property
, childPath
, childValue
);
402 function buildChoice(container
, property
, path
, value
, key
) {
404 function processChoiceChange(name
, value
) {
405 if (DescriptorModelFactory
.isContainer(this)) {
408 Transient State is stored for convenience in the uiState field.
409 The choice yang type uses case elements to describe the "options".
410 A choice can only ever have one option selected which allows
411 the system to determine which type is selected by the name of
412 the element contained within the field.
415 const stateExample = {
430 const statePath
= ['uiState.choice'].concat(name
);
431 const stateObject
= utils
.resolvePath(this.model
, statePath
.join('.')) || {};
432 const selected
= stateObject
.selected
? stateObject
.selected
.split('.')[1] : undefined;
433 // write state back to the model so the new state objects are captured
434 utils
.assignPathValue(this.model
, statePath
.join('.'), stateObject
);
436 // write the current choice value into the state
437 let choiceObject
= utils
.resolvePath(this.model
, [name
, selected
].join('.'));
438 let isTopCase
= false;
441 choiceObject
= utils
.resolvePath(this.model
, [selected
].join('.'));
443 utils
.assignPathValue(stateObject
, [selected
].join('.'), _cloneDeep(choiceObject
));
446 if(this.model
.uiState
.choice
.hasOwnProperty(name
)) {
447 delete this.model
[selected
];
448 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
450 // remove the current choice value from the model
451 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
455 // get any state for the new selected choice
456 const newChoiceObject
= utils
.resolvePath(stateObject
, [value
].join('.')) || {};
458 // assign new choice value to the model
460 utils
.assignPathValue(this.model
, [name
, value
].join('.'), newChoiceObject
);
462 utils
.assignPathValue(this.model
, [value
].join('.'), newChoiceObject
)
465 // update the selected name
466 utils
.assignPathValue(this.model
, statePath
.concat('selected').join('.'), value
);
468 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
472 const pathToChoice
= path
.join('.');
473 const caseByNameMap
= {};
475 const choiceChangeHandler
= processChoiceChange
.bind(container
, pathToChoice
);
476 const onChange
= ((handleChoiceChange
, event
) => {
477 event
.preventDefault();
478 handleChoiceChange(event
.target
.value
);
479 }).bind(null, choiceChangeHandler
);
482 const cases
= property
.properties
.map(d
=> {
483 if (d
.type
=== 'case') {
484 //Previous it was assumed that a case choice would have only one property. Now we pass on either the only item or the
485 caseByNameMap
[d
.name
] = d
.properties
&& (d
.properties
.length
== 1 ? d
.properties
[0] : d
.properties
);
488 optionTitle
: d
.description
,
489 //represents case name and case element name
490 optionValue
: [d
.name
, d
.properties
[0].name
].join('.')
493 caseByNameMap
[d
.name
] = d
;
494 return {optionName
: d
.name
};
497 const options
= [{optionName
: '', optionValue
: false}].concat(cases
).map((d
, i
) => {
499 <option key
={i
} value
={d
.optionValue
} title
={d
.optionTitle
}>
501 {i
? null : changeCase
.title(property
.name
)}
506 let selectedOptionPath
= ['uiState.choice', pathToChoice
, 'selected'].join('.');
507 //Currently selected choice/case statement on UI model
508 let selectedOptionValue
= utils
.resolvePath(container
.model
, selectedOptionPath
);
509 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
510 if(!selectedOptionValue
) {
511 //get field properties for choice on container model
512 let fieldProperties
= utils
.resolvePath(container
.model
, pathToChoice
);
513 if(fieldProperties
) {
514 //Check each case statement in model and see if it is present in container model.
515 cases
.map(function(c
){
516 if(fieldProperties
.hasOwnProperty(c
.optionValue
.split('.')[1])) {
517 utils
.assignPathValue(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'), c
.optionValue
);
520 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'));
522 property
.properties
.map(function(p
) {
523 let pname
= p
.properties
[0].name
;
524 if(container
.model
.hasOwnProperty(pname
)) {
525 utils
.assignPathValue(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'), [p
.name
, pname
].join('.'));
528 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'));
531 //If selectedOptionValue is present, take first item in string which represents the case name.
532 const valueProperty
= caseByNameMap
[selectedOptionValue
? selectedOptionValue
.split('.')[0] : undefined] || {properties
: []};
533 const isLeaf
= Property
.isLeaf(valueProperty
);
534 const hasProperties
= _isArray(valueProperty
.properties
) && valueProperty
.properties
.length
;
535 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(valueProperty
);
536 //Some magic that prevents errors for arising
537 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
]) :
538 valueProperty
.map
&& valueProperty
.map(valuePropertyFn
);
539 function valuePropertyFn(d
, i
) {
540 const childPath
= path
.concat(valueProperty
.name
, d
.name
);
541 const childValue
= utils
.resolvePath(container
.model
, childPath
.join('.'));
543 <div key
={childPath
.concat('info', i
).join(':')}>
544 {build(container
, d
, childPath
, childValue
, props
)}
549 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
552 <div key
={key
} className
="choice">
555 className
={ClassNames({'-value-not-set': !selectedOptionValue
})}
556 defaultValue
={selectedOptionValue
}
560 onMouseDown
={startEditing
}
561 onMouseOver
={startEditing
}
562 onMouseOut
={endEditing
}
563 onMouseLeave
={endEditing
}
564 disabled
={!isEditable
}
574 function buildSimpleListItem(container
, property
, path
, value
, uniqueId
, index
) {
575 // todo need to abstract this better
576 const title
= getTitle(value
);
577 var req
= require
.context("../", true, /\.svg
/);
579 <div key
={uniqueId
} >
580 <a href
="#select-list-item" className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
581 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
584 {buildRemovePropertyAction(container
, property
, path
)}
589 function buildRemoveListItem(container
, property
, valuePath
, index
) {
590 const className
= ClassNames(property
.name
+ '-remove actions');
592 <div className
={className
}>
594 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
595 <span className
="info">{index
+ 1}</span
>
596 {buildRemovePropertyAction(container
, property
, valuePath
)}
602 function buildLeafListItem(container
, property
, valuePath
, value
, uniqueId
, index
) {
603 // look at the type to determine how to parse the value
606 {buildRemoveListItem(container
, property
, valuePath
, index
)}
607 {buildField(container
, property
, valuePath
, value
, uniqueId
)}
613 function build(container
, property
, path
, value
, props
= {}) {
616 const isLeaf
= Property
.isLeaf(property
);
617 const isArray
= Property
.isArray(property
);
618 const isObject
= Property
.isObject(property
);
619 const isLeafList
= Property
.isLeafList(property
);
620 const isRequired
= Property
.isRequired(property
);
621 const title
= changeCase
.titleCase(property
.name
);
622 const columnCount
= property
.properties
.length
|| 1;
623 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
624 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
626 // create a unique Id for use as react component keys and html element ids
627 // use uid (from ui info) instead of id property (which is not stable)
628 let uniqueId
= container
.uid
;
629 let containerRef
= container
;
630 while (containerRef
.parent
) {
631 uniqueId
= containerRef
.parent
.uid
+ ':' + uniqueId
;
632 containerRef
= containerRef
.parent
;
634 uniqueId
+= ':' + path
.join(':')
636 if (!property
.properties
&& isObject
) {
637 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
638 property
.properties
= uiState
.properties
;
641 const hasProperties
= _isArray(property
.properties
) && property
.properties
.length
;
642 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
644 // ensure value is not undefined for non-leaf property types
646 if (typeof value
!== 'object') {
647 value
= isArray
? [] : {};
650 const valueAsArray
= _isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
652 const isMetaField
= property
.name
=== 'meta';
653 const isCVNFD
= property
.name
=== 'constituent-vnfd';
654 const isSimpleListView
= Property
.isSimpleList(property
);
656 valueAsArray
.forEach((value
, index
) => {
659 const valuePath
= path
.slice();
660 // create a unique field Id for use as react component keys and html element ids
662 // keys only need to be unique on components in the same array
663 // html element ids should be unique with the document (or form)
664 let fieldId
= uniqueId
;
667 valuePath
.push(index
);
672 if (typeof value
=== 'object') {
673 value
= JSON
.stringify(value
, undefined, 12);
674 } else if (typeof value
!== 'string') {
679 if (isMissingDescriptorMeta
) {
680 field
= <span key
={fieldId
} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
681 } else if (property
.type
=== 'choice') {
682 field
= buildChoice(container
, property
, valuePath
, value
, fieldId
);
683 } else if (isSimpleListView
) {
684 field
= buildSimpleListItem(container
, property
, valuePath
, value
, fieldId
, index
);
685 } else if (isLeafList
) {
686 field
= buildLeafListItem(container
, property
, valuePath
, value
, fieldId
, index
);
687 } else if (hasProperties
) {
688 field
= buildElement(container
, property
, valuePath
, value
, fieldId
)
690 field
= buildField(container
, property
, valuePath
, value
, fieldId
);
693 function onClickLeaf(property
, path
, value
, event
) {
694 if (event
.isDefaultPrevented()) {
697 event
.preventDefault();
698 event
.stopPropagation();
699 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
700 console
.debug('property selected', path
.join('.'));
701 ComposerAppActions
.propertySelected([path
.join('.')]);
704 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
705 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
709 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
710 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
711 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, index
) : null}
718 classNames
['-is-leaf'] = isLeaf
;
719 classNames
['-is-array'] = isArray
;
720 classNames
['cols-' + columnCount
] = isColumnar
;
722 if (property
.type
=== 'choice') {
723 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
725 property
.properties
.map(function(p
) {
726 let pname
= p
.properties
[0].name
;
727 if(container
.model
.hasOwnProperty(pname
)) {
728 value
= container
.model
[pname
];
734 let displayValue
= typeof value
=== 'object' ? '' : value
;
735 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
737 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
740 <div key
={uniqueId
} className
={ClassNames(property
.type
+ '-property property', classNames
)} onFocus
={onFocus
}>
741 <h3 className
="property-label">
742 <label htmlFor
={uniqueId
}>
743 <span className
={property
.type
+ '-name name'}>{title
}</span
>
745 <span className
={property
.type
+ '-info info'}>{displayValueInfo
}</span
>
746 <span className
={property
.type
+ '-value value'}>{displayValue
}</span
>
748 {isArray
? buildAddPropertyAction(container
, property
, path
.concat(valueAsArray
.length
)) : null}
751 <span className
={property
.type
+ '-description description'}>{property
.description
}</span
>
752 <val className
="property-value">
753 {isCVNFD
? <span className
={property
.type
+ '-tip tip'}>Drag a VNFD
from the Catalog to add more
.</span
> : null}
761 const containerType
= container
.uiState
['qualified-type'] || container
.uiState
.type
;
762 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
764 function buildBasicGroup() {
765 if (basicProperties
.length
=== 0) {
769 <div className
="basic-properties-group">
772 {basicProperties
.map(property
=> {
773 const path
= [property
.name
];
774 const value
= container
.model
[property
.name
];
775 return build(container
, property
, path
, value
);
782 function buildAdvancedGroup() {
783 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
784 if (properties
.length
=== 0) {
787 const hasBasicFields
= basicProperties
.length
> 0;
788 const closeGroup
= basicProperties
.length
> 0;
790 <div className
="advanced-properties-group">
791 <h1 data
-toggle
={closeGroup
? 'true' : 'false'} className
={ClassNames({'-is-toggled': closeGroup
})} onClick
={toggle
} style
={{display
: hasBasicFields
? 'block' : 'none'}}>
792 <a className
="toggle-show-more" href
="#show-more-properties">more
&hellip
;</a
>
793 <a className
="toggle-show-less" href
="#show-more-properties">less
&hellip
;</a
>
795 <div className
="toggleable">
796 {properties
.map(property
=> {
797 const path
= [property
.name
];
798 const value
= container
.model
[property
.name
];
799 return build(container
, property
, path
, value
, {toggle
: true, width
: props
.width
});
802 <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>
807 function buildMoreLess(d, i) {
809 <span key={'bread
-crumb
-part
-' + i}>
810 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
817 if (container.parent) {
818 path.push(container.parent);
820 path.push(container);
823 <div className="EditDescriptorModelProperties -is-tree-view">
824 <h1>{path.map(buildMoreLess)}</h1>
826 {buildAdvancedGroup()}