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
;
77 const readonly
= props
.readonly
;
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());
148 <Button className
="inline-hint" onClick
={onClickAddProperty
.bind(container
, property
, path
)} label
="Add" src
={imgAdd
} />
152 function buildRemovePropertyAction(container
, property
, path
) {
153 function onClickRemoveProperty(property
, path
, event
) {
154 event
.preventDefault();
155 const name
= path
.join('.');
156 const removeMethod
= Property
.getContainerMethod(property
, this, 'remove');
158 removeMethod(utils
.resolvePath(this.model
, name
));
160 utils
.removePathValue(this.model
, name
);
162 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
168 <Button className
="remove-property-action inline-hint" title
="Remove" onClick
={onClickRemoveProperty
.bind(container
, property
, path
)} label
="Remove" src
={imgRemove
}/>
172 function onFormFieldValueChanged(event
) {
173 if (DescriptorModelFactory
.isContainer(this)) {
174 event
.preventDefault();
175 const name
= event
.target
.name
;
176 const value
= event
.target
.value
;
177 utils
.assignPathValue(this.model
, name
, value
);
178 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
182 function buildField(container
, property
, path
, value
, fieldKey
) {
183 let cds
= CatalogDataStore
;
184 let catalogs
= cds
.getTransientCatalogs();
186 const name
= path
.join('.');
187 const isEditable
= !readonly
; //true
188 const isGuid
= Property
.isGuid(property
);
189 const isBoolean
= Property
.isBoolean(property
);
190 const onChange
= onFormFieldValueChanged
.bind(container
);
191 const isEnumeration
= Property
.isEnumeration(property
);
192 const isLeafRef
= Property
.isLeafRef(property
);
193 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
194 const placeholder
= changeCase
.title(property
.name
);
195 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
196 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : undefined;
198 const enumeration
= Property
.getEnumeration(property
, value
);
199 const options
= enumeration
.map((d
, i
) => {
200 // note yangforge generates values for enums but the system does not use them
201 // so we categorically ignore them
202 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
203 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
204 return <option key
={fieldKey
.toString() + ':' + i
} value
={d
.name
}>{d
.name
}</option
>;
206 const isValueSet
= enumeration
.filter(d
=> d
.isSelected
).length
> 0;
207 if (!isValueSet
|| property
.cardinality
=== '0..1') {
208 const noValueDisplayText
= changeCase
.title(property
.name
);
209 options
.unshift(<option key
={'(value-not-in-enum)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
211 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
} disabled
={!isEditable
}>{options
}</select
>;
215 let fullFieldKey
= _
.isArray(fieldKey
) ? fieldKey
.join(':') : fieldKey
;
216 let containerRef
= container
;
217 while (containerRef
.parent
) {
218 fullFieldKey
= containerRef
.parent
.key
+ ':' + fullFieldKey
;
219 containerRef
= containerRef
.parent
;
221 const leafRefPathValues
= Property
.getLeafRef(property
, path
, value
, fullFieldKey
, catalogs
, container
);
223 const options
= leafRefPathValues
&& leafRefPathValues
.map((d
, i
) => {
224 return <option key
={fieldKey
.toString() + ':' + i
} value
={d
.value
}>{d
.value
}</option
>;
226 const isValueSet
= leafRefPathValues
.filter(d
=> d
.isSelected
).length
> 0;
227 if (!isValueSet
|| property
.cardinality
=== '0..1') {
228 const noValueDisplayText
= changeCase
.title(property
.name
);
229 options
.unshift(<option key
={'(value-not-in-leafref)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
231 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
} disabled
={!isEditable
}>{options
}</select
>;
235 let fullFieldKey
= _
.isArray(fieldKey
) ? fieldKey
.join(':') : fieldKey
;
236 let containerRef
= container
;
237 while (containerRef
.parent
) {
238 fullFieldKey
= containerRef
.parent
.key
+ ':' + fullFieldKey
;
239 containerRef
= containerRef
.parent
;
243 <option key
={fieldKey
.toString() + '-true'} value
="TRUE">TRUE
</option
>,
244 <option key
={fieldKey
.toString() + '-false'} value
="FALSE">FALSE
</option
>
247 // if (!isValueSet) {
248 const noValueDisplayText
= changeCase
.title(property
.name
);
249 options
.unshift(<option key
={'(value-not-in-leafref)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}></option
>);
252 if(typeof(val
) == 'number') {
253 val
= value
? "TRUE" : "FALSE"
255 const isValueSet
= (val
!= '' && val
)
256 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
} disabled
={!isEditable
}>{options
}</select
>;
259 if (property
['preserve-line-breaks']) {
260 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
} />;
264 key
={fieldKey
.toString()}
265 id
={fieldKey
.toString()}
269 className
={className
}
270 placeholder
={placeholder
}
274 onMouseDown
={startEditing
}
275 onMouseOver
={startEditing
}
276 onMouseOut
={endEditing
}
277 onMouseLeave
={endEditing
}
278 readOnly
={!isEditable
}
283 function buildElement(container
, property
, valuePath
, value
) {
284 return property
.properties
.map((property
, index
) => {
286 const childPath
= valuePath
.slice();
287 if (typeof value
=== 'object') {
288 childValue
= value
[property
.name
];
290 if(property
.type
!= 'choice'){
291 childPath
.push(property
.name
);
293 return build(container
, property
, childPath
, childValue
);
298 function buildChoice(container
, property
, path
, value
, key
) {
300 function onFormFieldValueChanged(event
) {
301 if (DescriptorModelFactory
.isContainer(this)) {
303 event
.preventDefault();
305 let name
= event
.target
.name
;
306 const value
= event
.target
.value
;
310 Transient State is stored for convenience in the uiState field.
311 The choice yang type uses case elements to describe the "options".
312 A choice can only ever have one option selected which allows
313 the system to determine which type is selected by the name of
314 the element contained within the field.
317 const stateExample = {
332 const statePath
= ['uiState.choice'].concat(name
);
333 const stateObject
= utils
.resolvePath(this.model
, statePath
.join('.')) || {};
334 const selected
= stateObject
.selected
? stateObject
.selected
.split('.')[1] : undefined;
335 // write state back to the model so the new state objects are captured
336 utils
.assignPathValue(this.model
, statePath
.join('.'), stateObject
);
338 // write the current choice value into the state
339 let choiceObject
= utils
.resolvePath(this.model
, [name
, selected
].join('.'));
340 let isTopCase
= false;
343 choiceObject
= utils
.resolvePath(this.model
, [selected
].join('.'));
345 utils
.assignPathValue(stateObject
, [selected
].join('.'), _
.cloneDeep(choiceObject
));
348 if(this.model
.uiState
.choice
.hasOwnProperty(name
)) {
349 delete this.model
[selected
];
350 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
352 // remove the current choice value from the model
353 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
357 // get any state for the new selected choice
358 const newChoiceObject
= utils
.resolvePath(stateObject
, [value
].join('.')) || {};
360 // assign new choice value to the model
362 utils
.assignPathValue(this.model
, [name
, value
].join('.'), newChoiceObject
);
364 utils
.assignPathValue(this.model
, [value
].join('.'), newChoiceObject
)
368 // update the selected name
369 utils
.assignPathValue(this.model
, statePath
.concat('selected').join('.'), value
);
371 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
375 const caseByNameMap
= {};
377 const onChange
= onFormFieldValueChanged
.bind(container
);
379 const cases
= property
.properties
.map(d
=> {
380 if (d
.type
=== 'case') {
381 caseByNameMap
[d
.name
] = d
.properties
[0];
384 optionTitle
: d
.description
,
385 //represents case name and case element name
386 optionValue
: [d
.name
, d
.properties
[0].name
].join('.')
389 caseByNameMap
[d
.name
] = d
;
390 return {optionName
: d
.name
};
393 const options
= [{optionName
: '', optionValue
: false}].concat(cases
).map((d
, i
) => {
395 <option key
={i
} value
={d
.optionValue
} title
={d
.optionTitle
}>
397 {i
? null : changeCase
.title(property
.name
)}
402 const selectName
= path
.join('.');
403 let selectedOptionPath
= ['uiState.choice', selectName
, 'selected'].join('.');
404 //Currently selected choice/case statement on UI model
405 let selectedOptionValue
= utils
.resolvePath(container
.model
, selectedOptionPath
);
406 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
407 if(!selectedOptionValue
) {
408 //get field properties for choice on container model
409 let fieldProperties
= utils
.resolvePath(container
.model
, selectName
);
410 if(fieldProperties
) {
411 //Check each case statement in model and see if it is present in container model.
412 cases
.map(function(c
){
413 if(fieldProperties
.hasOwnProperty(c
.optionValue
.split('.')[1])) {
414 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), c
.optionValue
);
417 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
419 property
.properties
.map(function(p
) {
420 let pname
= p
.properties
[0].name
;
421 if(container
.model
.hasOwnProperty(pname
)) {
422 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), [p
.name
, pname
].join('.'));
425 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
428 //If selectedOptionValue is present, take first item in string which represents the case name.
429 const valueProperty
= caseByNameMap
[selectedOptionValue
? selectedOptionValue
.split('.')[0] : undefined] || {properties
: []};
430 const isLeaf
= Property
.isLeaf(valueProperty
);
431 const hasProperties
= _
.isArray(valueProperty
.properties
) && valueProperty
.properties
.length
;
432 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(valueProperty
);
433 //Some magic that prevents errors for arising
434 const valueResponse
= valueProperty
.properties
.length
? valueProperty
.properties
.map((d
, i
) => {
435 const childPath
= path
.concat(valueProperty
.name
, d
.name
);
436 const childValue
= utils
.resolvePath(container
.model
, childPath
.join('.'));
438 <div key
={childPath
.concat('info', i
).join(':')}>
439 {build(container
, d
, childPath
, childValue
, props
)}
442 }) : (!isMissingDescriptorMeta
) ? build(container
, valueProperty
, path
.concat(valueProperty
.name
), utils
.resolvePath(container
.model
, path
.concat(valueProperty
.name
).join('.')) || container
.model
[valueProperty
.name
]) : null
444 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
447 <div key
={key
} className
="choice">
448 <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
} readOnly
={!isEditable
}>
457 function buildSimpleListItem(container
, property
, path
, value
, key
, index
) {
458 // todo need to abstract this better
459 const title
= getTitle(value
);
460 var req
= require
.context("../", true, /\.svg
/);
463 <a href
="#select-list-item" key
={Date
.now()} className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
464 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
467 {buildRemovePropertyAction(container
, property
, path
)}
472 function buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) {
473 const className
= ClassNames(property
.name
+ '-remove actions');
475 <div key
={fieldKey
.concat(index
).join(':')} className
={className
}>
477 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
478 <span className
="info">{index
+ 1}</span
>
479 {buildRemovePropertyAction(container
, property
, valuePath
)}
485 function buildLeafListItem(container
, property
, valuePath
, value
, key
, index
) {
486 // look at the type to determine how to parse the value
489 {buildRemoveListItem(container
, property
, valuePath
, key
, index
)}
490 {buildField(container
, property
, valuePath
, value
, key
)}
496 function build(container
, property
, path
, value
, props
= {}) {
499 const isLeaf
= Property
.isLeaf(property
);
500 const isArray
= Property
.isArray(property
);
501 const isObject
= Property
.isObject(property
);
502 const isLeafList
= Property
.isLeafList(property
);
503 const fieldKey
= [container
.id
].concat(path
);
504 const isRequired
= Property
.isRequired(property
);
505 const title
= changeCase
.titleCase(property
.name
);
506 const columnCount
= property
.properties
.length
|| 1;
507 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
508 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
510 if (!property
.properties
&& isObject
) {
511 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
512 property
.properties
= uiState
.properties
;
515 const hasProperties
= _
.isArray(property
.properties
) && property
.properties
.length
;
516 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
518 // ensure value is not undefined for non-leaf property types
520 if (typeof value
!== 'object') {
521 value
= isArray
? [] : {};
524 const valueAsArray
= _
.isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
526 const isMetaField
= property
.name
=== 'meta';
527 const isCVNFD
= property
.name
=== 'constituent-vnfd';
528 const isSimpleListView
= Property
.isSimpleList(property
);
530 valueAsArray
.forEach((value
, index
) => {
533 const key
= fieldKey
.slice();
534 const valuePath
= path
.slice();
537 valuePath
.push(index
);
542 if (typeof value
=== 'object') {
543 value
= JSON
.stringify(value
, undefined, 12);
544 } else if (typeof value
!== 'string') {
549 if (isMissingDescriptorMeta
) {
550 field
= <span key
={key
.concat('warning').join(':')} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
551 } else if (property
.type
=== 'choice') {
552 field
= buildChoice(container
, property
, valuePath
, value
, key
.join(':'));
553 } else if (isSimpleListView
) {
554 field
= buildSimpleListItem(container
, property
, valuePath
, value
, key
, index
);
555 } else if (isLeafList
) {
556 field
= buildLeafListItem(container
, property
, valuePath
, value
, key
, index
);
557 } else if (hasProperties
) {
558 field
= buildElement(container
, property
, valuePath
, value
, key
.join(':'))
560 field
= buildField(container
, property
, valuePath
, value
, key
.join(':'));
563 function onClickLeaf(property
, path
, value
, event
) {
564 if (event
.isDefaultPrevented()) {
567 event
.preventDefault();
568 event
.stopPropagation();
569 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
570 console
.log('property selected', path
.join('.'));
571 ComposerAppActions
.propertySelected([path
.join('.')]);
574 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
575 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
578 <div key
={fieldKey
.concat(['property-content', index
]).join(':')}
579 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
580 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
581 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) : null}
588 classNames
['-is-leaf'] = isLeaf
;
589 classNames
['-is-array'] = isArray
;
590 classNames
['cols-' + columnCount
] = isColumnar
;
592 if (property
.type
=== 'choice') {
593 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
595 property
.properties
.map(function(p
) {
596 let pname
= p
.properties
[0].name
;
597 if(container
.model
.hasOwnProperty(pname
)) {
598 value
= container
.model
[pname
];
604 let displayValue
= typeof value
=== 'object' ? '' : value
;
605 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
607 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
610 <div key
={fieldKey
.join(':')} className
={ClassNames(property
.type
+ '-property property', classNames
)} onFocus
={onFocus
}>
611 <h3 className
="property-label">
612 <label htmlFor
={fieldKey
.join(':')}>
613 <span className
={property
.type
+ '-name name'}>{title
}</span
>
615 <span className
={property
.type
+ '-info info'}>{displayValueInfo
}</span
>
616 <span className
={property
.type
+ '-value value'}>{displayValue
}</span
>
618 {isArray
? buildAddPropertyAction(container
, property
, path
.concat(valueAsArray
.length
)) : null}
621 <span className
={property
.type
+ '-description description'}>{property
.description
}</span
>
622 <val className
="property-value">
623 {isCVNFD
? <span className
={property
.type
+ '-tip tip'}>Drag a VNFD
from the Catalog to add more
.</span
> : null}
631 const containerType
= container
.uiState
['qualified-type'] || container
.uiState
.type
;
632 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
634 function buildBasicGroup() {
635 if (basicProperties
.length
=== 0) {
639 <div className
="basic-properties-group">
642 {basicProperties
.map(property
=> {
643 const path
= [property
.name
];
644 const value
= container
.model
[property
.name
];
645 return build(container
, property
, path
, value
);
652 function buildAdvancedGroup() {
653 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
654 if (properties
.length
=== 0) {
657 const hasBasicFields
= basicProperties
.length
> 0;
658 const closeGroup
= basicProperties
.length
> 0;
660 <div className
="advanced-properties-group">
661 <h1 data
-toggle
={closeGroup
? 'true' : 'false'} className
={ClassNames({'-is-toggled': closeGroup
})} onClick
={toggle
} style
={{display
: hasBasicFields
? 'block' : 'none'}}>
662 <a className
="toggle-show-more" href
="#show-more-properties">more
&hellip
;</a
>
663 <a className
="toggle-show-less" href
="#show-more-properties">less
&hellip
;</a
>
665 <div className
="toggleable">
666 {properties
.map(property
=> {
667 const path
= [property
.name
];
668 const value
= container
.model
[property
.name
];
669 return build(container
, property
, path
, value
, {toggle
: true, width
: props
.width
});
672 <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>
677 function buildMoreLess(d, i) {
679 <span key={'bread
-crumb
-part
-' + i}>
680 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
687 if (container.parent) {
688 path.push(container.parent);
690 path.push(container);
693 <div className="EditDescriptorModelProperties -is-tree-view">
694 <h1>{path.map(buildMoreLess)}</h1>
696 {buildAdvancedGroup()}