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
;
82 if (!(DescriptorModelFactory
.isContainer(container
))) {
86 function startEditing() {
87 DeletionManager
.removeEventListeners();
90 function endEditing() {
91 DeletionManager
.addEventListeners();
94 function onClickSelectItem(property
, path
, value
, event
) {
95 event
.preventDefault();
96 const root
= this.getRoot();
97 if (SelectionManager
.select(value
)) {
98 CatalogItemsActions
.catalogItemMetaDataChanged(root
.model
);
102 function onFocusPropertyFormInputElement(property
, path
, value
, event
) {
104 event
.preventDefault();
107 function removeIsFocusedClass(event
) {
108 event
.target
.removeEventListener('blur', removeIsFocusedClass
);
109 Array
.from(document
.querySelectorAll('.-is-focused')).forEach(d
=> d
.classList
.remove('-is-focused'));
112 removeIsFocusedClass(event
);
114 const propertyWrapper
= getEventPath(event
).reduce((parent
, element
) => {
118 if (!element
.classList
) {
121 if (element
.classList
.contains('property')) {
126 if (propertyWrapper
) {
127 propertyWrapper
.classList
.add('-is-focused');
128 event
.target
.addEventListener('blur', removeIsFocusedClass
);
133 function buildAddPropertyAction(container
, property
, path
) {
134 function onClickAddProperty(property
, path
, event
) {
135 event
.preventDefault();
136 //SelectionManager.resume();
137 const create
= Property
.getContainerCreateMethod(property
, this);
140 create(model
, path
, property
);
142 const name
= path
.join('.');
143 // get a unique name for the new list item based on the current list content
144 // some lists, based on the key, may not get a uniqueName generated here
145 const uniqueName
= DescriptorModelMetaFactory
.generateItemUniqueName(container
.model
[property
.name
], property
);
146 const value
= Property
.createModelInstance(property
, uniqueName
);
147 utils
.assignPathValue(this.model
, name
, value
);
149 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
152 <Button className
="inline-hint" onClick
={onClickAddProperty
.bind(container
, property
, path
)} label
="Add" src
={imgAdd
} />
156 function buildRemovePropertyAction(container
, property
, path
) {
157 function onClickRemoveProperty(property
, path
, event
) {
158 event
.preventDefault();
159 const name
= path
.join('.');
160 const removeMethod
= Property
.getContainerMethod(property
, this, 'remove');
162 removeMethod(utils
.resolvePath(this.model
, name
));
164 utils
.removePathValue(this.model
, name
);
166 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
169 <Button className
="remove-property-action inline-hint" title
="Remove" onClick
={onClickRemoveProperty
.bind(container
, property
, path
)} label
="Remove" src
={imgRemove
}/>
173 function buildField(container
, property
, path
, value
, fieldKey
) {
174 let cds
= CatalogDataStore
;
175 let catalogs
= cds
.getTransientCatalogs();
177 const pathToProperty
= path
.join('.');
178 const isEditable
= true;
179 const isGuid
= Property
.isGuid(property
);
180 const isBoolean
= Property
.isBoolean(property
);
181 const isEnumeration
= Property
.isEnumeration(property
);
182 const isLeafRef
= Property
.isLeafRef(property
);
183 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
184 const placeholder
= changeCase
.title(property
.name
);
185 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
186 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : (isNaN(value
) ? undefined : value
);
188 // process the named field value change
189 function processFieldValueChange(name
, value
) {
190 console
.debug('processed change for -- ' + name
+ ' -- with value -- ' + value
);
191 // this = the container being edited
192 if (DescriptorModelFactory
.isContainer(this)) {
193 utils
.assignPathValue(this.model
, name
, value
);
194 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
198 // change handler used for onChange event
199 const changeHandler
= (handleValueChange
, event
) => {
200 event
.preventDefault();
201 console
.debug(event
.target
.value
);
202 handleValueChange(event
.target
.value
);
204 // create an onChange event handler for a text field for the specified field path (debounced to accumulate chars)
205 const onTextChange
= changeHandler
.bind(null, _debounce(
206 processFieldValueChange
.bind(container
, pathToProperty
), 2000, {maxWait
: 5000})); // max wait for short-name
207 // create an onChange event handler for a select field for the specified field path
208 const onSelectChange
= changeHandler
.bind(null, processFieldValueChange
.bind(container
, pathToProperty
));
211 const enumeration
= Property
.getEnumeration(property
, value
);
212 const options
= enumeration
.map((d
, i
) => {
213 // note yangforge generates values for enums but the system does not use them
214 // so we categorically ignore them
215 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
216 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
217 return <option key
={':' + i
} value
={d
.name
}>{d
.name
}</option
>;
219 const isValueSet
= enumeration
.filter(d
=> d
.isSelected
).length
> 0;
220 if (!isValueSet
|| property
.cardinality
=== '0..1') {
221 const noValueDisplayText
= changeCase
.title(property
.name
);
222 options
.unshift(<option key
={'(value-not-in-enum)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
228 className
={ClassNames({'-value-not-set': !isValueSet
})}
230 title
={pathToProperty
}
231 onChange
={onSelectChange
}
234 onMouseDown
={startEditing
}
235 onMouseOver
={startEditing
}
236 readOnly
={!isEditable
}>
243 let fullPathString
= container
.key
+ ':' + path
.join(':');
244 let containerRef
= container
;
245 while (containerRef
.parent
) {
246 fullPathString
= containerRef
.parent
.key
+ ':' + fullPathString
;
247 containerRef
= containerRef
.parent
;
249 const leafRefPathValues
= Property
.getLeafRef(property
, path
, value
, fullPathString
, catalogs
, container
);
251 const options
= leafRefPathValues
&& leafRefPathValues
.map((d
, i
) => {
252 return <option key
={':' + i
} value
={d
.value
}>{d
.value
}</option
>;
254 const isValueSet
= leafRefPathValues
.filter(d
=> d
.isSelected
).length
> 0;
255 if (!isValueSet
|| property
.cardinality
=== '0..1') {
256 const noValueDisplayText
= changeCase
.title(property
.name
);
257 options
.unshift(<option key
={'(value-not-in-leafref)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
263 className
={ClassNames({'-value-not-set': !isValueSet
})}
265 title
={pathToProperty
}
266 onChange
={onSelectChange
}
269 onMouseDown
={startEditing
}
270 onMouseOver
={startEditing
}
271 readOnly
={!isEditable
}>
279 <option key
={'true'} value
="TRUE">TRUE
</option
>,
280 <option key
={'false'} value
="FALSE">FALSE
</option
>
283 // if (!isValueSet) {
284 const noValueDisplayText
= changeCase
.title(property
.name
);
285 options
.unshift(<option key
={'(value-not-in-leafref)'} value
="" placeholder
={placeholder
}></option
>);
288 if(typeof(val
) == 'number') {
289 val
= value
? "TRUE" : "FALSE"
291 const isValueSet
= (val
!= '' && val
)
296 className
={ClassNames({'-value-not-set': !isValueSet
})}
297 defaultValue
={val
&& val
.toUpperCase()}
298 title
={pathToProperty
}
299 onChange
={onSelectChange
}
302 onMouseDown
={startEditing
}
303 onMouseOver
={startEditing
}
304 readOnly
={!isEditable
}>
310 if (Property
.isLeafEmpty(property
)) {
311 // A null value indicates the leaf exists (as opposed to undefined).
312 // We stick in a string when the user actually sets it to simplify things
313 // but the correct thing happens when we serialize to user data
314 let isEmptyLeafPresent
= (value
=== EMPTY_LEAF_PRESENT
|| value
=== null);
315 let present
= isEmptyLeafPresent
? EMPTY_LEAF_PRESENT
: "";
317 <option key
={'true'} value
={EMPTY_LEAF_PRESENT
}>Enabled
</option
>,
318 <option key
={'false'} value
="">Not Enabled
</option
>
325 className
={ClassNames({'-value-not-set': !isEmptyLeafPresent
})}
326 defaultValue
={present
}
327 title
={pathToProperty
}
328 onChange
={onSelectChange
}
331 onMouseDown
={startEditing
}
332 onMouseOver
={startEditing
}
333 readOnly
={!isEditable
}>
339 if (property
['preserve-line-breaks']) {
346 placeholder
={placeholder
}
347 onChange
={onTextChange
}
350 onMouseDown
={startEditing
}
351 onMouseOver
={startEditing
}
352 onMouseOut
={endEditing
}
353 onMouseLeave
={endEditing
}
354 readOnly
={!isEditable
} />
363 defaultValue
={fieldValue
}
364 className
={className
}
365 placeholder
={placeholder
}
366 onChange
={onTextChange
}
369 onMouseDown
={startEditing
}
370 onMouseOver
={startEditing
}
371 onMouseOut
={endEditing
}
372 onMouseLeave
={endEditing
}
373 readOnly
={!isEditable
}
379 function buildElement(container
, property
, valuePath
, value
) {
380 return property
.properties
.map((property
, index
) => {
382 const childPath
= valuePath
.slice();
383 if (typeof value
=== 'object') {
384 childValue
= value
[property
.name
];
386 if(property
.type
!= 'choice'){
387 childPath
.push(property
.name
);
389 return build(container
, property
, childPath
, childValue
);
394 function buildChoice(container
, property
, path
, value
, key
) {
396 function processChoiceChange(name
, value
) {
397 if (DescriptorModelFactory
.isContainer(this)) {
400 Transient State is stored for convenience in the uiState field.
401 The choice yang type uses case elements to describe the "options".
402 A choice can only ever have one option selected which allows
403 the system to determine which type is selected by the name of
404 the element contained within the field.
407 const stateExample = {
422 const statePath
= ['uiState.choice'].concat(name
);
423 const stateObject
= utils
.resolvePath(this.model
, statePath
.join('.')) || {};
424 const selected
= stateObject
.selected
? stateObject
.selected
.split('.')[1] : undefined;
425 // write state back to the model so the new state objects are captured
426 utils
.assignPathValue(this.model
, statePath
.join('.'), stateObject
);
428 // write the current choice value into the state
429 let choiceObject
= utils
.resolvePath(this.model
, [name
, selected
].join('.'));
430 let isTopCase
= false;
433 choiceObject
= utils
.resolvePath(this.model
, [selected
].join('.'));
435 utils
.assignPathValue(stateObject
, [selected
].join('.'), _cloneDeep(choiceObject
));
438 if(this.model
.uiState
.choice
.hasOwnProperty(name
)) {
439 delete this.model
[selected
];
440 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
442 // remove the current choice value from the model
443 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
447 // get any state for the new selected choice
448 const newChoiceObject
= utils
.resolvePath(stateObject
, [value
].join('.')) || {};
450 // assign new choice value to the model
452 utils
.assignPathValue(this.model
, [name
, value
].join('.'), newChoiceObject
);
454 utils
.assignPathValue(this.model
, [value
].join('.'), newChoiceObject
)
457 // update the selected name
458 utils
.assignPathValue(this.model
, statePath
.concat('selected').join('.'), value
);
460 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
464 const pathToChoice
= path
.join('.');
465 const caseByNameMap
= {};
467 const choiceChangeHandler
= processChoiceChange
.bind(container
, pathToChoice
);
468 const onChange
= ((handleChoiceChange
, event
) => {
469 event
.preventDefault();
470 handleChoiceChange(event
.target
.value
);
471 }).bind(null, choiceChangeHandler
);
474 const cases
= property
.properties
.map(d
=> {
475 if (d
.type
=== 'case') {
476 //Previous it was assumed that a case choice would have only one property. Now we pass on either the only item or the
477 caseByNameMap
[d
.name
] = d
.properties
&& (d
.properties
.length
== 1 ? d
.properties
[0] : d
.properties
);
480 optionTitle
: d
.description
,
481 //represents case name and case element name
482 optionValue
: [d
.name
, d
.properties
[0].name
].join('.')
485 caseByNameMap
[d
.name
] = d
;
486 return {optionName
: d
.name
};
489 const options
= [{optionName
: '', optionValue
: false}].concat(cases
).map((d
, i
) => {
491 <option key
={i
} value
={d
.optionValue
} title
={d
.optionTitle
}>
493 {i
? null : changeCase
.title(property
.name
)}
498 let selectedOptionPath
= ['uiState.choice', pathToChoice
, 'selected'].join('.');
499 //Currently selected choice/case statement on UI model
500 let selectedOptionValue
= utils
.resolvePath(container
.model
, selectedOptionPath
);
501 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
502 if(!selectedOptionValue
) {
503 //get field properties for choice on container model
504 let fieldProperties
= utils
.resolvePath(container
.model
, pathToChoice
);
505 if(fieldProperties
) {
506 //Check each case statement in model and see if it is present in container model.
507 cases
.map(function(c
){
508 if(fieldProperties
.hasOwnProperty(c
.optionValue
.split('.')[1])) {
509 utils
.assignPathValue(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'), c
.optionValue
);
512 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'));
514 property
.properties
.map(function(p
) {
515 let pname
= p
.properties
[0].name
;
516 if(container
.model
.hasOwnProperty(pname
)) {
517 utils
.assignPathValue(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'), [p
.name
, pname
].join('.'));
520 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'));
523 //If selectedOptionValue is present, take first item in string which represents the case name.
524 const valueProperty
= caseByNameMap
[selectedOptionValue
? selectedOptionValue
.split('.')[0] : undefined] || {properties
: []};
525 const isLeaf
= Property
.isLeaf(valueProperty
);
526 const hasProperties
= _isArray(valueProperty
.properties
) && valueProperty
.properties
.length
;
527 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(valueProperty
);
528 //Some magic that prevents errors for arising
529 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
]) :
530 valueProperty
.map
&& valueProperty
.map(valuePropertyFn
);
531 function valuePropertyFn(d
, i
) {
532 const childPath
= path
.concat(valueProperty
.name
, d
.name
);
533 const childValue
= utils
.resolvePath(container
.model
, childPath
.join('.'));
535 <div key
={childPath
.concat('info', i
).join(':')}>
536 {build(container
, d
, childPath
, childValue
, props
)}
541 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
544 <div key
={key
} className
="choice">
547 className
={ClassNames({'-value-not-set': !selectedOptionValue
})}
548 defaultValue
={selectedOptionValue
}
552 onMouseDown
={startEditing
}
553 onMouseOver
={startEditing
}
554 onMouseOut
={endEditing
}
555 onMouseLeave
={endEditing
}
565 function buildSimpleListItem(container
, property
, path
, value
, uniqueId
, index
) {
566 // todo need to abstract this better
567 const title
= getTitle(value
);
568 var req
= require
.context("../", true, /\.svg
/);
570 <div key
={uniqueId
} >
571 <a href
="#select-list-item" className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
572 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
575 {buildRemovePropertyAction(container
, property
, path
)}
580 function buildRemoveListItem(container
, property
, valuePath
, index
) {
581 const className
= ClassNames(property
.name
+ '-remove actions');
583 <div className
={className
}>
585 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
586 <span className
="info">{index
+ 1}</span
>
587 {buildRemovePropertyAction(container
, property
, valuePath
)}
593 function buildLeafListItem(container
, property
, valuePath
, value
, uniqueId
, index
) {
594 // look at the type to determine how to parse the value
597 {buildRemoveListItem(container
, property
, valuePath
, index
)}
598 {buildField(container
, property
, valuePath
, value
, uniqueId
)}
604 function build(container
, property
, path
, value
, props
= {}) {
607 const isLeaf
= Property
.isLeaf(property
);
608 const isArray
= Property
.isArray(property
);
609 const isObject
= Property
.isObject(property
);
610 const isLeafList
= Property
.isLeafList(property
);
611 const isRequired
= Property
.isRequired(property
);
612 const title
= changeCase
.titleCase(property
.name
);
613 const columnCount
= property
.properties
.length
|| 1;
614 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
615 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
617 // create a unique Id for use as react component keys and html element ids
618 // use uid (from ui info) instead of id property (which is not stable)
619 let uniqueId
= container
.uid
;
620 let containerRef
= container
;
621 while (containerRef
.parent
) {
622 uniqueId
= containerRef
.parent
.uid
+ ':' + uniqueId
;
623 containerRef
= containerRef
.parent
;
625 uniqueId
+= ':' + path
.join(':')
627 if (!property
.properties
&& isObject
) {
628 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
629 property
.properties
= uiState
.properties
;
632 const hasProperties
= _isArray(property
.properties
) && property
.properties
.length
;
633 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
635 // ensure value is not undefined for non-leaf property types
637 if (typeof value
!== 'object') {
638 value
= isArray
? [] : {};
641 const valueAsArray
= _isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
643 const isMetaField
= property
.name
=== 'meta';
644 const isCVNFD
= property
.name
=== 'constituent-vnfd';
645 const isSimpleListView
= Property
.isSimpleList(property
);
647 valueAsArray
.forEach((value
, index
) => {
650 const valuePath
= path
.slice();
651 // create a unique field Id for use as react component keys and html element ids
653 // keys only need to be unique on components in the same array
654 // html element ids should be unique with the document (or form)
655 let fieldId
= uniqueId
;
658 valuePath
.push(index
);
663 if (typeof value
=== 'object') {
664 value
= JSON
.stringify(value
, undefined, 12);
665 } else if (typeof value
!== 'string') {
670 if (isMissingDescriptorMeta
) {
671 field
= <span key
={fieldId
} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
672 } else if (property
.type
=== 'choice') {
673 field
= buildChoice(container
, property
, valuePath
, value
, fieldId
);
674 } else if (isSimpleListView
) {
675 field
= buildSimpleListItem(container
, property
, valuePath
, value
, fieldId
, index
);
676 } else if (isLeafList
) {
677 field
= buildLeafListItem(container
, property
, valuePath
, value
, fieldId
, index
);
678 } else if (hasProperties
) {
679 field
= buildElement(container
, property
, valuePath
, value
, fieldId
)
681 field
= buildField(container
, property
, valuePath
, value
, fieldId
);
684 function onClickLeaf(property
, path
, value
, event
) {
685 if (event
.isDefaultPrevented()) {
688 event
.preventDefault();
689 event
.stopPropagation();
690 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
691 console
.debug('property selected', path
.join('.'));
692 ComposerAppActions
.propertySelected([path
.join('.')]);
695 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
696 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
700 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
701 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
702 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, index
) : null}
709 classNames
['-is-leaf'] = isLeaf
;
710 classNames
['-is-array'] = isArray
;
711 classNames
['cols-' + columnCount
] = isColumnar
;
713 if (property
.type
=== 'choice') {
714 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
716 property
.properties
.map(function(p
) {
717 let pname
= p
.properties
[0].name
;
718 if(container
.model
.hasOwnProperty(pname
)) {
719 value
= container
.model
[pname
];
725 let displayValue
= typeof value
=== 'object' ? '' : value
;
726 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
728 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
731 <div key
={uniqueId
} className
={ClassNames(property
.type
+ '-property property', classNames
)} onFocus
={onFocus
}>
732 <h3 className
="property-label">
733 <label htmlFor
={uniqueId
}>
734 <span className
={property
.type
+ '-name name'}>{title
}</span
>
736 <span className
={property
.type
+ '-info info'}>{displayValueInfo
}</span
>
737 <span className
={property
.type
+ '-value value'}>{displayValue
}</span
>
739 {isArray
? buildAddPropertyAction(container
, property
, path
.concat(valueAsArray
.length
)) : null}
742 <span className
={property
.type
+ '-description description'}>{property
.description
}</span
>
743 <val className
="property-value">
744 {isCVNFD
? <span className
={property
.type
+ '-tip tip'}>Drag a VNFD
from the Catalog to add more
.</span
> : null}
752 const containerType
= container
.uiState
['qualified-type'] || container
.uiState
.type
;
753 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
755 function buildBasicGroup() {
756 if (basicProperties
.length
=== 0) {
760 <div className
="basic-properties-group">
763 {basicProperties
.map(property
=> {
764 const path
= [property
.name
];
765 const value
= container
.model
[property
.name
];
766 return build(container
, property
, path
, value
);
773 function buildAdvancedGroup() {
774 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
775 if (properties
.length
=== 0) {
778 const hasBasicFields
= basicProperties
.length
> 0;
779 const closeGroup
= basicProperties
.length
> 0;
781 <div className
="advanced-properties-group">
782 <h1 data
-toggle
={closeGroup
? 'true' : 'false'} className
={ClassNames({'-is-toggled': closeGroup
})} onClick
={toggle
} style
={{display
: hasBasicFields
? 'block' : 'none'}}>
783 <a className
="toggle-show-more" href
="#show-more-properties">more
&hellip
;</a
>
784 <a className
="toggle-show-less" href
="#show-more-properties">less
&hellip
;</a
>
786 <div className
="toggleable">
787 {properties
.map(property
=> {
788 const path
= [property
.name
];
789 const value
= container
.model
[property
.name
];
790 return build(container
, property
, path
, value
, {toggle
: true, width
: props
.width
});
793 <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>
798 function buildMoreLess(d, i) {
800 <span key={'bread
-crumb
-part
-' + i}>
801 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
808 if (container.parent) {
809 path.push(container.parent);
811 path.push(container);
814 <div className="EditDescriptorModelProperties -is-tree-view">
815 <h1>{path.map(buildMoreLess)}</h1>
817 {buildAdvancedGroup()}