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 _uniqueId
from 'lodash/uniqueId';
29 import _set
from 'lodash/set';
30 import _get
from 'lodash/get';
31 import _has
from 'lodash/has';
32 import utils
from '../libraries/utils'
33 import React
from 'react'
34 import ClassNames
from 'classnames'
35 import changeCase
from 'change-case'
36 import toggle
from '../libraries/ToggleElementHandler'
37 import Button
from './Button'
38 import Property
from '../libraries/model/DescriptorModelMetaProperty'
39 import ComposerAppActions
from '../actions/ComposerAppActions'
40 import CatalogItemsActions
from '../actions/CatalogItemsActions'
41 import DESCRIPTOR_MODEL_FIELDS
from '../libraries/model/DescriptorModelFields'
42 import DescriptorModelFactory
from '../libraries/model/DescriptorModelFactory'
43 import DescriptorModelMetaFactory
from '../libraries/model/DescriptorModelMetaFactory'
44 import SelectionManager
from '../libraries/SelectionManager'
45 import DeletionManager
from '../libraries/DeletionManager'
46 import DescriptorModelIconFactory
from '../libraries/model/IconFactory'
47 import getEventPath
from '../libraries/getEventPath'
48 import CatalogDataStore
from '../stores/CatalogDataStore'
50 import imgAdd
from '../../../node_modules/open-iconic/svg/plus.svg'
51 import imgRemove
from '../../../node_modules/open-iconic/svg/trash.svg'
53 import '../styles/EditDescriptorModelProperties.scss'
55 const EMPTY_LEAF_PRESENT
= '--empty-leaf-set--';
57 function resolveReactKey(value
) {
58 const keyPath
= ['uiState', 'fieldKey'];
59 if (!_has(value
, keyPath
)) {
60 _set(value
, keyPath
, _uniqueId());
62 return _get(value
, keyPath
);
65 function getDescriptorMetaBasicForType(type
) {
66 const basicPropertiesFilter
= d
=> _includes(DESCRIPTOR_MODEL_FIELDS
[type
], d
.name
);
67 return DescriptorModelMetaFactory
.getModelMetaForType(type
, basicPropertiesFilter
) || {properties
: []};
70 function getDescriptorMetaAdvancedForType(type
) {
71 const advPropertiesFilter
= d
=> !_includes(DESCRIPTOR_MODEL_FIELDS
[type
], d
.name
);
72 return DescriptorModelMetaFactory
.getModelMetaForType(type
, advPropertiesFilter
) || {properties
: []};
75 function getTitle(model
= {}) {
76 if (typeof model
['short-name'] === 'string' && model
['short-name']) {
77 return model
['short-name'];
79 if (typeof model
.name
=== 'string' && model
.name
) {
82 if (model
.uiState
&& typeof model
.uiState
.displayName
=== 'string' && model
.uiState
.displayName
) {
83 return model
.uiState
.displayName
85 if (typeof model
.id
=== 'string') {
90 export default function EditDescriptorModelProperties(props
) {
92 const container
= props
.container
;
94 if (!(DescriptorModelFactory
.isContainer(container
))) {
98 function startEditing() {
99 DeletionManager
.removeEventListeners();
102 function endEditing() {
103 DeletionManager
.addEventListeners();
106 function onClickSelectItem(property
, path
, value
, event
) {
107 event
.preventDefault();
108 const root
= this.getRoot();
109 if (SelectionManager
.select(value
)) {
110 CatalogItemsActions
.catalogItemMetaDataChanged(root
.model
);
114 function onFocusPropertyFormInputElement(property
, path
, value
, event
) {
116 event
.preventDefault();
119 function removeIsFocusedClass(event
) {
120 event
.target
.removeEventListener('blur', removeIsFocusedClass
);
121 Array
.from(document
.querySelectorAll('.-is-focused')).forEach(d
=> d
.classList
.remove('-is-focused'));
124 removeIsFocusedClass(event
);
126 const propertyWrapper
= getEventPath(event
).reduce((parent
, element
) => {
130 if (!element
.classList
) {
133 if (element
.classList
.contains('property')) {
138 if (propertyWrapper
) {
139 propertyWrapper
.classList
.add('-is-focused');
140 event
.target
.addEventListener('blur', removeIsFocusedClass
);
145 function buildAddPropertyAction(container
, property
, path
) {
146 function onClickAddProperty(property
, path
, event
) {
147 event
.preventDefault();
148 //SelectionManager.resume();
149 const create
= Property
.getContainerCreateMethod(property
, this);
152 create(model
, path
, property
);
154 const name
= path
.join('.');
155 // get a unique name for the new list item based on the current list content
156 // some lists, based on the key, may not get a uniqueName generated here
157 const uniqueName
= DescriptorModelMetaFactory
.generateItemUniqueName(container
.model
[property
.name
], property
);
158 const value
= Property
.createModelInstance(property
, uniqueName
);
159 utils
.assignPathValue(this.model
, name
, value
);
161 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
164 <Button className
="inline-hint" onClick
={onClickAddProperty
.bind(container
, property
, path
)} label
="Add" src
={imgAdd
} />
168 function buildRemovePropertyAction(container
, property
, path
) {
169 function onClickRemoveProperty(property
, path
, event
) {
170 event
.preventDefault();
171 const name
= path
.join('.');
172 const removeMethod
= Property
.getContainerMethod(property
, this, 'remove');
174 removeMethod(utils
.resolvePath(this.model
, name
));
176 utils
.removePathValue(this.model
, name
);
178 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
181 <Button className
="remove-property-action inline-hint" title
="Remove" onClick
={onClickRemoveProperty
.bind(container
, property
, path
)} label
="Remove" src
={imgRemove
}/>
185 function buildField(container
, property
, path
, value
, fieldKey
) {
186 let cds
= CatalogDataStore
;
187 let catalogs
= cds
.getTransientCatalogs();
189 const pathToProperty
= path
.join('.');
190 const isEditable
= true;
191 const isGuid
= Property
.isGuid(property
);
192 const isBoolean
= Property
.isBoolean(property
);
193 const isEnumeration
= Property
.isEnumeration(property
);
194 const isLeafRef
= Property
.isLeafRef(property
);
195 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
196 const placeholder
= changeCase
.title(property
.name
);
197 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
198 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : (isNaN(value
) ? undefined : value
);
200 // process the named field value change
201 function processFieldValueChange(name
, value
) {
202 console
.debug('processed change for -- ' + name
+ ' -- with value -- ' + value
);
203 // this = the container being edited
204 if (DescriptorModelFactory
.isContainer(this)) {
205 utils
.assignPathValue(this.model
, name
, value
);
206 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
210 // change handler used for onChange event
211 const changeHandler
= (handleValueChange
, event
) => {
212 event
.preventDefault();
213 console
.debug(event
.target
.value
);
214 handleValueChange(event
.target
.value
);
216 // create an onChange event handler for a text field for the specified field path (debounced to accumulate chars)
217 const onTextChange
= changeHandler
.bind(null, _debounce(
218 processFieldValueChange
.bind(container
, pathToProperty
), 2000, {maxWait
: 5000})); // max wait for short-name
219 // create an onChange event handler for a select field for the specified field path
220 const onSelectChange
= changeHandler
.bind(null, processFieldValueChange
.bind(container
, pathToProperty
));
223 const enumeration
= Property
.getEnumeration(property
, value
);
224 const options
= enumeration
.map((d
, i
) => {
225 // note yangforge generates values for enums but the system does not use them
226 // so we categorically ignore them
227 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
228 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
229 return <option key
={':' + i
} value
={d
.name
}>{d
.name
}</option
>;
231 const isValueSet
= enumeration
.filter(d
=> d
.isSelected
).length
> 0;
232 if (!isValueSet
|| property
.cardinality
=== '0..1') {
233 const noValueDisplayText
= changeCase
.title(property
.name
);
234 options
.unshift(<option key
={'(value-not-in-enum)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
240 className
={ClassNames({'-value-not-set': !isValueSet
})}
242 title
={pathToProperty
}
243 onChange
={onSelectChange
}
246 onMouseDown
={startEditing
}
247 onMouseOver
={startEditing
}
248 readOnly
={!isEditable
}>
255 let fullPathString
= container
.key
+ ':' + path
.join(':');
256 let containerRef
= container
;
257 while (containerRef
.parent
) {
258 fullPathString
= containerRef
.parent
.key
+ ':' + fullPathString
;
259 containerRef
= containerRef
.parent
;
261 const leafRefPathValues
= Property
.getLeafRef(property
, path
, value
, fullPathString
, catalogs
, container
);
263 const options
= leafRefPathValues
&& leafRefPathValues
.map((d
, i
) => {
264 return <option key
={':' + i
} value
={d
.value
}>{d
.value
}</option
>;
266 const isValueSet
= leafRefPathValues
.filter(d
=> d
.isSelected
).length
> 0;
267 if (!isValueSet
|| property
.cardinality
=== '0..1') {
268 const noValueDisplayText
= changeCase
.title(property
.name
);
269 options
.unshift(<option key
={'(value-not-in-leafref)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
275 className
={ClassNames({'-value-not-set': !isValueSet
})}
277 title
={pathToProperty
}
278 onChange
={onSelectChange
}
281 onMouseDown
={startEditing
}
282 onMouseOver
={startEditing
}
283 readOnly
={!isEditable
}>
291 <option key
={'true'} value
="TRUE">TRUE
</option
>,
292 <option key
={'false'} value
="FALSE">FALSE
</option
>
295 // if (!isValueSet) {
296 const noValueDisplayText
= changeCase
.title(property
.name
);
297 options
.unshift(<option key
={'(value-not-in-leafref)'} value
="" placeholder
={placeholder
}></option
>);
300 if(typeof(val
) == 'number') {
301 val
= value
? "TRUE" : "FALSE"
303 const isValueSet
= (val
!= '' && val
)
308 className
={ClassNames({'-value-not-set': !isValueSet
})}
309 defaultValue
={val
&& val
.toUpperCase()}
310 title
={pathToProperty
}
311 onChange
={onSelectChange
}
314 onMouseDown
={startEditing
}
315 onMouseOver
={startEditing
}
316 readOnly
={!isEditable
}>
322 if (Property
.isLeafEmpty(property
)) {
323 // A null value indicates the leaf exists (as opposed to undefined).
324 // We stick in a string when the user actually sets it to simplify things
325 // but the correct thing happens when we serialize to user data
326 let isEmptyLeafPresent
= (value
=== EMPTY_LEAF_PRESENT
|| value
=== null);
327 let present
= isEmptyLeafPresent
? EMPTY_LEAF_PRESENT
: "";
329 <option key
={'true'} value
={EMPTY_LEAF_PRESENT
}>Enabled
</option
>,
330 <option key
={'false'} value
="">Not Enabled
</option
>
337 className
={ClassNames({'-value-not-set': !isEmptyLeafPresent
})}
338 defaultValue
={present
}
339 title
={pathToProperty
}
340 onChange
={onSelectChange
}
343 onMouseDown
={startEditing
}
344 onMouseOver
={startEditing
}
345 readOnly
={!isEditable
}>
351 if (property
['preserve-line-breaks']) {
358 placeholder
={placeholder
}
359 onChange
={onTextChange
}
362 onMouseDown
={startEditing
}
363 onMouseOver
={startEditing
}
364 onMouseOut
={endEditing
}
365 onMouseLeave
={endEditing
}
366 readOnly
={!isEditable
} />
375 defaultValue
={fieldValue
}
376 className
={className
}
377 placeholder
={placeholder
}
378 onChange
={onTextChange
}
381 onMouseDown
={startEditing
}
382 onMouseOver
={startEditing
}
383 onMouseOut
={endEditing
}
384 onMouseLeave
={endEditing
}
385 readOnly
={!isEditable
}
391 function buildElement(container
, property
, valuePath
, value
) {
392 return property
.properties
.map((property
, index
) => {
394 const childPath
= valuePath
.slice();
395 if (typeof value
=== 'object') {
396 childValue
= value
[property
.name
];
398 if(property
.type
!= 'choice'){
399 childPath
.push(property
.name
);
401 return build(container
, property
, childPath
, childValue
);
406 function buildChoice(container
, property
, path
, value
, key
) {
408 function processChoiceChange(name
, value
) {
409 if (DescriptorModelFactory
.isContainer(this)) {
412 Transient State is stored for convenience in the uiState field.
413 The choice yang type uses case elements to describe the "options".
414 A choice can only ever have one option selected which allows
415 the system to determine which type is selected by the name of
416 the element contained within the field.
419 const stateExample = {
434 const statePath
= ['uiState.choice'].concat(name
);
435 const stateObject
= utils
.resolvePath(this.model
, statePath
.join('.')) || {};
436 const selected
= stateObject
.selected
? stateObject
.selected
.split('.')[1] : undefined;
437 // write state back to the model so the new state objects are captured
438 utils
.assignPathValue(this.model
, statePath
.join('.'), stateObject
);
440 // write the current choice value into the state
441 let choiceObject
= utils
.resolvePath(this.model
, [name
, selected
].join('.'));
442 let isTopCase
= false;
445 choiceObject
= utils
.resolvePath(this.model
, [selected
].join('.'));
447 utils
.assignPathValue(stateObject
, [selected
].join('.'), _cloneDeep(choiceObject
));
450 if(this.model
.uiState
.choice
.hasOwnProperty(name
)) {
451 delete this.model
[selected
];
452 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
454 // remove the current choice value from the model
455 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
459 // get any state for the new selected choice
460 const newChoiceObject
= utils
.resolvePath(stateObject
, [value
].join('.')) || {};
462 // assign new choice value to the model
464 utils
.assignPathValue(this.model
, [name
, value
].join('.'), newChoiceObject
);
466 utils
.assignPathValue(this.model
, [value
].join('.'), newChoiceObject
)
469 // update the selected name
470 utils
.assignPathValue(this.model
, statePath
.concat('selected').join('.'), value
);
472 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
476 const pathToChoice
= path
.join('.');
477 const caseByNameMap
= {};
479 const choiceChangeHandler
= processChoiceChange
.bind(container
, pathToChoice
);
480 const onChange
= ((handleChoiceChange
, event
) => {
481 event
.preventDefault();
482 handleChoiceChange(event
.target
.value
);
483 }).bind(null, choiceChangeHandler
);
486 const cases
= property
.properties
.map(d
=> {
487 if (d
.type
=== 'case') {
488 //Previous it was assumed that a case choice would have only one property. Now we pass on either the only item or the
489 caseByNameMap
[d
.name
] = d
.properties
&& (d
.properties
.length
== 1 ? d
.properties
[0] : d
.properties
);
492 optionTitle
: d
.description
,
493 //represents case name and case element name
494 optionValue
: [d
.name
, d
.properties
[0].name
].join('.')
497 caseByNameMap
[d
.name
] = d
;
498 return {optionName
: d
.name
};
501 const options
= [{optionName
: '', optionValue
: false}].concat(cases
).map((d
, i
) => {
503 <option key
={i
} value
={d
.optionValue
} title
={d
.optionTitle
}>
505 {i
? null : changeCase
.title(property
.name
)}
510 let selectedOptionPath
= ['uiState.choice', pathToChoice
, 'selected'].join('.');
511 //Currently selected choice/case statement on UI model
512 let selectedOptionValue
= utils
.resolvePath(container
.model
, selectedOptionPath
);
513 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
514 if(!selectedOptionValue
) {
515 //get field properties for choice on container model
516 let fieldProperties
= utils
.resolvePath(container
.model
, pathToChoice
);
517 if(fieldProperties
) {
518 //Check each case statement in model and see if it is present in container model.
519 cases
.map(function(c
){
520 if(fieldProperties
.hasOwnProperty(c
.optionValue
.split('.')[1])) {
521 utils
.assignPathValue(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'), c
.optionValue
);
524 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'));
526 property
.properties
.map(function(p
) {
527 let pname
= p
.properties
[0].name
;
528 if(container
.model
.hasOwnProperty(pname
)) {
529 utils
.assignPathValue(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'), [p
.name
, pname
].join('.'));
532 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'));
535 //If selectedOptionValue is present, take first item in string which represents the case name.
536 const valueProperty
= caseByNameMap
[selectedOptionValue
? selectedOptionValue
.split('.')[0] : undefined] || {properties
: []};
537 const isLeaf
= Property
.isLeaf(valueProperty
);
538 const hasProperties
= _isArray(valueProperty
.properties
) && valueProperty
.properties
.length
;
539 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(valueProperty
);
540 //Some magic that prevents errors for arising
541 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
]) :
542 valueProperty
.map
&& valueProperty
.map(valuePropertyFn
);
543 function valuePropertyFn(d
, i
) {
544 const childPath
= path
.concat(valueProperty
.name
, d
.name
);
545 const childValue
= utils
.resolvePath(container
.model
, childPath
.join('.'));
547 <div key
={childPath
.concat('info', i
).join(':')}>
548 {build(container
, d
, childPath
, childValue
, props
)}
553 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
556 <div key
={key
} className
="choice">
559 className
={ClassNames({'-value-not-set': !selectedOptionValue
})}
560 defaultValue
={selectedOptionValue
}
564 onMouseDown
={startEditing
}
565 onMouseOver
={startEditing
}
566 onMouseOut
={endEditing
}
567 onMouseLeave
={endEditing
}
577 function buildSimpleListItem(container
, property
, path
, value
, uniqueId
, index
) {
578 // todo need to abstract this better
579 const title
= getTitle(value
);
580 var req
= require
.context("../", true, /\.svg
/);
582 <div key
={uniqueId
} >
583 <a href
="#select-list-item" className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
584 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
587 {buildRemovePropertyAction(container
, property
, path
)}
592 function buildRemoveListItem(container
, property
, valuePath
, index
) {
593 const className
= ClassNames(property
.name
+ '-remove actions');
595 <div className
={className
}>
597 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
598 <span className
="info">{index
+ 1}</span
>
599 {buildRemovePropertyAction(container
, property
, valuePath
)}
605 function buildLeafListItem(container
, property
, valuePath
, value
, uniqueId
, index
) {
606 // look at the type to determine how to parse the value
609 {buildRemoveListItem(container
, property
, valuePath
, index
)}
610 {buildField(container
, property
, valuePath
, value
, uniqueId
)}
616 function build(container
, property
, path
, value
, props
= {}) {
619 const isLeaf
= Property
.isLeaf(property
);
620 const isArray
= Property
.isArray(property
);
621 const isObject
= Property
.isObject(property
);
622 const isLeafList
= Property
.isLeafList(property
);
623 const isRequired
= Property
.isRequired(property
);
624 const title
= changeCase
.titleCase(property
.name
);
625 const columnCount
= property
.properties
.length
|| 1;
626 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
627 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
629 // create a unique Id for use as react component keys and html element ids
630 // use uid (from ui info) instead of id property (which is not stable)
631 let uniqueId
= container
.uid
;
632 let containerRef
= container
;
633 while (containerRef
.parent
) {
634 uniqueId
= containerRef
.parent
.uid
+ ':' + uniqueId
;
635 containerRef
= containerRef
.parent
;
637 uniqueId
+= ':' + path
.join(':')
639 if (!property
.properties
&& isObject
) {
640 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
641 property
.properties
= uiState
.properties
;
644 const hasProperties
= _isArray(property
.properties
) && property
.properties
.length
;
645 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
647 // ensure value is not undefined for non-leaf property types
649 if (typeof value
!== 'object') {
650 value
= isArray
? [] : {};
653 const valueAsArray
= _isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
655 const isMetaField
= property
.name
=== 'meta';
656 const isCVNFD
= property
.name
=== 'constituent-vnfd';
657 const isSimpleListView
= Property
.isSimpleList(property
);
659 valueAsArray
.forEach((value
, index
) => {
662 const valuePath
= path
.slice();
663 // create a unique field Id for use as react component keys and html element ids
665 // keys only need to be unique on components in the same array
666 // html element ids should be unique with the document (or form)
667 let fieldId
= uniqueId
;
670 valuePath
.push(index
);
671 fieldId
= resolveReactKey(value
);
675 if (typeof value
=== 'object') {
676 value
= JSON
.stringify(value
, undefined, 12);
677 } else if (typeof value
!== 'string') {
682 if (isMissingDescriptorMeta
) {
683 field
= <span key
={fieldId
} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
684 } else if (property
.type
=== 'choice') {
685 field
= buildChoice(container
, property
, valuePath
, value
, fieldId
);
686 } else if (isSimpleListView
) {
687 field
= buildSimpleListItem(container
, property
, valuePath
, value
, fieldId
, index
);
688 } else if (isLeafList
) {
689 field
= buildLeafListItem(container
, property
, valuePath
, value
, fieldId
, index
);
690 } else if (hasProperties
) {
691 field
= buildElement(container
, property
, valuePath
, value
, fieldId
)
693 field
= buildField(container
, property
, valuePath
, value
, fieldId
);
696 function onClickLeaf(property
, path
, value
, event
) {
697 if (event
.isDefaultPrevented()) {
700 event
.preventDefault();
701 event
.stopPropagation();
702 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
703 console
.debug('property selected', path
.join('.'));
704 ComposerAppActions
.propertySelected([path
.join('.')]);
707 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
708 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
712 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
713 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
714 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, index
) : null}
721 classNames
['-is-leaf'] = isLeaf
;
722 classNames
['-is-array'] = isArray
;
723 classNames
['cols-' + columnCount
] = isColumnar
;
725 if (property
.type
=== 'choice') {
726 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
728 property
.properties
.map(function(p
) {
729 let pname
= p
.properties
[0].name
;
730 if(container
.model
.hasOwnProperty(pname
)) {
731 value
= container
.model
[pname
];
737 let displayValue
= typeof value
=== 'object' ? '' : value
;
738 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
740 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
743 <div key
={uniqueId
} className
={ClassNames(property
.type
+ '-property property', classNames
)} onFocus
={onFocus
}>
744 <h3 className
="property-label">
745 <label htmlFor
={uniqueId
}>
746 <span className
={property
.type
+ '-name name'}>{title
}</span
>
748 <span className
={property
.type
+ '-info info'}>{displayValueInfo
}</span
>
749 <span className
={property
.type
+ '-value value'}>{displayValue
}</span
>
751 {isArray
? buildAddPropertyAction(container
, property
, path
.concat(valueAsArray
.length
)) : null}
754 <span className
={property
.type
+ '-description description'}>{property
.description
}</span
>
755 <val className
="property-value">
756 {isCVNFD
? <span className
={property
.type
+ '-tip tip'}>Drag a VNFD
from the Catalog to add more
.</span
> : null}
764 const containerType
= container
.uiState
['qualified-type'] || container
.uiState
.type
;
765 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
767 function buildBasicGroup() {
768 if (basicProperties
.length
=== 0) {
772 <div className
="basic-properties-group">
775 {basicProperties
.map(property
=> {
776 const path
= [property
.name
];
777 const value
= container
.model
[property
.name
];
778 return build(container
, property
, path
, value
);
785 function buildAdvancedGroup() {
786 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
787 if (properties
.length
=== 0) {
790 const hasBasicFields
= basicProperties
.length
> 0;
791 const closeGroup
= basicProperties
.length
> 0;
793 <div className
="advanced-properties-group">
794 <h1 data
-toggle
={closeGroup
? 'true' : 'false'} className
={ClassNames({'-is-toggled': closeGroup
})} onClick
={toggle
} style
={{display
: hasBasicFields
? 'block' : 'none'}}>
795 <a className
="toggle-show-more" href
="#show-more-properties">more
&hellip
;</a
>
796 <a className
="toggle-show-less" href
="#show-more-properties">less
&hellip
;</a
>
798 <div className
="toggleable">
799 {properties
.map(property
=> {
800 const path
= [property
.name
];
801 const value
= container
.model
[property
.name
];
802 return build(container
, property
, path
, value
, {toggle
: true, width
: props
.width
});
805 <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>
810 function buildMoreLess(d, i) {
812 <span key={'bread
-crumb
-part
-' + i}>
813 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
820 if (container.parent) {
821 path.push(container.parent);
823 path.push(container);
826 <div className="EditDescriptorModelProperties -is-tree-view">
827 <h1>{path.map(buildMoreLess)}</h1>
829 {buildAdvancedGroup()}