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 const value
= Property
.createModelInstance(property
);
142 utils
.assignPathValue(this.model
, name
, value
);
144 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
147 <Button className
="inline-hint" onClick
={onClickAddProperty
.bind(container
, property
, path
)} label
="Add" src
={imgAdd
} />
151 function buildRemovePropertyAction(container
, property
, path
) {
152 function onClickRemoveProperty(property
, path
, event
) {
153 event
.preventDefault();
154 const name
= path
.join('.');
155 const removeMethod
= Property
.getContainerMethod(property
, this, 'remove');
157 removeMethod(utils
.resolvePath(this.model
, name
));
159 utils
.removePathValue(this.model
, name
);
161 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
164 <Button className
="remove-property-action inline-hint" title
="Remove" onClick
={onClickRemoveProperty
.bind(container
, property
, path
)} label
="Remove" src
={imgRemove
}/>
168 function onFormFieldValueChanged(event
) {
169 if (DescriptorModelFactory
.isContainer(this)) {
170 event
.preventDefault();
171 const name
= event
.target
.name
;
172 const value
= event
.target
.value
;
173 utils
.assignPathValue(this.model
, name
, value
);
174 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
178 function buildField(container
, property
, path
, value
, fieldId
) {
179 let cds
= CatalogDataStore
;
180 let catalogs
= cds
.getTransientCatalogs();
182 const pathToProperty
= path
.join('.');
183 const isEditable
= true;
184 const isGuid
= Property
.isGuid(property
);
185 const isBoolean
= Property
.isBoolean(property
);
186 const onChange
= onFormFieldValueChanged
.bind(container
);
187 const isEnumeration
= Property
.isEnumeration(property
);
188 const isLeafRef
= Property
.isLeafRef(property
);
189 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
190 const placeholder
= changeCase
.title(property
.name
);
191 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
192 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : (isNaN(value
) ? undefined : value
);
194 const enumeration
= Property
.getEnumeration(property
, value
);
195 const options
= enumeration
.map((d
, i
) => {
196 // note yangforge generates values for enums but the system does not use them
197 // so we categorically ignore them
198 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
199 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
200 return <option key
={':' + i
} value
={d
.name
}>{d
.name
}</option
>;
202 const isValueSet
= enumeration
.filter(d
=> d
.isSelected
).length
> 0;
203 if (!isValueSet
|| property
.cardinality
=== '0..1') {
204 const noValueDisplayText
= changeCase
.title(property
.name
);
205 options
.unshift(<option key
={'(value-not-in-enum)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
211 name
={pathToProperty
}
212 className
={ClassNames({'-value-not-set': !isValueSet
})}
214 title
={pathToProperty
}
218 onMouseDown
={startEditing
}
219 onMouseOver
={startEditing
}
220 readOnly
={!isEditable
}>
227 let fullPathString
= container
.key
+ ':' + path
.join(':');
228 let containerRef
= container
;
229 while (containerRef
.parent
) {
230 fullPathString
= containerRef
.parent
.key
+ ':' + fullPathString
;
231 containerRef
= containerRef
.parent
;
233 const leafRefPathValues
= Property
.getLeafRef(property
, path
, value
, fullPathString
, catalogs
, container
);
235 const options
= leafRefPathValues
&& leafRefPathValues
.map((d
, i
) => {
236 return <option key
={':' + i
} value
={d
.value
}>{d
.value
}</option
>;
238 const isValueSet
= leafRefPathValues
.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-leafref)'} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
247 name
={pathToProperty
}
248 className
={ClassNames({'-value-not-set': !isValueSet
})}
250 title
={pathToProperty
}
254 onMouseDown
={startEditing
}
255 onMouseOver
={startEditing
}
256 readOnly
={!isEditable
}>
264 <option key
={'true'} value
="TRUE">TRUE
</option
>,
265 <option key
={'false'} value
="FALSE">FALSE
</option
>
268 // if (!isValueSet) {
269 const noValueDisplayText
= changeCase
.title(property
.name
);
270 options
.unshift(<option key
={'(value-not-in-leafref)'} value
="" placeholder
={placeholder
}></option
>);
273 if(typeof(val
) == 'number') {
274 val
= value
? "TRUE" : "FALSE"
276 const isValueSet
= (val
!= '' && val
)
281 name
={pathToProperty
}
282 className
={ClassNames({'-value-not-set': !isValueSet
})}
283 value
={val
&& val
.toUpperCase()} title
={pathToProperty
}
284 onChange
={onChange
} onFocus
={onFocus
}
286 onMouseDown
={startEditing
}
287 onMouseOver
={startEditing
}
288 readOnly
={!isEditable
}>
294 if (property
['preserve-line-breaks']) {
300 name
={pathToProperty
}
302 placeholder
={placeholder
}
306 onMouseDown
={startEditing
}
307 onMouseOver
={startEditing
}
308 onMouseOut
={endEditing
}
309 onMouseLeave
={endEditing
}
310 readOnly
={!isEditable
} />
318 name
={pathToProperty
}
321 className
={className
}
322 placeholder
={placeholder
}
326 onMouseDown
={startEditing
}
327 onMouseOver
={startEditing
}
328 onMouseOut
={endEditing
}
329 onMouseLeave
={endEditing
}
330 readOnly
={!isEditable
}
336 function buildElement(container
, property
, valuePath
, value
) {
337 return property
.properties
.map((property
, index
) => {
339 const childPath
= valuePath
.slice();
340 if (typeof value
=== 'object') {
341 childValue
= value
[property
.name
];
343 if(property
.type
!= 'choice'){
344 childPath
.push(property
.name
);
346 return build(container
, property
, childPath
, childValue
);
351 function buildChoice(container
, property
, path
, value
, fieldId
) {
352 function onFormFieldValueChanged(event
) {
353 if (DescriptorModelFactory
.isContainer(this)) {
355 event
.preventDefault();
357 let name
= event
.target
.name
;
358 const value
= event
.target
.value
;
362 Transient State is stored for convenience in the uiState field.
363 The choice yang type uses case elements to describe the "options".
364 A choice can only ever have one option selected which allows
365 the system to determine which type is selected by the name of
366 the element contained within the field.
369 const stateExample = {
384 const statePath
= ['uiState.choice'].concat(name
);
385 const stateObject
= utils
.resolvePath(this.model
, statePath
.join('.')) || {};
386 const selected
= stateObject
.selected
? stateObject
.selected
.split('.')[1] : undefined;
387 // write state back to the model so the new state objects are captured
388 utils
.assignPathValue(this.model
, statePath
.join('.'), stateObject
);
390 // write the current choice value into the state
391 let choiceObject
= utils
.resolvePath(this.model
, [name
, selected
].join('.'));
392 let isTopCase
= false;
395 choiceObject
= utils
.resolvePath(this.model
, [selected
].join('.'));
397 utils
.assignPathValue(stateObject
, [selected
].join('.'), _cloneDeep(choiceObject
));
400 if(this.model
.uiState
.choice
.hasOwnProperty(name
)) {
401 delete this.model
[selected
];
402 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
404 // remove the current choice value from the model
405 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
409 // get any state for the new selected choice
410 const newChoiceObject
= utils
.resolvePath(stateObject
, [value
].join('.')) || {};
412 // assign new choice value to the model
414 utils
.assignPathValue(this.model
, [name
, value
].join('.'), newChoiceObject
);
416 utils
.assignPathValue(this.model
, [value
].join('.'), newChoiceObject
)
420 // update the selected name
421 utils
.assignPathValue(this.model
, statePath
.concat('selected').join('.'), value
);
423 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
427 const caseByNameMap
= {};
429 const onChange
= onFormFieldValueChanged
.bind(container
);
431 const cases
= property
.properties
.map(d
=> {
432 if (d
.type
=== 'case') {
433 //Previous it was assumed that a case choice would have only one property. Now we pass on either the only item or the
434 caseByNameMap
[d
.name
] = d
.properties
&& (d
.properties
.length
== 1 ? d
.properties
[0] : d
.properties
);
437 optionTitle
: d
.description
,
438 //represents case name and case element name
439 optionValue
: [d
.name
, d
.properties
[0].name
].join('.')
442 caseByNameMap
[d
.name
] = d
;
443 return {optionName
: d
.name
};
446 const options
= [{optionName
: '', optionValue
: false}].concat(cases
).map((d
, i
) => {
448 <option key
={i
} value
={d
.optionValue
} title
={d
.optionTitle
}>
450 {i
? null : changeCase
.title(property
.name
)}
455 const selectName
= path
.join('.');
456 let selectedOptionPath
= ['uiState.choice', selectName
, 'selected'].join('.');
457 //Currently selected choice/case statement on UI model
458 let selectedOptionValue
= utils
.resolvePath(container
.model
, selectedOptionPath
);
459 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
460 if(!selectedOptionValue
) {
461 //get field properties for choice on container model
462 let fieldProperties
= utils
.resolvePath(container
.model
, selectName
);
463 if(fieldProperties
) {
464 //Check each case statement in model and see if it is present in container model.
465 cases
.map(function(c
){
466 if(fieldProperties
.hasOwnProperty(c
.optionValue
.split('.')[1])) {
467 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), c
.optionValue
);
470 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
472 property
.properties
.map(function(p
) {
473 let pname
= p
.properties
[0].name
;
474 if(container
.model
.hasOwnProperty(pname
)) {
475 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), [p
.name
, pname
].join('.'));
478 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
481 //If selectedOptionValue is present, take first item in string which represents the case name.
482 const valueProperty
= caseByNameMap
[selectedOptionValue
? selectedOptionValue
.split('.')[0] : undefined] || {properties
: []};
483 const isLeaf
= Property
.isLeaf(valueProperty
);
484 const hasProperties
= _isArray(valueProperty
.properties
) && valueProperty
.properties
.length
;
485 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(valueProperty
);
486 //Some magic that prevents errors for arising
487 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
]) :
488 valueProperty
.map
&& valueProperty
.map(valuePropertyFn
);
489 function valuePropertyFn(d
, i
) {
490 const childPath
= path
.concat(valueProperty
.name
, d
.name
);
491 const childValue
= utils
.resolvePath(container
.model
, childPath
.join('.'));
493 <div key
={childPath
.concat('info', i
).join(':')}>
494 {build(container
, d
, childPath
, childValue
, props
)}
499 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
502 <div key
={fieldId
} className
="choice">
503 <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
}>
512 function buildSimpleListItem(container
, property
, path
, value
, uniqueId
, index
) {
513 // todo need to abstract this better
514 const title
= getTitle(value
);
515 var req
= require
.context("../", true, /\.svg
/);
517 <div key
={uniqueId
} >
518 <a href
="#select-list-item" className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
519 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
522 {buildRemovePropertyAction(container
, property
, path
)}
527 function buildRemoveListItem(container
, property
, valuePath
, index
) {
528 const className
= ClassNames(property
.name
+ '-remove actions');
530 <div className
={className
}>
532 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
533 <span className
="info">{index
+ 1}</span
>
534 {buildRemovePropertyAction(container
, property
, valuePath
)}
540 function buildLeafListItem(container
, property
, valuePath
, value
, index
) {
541 // look at the type to determine how to parse the value
544 {buildRemoveListItem(container
, property
, valuePath
, index
)}
545 {buildField(container
, property
, valuePath
, value
, uniqueId
)}
551 function build(container
, property
, path
, value
, props
= {}) {
554 const isLeaf
= Property
.isLeaf(property
);
555 const isArray
= Property
.isArray(property
);
556 const isObject
= Property
.isObject(property
);
557 const isLeafList
= Property
.isLeafList(property
);
558 const isRequired
= Property
.isRequired(property
);
559 const title
= changeCase
.titleCase(property
.name
);
560 const columnCount
= property
.properties
.length
|| 1;
561 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
562 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
564 // create a unique Id for use as react component keys and html element ids
565 // use uid (from ui info) instead of id property (which is not stable)
566 let uniqueId
= container
.uid
;
567 let containerRef
= container
;
568 while (containerRef
.parent
) {
569 uniqueId
= containerRef
.parent
.uid
+ ':' + uniqueId
;
570 containerRef
= containerRef
.parent
;
572 uniqueId
+= ':' + path
.join(':')
574 if (!property
.properties
&& isObject
) {
575 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
576 property
.properties
= uiState
.properties
;
579 const hasProperties
= _isArray(property
.properties
) && property
.properties
.length
;
580 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
582 // ensure value is not undefined for non-leaf property types
584 if (typeof value
!== 'object') {
585 value
= isArray
? [] : {};
588 const valueAsArray
= _isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
590 const isMetaField
= property
.name
=== 'meta';
591 const isCVNFD
= property
.name
=== 'constituent-vnfd';
592 const isSimpleListView
= Property
.isSimpleList(property
);
594 valueAsArray
.forEach((value
, index
) => {
597 const valuePath
= path
.slice();
598 // create a unique field Id for use as react component keys and html element ids
600 // keys only need to be unique on components in the same array
601 // html element ids should be unique with the document (or form)
602 let fieldId
= uniqueId
;
605 valuePath
.push(index
);
610 if (typeof value
=== 'object') {
611 value
= JSON
.stringify(value
, undefined, 12);
612 } else if (typeof value
!== 'string') {
617 if (isMissingDescriptorMeta
) {
618 field
= <span key
={fieldId
} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
619 } else if (property
.type
=== 'choice') {
620 field
= buildChoice(container
, property
, valuePath
, value
, fieldId
);
621 } else if (isSimpleListView
) {
622 field
= buildSimpleListItem(container
, property
, valuePath
, value
, fieldId
, index
);
623 } else if (isLeafList
) {
624 field
= buildLeafListItem(container
, property
, valuePath
, value
, fieldId
, index
);
625 } else if (hasProperties
) {
626 field
= buildElement(container
, property
, valuePath
, value
, fieldId
)
628 field
= buildField(container
, property
, valuePath
, value
, fieldId
);
631 function onClickLeaf(property
, path
, value
, event
) {
632 if (event
.isDefaultPrevented()) {
635 event
.preventDefault();
636 event
.stopPropagation();
637 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
638 console
.log('property selected', path
.join('.'));
639 ComposerAppActions
.propertySelected([path
.join('.')]);
642 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
643 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
647 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
648 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
649 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, index
) : null}
656 classNames
['-is-leaf'] = isLeaf
;
657 classNames
['-is-array'] = isArray
;
658 classNames
['cols-' + columnCount
] = isColumnar
;
660 if (property
.type
=== 'choice') {
661 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
663 property
.properties
.map(function(p
) {
664 let pname
= p
.properties
[0].name
;
665 if(container
.model
.hasOwnProperty(pname
)) {
666 value
= container
.model
[pname
];
672 let displayValue
= typeof value
=== 'object' ? '' : value
;
673 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
675 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
678 <div key
={uniqueId
} className
={ClassNames(property
.type
+ '-property property', classNames
)} onFocus
={onFocus
}>
679 <h3 className
="property-label">
680 <label htmlFor
={uniqueId
}>
681 <span className
={property
.type
+ '-name name'}>{title
}</span
>
683 <span className
={property
.type
+ '-info info'}>{displayValueInfo
}</span
>
684 <span className
={property
.type
+ '-value value'}>{displayValue
}</span
>
686 {isArray
? buildAddPropertyAction(container
, property
, path
.concat(valueAsArray
.length
)) : null}
689 <span className
={property
.type
+ '-description description'}>{property
.description
}</span
>
690 <val className
="property-value">
691 {isCVNFD
? <span className
={property
.type
+ '-tip tip'}>Drag a VNFD
from the Catalog to add more
.</span
> : null}
699 const containerType
= container
.uiState
['qualified-type'] || container
.uiState
.type
;
700 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
702 function buildBasicGroup() {
703 if (basicProperties
.length
=== 0) {
707 <div className
="basic-properties-group">
710 {basicProperties
.map(property
=> {
711 const path
= [property
.name
];
712 const value
= container
.model
[property
.name
];
713 return build(container
, property
, path
, value
);
720 function buildAdvancedGroup() {
721 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
722 if (properties
.length
=== 0) {
725 const hasBasicFields
= basicProperties
.length
> 0;
726 const closeGroup
= basicProperties
.length
> 0;
728 <div className
="advanced-properties-group">
729 <h1 data
-toggle
={closeGroup
? 'true' : 'false'} className
={ClassNames({'-is-toggled': closeGroup
})} onClick
={toggle
} style
={{display
: hasBasicFields
? 'block' : 'none'}}>
730 <a className
="toggle-show-more" href
="#show-more-properties">more
&hellip
;</a
>
731 <a className
="toggle-show-less" href
="#show-more-properties">less
&hellip
;</a
>
733 <div className
="toggleable">
734 {properties
.map(property
=> {
735 const path
= [property
.name
];
736 const value
= container
.model
[property
.name
];
737 return build(container
, property
, path
, value
, {toggle
: true, width
: props
.width
});
740 <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>
745 function buildMoreLess(d, i) {
747 <span key={'bread
-crumb
-part
-' + i}>
748 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
755 if (container.parent) {
756 path.push(container.parent);
758 path.push(container);
761 <div className="EditDescriptorModelProperties -is-tree-view">
762 <h1>{path.map(buildMoreLess)}</h1>
764 {buildAdvancedGroup()}