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
;
93 const readonly
= props
.readonly
;
94 const isEditable
= !readonly
; //true
96 if (!(DescriptorModelFactory
.isContainer(container
))) {
100 function startEditing() {
101 DeletionManager
.removeEventListeners();
104 function endEditing() {
105 DeletionManager
.addEventListeners();
108 function onClickSelectItem(property
, path
, value
, event
) {
109 event
.preventDefault();
110 const root
= this.getRoot();
111 if (SelectionManager
.select(value
)) {
112 CatalogItemsActions
.catalogItemMetaDataChanged(root
.model
);
116 function onFocusPropertyFormInputElement(property
, path
, value
, event
) {
118 event
.preventDefault();
121 function removeIsFocusedClass(event
) {
122 event
.target
.removeEventListener('blur', removeIsFocusedClass
);
123 Array
.from(document
.querySelectorAll('.-is-focused')).forEach(d
=> d
.classList
.remove('-is-focused'));
126 removeIsFocusedClass(event
);
128 const propertyWrapper
= getEventPath(event
).reduce((parent
, element
) => {
132 if (!element
.classList
) {
135 if (element
.classList
.contains('property')) {
140 if (propertyWrapper
) {
141 propertyWrapper
.classList
.add('-is-focused');
142 event
.target
.addEventListener('blur', removeIsFocusedClass
);
147 function buildAddPropertyAction(container
, property
, path
) {
148 function onClickAddProperty(property
, path
, event
) {
149 event
.preventDefault();
150 //SelectionManager.resume();
151 const create
= Property
.getContainerCreateMethod(property
, this);
154 create(model
, path
, property
);
156 const name
= path
.join('.');
157 // get a unique name for the new list item based on the current list content
158 // some lists, based on the key, may not get a uniqueName generated here
159 const uniqueName
= DescriptorModelMetaFactory
.generateItemUniqueName(container
.model
[property
.name
], property
);
160 const value
= Property
.createModelInstance(property
, uniqueName
);
161 utils
.assignPathValue(this.model
, name
, value
);
163 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
169 <Button className
="inline-hint" onClick
={onClickAddProperty
.bind(container
, property
, path
)} label
="Add" src
={imgAdd
} />
173 function buildRemovePropertyAction(container
, property
, path
) {
174 function onClickRemoveProperty(property
, path
, event
) {
175 event
.preventDefault();
176 const name
= path
.join('.');
177 const removeMethod
= Property
.getContainerMethod(property
, this, 'remove');
179 removeMethod(utils
.resolvePath(this.model
, name
));
181 utils
.removePathValue(this.model
, name
);
183 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
189 <Button className
="remove-property-action inline-hint" title
="Remove" onClick
={onClickRemoveProperty
.bind(container
, property
, path
)} label
="Remove" src
={imgRemove
}/>
193 function buildField(container
, property
, path
, value
, fieldKey
) {
194 let cds
= CatalogDataStore
;
195 let catalogs
= cds
.getTransientCatalogs();
197 const pathToProperty
= path
.join('.');
198 const isGuid
= Property
.isGuid(property
);
199 const isBoolean
= Property
.isBoolean(property
);
200 const isEnumeration
= Property
.isEnumeration(property
);
201 const isLeafRef
= Property
.isLeafRef(property
);
202 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
203 const placeholder
= changeCase
.title(property
.name
);
204 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
205 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : (isNaN(value
) ? undefined : value
);
207 // process the named field value change
208 function processFieldValueChange(name
, value
) {
209 console
.debug('processed change for -- ' + name
+ ' -- with value -- ' + value
);
210 // this = the container being edited
211 if (DescriptorModelFactory
.isContainer(this)) {
212 utils
.assignPathValue(this.model
, name
, value
);
213 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
217 // change handler used for onChange event
218 const changeHandler
= (handleValueChange
, event
) => {
219 event
.preventDefault();
220 console
.debug(event
.target
.value
);
221 handleValueChange(event
.target
.value
);
223 // create an onChange event handler for a text field for the specified field path (debounced to accumulate chars)
224 const onTextChange
= changeHandler
.bind(null, _debounce(
225 processFieldValueChange
.bind(container
, pathToProperty
), 2000, {maxWait
: 5000})); // max wait for short-name
226 // create an onChange event handler for a select field for the specified field path
227 const onSelectChange
= changeHandler
.bind(null, processFieldValueChange
.bind(container
, pathToProperty
));
230 const enumeration
= Property
.getEnumeration(property
, value
);
231 const options
= enumeration
.map((d
, i
) => {
232 // note yangforge generates values for enums but the system does not use them
233 // so we categorically ignore them
234 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
235 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
236 return <option key
={':' + i
} value
={d
.name
}>{d
.name
}</option
>;
238 const isValueSet
= enumeration
.filter(d
=> d
.isSelected
).length
> 0;
239 if (!isValueSet
|| property
.cardinality
=== '0..1') {
240 const noValueDisplayText
= changeCase
.title(property
.name
);
241 options
.unshift(<option key
={'(value-not-in-enum)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
247 className
={ClassNames({'-value-not-set': !isValueSet
})}
249 title
={pathToProperty
}
250 onChange
={onSelectChange
}
253 onMouseDown
={startEditing
}
254 onMouseOver
={startEditing
}
255 disabled
={!isEditable
}>
262 let fullPathString
= container
.key
+ ':' + path
.join(':');
263 let containerRef
= container
;
264 while (containerRef
.parent
) {
265 fullPathString
= containerRef
.parent
.key
+ ':' + fullPathString
;
266 containerRef
= containerRef
.parent
;
268 const leafRefPathValues
= Property
.getLeafRef(property
, path
, value
, fullPathString
, catalogs
, container
);
270 const options
= leafRefPathValues
&& leafRefPathValues
.map((d
, i
) => {
271 return <option key
={':' + i
} value
={d
.value
}>{d
.value
}</option
>;
273 const isValueSet
= leafRefPathValues
.filter(d
=> d
.isSelected
).length
> 0;
274 if (!isValueSet
|| property
.cardinality
=== '0..1') {
275 const noValueDisplayText
= changeCase
.title(property
.name
);
276 options
.unshift(<option key
={'(value-not-in-leafref)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
282 className
={ClassNames({'-value-not-set': !isValueSet
})}
284 title
={pathToProperty
}
285 onChange
={onSelectChange
}
288 onMouseDown
={startEditing
}
289 onMouseOver
={startEditing
}
290 disabled
={!isEditable
}>
298 <option key
={'true'} value
="TRUE">TRUE
</option
>,
299 <option key
={'false'} value
="FALSE">FALSE
</option
>
302 // if (!isValueSet) {
303 const noValueDisplayText
= changeCase
.title(property
.name
);
304 options
.unshift(<option key
={'(value-not-in-leafref)'} value
="" placeholder
={placeholder
}></option
>);
307 if(typeof(val
) == 'number') {
308 val
= value
? "TRUE" : "FALSE"
310 const isValueSet
= (val
!= '' && val
)
315 className
={ClassNames({'-value-not-set': !isValueSet
})}
316 defaultValue
={val
&& val
.toUpperCase()}
317 title
={pathToProperty
}
318 onChange
={onSelectChange
}
321 onMouseDown
={startEditing
}
322 onMouseOver
={startEditing
}
323 disabled
={!isEditable
}>
329 if (Property
.isLeafEmpty(property
)) {
330 // A null value indicates the leaf exists (as opposed to undefined).
331 // We stick in a string when the user actually sets it to simplify things
332 // but the correct thing happens when we serialize to user data
333 let isEmptyLeafPresent
= (value
=== EMPTY_LEAF_PRESENT
|| value
=== null);
334 let present
= isEmptyLeafPresent
? EMPTY_LEAF_PRESENT
: "";
336 <option key
={'true'} value
={EMPTY_LEAF_PRESENT
}>Enabled
</option
>,
337 <option key
={'false'} value
="">Not Enabled
</option
>
344 className
={ClassNames({'-value-not-set': !isEmptyLeafPresent
})}
345 defaultValue
={present
}
346 title
={pathToProperty
}
347 onChange
={onSelectChange
}
350 onMouseDown
={startEditing
}
351 onMouseOver
={startEditing
}
352 disabled
={!isEditable
}>
358 if (property
['preserve-line-breaks']) {
365 placeholder
={placeholder
}
366 onChange
={onTextChange
}
369 onMouseDown
={startEditing
}
370 onMouseOver
={startEditing
}
371 onMouseOut
={endEditing
}
372 onMouseLeave
={endEditing
}
373 disabled
={!isEditable
} />
382 defaultValue
={fieldValue
}
383 className
={className
}
384 placeholder
={placeholder
}
385 onChange
={onTextChange
}
388 onMouseDown
={startEditing
}
389 onMouseOver
={startEditing
}
390 onMouseOut
={endEditing
}
391 onMouseLeave
={endEditing
}
392 disabled
={!isEditable
}
399 * buiid and return an array of components representing an editor for each property.
401 * @param {any} container the master document being edited
402 * @param {[property]} properties
403 * @param {string} pathToProperties path within the container to the properties
404 * @param {Object} data source for each property
405 * @param {any} props object containing main data panel information, e.g. panel width {width: 375}
406 * which may be useful/necessary to a components rendering.
407 * @returns an array of react components
409 function buildComponentsForProperties(container
, properties
, pathToProperties
, data
, props
) {
410 return properties
.map((property
) => {
412 let propertyPath
= pathToProperties
.slice();
413 if (data
&& typeof data
=== 'object') {
414 value
= data
[property
.name
];
416 if(property
.type
!= 'choice'){
417 propertyPath
.push(property
.name
);
419 return build(container
, property
, propertyPath
, value
, props
);
423 function buildElement(container
, property
, valuePath
, value
) {
424 return buildComponentsForProperties(container
, property
.properties
, valuePath
, value
);
427 function buildChoice(container
, property
, path
, value
, key
) {
429 function processChoiceChange(name
, value
) {
430 if (DescriptorModelFactory
.isContainer(this)) {
433 Transient State is stored for convenience in the uiState field.
434 The choice yang type uses case elements to describe the "options".
435 A choice can only ever have one option selected which allows
436 the system to determine which type is selected by the name of
437 the element contained within the field.
440 const stateExample = {
455 const statePath
= ['uiState.choice'].concat(name
);
456 const stateObject
= utils
.resolvePath(this.model
, statePath
.join('.')) || {};
457 const selected
= stateObject
.selected
? stateObject
.selected
.split('.')[1] : undefined;
458 // write state back to the model so the new state objects are captured
459 utils
.assignPathValue(this.model
, statePath
.join('.'), stateObject
);
461 // write the current choice value into the state
462 let choiceObject
= utils
.resolvePath(this.model
, [name
, selected
].join('.'));
463 let isTopCase
= false;
466 choiceObject
= utils
.resolvePath(this.model
, [selected
].join('.'));
468 utils
.assignPathValue(stateObject
, [selected
].join('.'), _cloneDeep(choiceObject
));
471 if(this.model
.uiState
.choice
.hasOwnProperty(name
)) {
472 delete this.model
[selected
];
473 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
475 // remove the current choice value from the model
476 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
480 // get any state for the new selected choice
481 const newChoiceObject
= utils
.resolvePath(stateObject
, [value
].join('.')) || {};
483 // assign new choice value to the model
485 utils
.assignPathValue(this.model
, [name
, value
].join('.'), newChoiceObject
);
487 utils
.assignPathValue(this.model
, [value
].join('.'), newChoiceObject
)
490 // update the selected name
491 utils
.assignPathValue(this.model
, statePath
.concat('selected').join('.'), value
);
493 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
497 const pathToChoice
= path
.join('.');
498 const caseByNameMap
= {};
500 const choiceChangeHandler
= processChoiceChange
.bind(container
, pathToChoice
);
501 const onChange
= ((handleChoiceChange
, event
) => {
502 event
.preventDefault();
503 handleChoiceChange(event
.target
.value
);
504 }).bind(null, choiceChangeHandler
);
507 const cases
= property
.properties
.map(d
=> {
508 if (d
.type
=== 'case') {
509 //Previous it was assumed that a case choice would have only one property. Now we pass on either the only item or the
510 caseByNameMap
[d
.name
] = d
.properties
&& (d
.properties
.length
== 1 ? d
.properties
[0] : d
.properties
);
513 optionTitle
: d
.description
,
514 //represents case name and case element name
515 optionValue
: [d
.name
, d
.properties
[0].name
].join('.')
518 caseByNameMap
[d
.name
] = d
;
519 return {optionName
: d
.name
};
522 const options
= [{optionName
: '', optionValue
: false}].concat(cases
).map((d
, i
) => {
524 <option key
={i
} value
={d
.optionValue
} title
={d
.optionTitle
}>
526 {i
? null : changeCase
.title(property
.name
)}
531 let selectedOptionPath
= ['uiState.choice', pathToChoice
, 'selected'].join('.');
532 //Currently selected choice/case statement on UI model
533 let selectedOptionValue
= utils
.resolvePath(container
.model
, selectedOptionPath
);
534 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
535 if(!selectedOptionValue
) {
536 //get field properties for choice on container model
537 let fieldProperties
= utils
.resolvePath(container
.model
, pathToChoice
);
538 if(fieldProperties
) {
539 //Check each case statement in model and see if it is present in container model.
540 cases
.map(function(c
){
541 if(c
.optionValue
&& fieldProperties
.hasOwnProperty(c
.optionValue
.split('.')[1])) {
542 utils
.assignPathValue(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'), c
.optionValue
);
545 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'));
547 property
.properties
.map(function(p
) {
548 let pname
= p
.properties
[0] && p
.properties
[0].name
;
549 if(container
.model
.hasOwnProperty(pname
)) {
550 utils
.assignPathValue(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'), [p
.name
, pname
].join('.'));
553 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'));
556 //If selectedOptionValue is present, take first item in string which represents the case name.
557 const valueProperty
= caseByNameMap
[selectedOptionValue
? selectedOptionValue
.split('.')[0] : undefined] || {properties
: []};
558 const isLeaf
= Property
.isLeaf(valueProperty
);
559 const hasProperties
= _isArray(valueProperty
.properties
) && valueProperty
.properties
.length
;
560 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(valueProperty
);
561 //Some magic that prevents errors for arising
562 let valueResponse
= null;
563 if (valueProperty
.properties
&& valueProperty
.properties
.length
) {
564 valueResponse
= valueProperty
.properties
.map(valuePropertyFn
);
565 } else if (!isMissingDescriptorMeta
) {
566 let value
= utils
.resolvePath(container
.model
, path
.concat(valueProperty
.name
).join('.')) || container
.model
[valueProperty
.name
];
567 valueResponse
= build(container
, valueProperty
, path
.concat(valueProperty
.name
), value
)
569 valueResponse
= valueProperty
.map
&& valueProperty
.map(valuePropertyFn
);
571 function valuePropertyFn(d
, i
) {
572 const childPath
= path
.concat(valueProperty
.name
, d
.name
);
573 const childValue
= utils
.resolvePath(container
.model
, childPath
.join('.'));
575 <div key
={childPath
.concat('info', i
).join(':')}>
576 {build(container
, d
, childPath
, childValue
, props
)}
581 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
584 <div key
={key
} className
="choice">
587 className
={ClassNames({'-value-not-set': !selectedOptionValue
})}
588 defaultValue
={selectedOptionValue
}
592 onMouseDown
={startEditing
}
593 onMouseOver
={startEditing
}
594 onMouseOut
={endEditing
}
595 onMouseLeave
={endEditing
}
596 disabled
={!isEditable
}
606 function buildSimpleListItem(container
, property
, path
, value
, uniqueId
, index
) {
607 // todo need to abstract this better
608 const title
= getTitle(value
);
609 var req
= require
.context("../", true, /\.svg
/);
611 <div key
={uniqueId
} >
612 <a href
="#select-list-item" className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
613 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
616 {buildRemovePropertyAction(container
, property
, path
)}
621 function buildRemoveListItem(container
, property
, valuePath
, index
) {
622 const className
= ClassNames(property
.name
+ '-remove actions');
624 <div className
={className
}>
626 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
627 <span className
="info">{index
+ 1}</span
>
628 {buildRemovePropertyAction(container
, property
, valuePath
)}
634 function buildLeafListItem(container
, property
, valuePath
, value
, uniqueId
, index
) {
635 // look at the type to determine how to parse the value
638 {buildRemoveListItem(container
, property
, valuePath
, index
)}
639 {buildField(container
, property
, valuePath
, value
, uniqueId
)}
645 function build(container
, property
, path
, value
, props
= {}) {
648 const isLeaf
= Property
.isLeaf(property
);
649 const isArray
= Property
.isArray(property
);
650 const isObject
= Property
.isObject(property
);
651 const isLeafList
= Property
.isLeafList(property
);
652 const isRequired
= Property
.isRequired(property
);
653 const title
= changeCase
.titleCase(property
.name
);
654 const columnCount
= property
.properties
.length
|| 1;
655 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
656 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
658 // create a unique Id for use as react component keys and html element ids
659 // use uid (from ui info) instead of id property (which is not stable)
660 let uniqueId
= container
.uid
;
661 let containerRef
= container
;
662 while (containerRef
.parent
) {
663 uniqueId
= containerRef
.parent
.uid
+ ':' + uniqueId
;
664 containerRef
= containerRef
.parent
;
666 uniqueId
+= ':' + path
.join(':')
668 if (!property
.properties
&& isObject
) {
669 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
670 property
.properties
= uiState
.properties
;
673 const hasProperties
= _isArray(property
.properties
) && property
.properties
.length
;
674 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
676 // ensure value is not undefined for non-leaf property types
678 if (typeof value
!== 'object') {
679 value
= isArray
? [] : {};
682 const valueAsArray
= _isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
684 const isMetaField
= property
.name
=== 'meta';
685 const isCVNFD
= property
.name
=== 'constituent-vnfd';
686 const isSimpleListView
= Property
.isSimpleList(property
);
688 valueAsArray
.forEach((value
, index
) => {
691 const valuePath
= path
.slice();
692 // create a unique field Id for use as react component keys and html element ids
694 // keys only need to be unique on components in the same array
695 // html element ids should be unique with the document (or form)
696 let fieldId
= uniqueId
;
699 valuePath
.push(index
);
700 fieldId
= isLeafList
? fieldId
+ index
+ value
: resolveReactKey(value
);
704 if (typeof value
=== 'object') {
705 value
= JSON
.stringify(value
, undefined, 12);
706 } else if (typeof value
!== 'string') {
711 if (isMissingDescriptorMeta
) {
712 field
= <span key
={fieldId
} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
713 } else if (property
.type
=== 'choice') {
714 field
= buildChoice(container
, property
, valuePath
, value
, fieldId
);
715 } else if (isSimpleListView
) {
716 field
= buildSimpleListItem(container
, property
, valuePath
, value
, fieldId
, index
);
717 } else if (isLeafList
) {
718 field
= buildLeafListItem(container
, property
, valuePath
, value
, fieldId
, index
);
719 } else if (hasProperties
) {
720 field
= buildElement(container
, property
, valuePath
, value
, fieldId
)
722 field
= buildField(container
, property
, valuePath
, value
, fieldId
);
725 function onClickLeaf(property
, path
, value
, event
) {
726 if (event
.isDefaultPrevented()) {
729 event
.preventDefault();
730 event
.stopPropagation();
731 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
732 console
.debug('property selected', path
.join('.'));
733 ComposerAppActions
.propertySelected([path
.join('.')]);
736 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
737 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
741 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
742 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
743 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, index
) : null}
750 classNames
['-is-leaf'] = isLeaf
;
751 classNames
['-is-array'] = isArray
;
752 classNames
['cols-' + columnCount
] = isColumnar
;
754 if (property
.type
=== 'choice') {
755 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
757 property
.properties
.map(function(p
) {
758 let pname
= p
.properties
[0] && p
.properties
[0].name
;
759 if(container
.model
.hasOwnProperty(pname
)) {
760 value
= container
.model
[pname
];
766 let displayValue
= typeof value
=== 'object' ? '' : value
;
767 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
769 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
772 <div key
={uniqueId
} className
={ClassNames(property
.type
+ '-property property', classNames
)} onFocus
={onFocus
}>
773 <h3 className
="property-label">
774 <label htmlFor
={uniqueId
}>
775 <span className
={property
.type
+ '-name name'}>{title
}</span
>
777 <span className
={property
.type
+ '-info info'}>{displayValueInfo
}</span
>
778 <span className
={property
.type
+ '-value value'}>{displayValue
}</span
>
780 {isArray
? buildAddPropertyAction(container
, property
, path
.concat(valueAsArray
.length
)) : null}
783 <span className
={property
.type
+ '-description description'}>{property
.description
}</span
>
784 <val className
="property-value">
785 {isCVNFD
? <span className
={property
.type
+ '-tip tip'}>Drag a VNFD
from the Catalog to add more
.</span
> : null}
793 const containerType
= container
.uiState
['qualified-type'] || container
.uiState
.type
;
794 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
796 function buildBasicGroup() {
797 if (basicProperties
.length
=== 0) {
801 <div className
="basic-properties-group">
804 {buildComponentsForProperties(container
, basicProperties
, [], container
.model
)}
810 function buildAdvancedGroup() {
811 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
812 if (properties
.length
=== 0) {
815 const hasBasicFields
= basicProperties
.length
> 0;
816 const closeGroup
= basicProperties
.length
> 0;
818 <div className
="advanced-properties-group">
819 <h1 data
-toggle
={closeGroup
? 'true' : 'false'} className
={ClassNames({'-is-toggled': closeGroup
})} onClick
={toggle
} style
={{display
: hasBasicFields
? 'block' : 'none'}}>
820 <a className
="toggle-show-more" href
="#show-more-properties">more
&hellip
;</a
>
821 <a className
="toggle-show-less" href
="#show-more-properties">less
&hellip
;</a
>
823 <div className
="toggleable">
824 {buildComponentsForProperties(container
, properties
, [], container
.model
, {toggle
: true, width
: props
.width
})}
826 <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>
831 function buildMoreLess(d, i) {
833 <span key={'bread
-crumb
-part
-' + i}>
834 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
841 if (container.parent) {
842 path.push(container.parent);
844 path.push(container);
847 <div className="EditDescriptorModelProperties -is-tree-view">
848 <h1>{path.map(buildMoreLess)}</h1>
850 {buildAdvancedGroup()}