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.
25 import _
from 'lodash'
26 import utils
from '../libraries/utils'
27 import React
from 'react'
28 import ClassNames
from 'classnames'
29 import changeCase
from 'change-case'
30 import toggle
from '../libraries/ToggleElementHandler'
31 import Button
from './Button'
32 import Property
from '../libraries/model/DescriptorModelMetaProperty'
33 import ComposerAppActions
from '../actions/ComposerAppActions'
34 import CatalogItemsActions
from '../actions/CatalogItemsActions'
35 import DESCRIPTOR_MODEL_FIELDS
from '../libraries/model/DescriptorModelFields'
36 import DescriptorModelFactory
from '../libraries/model/DescriptorModelFactory'
37 import DescriptorModelMetaFactory
from '../libraries/model/DescriptorModelMetaFactory'
38 import SelectionManager
from '../libraries/SelectionManager'
39 import DeletionManager
from '../libraries/DeletionManager'
40 import DescriptorModelIconFactory
from '../libraries/model/IconFactory'
41 import getEventPath
from '../libraries/getEventPath'
42 import CatalogDataStore
from '../stores/CatalogDataStore'
44 import imgAdd
from '../../../node_modules/open-iconic/svg/plus.svg'
45 import imgRemove
from '../../../node_modules/open-iconic/svg/trash.svg'
47 import '../styles/EditDescriptorModelProperties.scss'
49 function getDescriptorMetaBasicForType(type
) {
50 const basicPropertiesFilter
= d
=> _
.includes(DESCRIPTOR_MODEL_FIELDS
[type
], d
.name
);
51 return DescriptorModelMetaFactory
.getModelMetaForType(type
, basicPropertiesFilter
) || {properties
: []};
54 function getDescriptorMetaAdvancedForType(type
) {
55 const advPropertiesFilter
= d
=> !_
.includes(DESCRIPTOR_MODEL_FIELDS
[type
], d
.name
);
56 return DescriptorModelMetaFactory
.getModelMetaForType(type
, advPropertiesFilter
) || {properties
: []};
59 function getTitle(model
= {}) {
60 if (typeof model
['short-name'] === 'string' && model
['short-name']) {
61 return model
['short-name'];
63 if (typeof model
.name
=== 'string' && model
.name
) {
66 if (model
.uiState
&& typeof model
.uiState
.displayName
=== 'string' && model
.uiState
.displayName
) {
67 return model
.uiState
.displayName
69 if (typeof model
.id
=== 'string') {
74 export default function EditDescriptorModelProperties(props
) {
76 const container
= props
.container
;
78 if (!(DescriptorModelFactory
.isContainer(container
))) {
82 function startEditing() {
83 DeletionManager
.removeEventListeners();
86 function endEditing() {
87 DeletionManager
.addEventListeners();
90 function onClickSelectItem(property
, path
, value
, event
) {
91 event
.preventDefault();
92 const root
= this.getRoot();
93 if (SelectionManager
.select(value
)) {
94 CatalogItemsActions
.catalogItemMetaDataChanged(root
.model
);
98 function onFocusPropertyFormInputElement(property
, path
, value
, event
) {
100 event
.preventDefault();
103 function removeIsFocusedClass(event
) {
104 event
.target
.removeEventListener('blur', removeIsFocusedClass
);
105 Array
.from(document
.querySelectorAll('.-is-focused')).forEach(d
=> d
.classList
.remove('-is-focused'));
108 removeIsFocusedClass(event
);
110 const propertyWrapper
= getEventPath(event
).reduce((parent
, element
) => {
114 if (!element
.classList
) {
117 if (element
.classList
.contains('property')) {
122 if (propertyWrapper
) {
123 propertyWrapper
.classList
.add('-is-focused');
124 event
.target
.addEventListener('blur', removeIsFocusedClass
);
129 function buildAddPropertyAction(container
, property
, path
) {
130 function onClickAddProperty(property
, path
, event
) {
131 event
.preventDefault();
132 //SelectionManager.resume();
133 const create
= Property
.getContainerCreateMethod(property
, this);
136 create(model
, path
, property
);
138 const name
= path
.join('.');
139 const value
= Property
.createModelInstance(property
);
140 utils
.assignPathValue(this.model
, name
, value
);
142 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
145 <Button className
="inline-hint" onClick
={onClickAddProperty
.bind(container
, property
, path
)} label
="Add" src
={imgAdd
} />
149 function buildRemovePropertyAction(container
, property
, path
) {
150 function onClickRemoveProperty(property
, path
, event
) {
151 event
.preventDefault();
152 const name
= path
.join('.');
153 const removeMethod
= Property
.getContainerMethod(property
, this, 'remove');
155 removeMethod(utils
.resolvePath(this.model
, name
));
157 utils
.removePathValue(this.model
, name
);
159 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
162 <Button className
="remove-property-action inline-hint" title
="Remove" onClick
={onClickRemoveProperty
.bind(container
, property
, path
)} label
="Remove" src
={imgRemove
}/>
166 function onFormFieldValueChanged(event
) {
167 if (DescriptorModelFactory
.isContainer(this)) {
168 event
.preventDefault();
169 const name
= event
.target
.name
;
170 const value
= event
.target
.value
;
171 utils
.assignPathValue(this.model
, name
, value
);
172 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
176 function buildField(container
, property
, path
, value
, fieldKey
) {
177 let cds
= CatalogDataStore
;
178 let catalogs
= cds
.getTransientCatalogs();
180 const name
= path
.join('.');
181 const isEditable
= true;
182 const isGuid
= Property
.isGuid(property
);
183 const isBoolean
= Property
.isBoolean(property
);
184 const onChange
= onFormFieldValueChanged
.bind(container
);
185 const isEnumeration
= Property
.isEnumeration(property
);
186 const isLeafRef
= Property
.isLeafRef(property
);
187 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
188 const placeholder
= changeCase
.title(property
.name
);
189 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
190 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : undefined;
192 const enumeration
= Property
.getEnumeration(property
, value
);
193 const options
= enumeration
.map((d
, i
) => {
194 // note yangforge generates values for enums but the system does not use them
195 // so we categorically ignore them
196 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
197 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
198 return <option key
={fieldKey
.toString() + ':' + i
} value
={d
.name
}>{d
.name
}</option
>;
200 const isValueSet
= enumeration
.filter(d
=> d
.isSelected
).length
> 0;
201 if (!isValueSet
|| property
.cardinality
=== '0..1') {
202 const noValueDisplayText
= changeCase
.title(property
.name
);
203 options
.unshift(<option key
={'(value-not-in-enum)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
205 return <select key
={fieldKey
.toString()} id
={fieldKey
.toString()} className
={ClassNames({'-value-not-set': !isValueSet
})} name
={name
} value
={value
} title
={name
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} readOnly
={!isEditable
}>{options
}</select
>;
209 let fullFieldKey
= _
.isArray(fieldKey
) ? fieldKey
.join(':') : fieldKey
;
210 let containerRef
= container
;
211 while (containerRef
.parent
) {
212 fullFieldKey
= containerRef
.parent
.key
+ ':' + fullFieldKey
;
213 containerRef
= containerRef
.parent
;
215 const leafRefPathValues
= Property
.getLeafRef(property
, path
, value
, fullFieldKey
, catalogs
, container
);
217 const options
= leafRefPathValues
&& leafRefPathValues
.map((d
, i
) => {
218 return <option key
={fieldKey
.toString() + ':' + i
} value
={d
.value
}>{d
.value
}</option
>;
220 const isValueSet
= leafRefPathValues
.filter(d
=> d
.isSelected
).length
> 0;
221 if (!isValueSet
|| property
.cardinality
=== '0..1') {
222 const noValueDisplayText
= changeCase
.title(property
.name
);
223 options
.unshift(<option key
={'(value-not-in-leafref)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
225 return <select key
={fieldKey
.toString()} id
={fieldKey
.toString()} className
={ClassNames({'-value-not-set': !isValueSet
})} name
={name
} value
={value
} title
={name
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} readOnly
={!isEditable
}>{options
}</select
>;
229 let fullFieldKey
= _
.isArray(fieldKey
) ? fieldKey
.join(':') : fieldKey
;
230 let containerRef
= container
;
231 while (containerRef
.parent
) {
232 fullFieldKey
= containerRef
.parent
.key
+ ':' + fullFieldKey
;
233 containerRef
= containerRef
.parent
;
237 <option key
={fieldKey
.toString() + '-true'} value
="TRUE">TRUE
</option
>,
238 <option key
={fieldKey
.toString() + '-false'} value
="FALSE">FALSE
</option
>
241 // if (!isValueSet) {
242 const noValueDisplayText
= changeCase
.title(property
.name
);
243 options
.unshift(<option key
={'(value-not-in-leafref)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}></option
>);
246 if(typeof(val
) == 'number') {
247 val
= value
? "TRUE" : "FALSE"
249 const isValueSet
= (val
!= '' && val
)
250 return <select key
={fieldKey
.toString()} id
={fieldKey
.toString()} className
={ClassNames({'-value-not-set': !isValueSet
})} name
={name
} value
={val
&& val
.toUpperCase()} title
={name
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} readOnly
={!isEditable
}>{options
}</select
>;
253 if (property
['preserve-line-breaks']) {
254 return <textarea key
={fieldKey
.toString()} cols
="5" id
={fieldKey
.toString()} name
={name
} value
={value
} placeholder
={placeholder
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} onMouseOut
={endEditing
} onMouseLeave
={endEditing
} readOnly
={!isEditable
} />;
257 return <input key
={fieldKey
.toString()}
258 id
={fieldKey
.toString()}
262 className
={className
}
263 placeholder
={placeholder
}
267 onMouseDown
={startEditing
}
268 onMouseOver
={startEditing
}
269 onMouseOut
={endEditing
}
270 onMouseLeave
={endEditing
}
271 readOnly
={!isEditable
}
276 function buildElement(container
, property
, valuePath
, value
) {
277 return property
.properties
.map((property
, index
) => {
279 const childPath
= valuePath
.slice();
280 if (typeof value
=== 'object') {
281 childValue
= value
[property
.name
];
283 if(property
.type
!= 'choice'){
284 childPath
.push(property
.name
);
286 return build(container
, property
, childPath
, childValue
);
291 function buildChoice(container
, property
, path
, value
, key
) {
293 function onFormFieldValueChanged(event
) {
294 if (DescriptorModelFactory
.isContainer(this)) {
296 event
.preventDefault();
298 let name
= event
.target
.name
;
299 const value
= event
.target
.value
;
303 Transient State is stored for convenience in the uiState field.
304 The choice yang type uses case elements to describe the "options".
305 A choice can only ever have one option selected which allows
306 the system to determine which type is selected by the name of
307 the element contained within the field.
310 const stateExample = {
325 const statePath
= ['uiState.choice'].concat(name
);
326 const stateObject
= utils
.resolvePath(this.model
, statePath
.join('.')) || {};
327 const selected
= stateObject
.selected
? stateObject
.selected
.split('.')[1] : undefined;
328 // write state back to the model so the new state objects are captured
329 utils
.assignPathValue(this.model
, statePath
.join('.'), stateObject
);
331 // write the current choice value into the state
332 let choiceObject
= utils
.resolvePath(this.model
, [name
, selected
].join('.'));
333 let isTopCase
= false;
336 choiceObject
= utils
.resolvePath(this.model
, [selected
].join('.'));
338 utils
.assignPathValue(stateObject
, [selected
].join('.'), _
.cloneDeep(choiceObject
));
341 if(this.model
.uiState
.choice
.hasOwnProperty(name
)) {
342 delete this.model
[selected
];
343 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
345 // remove the current choice value from the model
346 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
350 // get any state for the new selected choice
351 const newChoiceObject
= utils
.resolvePath(stateObject
, [value
].join('.')) || {};
353 // assign new choice value to the model
355 utils
.assignPathValue(this.model
, [name
, value
].join('.'), newChoiceObject
);
357 utils
.assignPathValue(this.model
, [value
].join('.'), newChoiceObject
)
361 // update the selected name
362 utils
.assignPathValue(this.model
, statePath
.concat('selected').join('.'), value
);
364 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
368 const caseByNameMap
= {};
370 const onChange
= onFormFieldValueChanged
.bind(container
);
372 const cases
= property
.properties
.map(d
=> {
373 if (d
.type
=== 'case') {
374 //Previous it was assumed that a case choice would have only one property. Now we pass on either the only item or the
375 caseByNameMap
[d
.name
] = d
.properties
&& (d
.properties
.length
== 1 ? d
.properties
[0] : d
.properties
);
378 optionTitle
: d
.description
,
379 //represents case name and case element name
380 optionValue
: [d
.name
, d
.properties
[0].name
].join('.')
383 caseByNameMap
[d
.name
] = d
;
384 return {optionName
: d
.name
};
387 const options
= [{optionName
: '', optionValue
: false}].concat(cases
).map((d
, i
) => {
389 <option key
={i
} value
={d
.optionValue
} title
={d
.optionTitle
}>
391 {i
? null : changeCase
.title(property
.name
)}
396 const selectName
= path
.join('.');
397 let selectedOptionPath
= ['uiState.choice', selectName
, 'selected'].join('.');
398 //Currently selected choice/case statement on UI model
399 let selectedOptionValue
= utils
.resolvePath(container
.model
, selectedOptionPath
);
400 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
401 if(!selectedOptionValue
) {
402 //get field properties for choice on container model
403 let fieldProperties
= utils
.resolvePath(container
.model
, selectName
);
404 if(fieldProperties
) {
405 //Check each case statement in model and see if it is present in container model.
406 cases
.map(function(c
){
407 if(fieldProperties
.hasOwnProperty(c
.optionValue
.split('.')[1])) {
408 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), c
.optionValue
);
411 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
413 property
.properties
.map(function(p
) {
414 let pname
= p
.properties
[0].name
;
415 if(container
.model
.hasOwnProperty(pname
)) {
416 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), [p
.name
, pname
].join('.'));
419 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
422 //If selectedOptionValue is present, take first item in string which represents the case name.
423 const valueProperty
= caseByNameMap
[selectedOptionValue
? selectedOptionValue
.split('.')[0] : undefined] || {properties
: []};
424 const isLeaf
= Property
.isLeaf(valueProperty
);
425 const hasProperties
= _
.isArray(valueProperty
.properties
) && valueProperty
.properties
.length
;
426 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(valueProperty
);
427 //Some magic that prevents errors for arising
428 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
]) :
429 valueProperty
.map
&& valueProperty
.map(valuePropertyFn
);
430 function valuePropertyFn(d
, i
) {
431 const childPath
= path
.concat(valueProperty
.name
, d
.name
);
432 const childValue
= utils
.resolvePath(container
.model
, childPath
.join('.'));
434 <div key
={childPath
.concat('info', i
).join(':')}>
435 {build(container
, d
, childPath
, childValue
, props
)}
440 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
443 <div key
={key
} className
="choice">
444 <select key
={Date
.now()} className
={ClassNames({'-value-not-set': !selectedOptionValue
})} name
={selectName
} value
={selectedOptionValue
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} onMouseOut
={endEditing
} onMouseLeave
={endEditing
}>
453 function buildSimpleListItem(container
, property
, path
, value
, key
, index
) {
454 // todo need to abstract this better
455 const title
= getTitle(value
);
456 var req
= require
.context("../", true, /\.svg
/);
459 <a href
="#select-list-item" key
={Date
.now()} className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
460 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
463 {buildRemovePropertyAction(container
, property
, path
)}
468 function buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) {
469 const className
= ClassNames(property
.name
+ '-remove actions');
471 <div key
={fieldKey
.concat(index
).join(':')} className
={className
}>
473 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
474 <span className
="info">{index
+ 1}</span
>
475 {buildRemovePropertyAction(container
, property
, valuePath
)}
481 function buildLeafListItem(container
, property
, valuePath
, value
, key
, index
) {
482 // look at the type to determine how to parse the value
485 {buildRemoveListItem(container
, property
, valuePath
, key
, index
)}
486 {buildField(container
, property
, valuePath
, value
, key
)}
492 function build(container
, property
, path
, value
, props
= {}) {
495 const isLeaf
= Property
.isLeaf(property
);
496 const isArray
= Property
.isArray(property
);
497 const isObject
= Property
.isObject(property
);
498 const isLeafList
= Property
.isLeafList(property
);
499 const fieldKey
= [container
.id
].concat(path
);
500 const isRequired
= Property
.isRequired(property
);
501 const title
= changeCase
.titleCase(property
.name
);
502 const columnCount
= property
.properties
.length
|| 1;
503 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
504 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
506 if (!property
.properties
&& isObject
) {
507 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
508 property
.properties
= uiState
.properties
;
511 const hasProperties
= _
.isArray(property
.properties
) && property
.properties
.length
;
512 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
514 // ensure value is not undefined for non-leaf property types
516 if (typeof value
!== 'object') {
517 value
= isArray
? [] : {};
520 const valueAsArray
= _
.isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
522 const isMetaField
= property
.name
=== 'meta';
523 const isCVNFD
= property
.name
=== 'constituent-vnfd';
524 const isSimpleListView
= Property
.isSimpleList(property
);
526 valueAsArray
.forEach((value
, index
) => {
529 const key
= fieldKey
.slice();
530 const valuePath
= path
.slice();
533 valuePath
.push(index
);
538 if (typeof value
=== 'object') {
539 value
= JSON
.stringify(value
, undefined, 12);
540 } else if (typeof value
!== 'string') {
545 if (isMissingDescriptorMeta
) {
546 field
= <span key
={key
.concat('warning').join(':')} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
547 } else if (property
.type
=== 'choice') {
548 field
= buildChoice(container
, property
, valuePath
, value
, key
.join(':'));
549 } else if (isSimpleListView
) {
550 field
= buildSimpleListItem(container
, property
, valuePath
, value
, key
, index
);
551 } else if (isLeafList
) {
552 field
= buildLeafListItem(container
, property
, valuePath
, value
, key
, index
);
553 } else if (hasProperties
) {
554 field
= buildElement(container
, property
, valuePath
, value
, key
.join(':'))
556 field
= buildField(container
, property
, valuePath
, value
, key
.join(':'));
559 function onClickLeaf(property
, path
, value
, event
) {
560 if (event
.isDefaultPrevented()) {
563 event
.preventDefault();
564 event
.stopPropagation();
565 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
566 console
.log('property selected', path
.join('.'));
567 ComposerAppActions
.propertySelected([path
.join('.')]);
570 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
571 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
574 <div key
={fieldKey
.concat(['property-content', index
]).join(':')}
575 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
576 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
577 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) : null}
584 classNames
['-is-leaf'] = isLeaf
;
585 classNames
['-is-array'] = isArray
;
586 classNames
['cols-' + columnCount
] = isColumnar
;
588 if (property
.type
=== 'choice') {
589 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
591 property
.properties
.map(function(p
) {
592 let pname
= p
.properties
[0].name
;
593 if(container
.model
.hasOwnProperty(pname
)) {
594 value
= container
.model
[pname
];
600 let displayValue
= typeof value
=== 'object' ? '' : value
;
601 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
603 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
606 <div key
={fieldKey
.join(':')} className
={ClassNames(property
.type
+ '-property property', classNames
)} onFocus
={onFocus
}>
607 <h3 className
="property-label">
608 <label htmlFor
={fieldKey
.join(':')}>
609 <span className
={property
.type
+ '-name name'}>{title
}</span
>
611 <span className
={property
.type
+ '-info info'}>{displayValueInfo
}</span
>
612 <span className
={property
.type
+ '-value value'}>{displayValue
}</span
>
614 {isArray
? buildAddPropertyAction(container
, property
, path
.concat(valueAsArray
.length
)) : null}
617 <span className
={property
.type
+ '-description description'}>{property
.description
}</span
>
618 <val className
="property-value">
619 {isCVNFD
? <span className
={property
.type
+ '-tip tip'}>Drag a VNFD
from the Catalog to add more
.</span
> : null}
627 const containerType
= container
.uiState
['qualified-type'] || container
.uiState
.type
;
628 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
630 function buildBasicGroup() {
631 if (basicProperties
.length
=== 0) {
635 <div className
="basic-properties-group">
638 {basicProperties
.map(property
=> {
639 const path
= [property
.name
];
640 const value
= container
.model
[property
.name
];
641 return build(container
, property
, path
, value
);
648 function buildAdvancedGroup() {
649 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
650 if (properties
.length
=== 0) {
653 const hasBasicFields
= basicProperties
.length
> 0;
654 const closeGroup
= basicProperties
.length
> 0;
656 <div className
="advanced-properties-group">
657 <h1 data
-toggle
={closeGroup
? 'true' : 'false'} className
={ClassNames({'-is-toggled': closeGroup
})} onClick
={toggle
} style
={{display
: hasBasicFields
? 'block' : 'none'}}>
658 <a className
="toggle-show-more" href
="#show-more-properties">more
&hellip
;</a
>
659 <a className
="toggle-show-less" href
="#show-more-properties">less
&hellip
;</a
>
661 <div className
="toggleable">
662 {properties
.map(property
=> {
663 const path
= [property
.name
];
664 const value
= container
.model
[property
.name
];
665 return build(container
, property
, path
, value
, {toggle
: true, width
: props
.width
});
668 <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>
673 function buildMoreLess(d, i) {
675 <span key={'bread
-crumb
-part
-' + i}>
676 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
683 if (container.parent) {
684 path.push(container.parent);
686 path.push(container);
689 <div className="EditDescriptorModelProperties -is-tree-view">
690 <h1>{path.map(buildMoreLess)}</h1>
692 {buildAdvancedGroup()}