3 * Copyright 2016 RIFT.IO Inc
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
19 * Created by onvelocity on 1/18/16.
21 * This class generates the form fields used to edit the CONFD JSON model.
24 import _includes
from 'lodash/includes'
25 import _isArray
from 'lodash/isArray'
26 import _cloneDeep
from 'lodash/cloneDeep'
27 import _debounce
from 'lodash/debounce';
28 import _uniqueId
from 'lodash/uniqueId';
29 import _set
from 'lodash/set';
30 import _get
from 'lodash/get';
31 import _has
from 'lodash/has';
32 import utils
from '../libraries/utils'
33 import React
from 'react'
34 import ClassNames
from 'classnames'
35 import changeCase
from 'change-case'
36 import toggle
from '../libraries/ToggleElementHandler'
37 import Button
from './Button'
38 import Property
from '../libraries/model/DescriptorModelMetaProperty'
39 import ComposerAppActions
from '../actions/ComposerAppActions'
40 import CatalogItemsActions
from '../actions/CatalogItemsActions'
41 import DESCRIPTOR_MODEL_FIELDS
from '../libraries/model/DescriptorModelFields'
42 import DescriptorModelFactory
from '../libraries/model/DescriptorModelFactory'
43 import DescriptorModelMetaFactory
from '../libraries/model/DescriptorModelMetaFactory'
44 import SelectionManager
from '../libraries/SelectionManager'
45 import DeletionManager
from '../libraries/DeletionManager'
46 import DescriptorModelIconFactory
from '../libraries/model/IconFactory'
47 import getEventPath
from '../libraries/getEventPath'
48 import CatalogDataStore
from '../stores/CatalogDataStore'
50 import imgAdd
from '../../../node_modules/open-iconic/svg/plus.svg'
51 import imgRemove
from '../../../node_modules/open-iconic/svg/trash.svg'
53 import '../styles/EditDescriptorModelProperties.scss'
55 const EMPTY_LEAF_PRESENT
= '--empty-leaf-set--';
57 function resolveReactKey(value
) {
58 const keyPath
= ['uiState', 'fieldKey'];
59 if (!_has(value
, keyPath
)) {
60 _set(value
, keyPath
, _uniqueId());
62 return _get(value
, keyPath
);
65 function getDescriptorMetaBasicForType(type
) {
66 const basicPropertiesFilter
= d
=> _includes(DESCRIPTOR_MODEL_FIELDS
[type
], d
.name
);
67 return DescriptorModelMetaFactory
.getModelMetaForType(type
, basicPropertiesFilter
) || {properties
: []};
70 function getDescriptorMetaAdvancedForType(type
) {
71 const advPropertiesFilter
= d
=> !_includes(DESCRIPTOR_MODEL_FIELDS
[type
], d
.name
);
72 return DescriptorModelMetaFactory
.getModelMetaForType(type
, advPropertiesFilter
) || {properties
: []};
75 function getTitle(model
= {}) {
76 if (typeof model
['short-name'] === 'string' && model
['short-name']) {
77 return model
['short-name'];
79 if (typeof model
.name
=== 'string' && model
.name
) {
82 if (model
.uiState
&& typeof model
.uiState
.displayName
=== 'string' && model
.uiState
.displayName
) {
83 return model
.uiState
.displayName
85 if (typeof model
.id
=== 'string') {
90 export default function EditDescriptorModelProperties(props
) {
92 const container
= props
.container
;
94 if (!(DescriptorModelFactory
.isContainer(container
))) {
98 function startEditing() {
99 DeletionManager
.removeEventListeners();
102 function endEditing() {
103 DeletionManager
.addEventListeners();
106 function onClickSelectItem(property
, path
, value
, event
) {
107 event
.preventDefault();
108 const root
= this.getRoot();
109 if (SelectionManager
.select(value
)) {
110 CatalogItemsActions
.catalogItemMetaDataChanged(root
.model
);
114 function onFocusPropertyFormInputElement(property
, path
, value
, event
) {
116 event
.preventDefault();
119 function removeIsFocusedClass(event
) {
120 event
.target
.removeEventListener('blur', removeIsFocusedClass
);
121 Array
.from(document
.querySelectorAll('.-is-focused')).forEach(d
=> d
.classList
.remove('-is-focused'));
124 removeIsFocusedClass(event
);
126 const propertyWrapper
= getEventPath(event
).reduce((parent
, element
) => {
130 if (!element
.classList
) {
133 if (element
.classList
.contains('property')) {
138 if (propertyWrapper
) {
139 propertyWrapper
.classList
.add('-is-focused');
140 event
.target
.addEventListener('blur', removeIsFocusedClass
);
145 function buildAddPropertyAction(container
, property
, path
) {
146 function onClickAddProperty(property
, path
, event
) {
147 event
.preventDefault();
148 //SelectionManager.resume();
149 const create
= Property
.getContainerCreateMethod(property
, this);
152 create(model
, path
, property
);
154 const name
= path
.join('.');
155 // get a unique name for the new list item based on the current list content
156 // some lists, based on the key, may not get a uniqueName generated here
157 const uniqueName
= DescriptorModelMetaFactory
.generateItemUniqueName(container
.model
[property
.name
], property
);
158 const value
= Property
.createModelInstance(property
, uniqueName
);
159 utils
.assignPathValue(this.model
, name
, value
);
161 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
164 <Button className
="inline-hint" onClick
={onClickAddProperty
.bind(container
, property
, path
)} label
="Add" src
={imgAdd
} />
168 function buildRemovePropertyAction(container
, property
, path
) {
169 function onClickRemoveProperty(property
, path
, event
) {
170 event
.preventDefault();
171 const name
= path
.join('.');
172 const removeMethod
= Property
.getContainerMethod(property
, this, 'remove');
174 removeMethod(utils
.resolvePath(this.model
, name
));
176 utils
.removePathValue(this.model
, name
);
178 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
181 <Button className
="remove-property-action inline-hint" title
="Remove" onClick
={onClickRemoveProperty
.bind(container
, property
, path
)} label
="Remove" src
={imgRemove
}/>
185 function buildField(container
, property
, path
, value
, fieldKey
) {
186 let cds
= CatalogDataStore
;
187 let catalogs
= cds
.getTransientCatalogs();
189 const pathToProperty
= path
.join('.');
190 const isEditable
= true;
191 const isGuid
= Property
.isGuid(property
);
192 const isBoolean
= Property
.isBoolean(property
);
193 const isEnumeration
= Property
.isEnumeration(property
);
194 const isLeafRef
= Property
.isLeafRef(property
);
195 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
196 const placeholder
= changeCase
.title(property
.name
);
197 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
198 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : (isNaN(value
) ? undefined : value
);
200 // process the named field value change
201 function processFieldValueChange(name
, value
) {
202 console
.debug('processed change for -- ' + name
+ ' -- with value -- ' + value
);
203 // this = the container being edited
204 if (DescriptorModelFactory
.isContainer(this)) {
205 utils
.assignPathValue(this.model
, name
, value
);
206 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
210 // change handler used for onChange event
211 const changeHandler
= (handleValueChange
, event
) => {
212 event
.preventDefault();
213 console
.debug(event
.target
.value
);
214 handleValueChange(event
.target
.value
);
216 // create an onChange event handler for a text field for the specified field path (debounced to accumulate chars)
217 const onTextChange
= changeHandler
.bind(null, _debounce(
218 processFieldValueChange
.bind(container
, pathToProperty
), 2000, {maxWait
: 5000})); // max wait for short-name
219 // create an onChange event handler for a select field for the specified field path
220 const onSelectChange
= changeHandler
.bind(null, processFieldValueChange
.bind(container
, pathToProperty
));
223 const enumeration
= Property
.getEnumeration(property
, value
);
224 const options
= enumeration
.map((d
, i
) => {
225 // note yangforge generates values for enums but the system does not use them
226 // so we categorically ignore them
227 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
228 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
229 return <option key
={':' + i
} value
={d
.name
}>{d
.name
}</option
>;
231 const isValueSet
= enumeration
.filter(d
=> d
.isSelected
).length
> 0;
232 if (!isValueSet
|| property
.cardinality
=== '0..1') {
233 const noValueDisplayText
= changeCase
.title(property
.name
);
234 options
.unshift(<option key
={'(value-not-in-enum)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
240 className
={ClassNames({'-value-not-set': !isValueSet
})}
242 title
={pathToProperty
}
243 onChange
={onSelectChange
}
246 onMouseDown
={startEditing
}
247 onMouseOver
={startEditing
}
248 readOnly
={!isEditable
}>
255 let fullPathString
= container
.key
+ ':' + path
.join(':');
256 let containerRef
= container
;
257 while (containerRef
.parent
) {
258 fullPathString
= containerRef
.parent
.key
+ ':' + fullPathString
;
259 containerRef
= containerRef
.parent
;
261 const leafRefPathValues
= Property
.getLeafRef(property
, path
, value
, fullPathString
, catalogs
, container
);
263 const options
= leafRefPathValues
&& leafRefPathValues
.map((d
, i
) => {
264 return <option key
={':' + i
} value
={d
.value
}>{d
.value
}</option
>;
266 const isValueSet
= leafRefPathValues
.filter(d
=> d
.isSelected
).length
> 0;
267 if (!isValueSet
|| property
.cardinality
=== '0..1') {
268 const noValueDisplayText
= changeCase
.title(property
.name
);
269 options
.unshift(<option key
={'(value-not-in-leafref)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
275 className
={ClassNames({'-value-not-set': !isValueSet
})}
277 title
={pathToProperty
}
278 onChange
={onSelectChange
}
281 onMouseDown
={startEditing
}
282 onMouseOver
={startEditing
}
283 readOnly
={!isEditable
}>
291 <option key
={'true'} value
="TRUE">TRUE
</option
>,
292 <option key
={'false'} value
="FALSE">FALSE
</option
>
295 // if (!isValueSet) {
296 const noValueDisplayText
= changeCase
.title(property
.name
);
297 options
.unshift(<option key
={'(value-not-in-leafref)'} value
="" placeholder
={placeholder
}></option
>);
300 if(typeof(val
) == 'number') {
301 val
= value
? "TRUE" : "FALSE"
303 const isValueSet
= (val
!= '' && val
)
308 className
={ClassNames({'-value-not-set': !isValueSet
})}
309 defaultValue
={val
&& val
.toUpperCase()}
310 title
={pathToProperty
}
311 onChange
={onSelectChange
}
314 onMouseDown
={startEditing
}
315 onMouseOver
={startEditing
}
316 readOnly
={!isEditable
}>
322 if (Property
.isLeafEmpty(property
)) {
323 // A null value indicates the leaf exists (as opposed to undefined).
324 // We stick in a string when the user actually sets it to simplify things
325 // but the correct thing happens when we serialize to user data
326 let isEmptyLeafPresent
= (value
=== EMPTY_LEAF_PRESENT
|| value
=== null);
327 let present
= isEmptyLeafPresent
? EMPTY_LEAF_PRESENT
: "";
329 <option key
={'true'} value
={EMPTY_LEAF_PRESENT
}>Enabled
</option
>,
330 <option key
={'false'} value
="">Not Enabled
</option
>
337 className
={ClassNames({'-value-not-set': !isEmptyLeafPresent
})}
338 defaultValue
={present
}
339 title
={pathToProperty
}
340 onChange
={onSelectChange
}
343 onMouseDown
={startEditing
}
344 onMouseOver
={startEditing
}
345 readOnly
={!isEditable
}>
351 if (property
['preserve-line-breaks']) {
358 placeholder
={placeholder
}
359 onChange
={onTextChange
}
362 onMouseDown
={startEditing
}
363 onMouseOver
={startEditing
}
364 onMouseOut
={endEditing
}
365 onMouseLeave
={endEditing
}
366 readOnly
={!isEditable
} />
375 defaultValue
={fieldValue
}
376 className
={className
}
377 placeholder
={placeholder
}
378 onChange
={onTextChange
}
381 onMouseDown
={startEditing
}
382 onMouseOver
={startEditing
}
383 onMouseOut
={endEditing
}
384 onMouseLeave
={endEditing
}
385 readOnly
={!isEditable
}
392 * buiid and return an array of components representing an editor for each property.
394 * @param {any} container the master document being edited
395 * @param {[property]} properties
396 * @param {string} pathToProperties path within the container to the properties
397 * @param {Object} data source for each property
398 * @param {any} props object containing main data panel information, e.g. panel width {width: 375}
399 * which may be useful/necessary to a components rendering.
400 * @returns an array of react components
402 function buildComponentsForProperties(container
, properties
, pathToProperties
, data
, props
) {
403 return properties
.map((property
) => {
405 let propertyPath
= pathToProperties
.slice();
406 if (data
&& typeof data
=== 'object') {
407 value
= data
[property
.name
];
409 if(property
.type
!= 'choice'){
410 propertyPath
.push(property
.name
);
412 return build(container
, property
, propertyPath
, value
, props
);
416 function buildElement(container
, property
, valuePath
, value
) {
417 return buildComponentsForProperties(container
, property
.properties
, valuePath
, value
);
420 function buildChoice(container
, property
, path
, value
, key
) {
422 function processChoiceChange(name
, value
) {
423 if (DescriptorModelFactory
.isContainer(this)) {
426 Transient State is stored for convenience in the uiState field.
427 The choice yang type uses case elements to describe the "options".
428 A choice can only ever have one option selected which allows
429 the system to determine which type is selected by the name of
430 the element contained within the field.
433 const stateExample = {
448 const statePath
= ['uiState.choice'].concat(name
);
449 const stateObject
= utils
.resolvePath(this.model
, statePath
.join('.')) || {};
450 const selected
= stateObject
.selected
? stateObject
.selected
.split('.')[1] : undefined;
451 // write state back to the model so the new state objects are captured
452 utils
.assignPathValue(this.model
, statePath
.join('.'), stateObject
);
454 // write the current choice value into the state
455 let choiceObject
= utils
.resolvePath(this.model
, [name
, selected
].join('.'));
456 let isTopCase
= false;
459 choiceObject
= utils
.resolvePath(this.model
, [selected
].join('.'));
461 utils
.assignPathValue(stateObject
, [selected
].join('.'), _cloneDeep(choiceObject
));
464 if(this.model
.uiState
.choice
.hasOwnProperty(name
)) {
465 delete this.model
[selected
];
466 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
468 // remove the current choice value from the model
469 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
473 // get any state for the new selected choice
474 const newChoiceObject
= utils
.resolvePath(stateObject
, [value
].join('.')) || {};
476 // assign new choice value to the model
478 utils
.assignPathValue(this.model
, [name
, value
].join('.'), newChoiceObject
);
480 utils
.assignPathValue(this.model
, [value
].join('.'), newChoiceObject
)
483 // update the selected name
484 utils
.assignPathValue(this.model
, statePath
.concat('selected').join('.'), value
);
486 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
490 const pathToChoice
= path
.join('.');
491 const caseByNameMap
= {};
493 const choiceChangeHandler
= processChoiceChange
.bind(container
, pathToChoice
);
494 const onChange
= ((handleChoiceChange
, event
) => {
495 event
.preventDefault();
496 handleChoiceChange(event
.target
.value
);
497 }).bind(null, choiceChangeHandler
);
500 const cases
= property
.properties
.map(d
=> {
501 if (d
.type
=== 'case') {
502 //Previous it was assumed that a case choice would have only one property. Now we pass on either the only item or the
503 caseByNameMap
[d
.name
] = d
.properties
&& (d
.properties
.length
== 1 ? d
.properties
[0] : d
.properties
);
506 optionTitle
: d
.description
,
507 //represents case name and case element name
508 optionValue
: [d
.name
, d
.properties
[0].name
].join('.')
511 caseByNameMap
[d
.name
] = d
;
512 return {optionName
: d
.name
};
515 const options
= [{optionName
: '', optionValue
: false}].concat(cases
).map((d
, i
) => {
517 <option key
={i
} value
={d
.optionValue
} title
={d
.optionTitle
}>
519 {i
? null : changeCase
.title(property
.name
)}
524 let selectedOptionPath
= ['uiState.choice', pathToChoice
, 'selected'].join('.');
525 //Currently selected choice/case statement on UI model
526 let selectedOptionValue
= utils
.resolvePath(container
.model
, selectedOptionPath
);
527 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
528 if(!selectedOptionValue
) {
529 //get field properties for choice on container model
530 let fieldProperties
= utils
.resolvePath(container
.model
, pathToChoice
);
531 if(fieldProperties
) {
532 //Check each case statement in model and see if it is present in container model.
533 cases
.map(function(c
){
534 if(c
.optionValue
&& fieldProperties
.hasOwnProperty(c
.optionValue
.split('.')[1])) {
535 utils
.assignPathValue(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'), c
.optionValue
);
538 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'));
540 property
.properties
.map(function(p
) {
541 let pname
= p
.properties
[0] && p
.properties
[0].name
;
542 if(container
.model
.hasOwnProperty(pname
)) {
543 utils
.assignPathValue(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'), [p
.name
, pname
].join('.'));
546 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', pathToChoice
, 'selected'].join('.'));
549 //If selectedOptionValue is present, take first item in string which represents the case name.
550 const valueProperty
= caseByNameMap
[selectedOptionValue
? selectedOptionValue
.split('.')[0] : undefined] || {properties
: []};
551 const isLeaf
= Property
.isLeaf(valueProperty
);
552 const hasProperties
= _isArray(valueProperty
.properties
) && valueProperty
.properties
.length
;
553 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(valueProperty
);
554 //Some magic that prevents errors for arising
555 let valueResponse
= null;
556 if (valueProperty
.properties
&& valueProperty
.properties
.length
) {
557 valueResponse
= valueProperty
.properties
.map(valuePropertyFn
);
558 } else if (!isMissingDescriptorMeta
) {
559 let value
= utils
.resolvePath(container
.model
, path
.concat(valueProperty
.name
).join('.')) || container
.model
[valueProperty
.name
];
560 valueResponse
= build(container
, valueProperty
, path
.concat(valueProperty
.name
), value
)
562 valueResponse
= valueProperty
.map
&& valueProperty
.map(valuePropertyFn
);
564 function valuePropertyFn(d
, i
) {
565 const childPath
= path
.concat(valueProperty
.name
, d
.name
);
566 const childValue
= utils
.resolvePath(container
.model
, childPath
.join('.'));
568 <div key
={childPath
.concat('info', i
).join(':')}>
569 {build(container
, d
, childPath
, childValue
, props
)}
574 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
577 <div key
={key
} className
="choice">
580 className
={ClassNames({'-value-not-set': !selectedOptionValue
})}
581 defaultValue
={selectedOptionValue
}
585 onMouseDown
={startEditing
}
586 onMouseOver
={startEditing
}
587 onMouseOut
={endEditing
}
588 onMouseLeave
={endEditing
}
598 function buildSimpleListItem(container
, property
, path
, value
, uniqueId
, index
) {
599 // todo need to abstract this better
600 const title
= getTitle(value
);
601 var req
= require
.context("../", true, /\.svg
/);
603 <div key
={uniqueId
} >
604 <a href
="#select-list-item" className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
605 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
608 {buildRemovePropertyAction(container
, property
, path
)}
613 function buildRemoveListItem(container
, property
, valuePath
, index
) {
614 const className
= ClassNames(property
.name
+ '-remove actions');
616 <div className
={className
}>
618 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
619 <span className
="info">{index
+ 1}</span
>
620 {buildRemovePropertyAction(container
, property
, valuePath
)}
626 function buildLeafListItem(container
, property
, valuePath
, value
, uniqueId
, index
) {
627 // look at the type to determine how to parse the value
630 {buildRemoveListItem(container
, property
, valuePath
, index
)}
631 {buildField(container
, property
, valuePath
, value
, uniqueId
)}
637 function build(container
, property
, path
, value
, props
= {}) {
640 const isLeaf
= Property
.isLeaf(property
);
641 const isArray
= Property
.isArray(property
);
642 const isObject
= Property
.isObject(property
);
643 const isLeafList
= Property
.isLeafList(property
);
644 const isRequired
= Property
.isRequired(property
);
645 const title
= changeCase
.titleCase(property
.name
);
646 const columnCount
= property
.properties
.length
|| 1;
647 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
648 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
650 // create a unique Id for use as react component keys and html element ids
651 // use uid (from ui info) instead of id property (which is not stable)
652 let uniqueId
= container
.uid
;
653 let containerRef
= container
;
654 while (containerRef
.parent
) {
655 uniqueId
= containerRef
.parent
.uid
+ ':' + uniqueId
;
656 containerRef
= containerRef
.parent
;
658 uniqueId
+= ':' + path
.join(':')
660 if (!property
.properties
&& isObject
) {
661 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
662 property
.properties
= uiState
.properties
;
665 const hasProperties
= _isArray(property
.properties
) && property
.properties
.length
;
666 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
668 // ensure value is not undefined for non-leaf property types
670 if (typeof value
!== 'object') {
671 value
= isArray
? [] : {};
674 const valueAsArray
= _isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
676 const isMetaField
= property
.name
=== 'meta';
677 const isCVNFD
= property
.name
=== 'constituent-vnfd';
678 const isSimpleListView
= Property
.isSimpleList(property
);
680 valueAsArray
.forEach((value
, index
) => {
683 const valuePath
= path
.slice();
684 // create a unique field Id for use as react component keys and html element ids
686 // keys only need to be unique on components in the same array
687 // html element ids should be unique with the document (or form)
688 let fieldId
= uniqueId
;
691 valuePath
.push(index
);
692 fieldId
= isLeafList
? fieldId
+ index
+ value
: resolveReactKey(value
);
696 if (typeof value
=== 'object') {
697 value
= JSON
.stringify(value
, undefined, 12);
698 } else if (typeof value
!== 'string') {
703 if (isMissingDescriptorMeta
) {
704 field
= <span key
={fieldId
} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
705 } else if (property
.type
=== 'choice') {
706 field
= buildChoice(container
, property
, valuePath
, value
, fieldId
);
707 } else if (isSimpleListView
) {
708 field
= buildSimpleListItem(container
, property
, valuePath
, value
, fieldId
, index
);
709 } else if (isLeafList
) {
710 field
= buildLeafListItem(container
, property
, valuePath
, value
, fieldId
, index
);
711 } else if (hasProperties
) {
712 field
= buildElement(container
, property
, valuePath
, value
, fieldId
)
714 field
= buildField(container
, property
, valuePath
, value
, fieldId
);
717 function onClickLeaf(property
, path
, value
, event
) {
718 if (event
.isDefaultPrevented()) {
721 event
.preventDefault();
722 event
.stopPropagation();
723 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
724 console
.debug('property selected', path
.join('.'));
725 ComposerAppActions
.propertySelected([path
.join('.')]);
728 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
729 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
733 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
734 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
735 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, index
) : null}
742 classNames
['-is-leaf'] = isLeaf
;
743 classNames
['-is-array'] = isArray
;
744 classNames
['cols-' + columnCount
] = isColumnar
;
746 if (property
.type
=== 'choice') {
747 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
749 property
.properties
.map(function(p
) {
750 let pname
= p
.properties
[0] && p
.properties
[0].name
;
751 if(container
.model
.hasOwnProperty(pname
)) {
752 value
= container
.model
[pname
];
758 let displayValue
= typeof value
=== 'object' ? '' : value
;
759 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
761 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
764 <div key
={uniqueId
} className
={ClassNames(property
.type
+ '-property property', classNames
)} onFocus
={onFocus
}>
765 <h3 className
="property-label">
766 <label htmlFor
={uniqueId
}>
767 <span className
={property
.type
+ '-name name'}>{title
}</span
>
769 <span className
={property
.type
+ '-info info'}>{displayValueInfo
}</span
>
770 <span className
={property
.type
+ '-value value'}>{displayValue
}</span
>
772 {isArray
? buildAddPropertyAction(container
, property
, path
.concat(valueAsArray
.length
)) : null}
775 <span className
={property
.type
+ '-description description'}>{property
.description
}</span
>
776 <val className
="property-value">
777 {isCVNFD
? <span className
={property
.type
+ '-tip tip'}>Drag a VNFD
from the Catalog to add more
.</span
> : null}
785 const containerType
= container
.uiState
['qualified-type'] || container
.uiState
.type
;
786 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
788 function buildBasicGroup() {
789 if (basicProperties
.length
=== 0) {
793 <div className
="basic-properties-group">
796 {buildComponentsForProperties(container
, basicProperties
, [], container
.model
)}
802 function buildAdvancedGroup() {
803 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
804 if (properties
.length
=== 0) {
807 const hasBasicFields
= basicProperties
.length
> 0;
808 const closeGroup
= basicProperties
.length
> 0;
810 <div className
="advanced-properties-group">
811 <h1 data
-toggle
={closeGroup
? 'true' : 'false'} className
={ClassNames({'-is-toggled': closeGroup
})} onClick
={toggle
} style
={{display
: hasBasicFields
? 'block' : 'none'}}>
812 <a className
="toggle-show-more" href
="#show-more-properties">more
&hellip
;</a
>
813 <a className
="toggle-show-less" href
="#show-more-properties">less
&hellip
;</a
>
815 <div className
="toggleable">
816 {buildComponentsForProperties(container
, properties
, [], container
.model
, {toggle
: true, width
: props
.width
})}
818 <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>
823 function buildMoreLess(d, i) {
825 <span key={'bread
-crumb
-part
-' + i}>
826 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
833 if (container.parent) {
834 path.push(container.parent);
836 path.push(container);
839 <div className="EditDescriptorModelProperties -is-tree-view">
840 <h1>{path.map(buildMoreLess)}</h1>
842 {buildAdvancedGroup()}