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 const isEditable
= !readonly
; //true
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());
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());
170 <Button className
="remove-property-action inline-hint" title
="Remove" onClick
={onClickRemoveProperty
.bind(container
, property
, path
)} label
="Remove" src
={imgRemove
}/>
174 function onFormFieldValueChanged(event
) {
175 if (DescriptorModelFactory
.isContainer(this)) {
176 event
.preventDefault();
177 const name
= event
.target
.name
;
178 const value
= event
.target
.value
;
179 utils
.assignPathValue(this.model
, name
, value
);
180 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
184 function buildField(container
, property
, path
, value
, fieldKey
) {
185 let cds
= CatalogDataStore
;
186 let catalogs
= cds
.getTransientCatalogs();
188 const name
= path
.join('.');
189 const isGuid
= Property
.isGuid(property
);
190 const isBoolean
= Property
.isBoolean(property
);
191 const onChange
= onFormFieldValueChanged
.bind(container
);
192 const isEnumeration
= Property
.isEnumeration(property
);
193 const isLeafRef
= Property
.isLeafRef(property
);
194 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
195 const placeholder
= changeCase
.title(property
.name
);
196 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
197 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : undefined;
199 const enumeration
= Property
.getEnumeration(property
, value
);
200 const options
= enumeration
.map((d
, i
) => {
201 // note yangforge generates values for enums but the system does not use them
202 // so we categorically ignore them
203 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
204 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
205 return <option key
={fieldKey
.toString() + ':' + i
} value
={d
.name
}>{d
.name
}</option
>;
207 const isValueSet
= enumeration
.filter(d
=> d
.isSelected
).length
> 0;
208 if (!isValueSet
|| property
.cardinality
=== '0..1') {
209 const noValueDisplayText
= changeCase
.title(property
.name
);
210 options
.unshift(<option key
={'(value-not-in-enum)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
212 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
>;
216 let fullFieldKey
= _
.isArray(fieldKey
) ? fieldKey
.join(':') : fieldKey
;
217 let containerRef
= container
;
218 while (containerRef
.parent
) {
219 fullFieldKey
= containerRef
.parent
.key
+ ':' + fullFieldKey
;
220 containerRef
= containerRef
.parent
;
222 const leafRefPathValues
= Property
.getLeafRef(property
, path
, value
, fullFieldKey
, catalogs
, container
);
224 const options
= leafRefPathValues
&& leafRefPathValues
.map((d
, i
) => {
225 return <option key
={fieldKey
.toString() + ':' + i
} value
={d
.value
}>{d
.value
}</option
>;
227 const isValueSet
= leafRefPathValues
.filter(d
=> d
.isSelected
).length
> 0;
228 if (!isValueSet
|| property
.cardinality
=== '0..1') {
229 const noValueDisplayText
= changeCase
.title(property
.name
);
230 options
.unshift(<option key
={'(value-not-in-leafref)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
232 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
>;
236 let fullFieldKey
= _
.isArray(fieldKey
) ? fieldKey
.join(':') : fieldKey
;
237 let containerRef
= container
;
238 while (containerRef
.parent
) {
239 fullFieldKey
= containerRef
.parent
.key
+ ':' + fullFieldKey
;
240 containerRef
= containerRef
.parent
;
244 <option key
={fieldKey
.toString() + '-true'} value
="TRUE">TRUE
</option
>,
245 <option key
={fieldKey
.toString() + '-false'} value
="FALSE">FALSE
</option
>
248 // if (!isValueSet) {
249 const noValueDisplayText
= changeCase
.title(property
.name
);
250 options
.unshift(<option key
={'(value-not-in-leafref)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}></option
>);
253 if(typeof(val
) == 'number') {
254 val
= value
? "TRUE" : "FALSE"
256 const isValueSet
= (val
!= '' && val
)
257 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
>;
260 if (property
['preserve-line-breaks']) {
261 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
} />;
265 key
={fieldKey
.toString()}
266 id
={fieldKey
.toString()}
270 className
={className
}
271 placeholder
={placeholder
}
275 onMouseDown
={startEditing
}
276 onMouseOver
={startEditing
}
277 onMouseOut
={endEditing
}
278 onMouseLeave
={endEditing
}
279 readOnly
={!isEditable
}
284 function buildElement(container
, property
, valuePath
, value
) {
285 return property
.properties
.map((property
, index
) => {
287 const childPath
= valuePath
.slice();
288 if (typeof value
=== 'object') {
289 childValue
= value
[property
.name
];
291 if(property
.type
!= 'choice'){
292 childPath
.push(property
.name
);
294 return build(container
, property
, childPath
, childValue
);
299 function buildChoice(container
, property
, path
, value
, key
) {
301 function onFormFieldValueChanged(event
) {
302 if (DescriptorModelFactory
.isContainer(this)) {
304 event
.preventDefault();
306 let name
= event
.target
.name
;
307 const value
= event
.target
.value
;
311 Transient State is stored for convenience in the uiState field.
312 The choice yang type uses case elements to describe the "options".
313 A choice can only ever have one option selected which allows
314 the system to determine which type is selected by the name of
315 the element contained within the field.
318 const stateExample = {
333 const statePath
= ['uiState.choice'].concat(name
);
334 const stateObject
= utils
.resolvePath(this.model
, statePath
.join('.')) || {};
335 const selected
= stateObject
.selected
? stateObject
.selected
.split('.')[1] : undefined;
336 // write state back to the model so the new state objects are captured
337 utils
.assignPathValue(this.model
, statePath
.join('.'), stateObject
);
339 // write the current choice value into the state
340 let choiceObject
= utils
.resolvePath(this.model
, [name
, selected
].join('.'));
341 let isTopCase
= false;
344 choiceObject
= utils
.resolvePath(this.model
, [selected
].join('.'));
346 utils
.assignPathValue(stateObject
, [selected
].join('.'), _
.cloneDeep(choiceObject
));
349 if(this.model
.uiState
.choice
.hasOwnProperty(name
)) {
350 delete this.model
[selected
];
351 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
353 // remove the current choice value from the model
354 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
358 // get any state for the new selected choice
359 const newChoiceObject
= utils
.resolvePath(stateObject
, [value
].join('.')) || {};
361 // assign new choice value to the model
363 utils
.assignPathValue(this.model
, [name
, value
].join('.'), newChoiceObject
);
365 utils
.assignPathValue(this.model
, [value
].join('.'), newChoiceObject
)
369 // update the selected name
370 utils
.assignPathValue(this.model
, statePath
.concat('selected').join('.'), value
);
372 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
376 const caseByNameMap
= {};
378 const onChange
= onFormFieldValueChanged
.bind(container
);
380 const cases
= property
.properties
.map(d
=> {
381 if (d
.type
=== 'case') {
382 caseByNameMap
[d
.name
] = d
.properties
[0];
385 optionTitle
: d
.description
,
386 //represents case name and case element name
387 optionValue
: [d
.name
, d
.properties
[0].name
].join('.')
390 caseByNameMap
[d
.name
] = d
;
391 return {optionName
: d
.name
};
394 const options
= [{optionName
: '', optionValue
: false}].concat(cases
).map((d
, i
) => {
396 <option key
={i
} value
={d
.optionValue
} title
={d
.optionTitle
}>
398 {i
? null : changeCase
.title(property
.name
)}
403 const selectName
= path
.join('.');
404 let selectedOptionPath
= ['uiState.choice', selectName
, 'selected'].join('.');
405 //Currently selected choice/case statement on UI model
406 let selectedOptionValue
= utils
.resolvePath(container
.model
, selectedOptionPath
);
407 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
408 if(!selectedOptionValue
) {
409 //get field properties for choice on container model
410 let fieldProperties
= utils
.resolvePath(container
.model
, selectName
);
411 if(fieldProperties
) {
412 //Check each case statement in model and see if it is present in container model.
413 cases
.map(function(c
){
414 if(fieldProperties
.hasOwnProperty(c
.optionValue
.split('.')[1])) {
415 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), c
.optionValue
);
418 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
420 property
.properties
.map(function(p
) {
421 let pname
= p
.properties
[0].name
;
422 if(container
.model
.hasOwnProperty(pname
)) {
423 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), [p
.name
, pname
].join('.'));
426 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
429 //If selectedOptionValue is present, take first item in string which represents the case name.
430 const valueProperty
= caseByNameMap
[selectedOptionValue
? selectedOptionValue
.split('.')[0] : undefined] || {properties
: []};
431 const isLeaf
= Property
.isLeaf(valueProperty
);
432 const hasProperties
= _
.isArray(valueProperty
.properties
) && valueProperty
.properties
.length
;
433 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(valueProperty
);
434 //Some magic that prevents errors for arising
435 const valueResponse
= valueProperty
.properties
.length
? valueProperty
.properties
.map((d
, i
) => {
436 const childPath
= path
.concat(valueProperty
.name
, d
.name
);
437 const childValue
= utils
.resolvePath(container
.model
, childPath
.join('.'));
439 <div key
={childPath
.concat('info', i
).join(':')}>
440 {build(container
, d
, childPath
, childValue
, props
)}
443 }) : (!isMissingDescriptorMeta
) ? build(container
, valueProperty
, path
.concat(valueProperty
.name
), utils
.resolvePath(container
.model
, path
.concat(valueProperty
.name
).join('.')) || container
.model
[valueProperty
.name
]) : null
445 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
448 <div key
={key
} className
="choice">
449 <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
}>
458 function buildSimpleListItem(container
, property
, path
, value
, key
, index
) {
459 // todo need to abstract this better
460 const title
= getTitle(value
);
461 var req
= require
.context("../", true, /\.svg
/);
464 <a href
="#select-list-item" key
={Date
.now()} className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
465 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
468 {buildRemovePropertyAction(container
, property
, path
)}
473 function buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) {
474 const className
= ClassNames(property
.name
+ '-remove actions');
476 <div key
={fieldKey
.concat(index
).join(':')} className
={className
}>
478 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
479 <span className
="info">{index
+ 1}</span
>
480 {buildRemovePropertyAction(container
, property
, valuePath
)}
486 function buildLeafListItem(container
, property
, valuePath
, value
, key
, index
) {
487 // look at the type to determine how to parse the value
490 {buildRemoveListItem(container
, property
, valuePath
, key
, index
)}
491 {buildField(container
, property
, valuePath
, value
, key
)}
497 function build(container
, property
, path
, value
, props
= {}) {
500 const isLeaf
= Property
.isLeaf(property
);
501 const isArray
= Property
.isArray(property
);
502 const isObject
= Property
.isObject(property
);
503 const isLeafList
= Property
.isLeafList(property
);
504 const fieldKey
= [container
.id
].concat(path
);
505 const isRequired
= Property
.isRequired(property
);
506 const title
= changeCase
.titleCase(property
.name
);
507 const columnCount
= property
.properties
.length
|| 1;
508 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
509 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
511 if (!property
.properties
&& isObject
) {
512 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
513 property
.properties
= uiState
.properties
;
516 const hasProperties
= _
.isArray(property
.properties
) && property
.properties
.length
;
517 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
519 // ensure value is not undefined for non-leaf property types
521 if (typeof value
!== 'object') {
522 value
= isArray
? [] : {};
525 const valueAsArray
= _
.isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
527 const isMetaField
= property
.name
=== 'meta';
528 const isCVNFD
= property
.name
=== 'constituent-vnfd';
529 const isSimpleListView
= Property
.isSimpleList(property
);
531 valueAsArray
.forEach((value
, index
) => {
534 const key
= fieldKey
.slice();
535 const valuePath
= path
.slice();
538 valuePath
.push(index
);
543 if (typeof value
=== 'object') {
544 value
= JSON
.stringify(value
, undefined, 12);
545 } else if (typeof value
!== 'string') {
550 if (isMissingDescriptorMeta
) {
551 field
= <span key
={key
.concat('warning').join(':')} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
552 } else if (property
.type
=== 'choice') {
553 field
= buildChoice(container
, property
, valuePath
, value
, key
.join(':'));
554 } else if (isSimpleListView
) {
555 field
= buildSimpleListItem(container
, property
, valuePath
, value
, key
, index
);
556 } else if (isLeafList
) {
557 field
= buildLeafListItem(container
, property
, valuePath
, value
, key
, index
);
558 } else if (hasProperties
) {
559 field
= buildElement(container
, property
, valuePath
, value
, key
.join(':'))
561 field
= buildField(container
, property
, valuePath
, value
, key
.join(':'));
564 function onClickLeaf(property
, path
, value
, event
) {
565 if (event
.isDefaultPrevented()) {
568 event
.preventDefault();
569 event
.stopPropagation();
570 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
571 console
.log('property selected', path
.join('.'));
572 ComposerAppActions
.propertySelected([path
.join('.')]);
575 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
576 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
579 <div key
={fieldKey
.concat(['property-content', index
]).join(':')}
580 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
581 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
582 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) : null}
589 classNames
['-is-leaf'] = isLeaf
;
590 classNames
['-is-array'] = isArray
;
591 classNames
['cols-' + columnCount
] = isColumnar
;
593 if (property
.type
=== 'choice') {
594 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
596 property
.properties
.map(function(p
) {
597 let pname
= p
.properties
[0].name
;
598 if(container
.model
.hasOwnProperty(pname
)) {
599 value
= container
.model
[pname
];
605 let displayValue
= typeof value
=== 'object' ? '' : value
;
606 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
608 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
611 <div key
={fieldKey
.join(':')} className
={ClassNames(property
.type
+ '-property property', classNames
)} onFocus
={onFocus
}>
612 <h3 className
="property-label">
613 <label htmlFor
={fieldKey
.join(':')}>
614 <span className
={property
.type
+ '-name name'}>{title
}</span
>
616 <span className
={property
.type
+ '-info info'}>{displayValueInfo
}</span
>
617 <span className
={property
.type
+ '-value value'}>{displayValue
}</span
>
619 {isArray
? buildAddPropertyAction(container
, property
, path
.concat(valueAsArray
.length
)) : null}
622 <span className
={property
.type
+ '-description description'}>{property
.description
}</span
>
623 <val className
="property-value">
624 {isCVNFD
? <span className
={property
.type
+ '-tip tip'}>Drag a VNFD
from the Catalog to add more
.</span
> : null}
632 const containerType
= container
.uiState
['qualified-type'] || container
.uiState
.type
;
633 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
635 function buildBasicGroup() {
636 if (basicProperties
.length
=== 0) {
640 <div className
="basic-properties-group">
643 {basicProperties
.map(property
=> {
644 const path
= [property
.name
];
645 const value
= container
.model
[property
.name
];
646 return build(container
, property
, path
, value
);
653 function buildAdvancedGroup() {
654 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
655 if (properties
.length
=== 0) {
658 const hasBasicFields
= basicProperties
.length
> 0;
659 const closeGroup
= basicProperties
.length
> 0;
661 <div className
="advanced-properties-group">
662 <h1 data
-toggle
={closeGroup
? 'true' : 'false'} className
={ClassNames({'-is-toggled': closeGroup
})} onClick
={toggle
} style
={{display
: hasBasicFields
? 'block' : 'none'}}>
663 <a className
="toggle-show-more" href
="#show-more-properties">more
&hellip
;</a
>
664 <a className
="toggle-show-less" href
="#show-more-properties">less
&hellip
;</a
>
666 <div className
="toggleable">
667 {properties
.map(property
=> {
668 const path
= [property
.name
];
669 const value
= container
.model
[property
.name
];
670 return build(container
, property
, path
, value
, {toggle
: true, width
: props
.width
});
673 <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>
678 function buildMoreLess(d, i) {
680 <span key={'bread
-crumb
-part
-' + i}>
681 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
688 if (container.parent) {
689 path.push(container.parent);
691 path.push(container);
694 <div className="EditDescriptorModelProperties -is-tree-view">
695 <h1>{path.map(buildMoreLess)}</h1>
697 {buildAdvancedGroup()}