3 * Copyright 2016 RIFT.IO Inc
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
19 * Created by onvelocity on 1/18/16.
21 * This class generates the form fields used to edit the CONFD JSON model.
25 import _
from 'lodash'
26 import utils
from '../libraries/utils'
27 import React
from 'react'
28 import ClassNames
from 'classnames'
29 import changeCase
from 'change-case'
30 import toggle
from '../libraries/ToggleElementHandler'
31 import Button
from './Button'
32 import Property
from '../libraries/model/DescriptorModelMetaProperty'
33 import ComposerAppActions
from '../actions/ComposerAppActions'
34 import CatalogItemsActions
from '../actions/CatalogItemsActions'
35 import DESCRIPTOR_MODEL_FIELDS
from '../libraries/model/DescriptorModelFields'
36 import DescriptorModelFactory
from '../libraries/model/DescriptorModelFactory'
37 import DescriptorModelMetaFactory
from '../libraries/model/DescriptorModelMetaFactory'
38 import SelectionManager
from '../libraries/SelectionManager'
39 import DeletionManager
from '../libraries/DeletionManager'
40 import DescriptorModelIconFactory
from '../libraries/model/IconFactory'
41 import getEventPath
from '../libraries/getEventPath'
42 import CatalogDataStore
from '../stores/CatalogDataStore'
44 import imgAdd
from '../../../node_modules/open-iconic/svg/plus.svg'
45 import imgRemove
from '../../../node_modules/open-iconic/svg/trash.svg'
47 import '../styles/EditDescriptorModelProperties.scss'
49 function getDescriptorMetaBasicForType(type
) {
50 const basicPropertiesFilter
= d
=> _
.includes(DESCRIPTOR_MODEL_FIELDS
[type
], d
.name
);
51 return DescriptorModelMetaFactory
.getModelMetaForType(type
, basicPropertiesFilter
) || {properties
: []};
54 function getDescriptorMetaAdvancedForType(type
) {
55 const advPropertiesFilter
= d
=> !_
.includes(DESCRIPTOR_MODEL_FIELDS
[type
], d
.name
);
56 return DescriptorModelMetaFactory
.getModelMetaForType(type
, advPropertiesFilter
) || {properties
: []};
59 function getTitle(model
= {}) {
60 if (typeof model
['short-name'] === 'string' && model
['short-name']) {
61 return model
['short-name'];
63 if (typeof model
.name
=== 'string' && model
.name
) {
66 if (model
.uiState
&& typeof model
.uiState
.displayName
=== 'string' && model
.uiState
.displayName
) {
67 return model
.uiState
.displayName
69 if (typeof model
.id
=== 'string') {
74 export default function EditDescriptorModelProperties(props
) {
76 const container
= props
.container
;
78 if (!(DescriptorModelFactory
.isContainer(container
))) {
82 function startEditing() {
83 DeletionManager
.removeEventListeners();
86 function endEditing() {
87 DeletionManager
.addEventListeners();
90 function onClickSelectItem(property
, path
, value
, event
) {
91 event
.preventDefault();
92 const root
= this.getRoot();
93 if (SelectionManager
.select(value
)) {
94 CatalogItemsActions
.catalogItemMetaDataChanged(root
.model
);
98 function onFocusPropertyFormInputElement(property
, path
, value
, event
) {
100 event
.preventDefault();
103 function removeIsFocusedClass(event
) {
104 event
.target
.removeEventListener('blur', removeIsFocusedClass
);
105 Array
.from(document
.querySelectorAll('.-is-focused')).forEach(d
=> d
.classList
.remove('-is-focused'));
108 removeIsFocusedClass(event
);
110 const propertyWrapper
= getEventPath(event
).reduce((parent
, element
) => {
114 if (!element
.classList
) {
117 if (element
.classList
.contains('property')) {
122 if (propertyWrapper
) {
123 propertyWrapper
.classList
.add('-is-focused');
124 event
.target
.addEventListener('blur', removeIsFocusedClass
);
129 function buildAddPropertyAction(container
, property
, path
) {
130 function onClickAddProperty(property
, path
, event
) {
131 event
.preventDefault();
132 //SelectionManager.resume();
133 const create
= Property
.getContainerCreateMethod(property
, this);
136 create(model
, path
, property
);
138 const name
= path
.join('.');
139 const value
= Property
.createModelInstance(property
);
140 utils
.assignPathValue(this.model
, name
, value
);
142 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
145 <Button className
="inline-hint" onClick
={onClickAddProperty
.bind(container
, property
, path
)} label
="Add" src
={imgAdd
} />
149 function buildRemovePropertyAction(container
, property
, path
) {
150 function onClickRemoveProperty(property
, path
, event
) {
151 event
.preventDefault();
152 const name
= path
.join('.');
153 const removeMethod
= Property
.getContainerMethod(property
, this, 'remove');
155 removeMethod(utils
.resolvePath(this.model
, name
));
157 utils
.removePathValue(this.model
, name
);
159 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
162 <Button className
="remove-property-action inline-hint" title
="Remove" onClick
={onClickRemoveProperty
.bind(container
, property
, path
)} label
="Remove" src
={imgRemove
}/>
166 function onFormFieldValueChanged(event
) {
167 if (DescriptorModelFactory
.isContainer(this)) {
168 event
.preventDefault();
169 const name
= event
.target
.name
;
170 const value
= event
.target
.value
;
171 utils
.assignPathValue(this.model
, name
, value
);
172 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
176 function buildField(container
, property
, path
, value
, fieldKey
) {
177 let cds
= CatalogDataStore
;
178 let catalogs
= cds
.getTransientCatalogs();
180 const name
= path
.join('.');
181 const isEditable
= true;
182 const isGuid
= Property
.isGuid(property
);
183 const isBoolean
= Property
.isBoolean(property
);
184 const onChange
= onFormFieldValueChanged
.bind(container
);
185 const isEnumeration
= Property
.isEnumeration(property
);
186 const isLeafRef
= Property
.isLeafRef(property
);
187 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
188 const placeholder
= changeCase
.title(property
.name
);
189 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
190 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : undefined;
192 const enumeration
= Property
.getEnumeration(property
, value
);
193 const options
= enumeration
.map((d
, i
) => {
194 // note yangforge generates values for enums but the system does not use them
195 // so we categorically ignore them
196 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
197 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
198 return <option key
={fieldKey
.toString() + ':' + i
} value
={d
.name
}>{d
.name
}</option
>;
200 const isValueSet
= enumeration
.filter(d
=> d
.isSelected
).length
> 0;
201 if (!isValueSet
|| property
.cardinality
=== '0..1') {
202 const noValueDisplayText
= changeCase
.title(property
.name
);
203 options
.unshift(<option key
={'(value-not-in-enum)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
205 return <select key
={fieldKey
.toString()} id
={fieldKey
.toString()} className
={ClassNames({'-value-not-set': !isValueSet
})} name
={name
} value
={value
} title
={name
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} readOnly
={!isEditable
}>{options
}</select
>;
209 let fullFieldKey
= _
.isArray(fieldKey
) ? fieldKey
.join(':') : fieldKey
;
210 let containerRef
= container
;
211 while (containerRef
.parent
) {
212 fullFieldKey
= containerRef
.parent
.key
+ ':' + fullFieldKey
;
213 containerRef
= containerRef
.parent
;
215 const leafRefPathValues
= Property
.getLeafRef(property
, path
, value
, fullFieldKey
, catalogs
, container
);
217 const options
= leafRefPathValues
&& leafRefPathValues
.map((d
, i
) => {
218 return <option key
={fieldKey
.toString() + ':' + i
} value
={d
.value
}>{d
.value
}</option
>;
220 const isValueSet
= leafRefPathValues
.filter(d
=> d
.isSelected
).length
> 0;
221 if (!isValueSet
|| property
.cardinality
=== '0..1') {
222 const noValueDisplayText
= changeCase
.title(property
.name
);
223 options
.unshift(<option key
={'(value-not-in-leafref)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
225 return <select key
={fieldKey
.toString()} id
={fieldKey
.toString()} className
={ClassNames({'-value-not-set': !isValueSet
})} name
={name
} value
={value
} title
={name
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} readOnly
={!isEditable
}>{options
}</select
>;
229 let fullFieldKey
= _
.isArray(fieldKey
) ? fieldKey
.join(':') : fieldKey
;
230 let containerRef
= container
;
231 while (containerRef
.parent
) {
232 fullFieldKey
= containerRef
.parent
.key
+ ':' + fullFieldKey
;
233 containerRef
= containerRef
.parent
;
237 <option key
={fieldKey
.toString() + '-true'} value
="TRUE">TRUE
</option
>,
238 <option key
={fieldKey
.toString() + '-false'} value
="FALSE">FALSE
</option
>
241 // if (!isValueSet) {
242 const noValueDisplayText
= changeCase
.title(property
.name
);
243 options
.unshift(<option key
={'(value-not-in-leafref)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}></option
>);
246 if(typeof(val
) == 'number') {
247 val
= value
? "TRUE" : "FALSE"
249 const isValueSet
= (val
!= '' && val
)
250 return <select key
={fieldKey
.toString()} id
={fieldKey
.toString()} className
={ClassNames({'-value-not-set': !isValueSet
})} name
={name
} value
={val
&& val
.toUpperCase()} title
={name
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} readOnly
={!isEditable
}>{options
}</select
>;
253 if (property
['preserve-line-breaks']) {
254 return <textarea key
={fieldKey
.toString()} cols
="5" id
={fieldKey
.toString()} name
={name
} value
={value
} placeholder
={placeholder
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} onMouseOut
={endEditing
} onMouseLeave
={endEditing
} readOnly
={!isEditable
} />;
257 return <input key
={fieldKey
.toString()}
258 id
={fieldKey
.toString()}
262 className
={className
}
263 placeholder
={placeholder
}
267 onMouseDown
={startEditing
}
268 onMouseOver
={startEditing
}
269 onMouseOut
={endEditing
}
270 onMouseLeave
={endEditing
}
271 readOnly
={!isEditable
}
276 function buildElement(container
, property
, valuePath
, value
) {
277 return property
.properties
.map((property
, index
) => {
279 const childPath
= valuePath
.slice();
280 if (typeof value
=== 'object') {
281 childValue
= value
[property
.name
];
283 if(property
.type
!= 'choice'){
284 childPath
.push(property
.name
);
286 return build(container
, property
, childPath
, childValue
);
291 function buildChoice(container
, property
, path
, value
, key
) {
293 function onFormFieldValueChanged(event
) {
294 if (DescriptorModelFactory
.isContainer(this)) {
296 event
.preventDefault();
298 let name
= event
.target
.name
;
299 const value
= event
.target
.value
;
303 Transient State is stored for convenience in the uiState field.
304 The choice yang type uses case elements to describe the "options".
305 A choice can only ever have one option selected which allows
306 the system to determine which type is selected by the name of
307 the element contained within the field.
310 const stateExample = {
325 const statePath
= ['uiState.choice'].concat(name
);
326 const stateObject
= utils
.resolvePath(this.model
, statePath
.join('.')) || {};
327 const selected
= stateObject
.selected
? stateObject
.selected
.split('.')[1] : undefined;
328 // write state back to the model so the new state objects are captured
329 utils
.assignPathValue(this.model
, statePath
.join('.'), stateObject
);
331 // write the current choice value into the state
332 let choiceObject
= utils
.resolvePath(this.model
, [name
, selected
].join('.'));
333 let isTopCase
= false;
336 choiceObject
= utils
.resolvePath(this.model
, [selected
].join('.'));
338 utils
.assignPathValue(stateObject
, [selected
].join('.'), _
.cloneDeep(choiceObject
));
341 if(this.model
.uiState
.choice
.hasOwnProperty(name
)) {
342 delete this.model
[selected
];
343 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
345 // remove the current choice value from the model
346 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
350 // get any state for the new selected choice
351 const newChoiceObject
= utils
.resolvePath(stateObject
, [value
].join('.')) || {};
353 // assign new choice value to the model
355 utils
.assignPathValue(this.model
, [name
, value
].join('.'), newChoiceObject
);
357 utils
.assignPathValue(this.model
, [value
].join('.'), newChoiceObject
)
361 // update the selected name
362 utils
.assignPathValue(this.model
, statePath
.concat('selected').join('.'), value
);
364 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
368 const caseByNameMap
= {};
370 const onChange
= onFormFieldValueChanged
.bind(container
);
372 const cases
= property
.properties
.map(d
=> {
373 if (d
.type
=== 'case') {
374 caseByNameMap
[d
.name
] = d
.properties
[0];
377 optionTitle
: d
.description
,
378 //represents case name and case element name
379 optionValue
: [d
.name
, d
.properties
[0].name
].join('.')
382 caseByNameMap
[d
.name
] = d
;
383 return {optionName
: d
.name
};
386 const options
= [{optionName
: '', optionValue
: false}].concat(cases
).map((d
, i
) => {
388 <option key
={i
} value
={d
.optionValue
} title
={d
.optionTitle
}>
390 {i
? null : changeCase
.title(property
.name
)}
395 const selectName
= path
.join('.');
396 let selectedOptionPath
= ['uiState.choice', selectName
, 'selected'].join('.');
397 //Currently selected choice/case statement on UI model
398 let selectedOptionValue
= utils
.resolvePath(container
.model
, selectedOptionPath
);
399 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
400 if(!selectedOptionValue
) {
401 //get field properties for choice on container model
402 let fieldProperties
= utils
.resolvePath(container
.model
, selectName
);
403 if(fieldProperties
) {
404 //Check each case statement in model and see if it is present in container model.
405 cases
.map(function(c
){
406 if(fieldProperties
.hasOwnProperty(c
.optionValue
.split('.')[1])) {
407 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), c
.optionValue
);
410 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
412 property
.properties
.map(function(p
) {
413 let pname
= p
.properties
[0].name
;
414 if(container
.model
.hasOwnProperty(pname
)) {
415 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), [p
.name
, pname
].join('.'));
418 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
421 //If selectedOptionValue is present, take first item in string which represents the case name.
422 const valueProperty
= caseByNameMap
[selectedOptionValue
? selectedOptionValue
.split('.')[0] : undefined] || {properties
: []};
423 const isLeaf
= Property
.isLeaf(valueProperty
);
424 const hasProperties
= _
.isArray(valueProperty
.properties
) && valueProperty
.properties
.length
;
425 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(valueProperty
);
426 //Some magic that prevents errors for arising
427 const valueResponse
= valueProperty
.properties
.length
? valueProperty
.properties
.map((d
, i
) => {
428 const childPath
= path
.concat(valueProperty
.name
, d
.name
);
429 const childValue
= utils
.resolvePath(container
.model
, childPath
.join('.'));
431 <div key
={childPath
.concat('info', i
).join(':')}>
432 {build(container
, d
, childPath
, childValue
, props
)}
435 }) : (!isMissingDescriptorMeta
) ? build(container
, valueProperty
, path
.concat(valueProperty
.name
), utils
.resolvePath(container
.model
, path
.concat(valueProperty
.name
).join('.')) || container
.model
[valueProperty
.name
]) : null
437 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
440 <div key
={key
} className
="choice">
441 <select key
={Date
.now()} className
={ClassNames({'-value-not-set': !selectedOptionValue
})} name
={selectName
} value
={selectedOptionValue
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} onMouseOut
={endEditing
} onMouseLeave
={endEditing
}>
450 function buildSimpleListItem(container
, property
, path
, value
, key
, index
) {
451 // todo need to abstract this better
452 const title
= getTitle(value
);
453 var req
= require
.context("../", true, /\.svg
/);
456 <a href
="#select-list-item" key
={Date
.now()} className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
457 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
460 {buildRemovePropertyAction(container
, property
, path
)}
465 function buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) {
466 const className
= ClassNames(property
.name
+ '-remove actions');
468 <div key
={fieldKey
.concat(index
).join(':')} className
={className
}>
470 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
471 <span className
="info">{index
+ 1}</span
>
472 {buildRemovePropertyAction(container
, property
, valuePath
)}
478 function buildLeafListItem(container
, property
, valuePath
, value
, key
, index
) {
479 // look at the type to determine how to parse the value
482 {buildRemoveListItem(container
, property
, valuePath
, key
, index
)}
483 {buildField(container
, property
, valuePath
, value
, key
)}
489 function build(container
, property
, path
, value
, props
= {}) {
492 const isLeaf
= Property
.isLeaf(property
);
493 const isArray
= Property
.isArray(property
);
494 const isObject
= Property
.isObject(property
);
495 const isLeafList
= Property
.isLeafList(property
);
496 const fieldKey
= [container
.id
].concat(path
);
497 const isRequired
= Property
.isRequired(property
);
498 const title
= changeCase
.titleCase(property
.name
);
499 const columnCount
= property
.properties
.length
|| 1;
500 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
501 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
503 if (!property
.properties
&& isObject
) {
504 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
505 property
.properties
= uiState
.properties
;
508 const hasProperties
= _
.isArray(property
.properties
) && property
.properties
.length
;
509 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
511 // ensure value is not undefined for non-leaf property types
513 if (typeof value
!== 'object') {
514 value
= isArray
? [] : {};
517 const valueAsArray
= _
.isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
519 const isMetaField
= property
.name
=== 'meta';
520 const isCVNFD
= property
.name
=== 'constituent-vnfd';
521 const isSimpleListView
= Property
.isSimpleList(property
);
523 valueAsArray
.forEach((value
, index
) => {
526 const key
= fieldKey
.slice();
527 const valuePath
= path
.slice();
530 valuePath
.push(index
);
535 if (typeof value
=== 'object') {
536 value
= JSON
.stringify(value
, undefined, 12);
537 } else if (typeof value
!== 'string') {
542 if (isMissingDescriptorMeta
) {
543 field
= <span key
={key
.concat('warning').join(':')} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
544 } else if (property
.type
=== 'choice') {
545 field
= buildChoice(container
, property
, valuePath
, value
, key
.join(':'));
546 } else if (isSimpleListView
) {
547 field
= buildSimpleListItem(container
, property
, valuePath
, value
, key
, index
);
548 } else if (isLeafList
) {
549 field
= buildLeafListItem(container
, property
, valuePath
, value
, key
, index
);
550 } else if (hasProperties
) {
551 field
= buildElement(container
, property
, valuePath
, value
, key
.join(':'))
553 field
= buildField(container
, property
, valuePath
, value
, key
.join(':'));
556 function onClickLeaf(property
, path
, value
, event
) {
557 if (event
.isDefaultPrevented()) {
560 event
.preventDefault();
561 event
.stopPropagation();
562 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
563 console
.log('property selected', path
.join('.'));
564 ComposerAppActions
.propertySelected([path
.join('.')]);
567 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
568 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
571 <div key
={fieldKey
.concat(['property-content', index
]).join(':')}
572 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
573 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
574 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) : null}
581 classNames
['-is-leaf'] = isLeaf
;
582 classNames
['-is-array'] = isArray
;
583 classNames
['cols-' + columnCount
] = isColumnar
;
585 if (property
.type
=== 'choice') {
586 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
588 property
.properties
.map(function(p
) {
589 let pname
= p
.properties
[0].name
;
590 if(container
.model
.hasOwnProperty(pname
)) {
591 value
= container
.model
[pname
];
597 let displayValue
= typeof value
=== 'object' ? '' : value
;
598 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
600 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
603 <div key
={fieldKey
.join(':')} className
={ClassNames(property
.type
+ '-property property', classNames
)} onFocus
={onFocus
}>
604 <h3 className
="property-label">
605 <label htmlFor
={fieldKey
.join(':')}>
606 <span className
={property
.type
+ '-name name'}>{title
}</span
>
608 <span className
={property
.type
+ '-info info'}>{displayValueInfo
}</span
>
609 <span className
={property
.type
+ '-value value'}>{displayValue
}</span
>
611 {isArray
? buildAddPropertyAction(container
, property
, path
.concat(valueAsArray
.length
)) : null}
614 <span className
={property
.type
+ '-description description'}>{property
.description
}</span
>
615 <val className
="property-value">
616 {isCVNFD
? <span className
={property
.type
+ '-tip tip'}>Drag a VNFD
from the Catalog to add more
.</span
> : null}
624 const containerType
= container
.uiState
['qualified-type'] || container
.uiState
.type
;
625 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
627 function buildBasicGroup() {
628 if (basicProperties
.length
=== 0) {
632 <div className
="basic-properties-group">
635 {basicProperties
.map(property
=> {
636 const path
= [property
.name
];
637 const value
= container
.model
[property
.name
];
638 return build(container
, property
, path
, value
);
645 function buildAdvancedGroup() {
646 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
647 if (properties
.length
=== 0) {
650 const hasBasicFields
= basicProperties
.length
> 0;
651 const closeGroup
= basicProperties
.length
> 0;
653 <div className
="advanced-properties-group">
654 <h1 data
-toggle
={closeGroup
? 'true' : 'false'} className
={ClassNames({'-is-toggled': closeGroup
})} onClick
={toggle
} style
={{display
: hasBasicFields
? 'block' : 'none'}}>
655 <a className
="toggle-show-more" href
="#show-more-properties">more
&hellip
;</a
>
656 <a className
="toggle-show-less" href
="#show-more-properties">less
&hellip
;</a
>
658 <div className
="toggleable">
659 {properties
.map(property
=> {
660 const path
= [property
.name
];
661 const value
= container
.model
[property
.name
];
662 return build(container
, property
, path
, value
, {toggle
: true, width
: props
.width
});
665 <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>
670 function buildMoreLess(d, i) {
672 <span key={'bread
-crumb
-part
-' + i}>
673 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
680 if (container.parent) {
681 path.push(container.parent);
683 path.push(container);
686 <div className="EditDescriptorModelProperties -is-tree-view">
687 <h1>{path.map(buildMoreLess)}</h1>
689 {buildAdvancedGroup()}