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 _includes
from 'lodash/includes'
26 import _isArray
from 'lodash/isArray'
27 import _cloneDeep
from 'lodash/cloneDeep'
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 function getDescriptorMetaBasicForType(type
) {
52 const basicPropertiesFilter
= d
=> _includes(DESCRIPTOR_MODEL_FIELDS
[type
], d
.name
);
53 return DescriptorModelMetaFactory
.getModelMetaForType(type
, basicPropertiesFilter
) || {properties
: []};
56 function getDescriptorMetaAdvancedForType(type
) {
57 const advPropertiesFilter
= d
=> !_includes(DESCRIPTOR_MODEL_FIELDS
[type
], d
.name
);
58 return DescriptorModelMetaFactory
.getModelMetaForType(type
, advPropertiesFilter
) || {properties
: []};
61 function getTitle(model
= {}) {
62 if (typeof model
['short-name'] === 'string' && model
['short-name']) {
63 return model
['short-name'];
65 if (typeof model
.name
=== 'string' && model
.name
) {
68 if (model
.uiState
&& typeof model
.uiState
.displayName
=== 'string' && model
.uiState
.displayName
) {
69 return model
.uiState
.displayName
71 if (typeof model
.id
=== 'string') {
76 export default function EditDescriptorModelProperties(props
) {
78 const container
= props
.container
;
80 if (!(DescriptorModelFactory
.isContainer(container
))) {
84 function startEditing() {
85 DeletionManager
.removeEventListeners();
88 function endEditing() {
89 DeletionManager
.addEventListeners();
92 function onClickSelectItem(property
, path
, value
, event
) {
93 event
.preventDefault();
94 const root
= this.getRoot();
95 if (SelectionManager
.select(value
)) {
96 CatalogItemsActions
.catalogItemMetaDataChanged(root
.model
);
100 function onFocusPropertyFormInputElement(property
, path
, value
, event
) {
102 event
.preventDefault();
105 function removeIsFocusedClass(event
) {
106 event
.target
.removeEventListener('blur', removeIsFocusedClass
);
107 Array
.from(document
.querySelectorAll('.-is-focused')).forEach(d
=> d
.classList
.remove('-is-focused'));
110 removeIsFocusedClass(event
);
112 const propertyWrapper
= getEventPath(event
).reduce((parent
, element
) => {
116 if (!element
.classList
) {
119 if (element
.classList
.contains('property')) {
124 if (propertyWrapper
) {
125 propertyWrapper
.classList
.add('-is-focused');
126 event
.target
.addEventListener('blur', removeIsFocusedClass
);
131 function buildAddPropertyAction(container
, property
, path
) {
132 function onClickAddProperty(property
, path
, event
) {
133 event
.preventDefault();
134 //SelectionManager.resume();
135 const create
= Property
.getContainerCreateMethod(property
, this);
138 create(model
, path
, property
);
140 const name
= path
.join('.');
141 // get a unique name for the new list item based on the current list content
142 // some lists, based on the key, may not get a uniqueName generated here
143 const uniqueName
= DescriptorModelMetaFactory
.generateItemUniqueName(container
.model
[property
.name
], property
);
144 const value
= Property
.createModelInstance(property
, uniqueName
);
145 utils
.assignPathValue(this.model
, name
, value
);
147 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
150 <Button className
="inline-hint" onClick
={onClickAddProperty
.bind(container
, property
, path
)} label
="Add" src
={imgAdd
} />
154 function buildRemovePropertyAction(container
, property
, path
) {
155 function onClickRemoveProperty(property
, path
, event
) {
156 event
.preventDefault();
157 const name
= path
.join('.');
158 const removeMethod
= Property
.getContainerMethod(property
, this, 'remove');
160 removeMethod(utils
.resolvePath(this.model
, name
));
162 utils
.removePathValue(this.model
, name
);
164 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
167 <Button className
="remove-property-action inline-hint" title
="Remove" onClick
={onClickRemoveProperty
.bind(container
, property
, path
)} label
="Remove" src
={imgRemove
}/>
171 function onFormFieldValueChanged(event
) {
172 if (DescriptorModelFactory
.isContainer(this)) {
173 event
.preventDefault();
174 const name
= event
.target
.name
;
175 const value
= event
.target
.value
;
176 utils
.assignPathValue(this.model
, name
, value
);
177 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
181 function buildField(container
, property
, path
, value
, fieldId
) {
182 let cds
= CatalogDataStore
;
183 let catalogs
= cds
.getTransientCatalogs();
185 const pathToProperty
= path
.join('.');
186 const isEditable
= true;
187 const isGuid
= Property
.isGuid(property
);
188 const isBoolean
= Property
.isBoolean(property
);
189 const onChange
= onFormFieldValueChanged
.bind(container
);
190 const isEnumeration
= Property
.isEnumeration(property
);
191 const isLeafRef
= Property
.isLeafRef(property
);
192 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
193 const placeholder
= changeCase
.title(property
.name
);
194 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
195 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : (isNaN(value
) ? undefined : value
);
197 const enumeration
= Property
.getEnumeration(property
, value
);
198 const options
= enumeration
.map((d
, i
) => {
199 // note yangforge generates values for enums but the system does not use them
200 // so we categorically ignore them
201 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
202 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
203 return <option key
={':' + i
} value
={d
.name
}>{d
.name
}</option
>;
205 const isValueSet
= enumeration
.filter(d
=> d
.isSelected
).length
> 0;
206 if (!isValueSet
|| property
.cardinality
=== '0..1') {
207 const noValueDisplayText
= changeCase
.title(property
.name
);
208 options
.unshift(<option key
={'(value-not-in-enum)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
214 name
={pathToProperty
}
215 className
={ClassNames({'-value-not-set': !isValueSet
})}
217 title
={pathToProperty
}
221 onMouseDown
={startEditing
}
222 onMouseOver
={startEditing
}
223 readOnly
={!isEditable
}>
230 let fullPathString
= container
.key
+ ':' + path
.join(':');
231 let containerRef
= container
;
232 while (containerRef
.parent
) {
233 fullPathString
= containerRef
.parent
.key
+ ':' + fullPathString
;
234 containerRef
= containerRef
.parent
;
236 const leafRefPathValues
= Property
.getLeafRef(property
, path
, value
, fullPathString
, catalogs
, container
);
238 const options
= leafRefPathValues
&& leafRefPathValues
.map((d
, i
) => {
239 return <option key
={':' + i
} value
={d
.value
}>{d
.value
}</option
>;
241 const isValueSet
= leafRefPathValues
.filter(d
=> d
.isSelected
).length
> 0;
242 if (!isValueSet
|| property
.cardinality
=== '0..1') {
243 const noValueDisplayText
= changeCase
.title(property
.name
);
244 options
.unshift(<option key
={'(value-not-in-leafref)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
250 name
={pathToProperty
}
251 className
={ClassNames({'-value-not-set': !isValueSet
})}
253 title
={pathToProperty
}
257 onMouseDown
={startEditing
}
258 onMouseOver
={startEditing
}
259 readOnly
={!isEditable
}>
267 <option key
={'true'} value
="TRUE">TRUE
</option
>,
268 <option key
={'false'} value
="FALSE">FALSE
</option
>
271 // if (!isValueSet) {
272 const noValueDisplayText
= changeCase
.title(property
.name
);
273 options
.unshift(<option key
={'(value-not-in-leafref)'} value
="" placeholder
={placeholder
}></option
>);
276 if(typeof(val
) == 'number') {
277 val
= value
? "TRUE" : "FALSE"
279 const isValueSet
= (val
!= '' && val
)
284 name
={pathToProperty
}
285 className
={ClassNames({'-value-not-set': !isValueSet
})}
286 value
={val
&& val
.toUpperCase()} title
={pathToProperty
}
287 onChange
={onChange
} onFocus
={onFocus
}
289 onMouseDown
={startEditing
}
290 onMouseOver
={startEditing
}
291 readOnly
={!isEditable
}>
297 if (property
['preserve-line-breaks']) {
303 name
={pathToProperty
}
305 placeholder
={placeholder
}
309 onMouseDown
={startEditing
}
310 onMouseOver
={startEditing
}
311 onMouseOut
={endEditing
}
312 onMouseLeave
={endEditing
}
313 readOnly
={!isEditable
} />
321 name
={pathToProperty
}
324 className
={className
}
325 placeholder
={placeholder
}
329 onMouseDown
={startEditing
}
330 onMouseOver
={startEditing
}
331 onMouseOut
={endEditing
}
332 onMouseLeave
={endEditing
}
333 readOnly
={!isEditable
}
339 function buildElement(container
, property
, valuePath
, value
) {
340 return property
.properties
.map((property
, index
) => {
342 const childPath
= valuePath
.slice();
343 if (typeof value
=== 'object') {
344 childValue
= value
[property
.name
];
346 if(property
.type
!= 'choice'){
347 childPath
.push(property
.name
);
349 return build(container
, property
, childPath
, childValue
);
354 function buildChoice(container
, property
, path
, value
, fieldId
) {
355 function onFormFieldValueChanged(event
) {
356 if (DescriptorModelFactory
.isContainer(this)) {
358 event
.preventDefault();
360 let name
= event
.target
.name
;
361 const value
= event
.target
.value
;
365 Transient State is stored for convenience in the uiState field.
366 The choice yang type uses case elements to describe the "options".
367 A choice can only ever have one option selected which allows
368 the system to determine which type is selected by the name of
369 the element contained within the field.
372 const stateExample = {
387 const statePath
= ['uiState.choice'].concat(name
);
388 const stateObject
= utils
.resolvePath(this.model
, statePath
.join('.')) || {};
389 const selected
= stateObject
.selected
? stateObject
.selected
.split('.')[1] : undefined;
390 // write state back to the model so the new state objects are captured
391 utils
.assignPathValue(this.model
, statePath
.join('.'), stateObject
);
393 // write the current choice value into the state
394 let choiceObject
= utils
.resolvePath(this.model
, [name
, selected
].join('.'));
395 let isTopCase
= false;
398 choiceObject
= utils
.resolvePath(this.model
, [selected
].join('.'));
400 utils
.assignPathValue(stateObject
, [selected
].join('.'), _cloneDeep(choiceObject
));
403 if(this.model
.uiState
.choice
.hasOwnProperty(name
)) {
404 delete this.model
[selected
];
405 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
407 // remove the current choice value from the model
408 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
412 // get any state for the new selected choice
413 const newChoiceObject
= utils
.resolvePath(stateObject
, [value
].join('.')) || {};
415 // assign new choice value to the model
417 utils
.assignPathValue(this.model
, [name
, value
].join('.'), newChoiceObject
);
419 utils
.assignPathValue(this.model
, [value
].join('.'), newChoiceObject
)
423 // update the selected name
424 utils
.assignPathValue(this.model
, statePath
.concat('selected').join('.'), value
);
426 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
430 const caseByNameMap
= {};
432 const onChange
= onFormFieldValueChanged
.bind(container
);
434 const cases
= property
.properties
.map(d
=> {
435 if (d
.type
=== 'case') {
436 //Previous it was assumed that a case choice would have only one property. Now we pass on either the only item or the
437 caseByNameMap
[d
.name
] = d
.properties
&& (d
.properties
.length
== 1 ? d
.properties
[0] : d
.properties
);
440 optionTitle
: d
.description
,
441 //represents case name and case element name
442 optionValue
: [d
.name
, d
.properties
[0].name
].join('.')
445 caseByNameMap
[d
.name
] = d
;
446 return {optionName
: d
.name
};
449 const options
= [{optionName
: '', optionValue
: false}].concat(cases
).map((d
, i
) => {
451 <option key
={i
} value
={d
.optionValue
} title
={d
.optionTitle
}>
453 {i
? null : changeCase
.title(property
.name
)}
458 const selectName
= path
.join('.');
459 let selectedOptionPath
= ['uiState.choice', selectName
, 'selected'].join('.');
460 //Currently selected choice/case statement on UI model
461 let selectedOptionValue
= utils
.resolvePath(container
.model
, selectedOptionPath
);
462 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
463 if(!selectedOptionValue
) {
464 //get field properties for choice on container model
465 let fieldProperties
= utils
.resolvePath(container
.model
, selectName
);
466 if(fieldProperties
) {
467 //Check each case statement in model and see if it is present in container model.
468 cases
.map(function(c
){
469 if(fieldProperties
.hasOwnProperty(c
.optionValue
.split('.')[1])) {
470 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), c
.optionValue
);
473 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
475 property
.properties
.map(function(p
) {
476 let pname
= p
.properties
[0].name
;
477 if(container
.model
.hasOwnProperty(pname
)) {
478 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), [p
.name
, pname
].join('.'));
481 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
484 //If selectedOptionValue is present, take first item in string which represents the case name.
485 const valueProperty
= caseByNameMap
[selectedOptionValue
? selectedOptionValue
.split('.')[0] : undefined] || {properties
: []};
486 const isLeaf
= Property
.isLeaf(valueProperty
);
487 const hasProperties
= _isArray(valueProperty
.properties
) && valueProperty
.properties
.length
;
488 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(valueProperty
);
489 //Some magic that prevents errors for arising
490 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
]) :
491 valueProperty
.map
&& valueProperty
.map(valuePropertyFn
);
492 function valuePropertyFn(d
, i
) {
493 const childPath
= path
.concat(valueProperty
.name
, d
.name
);
494 const childValue
= utils
.resolvePath(container
.model
, childPath
.join('.'));
496 <div key
={childPath
.concat('info', i
).join(':')}>
497 {build(container
, d
, childPath
, childValue
, props
)}
502 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
505 <div key
={fieldId
} className
="choice">
506 <select id
={fieldId
} className
={ClassNames({'-value-not-set': !selectedOptionValue
})} name
={selectName
} value
={selectedOptionValue
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} onMouseOut
={endEditing
} onMouseLeave
={endEditing
}>
515 function buildSimpleListItem(container
, property
, path
, value
, uniqueId
, index
) {
516 // todo need to abstract this better
517 const title
= getTitle(value
);
518 var req
= require
.context("../", true, /\.svg
/);
520 <div key
={uniqueId
} >
521 <a href
="#select-list-item" className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
522 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
525 {buildRemovePropertyAction(container
, property
, path
)}
530 function buildRemoveListItem(container
, property
, valuePath
, index
) {
531 const className
= ClassNames(property
.name
+ '-remove actions');
533 <div className
={className
}>
535 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
536 <span className
="info">{index
+ 1}</span
>
537 {buildRemovePropertyAction(container
, property
, valuePath
)}
543 function buildLeafListItem(container
, property
, valuePath
, value
, index
) {
544 // look at the type to determine how to parse the value
547 {buildRemoveListItem(container
, property
, valuePath
, index
)}
548 {buildField(container
, property
, valuePath
, value
, uniqueId
)}
554 function build(container
, property
, path
, value
, props
= {}) {
557 const isLeaf
= Property
.isLeaf(property
);
558 const isArray
= Property
.isArray(property
);
559 const isObject
= Property
.isObject(property
);
560 const isLeafList
= Property
.isLeafList(property
);
561 const isRequired
= Property
.isRequired(property
);
562 const title
= changeCase
.titleCase(property
.name
);
563 const columnCount
= property
.properties
.length
|| 1;
564 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
565 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
567 // create a unique Id for use as react component keys and html element ids
568 // use uid (from ui info) instead of id property (which is not stable)
569 let uniqueId
= container
.uid
;
570 let containerRef
= container
;
571 while (containerRef
.parent
) {
572 uniqueId
= containerRef
.parent
.uid
+ ':' + uniqueId
;
573 containerRef
= containerRef
.parent
;
575 uniqueId
+= ':' + path
.join(':')
577 if (!property
.properties
&& isObject
) {
578 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
579 property
.properties
= uiState
.properties
;
582 const hasProperties
= _isArray(property
.properties
) && property
.properties
.length
;
583 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
585 // ensure value is not undefined for non-leaf property types
587 if (typeof value
!== 'object') {
588 value
= isArray
? [] : {};
591 const valueAsArray
= _isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
593 const isMetaField
= property
.name
=== 'meta';
594 const isCVNFD
= property
.name
=== 'constituent-vnfd';
595 const isSimpleListView
= Property
.isSimpleList(property
);
597 valueAsArray
.forEach((value
, index
) => {
600 const valuePath
= path
.slice();
601 // create a unique field Id for use as react component keys and html element ids
603 // keys only need to be unique on components in the same array
604 // html element ids should be unique with the document (or form)
605 let fieldId
= uniqueId
;
608 valuePath
.push(index
);
613 if (typeof value
=== 'object') {
614 value
= JSON
.stringify(value
, undefined, 12);
615 } else if (typeof value
!== 'string') {
620 if (isMissingDescriptorMeta
) {
621 field
= <span key
={fieldId
} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
622 } else if (property
.type
=== 'choice') {
623 field
= buildChoice(container
, property
, valuePath
, value
, fieldId
);
624 } else if (isSimpleListView
) {
625 field
= buildSimpleListItem(container
, property
, valuePath
, value
, fieldId
, index
);
626 } else if (isLeafList
) {
627 field
= buildLeafListItem(container
, property
, valuePath
, value
, fieldId
, index
);
628 } else if (hasProperties
) {
629 field
= buildElement(container
, property
, valuePath
, value
, fieldId
)
631 field
= buildField(container
, property
, valuePath
, value
, fieldId
);
634 function onClickLeaf(property
, path
, value
, event
) {
635 if (event
.isDefaultPrevented()) {
638 event
.preventDefault();
639 event
.stopPropagation();
640 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
641 console
.log('property selected', path
.join('.'));
642 ComposerAppActions
.propertySelected([path
.join('.')]);
645 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
646 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
650 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
651 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
652 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, index
) : null}
659 classNames
['-is-leaf'] = isLeaf
;
660 classNames
['-is-array'] = isArray
;
661 classNames
['cols-' + columnCount
] = isColumnar
;
663 if (property
.type
=== 'choice') {
664 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
666 property
.properties
.map(function(p
) {
667 let pname
= p
.properties
[0].name
;
668 if(container
.model
.hasOwnProperty(pname
)) {
669 value
= container
.model
[pname
];
675 let displayValue
= typeof value
=== 'object' ? '' : value
;
676 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
678 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
681 <div key
={uniqueId
} className
={ClassNames(property
.type
+ '-property property', classNames
)} onFocus
={onFocus
}>
682 <h3 className
="property-label">
683 <label htmlFor
={uniqueId
}>
684 <span className
={property
.type
+ '-name name'}>{title
}</span
>
686 <span className
={property
.type
+ '-info info'}>{displayValueInfo
}</span
>
687 <span className
={property
.type
+ '-value value'}>{displayValue
}</span
>
689 {isArray
? buildAddPropertyAction(container
, property
, path
.concat(valueAsArray
.length
)) : null}
692 <span className
={property
.type
+ '-description description'}>{property
.description
}</span
>
693 <val className
="property-value">
694 {isCVNFD
? <span className
={property
.type
+ '-tip tip'}>Drag a VNFD
from the Catalog to add more
.</span
> : null}
702 const containerType
= container
.uiState
['qualified-type'] || container
.uiState
.type
;
703 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
705 function buildBasicGroup() {
706 if (basicProperties
.length
=== 0) {
710 <div className
="basic-properties-group">
713 {basicProperties
.map(property
=> {
714 const path
= [property
.name
];
715 const value
= container
.model
[property
.name
];
716 return build(container
, property
, path
, value
);
723 function buildAdvancedGroup() {
724 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
725 if (properties
.length
=== 0) {
728 const hasBasicFields
= basicProperties
.length
> 0;
729 const closeGroup
= basicProperties
.length
> 0;
731 <div className
="advanced-properties-group">
732 <h1 data
-toggle
={closeGroup
? 'true' : 'false'} className
={ClassNames({'-is-toggled': closeGroup
})} onClick
={toggle
} style
={{display
: hasBasicFields
? 'block' : 'none'}}>
733 <a className
="toggle-show-more" href
="#show-more-properties">more
&hellip
;</a
>
734 <a className
="toggle-show-less" href
="#show-more-properties">less
&hellip
;</a
>
736 <div className
="toggleable">
737 {properties
.map(property
=> {
738 const path
= [property
.name
];
739 const value
= container
.model
[property
.name
];
740 return build(container
, property
, path
, value
, {toggle
: true, width
: props
.width
});
743 <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>
748 function buildMoreLess(d, i) {
750 <span key={'bread
-crumb
-part
-' + i}>
751 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
758 if (container.parent) {
759 path.push(container.parent);
761 path.push(container);
764 <div className="EditDescriptorModelProperties -is-tree-view">
765 <h1>{path.map(buildMoreLess)}</h1>
767 {buildAdvancedGroup()}