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'
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') {
75 function startEditing() {
76 DeletionManager
.removeEventListeners();
79 function endEditing() {
80 DeletionManager
.addEventListeners();
83 function onClickSelectItem(property
, path
, value
, event
) {
84 event
.preventDefault();
85 const root
= this.getRoot();
86 if (SelectionManager
.select(value
)) {
87 CatalogItemsActions
.catalogItemMetaDataChanged(root
.model
);
91 function onFocusPropertyFormInputElement(property
, path
, value
, event
) {
93 event
.preventDefault();
96 function removeIsFocusedClass(event
) {
97 event
.target
.removeEventListener('blur', removeIsFocusedClass
);
98 Array
.from(document
.querySelectorAll('.-is-focused')).forEach(d
=> d
.classList
.remove('-is-focused'));
101 removeIsFocusedClass(event
);
103 const propertyWrapper
= getEventPath(event
).reduce((parent
, element
) => {
107 if (!element
.classList
) {
110 if (element
.classList
.contains('property')) {
115 if (propertyWrapper
) {
116 propertyWrapper
.classList
.add('-is-focused');
117 event
.target
.addEventListener('blur', removeIsFocusedClass
);
122 function buildAddPropertyAction(container
, property
, path
) {
123 function onClickAddProperty(property
, path
, event
) {
124 event
.preventDefault();
125 //SelectionManager.resume();
126 const create
= Property
.getContainerCreateMethod(property
, this);
129 create(model
, path
, property
);
131 const name
= path
.join('.');
132 const value
= Property
.createModelInstance(property
);
133 utils
.assignPathValue(this.model
, name
, value
);
135 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
138 <Button className
="inline-hint" onClick
={onClickAddProperty
.bind(container
, property
, path
)} label
="Add" src
={imgAdd
} />
142 function buildRemovePropertyAction(container
, property
, path
) {
143 function onClickRemoveProperty(property
, path
, event
) {
144 event
.preventDefault();
145 const name
= path
.join('.');
146 const removeMethod
= Property
.getContainerMethod(property
, this, 'remove');
148 removeMethod(utils
.resolvePath(this.model
, name
));
150 utils
.removePathValue(this.model
, name
);
152 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
155 <Button className
="remove-property-action inline-hint" title
="Remove" onClick
={onClickRemoveProperty
.bind(container
, property
, path
)} label
="Remove" src
={imgRemove
}/>
159 function onFormFieldValueChanged(event
) {
160 if (DescriptorModelFactory
.isContainer(this)) {
161 event
.preventDefault();
162 const name
= event
.target
.name
;
163 const value
= event
.target
.value
;
164 utils
.assignPathValue(this.model
, name
, value
);
165 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
169 function buildField(container
, property
, path
, value
, fieldKey
) {
170 let cds
= CatalogDataStore
;
171 let catalogs
= cds
.getTransientCatalogs();
173 const name
= path
.join('.');
174 const isEditable
= true;
175 const isGuid
= Property
.isGuid(property
);
176 const isBoolean
= Property
.isBoolean(property
);
177 const onChange
= onFormFieldValueChanged
.bind(container
);
178 const isEnumeration
= Property
.isEnumeration(property
);
179 const isLeafRef
= Property
.isLeafRef(property
);
180 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
181 const placeholder
= changeCase
.title(property
.name
);
182 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
183 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : undefined;
185 const enumeration
= Property
.getEnumeration(property
, value
);
186 const options
= enumeration
.map((d
, i
) => {
187 // note yangforge generates values for enums but the system does not use them
188 // so we categorically ignore them
189 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
190 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
191 return <option key
={fieldKey
.toString() + ':' + i
} value
={d
.name
}>{d
.name
}</option
>;
193 const isValueSet
= enumeration
.filter(d
=> d
.isSelected
).length
> 0;
194 if (!isValueSet
|| property
.cardinality
=== '0..1') {
195 const noValueDisplayText
= changeCase
.title(property
.name
);
196 options
.unshift(<option key
={'(value-not-in-enum)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
198 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
>;
202 let fullFieldKey
= _
.isArray(fieldKey
) ? fieldKey
.join(':') : fieldKey
;
203 let containerRef
= container
;
204 while (containerRef
.parent
) {
205 fullFieldKey
= containerRef
.parent
.key
+ ':' + fullFieldKey
;
206 containerRef
= containerRef
.parent
;
208 const leafRefPathValues
= Property
.getLeafRef(property
, path
, value
, fullFieldKey
, catalogs
, container
);
210 const options
= leafRefPathValues
&& leafRefPathValues
.map((d
, i
) => {
211 return <option key
={fieldKey
.toString() + ':' + i
} value
={d
.value
}>{d
.value
}</option
>;
213 const isValueSet
= leafRefPathValues
.filter(d
=> d
.isSelected
).length
> 0;
214 if (!isValueSet
|| property
.cardinality
=== '0..1') {
215 const noValueDisplayText
= changeCase
.title(property
.name
);
216 options
.unshift(<option key
={'(value-not-in-leafref)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
218 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
>;
222 let fullFieldKey
= _
.isArray(fieldKey
) ? fieldKey
.join(':') : fieldKey
;
223 let containerRef
= container
;
224 while (containerRef
.parent
) {
225 fullFieldKey
= containerRef
.parent
.key
+ ':' + fullFieldKey
;
226 containerRef
= containerRef
.parent
;
230 <option key
={fieldKey
.toString() + '-true'} value
="TRUE">TRUE
</option
>,
231 <option key
={fieldKey
.toString() + '-false'} value
="FALSE">FALSE
</option
>
234 // if (!isValueSet) {
235 const noValueDisplayText
= changeCase
.title(property
.name
);
236 options
.unshift(<option key
={'(value-not-in-leafref)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}></option
>);
239 if(typeof(val
) == 'number') {
240 val
= value
? "TRUE" : "FALSE"
242 const isValueSet
= (val
!= '' && val
)
243 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
>;
246 if (property
['preserve-line-breaks']) {
247 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
} />;
250 return <input key
={fieldKey
.toString()}
251 id
={fieldKey
.toString()}
255 className
={className
}
256 placeholder
={placeholder
}
260 onMouseDown
={startEditing
}
261 onMouseOver
={startEditing
}
262 onMouseOut
={endEditing
}
263 onMouseLeave
={endEditing
}
264 readOnly
={!isEditable
}
269 function buildElement(container
, property
, valuePath
, value
) {
270 return property
.properties
.map((property
, index
) => {
272 const childPath
= valuePath
.slice();
273 if (typeof value
=== 'object') {
274 childValue
= value
[property
.name
];
276 if(property
.type
!= 'choice'){
277 childPath
.push(property
.name
);
279 return build(container
, property
, childPath
, childValue
);
284 function buildChoice(container
, property
, path
, value
, key
, props
={}) {
286 function onFormFieldValueChanged(event
) {
287 if (DescriptorModelFactory
.isContainer(this)) {
289 event
.preventDefault();
291 let name
= event
.target
.name
;
292 const value
= event
.target
.value
;
296 Transient State is stored for convenience in the uiState field.
297 The choice yang type uses case elements to describe the "options".
298 A choice can only ever have one option selected which allows
299 the system to determine which type is selected by the name of
300 the element contained within the field.
303 const stateExample = {
318 const statePath
= ['uiState.choice'].concat(name
);
319 const stateObject
= utils
.resolvePath(this.model
, statePath
.join('.')) || {};
320 const selected
= stateObject
.selected
? stateObject
.selected
.split('.')[1] : undefined;
321 // write state back to the model so the new state objects are captured
322 utils
.assignPathValue(this.model
, statePath
.join('.'), stateObject
);
324 // write the current choice value into the state
325 let choiceObject
= utils
.resolvePath(this.model
, [name
, selected
].join('.'));
326 let isTopCase
= false;
329 choiceObject
= utils
.resolvePath(this.model
, [selected
].join('.'));
331 utils
.assignPathValue(stateObject
, [selected
].join('.'), _
.cloneDeep(choiceObject
));
334 if(this.model
.uiState
.choice
.hasOwnProperty(name
)) {
335 delete this.model
[selected
];
336 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
338 // remove the current choice value from the model
339 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
343 // get any state for the new selected choice
344 const newChoiceObject
= utils
.resolvePath(stateObject
, [value
].join('.')) || {};
346 // assign new choice value to the model
348 utils
.assignPathValue(this.model
, [name
, value
].join('.'), newChoiceObject
);
350 utils
.assignPathValue(this.model
, [value
].join('.'), newChoiceObject
)
354 // update the selected name
355 utils
.assignPathValue(this.model
, statePath
.concat('selected').join('.'), value
);
357 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
361 const caseByNameMap
= {};
363 const onChange
= onFormFieldValueChanged
.bind(container
);
365 const cases
= property
.properties
.map(d
=> {
366 if (d
.type
=== 'case') {
367 caseByNameMap
[d
.name
] = d
.properties
[0];
370 optionTitle
: d
.description
,
371 //represents case name and case element name
372 optionValue
: [d
.name
, d
.properties
[0].name
].join('.')
375 caseByNameMap
[d
.name
] = d
;
376 return {optionName
: d
.name
};
379 const options
= [{optionName
: '', optionValue
: false}].concat(cases
).map((d
, i
) => {
381 <option key
={i
} value
={d
.optionValue
} title
={d
.optionTitle
}>
383 {i
? null : changeCase
.title(property
.name
)}
388 const selectName
= path
.join('.');
389 let selectedOptionPath
= ['uiState.choice', selectName
, 'selected'].join('.');
390 //Currently selected choice/case statement on UI model
391 let selectedOptionValue
= utils
.resolvePath(container
.model
, selectedOptionPath
);
392 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
393 if(!selectedOptionValue
) {
394 //get field properties for choice on container model
395 let fieldProperties
= utils
.resolvePath(container
.model
, selectName
);
396 if(fieldProperties
) {
397 //Check each case statement in model and see if it is present in container model.
398 cases
.map(function(c
){
399 if(fieldProperties
.hasOwnProperty(c
.optionValue
.split('.')[1])) {
400 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), c
.optionValue
);
403 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
405 property
.properties
.map(function(p
) {
406 let pname
= p
.properties
[0].name
;
407 if(container
.model
.hasOwnProperty(pname
)) {
408 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), [p
.name
, pname
].join('.'));
411 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
414 //If selectedOptionValue is present, take first item in string which represents the case name.
415 const valueProperty
= caseByNameMap
[selectedOptionValue
? selectedOptionValue
.split('.')[0] : undefined] || {properties
: []};
416 const isLeaf
= Property
.isLeaf(valueProperty
);
417 const hasProperties
= _
.isArray(valueProperty
.properties
) && valueProperty
.properties
.length
;
418 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(valueProperty
);
419 //Some magic that prevents errors for arising
420 const valueResponse
= valueProperty
.properties
.length
? valueProperty
.properties
.map((d
, i
) => {
421 const childPath
= path
.concat(valueProperty
.name
, d
.name
);
422 const childValue
= utils
.resolvePath(container
.model
, childPath
.join('.'));
424 <div key
={childPath
.concat('info', i
).join(':')}>
425 {build(container
, d
, childPath
, childValue
, props
)}
428 }) : (!isMissingDescriptorMeta
) ? build(container
, valueProperty
, path
.concat(valueProperty
.name
), utils
.resolvePath(container
.model
, path
.concat(valueProperty
.name
).join('.')) || container
.model
[valueProperty
.name
]) : null
430 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
433 <div key
={key
} className
="choice">
434 <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
}>
443 function buildSimpleListItem(container
, property
, path
, value
, key
, index
) {
444 // todo need to abstract this better
445 const title
= getTitle(value
);
446 var req
= require
.context("../", true, /\.svg
/);
449 <a href
="#select-list-item" key
={Date
.now()} className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
450 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
453 {buildRemovePropertyAction(container
, property
, path
)}
458 function buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) {
459 const className
= ClassNames(property
.name
+ '-remove actions');
461 <div key
={fieldKey
.concat(index
).join(':')} className
={className
}>
463 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
464 <span className
="info">{index
+ 1}</span
>
465 {buildRemovePropertyAction(container
, property
, valuePath
)}
471 function buildLeafListItem(container
, property
, valuePath
, value
, key
, index
) {
472 // look at the type to determine how to parse the value
475 {buildRemoveListItem(container
, property
, valuePath
, key
, index
)}
476 {buildField(container
, property
, valuePath
, value
, key
)}
482 function build(container
, property
, path
, value
, props
= {}) {
485 const isLeaf
= Property
.isLeaf(property
);
486 const isArray
= Property
.isArray(property
);
487 const isObject
= Property
.isObject(property
);
488 const isLeafList
= Property
.isLeafList(property
);
489 const fieldKey
= [container
.id
].concat(path
);
490 const isRequired
= Property
.isRequired(property
);
491 const title
= changeCase
.titleCase(property
.name
);
492 const columnCount
= property
.properties
.length
|| 1;
493 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
494 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
496 if (!property
.properties
&& isObject
) {
497 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
498 property
.properties
= uiState
.properties
;
501 const hasProperties
= _
.isArray(property
.properties
) && property
.properties
.length
;
502 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
504 // ensure value is not undefined for non-leaf property types
506 if (typeof value
!== 'object') {
507 value
= isArray
? [] : {};
510 const valueAsArray
= _
.isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
512 const isMetaField
= property
.name
=== 'meta';
513 const isCVNFD
= property
.name
=== 'constituent-vnfd';
514 const isSimpleListView
= Property
.isSimpleList(property
);
516 valueAsArray
.forEach((value
, index
) => {
519 const key
= fieldKey
.slice();
520 const valuePath
= path
.slice();
523 valuePath
.push(index
);
528 if (typeof value
=== 'object') {
529 value
= JSON
.stringify(value
, undefined, 12);
530 } else if (typeof value
!== 'string') {
535 if (isMissingDescriptorMeta
) {
536 field
= <span key
={key
.concat('warning').join(':')} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
537 } else if (property
.type
=== 'choice') {
538 field
= buildChoice(container
, property
, valuePath
, value
, key
.join(':'), props
);
539 } else if (isSimpleListView
) {
540 field
= buildSimpleListItem(container
, property
, valuePath
, value
, key
, index
, props
);
541 } else if (isLeafList
) {
542 field
= buildLeafListItem(container
, property
, valuePath
, value
, key
, index
, props
);
543 } else if (hasProperties
) {
544 field
= buildElement(container
, property
, valuePath
, value
, key
.join(':'), props
)
546 field
= buildField(container
, property
, valuePath
, value
, key
.join(':'), props
);
549 function onClickLeaf(property
, path
, value
, event
) {
550 if (event
.isDefaultPrevented()) {
553 event
.preventDefault();
554 event
.stopPropagation();
555 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
556 console
.log('property selected', path
.join('.'));
557 ComposerAppActions
.propertySelected([path
.join('.')]);
560 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
561 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
564 <div key
={fieldKey
.concat(['property-content', index
]).join(':')}
565 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
566 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
567 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) : null}
574 classNames
['-is-leaf'] = isLeaf
;
575 classNames
['-is-array'] = isArray
;
576 classNames
['cols-' + columnCount
] = isColumnar
;
578 if (property
.type
=== 'choice') {
579 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
581 property
.properties
.map(function(p
) {
582 let pname
= p
.properties
[0].name
;
583 if(container
.model
.hasOwnProperty(pname
)) {
584 value
= container
.model
[pname
];
590 let displayValue
= typeof value
=== 'object' ? '' : value
;
591 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
593 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
596 <div key
={fieldKey
.join(':')} className
={ClassNames(property
.type
+ '-property property', classNames
)} onFocus
={onFocus
}>
597 <h3 className
="property-label">
598 <label htmlFor
={fieldKey
.join(':')}>
599 <span className
={property
.type
+ '-name name'}>{title
}</span
>
601 <span className
={property
.type
+ '-info info'}>{displayValueInfo
}</span
>
602 <span className
={property
.type
+ '-value value'}>{displayValue
}</span
>
604 {isArray
? buildAddPropertyAction(container
, property
, path
.concat(valueAsArray
.length
)) : null}
607 <span className
={property
.type
+ '-description description'}>{property
.description
}</span
>
608 <val className
="property-value">
609 {isCVNFD
? <span className
={property
.type
+ '-tip tip'}>Drag a VNFD
from the Catalog to add more
.</span
> : null}
616 export default function EditDescriptorModelProperties(props
, type
) {
618 const container
= props
.container
;
620 if (!(DescriptorModelFactory
.isContainer(container
))) {
626 const containerType
= (_
.isEmpty(type
) ? false : type
)|| container
.uiState
['qualified-type'] || container
.uiState
.type
;
627 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
629 function buildBasicGroup() {
630 if (basicProperties
.length
=== 0) {
634 <div className
="basic-properties-group">
637 {basicProperties
.map(property
=> {
638 const path
= [property
.name
];
639 const value
= container
.model
[property
.name
];
640 return build(container
, property
, path
, value
);
647 function buildAdvancedGroup() {
648 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
649 if (properties
.length
=== 0) {
652 const hasBasicFields
= basicProperties
.length
> 0;
653 const closeGroup
= basicProperties
.length
> 0;
655 <div className
="advanced-properties-group">
656 <h1 data
-toggle
={closeGroup
? 'true' : 'false'} className
={ClassNames({'-is-toggled': closeGroup
})} onClick
={toggle
} style
={{display
: hasBasicFields
? 'block' : 'none'}}>
657 <a className
="toggle-show-more" href
="#show-more-properties">more
&hellip
;</a
>
658 <a className
="toggle-show-less" href
="#show-more-properties">less
&hellip
;</a
>
660 <div className
="toggleable">
661 {properties
.map(property
=> {
662 const path
= [property
.name
];
663 const value
= container
.model
[property
.name
];
664 return build(container
, property
, path
, value
, _
.assign({toggle
: true, width
: props
.width
}, props
));
667 <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>
672 function buildMoreLess(d, i) {
674 <span key={'bread
-crumb
-part
-' + i}>
675 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
682 if (container.parent) {
683 path.push(container.parent);
685 path.push(container);
688 <div className="EditDescriptorModelProperties -is-tree-view">
689 <h1>{path.map(buildMoreLess)}</h1>
691 {buildAdvancedGroup()}
697 // export buildElement;
698 // export buildChoice;