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 isGuid
= Property
.isGuid(property
);
187 const isBoolean
= Property
.isBoolean(property
);
188 const isEnumeration
= Property
.isEnumeration(property
);
189 const isLeafRef
= Property
.isLeafRef(property
);
190 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
191 const placeholder
= changeCase
.title(property
.name
);
192 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
193 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : (isNaN(value
) ? undefined : value
);
195 // process the named field value change
196 function processFieldValueChange(name
, value
) {
197 console
.debug('processed change for -- ' + name
+ ' -- with value -- ' + value
);
198 // this = the container being edited
199 if (DescriptorModelFactory
.isContainer(this)) {
200 utils
.assignPathValue(this.model
, name
, value
);
201 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
205 // change handler used for onChange event
206 const changeHandler
= (handleValueChange
, event
) => {
207 event
.preventDefault();
208 console
.debug(event
.target
.value
);
209 handleValueChange(event
.target
.value
);
211 // create an onChange event handler for a text field for the specified field path (debounced to accumulate chars)
212 const onTextChange
= changeHandler
.bind(null, _debounce(
213 processFieldValueChange
.bind(container
, pathToProperty
), 2000, {maxWait
: 5000})); // max wait for short-name
214 // create an onChange event handler for a select field for the specified field path
215 const onSelectChange
= changeHandler
.bind(null, processFieldValueChange
.bind(container
, pathToProperty
));
218 const enumeration
= Property
.getEnumeration(property
, value
);
219 const options
= enumeration
.map((d
, i
) => {
220 // note yangforge generates values for enums but the system does not use them
221 // so we categorically ignore them
222 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
223 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
224 return <option key
={':' + i
} value
={d
.name
}>{d
.name
}</option
>;
226 const isValueSet
= enumeration
.filter(d
=> d
.isSelected
).length
> 0;
227 if (!isValueSet
|| property
.cardinality
=== '0..1') {
228 const noValueDisplayText
= changeCase
.title(property
.name
);
229 options
.unshift(<option key
={'(value-not-in-enum)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
235 className
={ClassNames({'-value-not-set': !isValueSet
})}
237 title
={pathToProperty
}
238 onChange
={onSelectChange
}
241 onMouseDown
={startEditing
}
242 onMouseOver
={startEditing
}
243 disabled
={!isEditable
}>
250 let fullPathString
= container
.key
+ ':' + path
.join(':');
251 let containerRef
= container
;
252 while (containerRef
.parent
) {
253 fullPathString
= containerRef
.parent
.key
+ ':' + fullPathString
;
254 containerRef
= containerRef
.parent
;
256 const leafRefPathValues
= Property
.getLeafRef(property
, path
, value
, fullPathString
, catalogs
, container
);
258 const options
= leafRefPathValues
&& leafRefPathValues
.map((d
, i
) => {
259 return <option key
={':' + i
} value
={d
.value
}>{d
.value
}</option
>;
261 const isValueSet
= leafRefPathValues
.filter(d
=> d
.isSelected
).length
> 0;
262 if (!isValueSet
|| property
.cardinality
=== '0..1') {
263 const noValueDisplayText
= changeCase
.title(property
.name
);
264 options
.unshift(<option key
={'(value-not-in-leafref)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
270 className
={ClassNames({'-value-not-set': !isValueSet
})}
272 title
={pathToProperty
}
273 onChange
={onSelectChange
}
276 onMouseDown
={startEditing
}
277 onMouseOver
={startEditing
}
278 disabled
={!isEditable
}>
286 <option key
={'true'} value
="TRUE">TRUE
</option
>,
287 <option key
={'false'} value
="FALSE">FALSE
</option
>
290 // if (!isValueSet) {
291 const noValueDisplayText
= changeCase
.title(property
.name
);
292 options
.unshift(<option key
={'(value-not-in-leafref)'} value
="" placeholder
={placeholder
}></option
>);
295 if(typeof(val
) == 'number') {
296 val
= value
? "TRUE" : "FALSE"
298 const isValueSet
= (val
!= '' && val
)
303 className
={ClassNames({'-value-not-set': !isValueSet
})}
304 defaultValue
={val
&& val
.toUpperCase()}
305 title
={pathToProperty
}
306 onChange
={onSelectChange
}
309 onMouseDown
={startEditing
}
310 onMouseOver
={startEditing
}
311 disabled
={!isEditable
}>
317 if (Property
.isLeafEmpty(property
)) {
318 // A null value indicates the leaf exists (as opposed to undefined).
319 // We stick in a string when the user actually sets it to simplify things
320 // but the correct thing happens when we serialize to user data
321 let isEmptyLeafPresent
= (value
=== EMPTY_LEAF_PRESENT
|| value
=== null);
322 let present
= isEmptyLeafPresent
? EMPTY_LEAF_PRESENT
: "";
324 <option key
={'true'} value
={EMPTY_LEAF_PRESENT
}>Enabled
</option
>,
325 <option key
={'false'} value
="">Not Enabled
</option
>
332 className
={ClassNames({'-value-not-set': !isEmptyLeafPresent
})}
333 defaultValue
={present
}
334 title
={pathToProperty
}
335 onChange
={onSelectChange
}
338 onMouseDown
={startEditing
}
339 onMouseOver
={startEditing
}
340 disabled
={!isEditable
}>
346 if (property
['preserve-line-breaks']) {
353 placeholder
={placeholder
}
354 onChange
={onTextChange
}
357 onMouseDown
={startEditing
}
358 onMouseOver
={startEditing
}
359 onMouseOut
={endEditing
}
360 onMouseLeave
={endEditing
}
361 disabled
={!isEditable
} />
370 defaultValue
={fieldValue
}
371 className
={className
}
372 placeholder
={placeholder
}
373 onChange
={onTextChange
}
376 onMouseDown
={startEditing
}
377 onMouseOver
={startEditing
}
378 onMouseOut
={endEditing
}
379 onMouseLeave
={endEditing
}
380 disabled
={!isEditable
}
386 function buildElement(container
, property
, valuePath
, value
) {
387 return property
.properties
.map((property
, index
) => {
389 const childPath
= valuePath
.slice();
390 if (typeof value
=== 'object') {
391 childValue
= value
[property
.name
];
393 if(property
.type
!= 'choice'){
394 childPath
.push(property
.name
);
396 return build(container
, property
, childPath
, childValue
);
401 function buildChoice(container
, property
, path
, value
, key
) {
403 function processChoiceChange(name
, value
) {
404 if (DescriptorModelFactory
.isContainer(this)) {
407 Transient State is stored for convenience in the uiState field.
408 The choice yang type uses case elements to describe the "options".
409 A choice can only ever have one option selected which allows
410 the system to determine which type is selected by the name of
411 the element contained within the field.
414 const stateExample = {
429 const statePath
= ['uiState.choice'].concat(name
);
430 const stateObject
= utils
.resolvePath(this.model
, statePath
.join('.')) || {};
431 const selected
= stateObject
.selected
? stateObject
.selected
.split('.')[1] : undefined;
432 // write state back to the model so the new state objects are captured
433 utils
.assignPathValue(this.model
, statePath
.join('.'), stateObject
);
435 // write the current choice value into the state
436 let choiceObject
= utils
.resolvePath(this.model
, [name
, selected
].join('.'));
437 let isTopCase
= false;
440 choiceObject
= utils
.resolvePath(this.model
, [selected
].join('.'));
442 utils
.assignPathValue(stateObject
, [selected
].join('.'), _cloneDeep(choiceObject
));
445 if(this.model
.uiState
.choice
.hasOwnProperty(name
)) {
446 delete this.model
[selected
];
447 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
449 // remove the current choice value from the model
450 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
454 // get any state for the new selected choice
455 const newChoiceObject
= utils
.resolvePath(stateObject
, [value
].join('.')) || {};
457 // assign new choice value to the model
459 utils
.assignPathValue(this.model
, [name
, value
].join('.'), newChoiceObject
);
461 utils
.assignPathValue(this.model
, [value
].join('.'), newChoiceObject
)
464 // update the selected name
465 utils
.assignPathValue(this.model
, statePath
.concat('selected').join('.'), value
);
467 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
471 const pathToChoice
= path
.join('.');
472 const caseByNameMap
= {};
474 const choiceChangeHandler
= processChoiceChange
.bind(container
, pathToChoice
);
475 const onChange
= ((handleChoiceChange
, event
) => {
476 event
.preventDefault();
477 handleChoiceChange(event
.target
.value
);
478 }).bind(null, choiceChangeHandler
);
481 const cases
= property
.properties
.map(d
=> {
482 if (d
.type
=== 'case') {
483 //Previous it was assumed that a case choice would have only one property. Now we pass on either the only item or the
484 caseByNameMap
[d
.name
] = d
.properties
&& (d
.properties
.length
== 1 ? d
.properties
[0] : d
.properties
);
487 optionTitle
: d
.description
,
488 //represents case name and case element name
489 optionValue
: [d
.name
, d
.properties
[0].name
].join('.')
492 caseByNameMap
[d
.name
] = d
;
493 return {optionName
: d
.name
};
496 const options
= [{optionName
: '', optionValue
: false}].concat(cases
).map((d
, i
) => {
498 <option key
={i
} value
={d
.optionValue
} title
={d
.optionTitle
}>
500 {i
? null : changeCase
.title(property
.name
)}
505 let selectedOptionPath
= ['uiState.choice', pathToChoice
, 'selected'].join('.');
506 //Currently selected choice/case statement on UI model
507 let selectedOptionValue
= utils
.resolvePath(container
.model
, selectedOptionPath
);
508 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
509 if(!selectedOptionValue
) {
510 //get field properties for choice on container model
511 let fieldProperties
= utils
.resolvePath(container
.model
, pathToChoice
);
512 if(fieldProperties
) {
513 //Check each case statement in model and see if it is present in container model.
514 cases
.map(function(c
){
515 if(fieldProperties
.hasOwnProperty(c
.optionValue
.split('.')[1])) {
516 utils
.assignPathValue(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'), c
.optionValue
);
519 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'));
521 property
.properties
.map(function(p
) {
522 let pname
= p
.properties
[0].name
;
523 if(container
.model
.hasOwnProperty(pname
)) {
524 utils
.assignPathValue(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'), [p
.name
, pname
].join('.'));
527 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'));
530 //If selectedOptionValue is present, take first item in string which represents the case name.
531 const valueProperty
= caseByNameMap
[selectedOptionValue
? selectedOptionValue
.split('.')[0] : undefined] || {properties
: []};
532 const isLeaf
= Property
.isLeaf(valueProperty
);
533 const hasProperties
= _isArray(valueProperty
.properties
) && valueProperty
.properties
.length
;
534 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(valueProperty
);
535 //Some magic that prevents errors for arising
536 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
]) :
537 valueProperty
.map
&& valueProperty
.map(valuePropertyFn
);
538 function valuePropertyFn(d
, i
) {
539 const childPath
= path
.concat(valueProperty
.name
, d
.name
);
540 const childValue
= utils
.resolvePath(container
.model
, childPath
.join('.'));
542 <div key
={childPath
.concat('info', i
).join(':')}>
543 {build(container
, d
, childPath
, childValue
, props
)}
548 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
551 <div key
={key
} className
="choice">
554 className
={ClassNames({'-value-not-set': !selectedOptionValue
})}
555 defaultValue
={selectedOptionValue
}
559 onMouseDown
={startEditing
}
560 onMouseOver
={startEditing
}
561 onMouseOut
={endEditing
}
562 onMouseLeave
={endEditing
}
563 disabled
={!isEditable
}
573 function buildSimpleListItem(container
, property
, path
, value
, uniqueId
, index
) {
574 // todo need to abstract this better
575 const title
= getTitle(value
);
576 var req
= require
.context("../", true, /\.svg
/);
578 <div key
={uniqueId
} >
579 <a href
="#select-list-item" className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
580 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
583 {buildRemovePropertyAction(container
, property
, path
)}
588 function buildRemoveListItem(container
, property
, valuePath
, index
) {
589 const className
= ClassNames(property
.name
+ '-remove actions');
591 <div className
={className
}>
593 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
594 <span className
="info">{index
+ 1}</span
>
595 {buildRemovePropertyAction(container
, property
, valuePath
)}
601 function buildLeafListItem(container
, property
, valuePath
, value
, uniqueId
, index
) {
602 // look at the type to determine how to parse the value
605 {buildRemoveListItem(container
, property
, valuePath
, index
)}
606 {buildField(container
, property
, valuePath
, value
, uniqueId
)}
612 function build(container
, property
, path
, value
, props
= {}) {
615 const isLeaf
= Property
.isLeaf(property
);
616 const isArray
= Property
.isArray(property
);
617 const isObject
= Property
.isObject(property
);
618 const isLeafList
= Property
.isLeafList(property
);
619 const isRequired
= Property
.isRequired(property
);
620 const title
= changeCase
.titleCase(property
.name
);
621 const columnCount
= property
.properties
.length
|| 1;
622 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
623 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
625 // create a unique Id for use as react component keys and html element ids
626 // use uid (from ui info) instead of id property (which is not stable)
627 let uniqueId
= container
.uid
;
628 let containerRef
= container
;
629 while (containerRef
.parent
) {
630 uniqueId
= containerRef
.parent
.uid
+ ':' + uniqueId
;
631 containerRef
= containerRef
.parent
;
633 uniqueId
+= ':' + path
.join(':')
635 if (!property
.properties
&& isObject
) {
636 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
637 property
.properties
= uiState
.properties
;
640 const hasProperties
= _isArray(property
.properties
) && property
.properties
.length
;
641 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
643 // ensure value is not undefined for non-leaf property types
645 if (typeof value
!== 'object') {
646 value
= isArray
? [] : {};
649 const valueAsArray
= _isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
651 const isMetaField
= property
.name
=== 'meta';
652 const isCVNFD
= property
.name
=== 'constituent-vnfd';
653 const isSimpleListView
= Property
.isSimpleList(property
);
655 valueAsArray
.forEach((value
, index
) => {
658 const valuePath
= path
.slice();
659 // create a unique field Id for use as react component keys and html element ids
661 // keys only need to be unique on components in the same array
662 // html element ids should be unique with the document (or form)
663 let fieldId
= uniqueId
;
666 valuePath
.push(index
);
671 if (typeof value
=== 'object') {
672 value
= JSON
.stringify(value
, undefined, 12);
673 } else if (typeof value
!== 'string') {
678 if (isMissingDescriptorMeta
) {
679 field
= <span key
={fieldId
} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
680 } else if (property
.type
=== 'choice') {
681 field
= buildChoice(container
, property
, valuePath
, value
, fieldId
);
682 } else if (isSimpleListView
) {
683 field
= buildSimpleListItem(container
, property
, valuePath
, value
, fieldId
, index
);
684 } else if (isLeafList
) {
685 field
= buildLeafListItem(container
, property
, valuePath
, value
, fieldId
, index
);
686 } else if (hasProperties
) {
687 field
= buildElement(container
, property
, valuePath
, value
, fieldId
)
689 field
= buildField(container
, property
, valuePath
, value
, fieldId
);
692 function onClickLeaf(property
, path
, value
, event
) {
693 if (event
.isDefaultPrevented()) {
696 event
.preventDefault();
697 event
.stopPropagation();
698 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
699 console
.debug('property selected', path
.join('.'));
700 ComposerAppActions
.propertySelected([path
.join('.')]);
703 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
704 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
708 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
709 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
710 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, index
) : null}
717 classNames
['-is-leaf'] = isLeaf
;
718 classNames
['-is-array'] = isArray
;
719 classNames
['cols-' + columnCount
] = isColumnar
;
721 if (property
.type
=== 'choice') {
722 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
724 property
.properties
.map(function(p
) {
725 let pname
= p
.properties
[0].name
;
726 if(container
.model
.hasOwnProperty(pname
)) {
727 value
= container
.model
[pname
];
733 let displayValue
= typeof value
=== 'object' ? '' : value
;
734 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
736 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
739 <div key
={uniqueId
} className
={ClassNames(property
.type
+ '-property property', classNames
)} onFocus
={onFocus
}>
740 <h3 className
="property-label">
741 <label htmlFor
={uniqueId
}>
742 <span className
={property
.type
+ '-name name'}>{title
}</span
>
744 <span className
={property
.type
+ '-info info'}>{displayValueInfo
}</span
>
745 <span className
={property
.type
+ '-value value'}>{displayValue
}</span
>
747 {isArray
? buildAddPropertyAction(container
, property
, path
.concat(valueAsArray
.length
)) : null}
750 <span className
={property
.type
+ '-description description'}>{property
.description
}</span
>
751 <val className
="property-value">
752 {isCVNFD
? <span className
={property
.type
+ '-tip tip'}>Drag a VNFD
from the Catalog to add more
.</span
> : null}
760 const containerType
= container
.uiState
['qualified-type'] || container
.uiState
.type
;
761 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
763 function buildBasicGroup() {
764 if (basicProperties
.length
=== 0) {
768 <div className
="basic-properties-group">
771 {basicProperties
.map(property
=> {
772 const path
= [property
.name
];
773 const value
= container
.model
[property
.name
];
774 return build(container
, property
, path
, value
);
781 function buildAdvancedGroup() {
782 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
783 if (properties
.length
=== 0) {
786 const hasBasicFields
= basicProperties
.length
> 0;
787 const closeGroup
= basicProperties
.length
> 0;
789 <div className
="advanced-properties-group">
790 <h1 data
-toggle
={closeGroup
? 'true' : 'false'} className
={ClassNames({'-is-toggled': closeGroup
})} onClick
={toggle
} style
={{display
: hasBasicFields
? 'block' : 'none'}}>
791 <a className
="toggle-show-more" href
="#show-more-properties">more
&hellip
;</a
>
792 <a className
="toggle-show-less" href
="#show-more-properties">less
&hellip
;</a
>
794 <div className
="toggleable">
795 {properties
.map(property
=> {
796 const path
= [property
.name
];
797 const value
= container
.model
[property
.name
];
798 return build(container
, property
, path
, value
, {toggle
: true, width
: props
.width
});
801 <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>
806 function buildMoreLess(d, i) {
808 <span key={'bread
-crumb
-part
-' + i}>
809 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
816 if (container.parent) {
817 path.push(container.parent);
819 path.push(container);
822 <div className="EditDescriptorModelProperties -is-tree-view">
823 <h1>{path.map(buildMoreLess)}</h1>
825 {buildAdvancedGroup()}