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 onChange
= onFormFieldValueChanged
.bind(container
);
184 const isEnumeration
= Property
.isEnumeration(property
);
185 const isLeafRef
= Property
.isLeafRef(property
);
186 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
187 const placeholder
= changeCase
.title(property
.name
);
188 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
189 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : undefined;
191 const enumeration
= Property
.getEnumeration(property
, value
);
192 const options
= enumeration
.map((d
, i
) => {
193 // note yangforge generates values for enums but the system does not use them
194 // so we categorically ignore them
195 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
196 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
197 return <option key
={fieldKey
.toString() + ':' + i
} value
={d
.name
}>{d
.name
}</option
>;
199 const isValueSet
= enumeration
.filter(d
=> d
.isSelected
).length
> 0;
200 if (!isValueSet
|| property
.cardinality
=== '0..1') {
201 const noValueDisplayText
= changeCase
.title(property
.name
);
202 options
.unshift(<option key
={'(value-not-in-enum)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
204 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
>;
208 let fullFieldKey
= _
.isArray(fieldKey
) ? fieldKey
.join(':') : fieldKey
;
209 let containerRef
= container
;
210 while (containerRef
.parent
) {
211 fullFieldKey
= containerRef
.parent
.key
+ ':' + fullFieldKey
;
212 containerRef
= containerRef
.parent
;
214 const leafRefPathValues
= Property
.getLeafRef(property
, path
, value
, fullFieldKey
, catalogs
, container
);
216 const options
= leafRefPathValues
&& leafRefPathValues
.map((d
, i
) => {
217 return <option key
={fieldKey
.toString() + ':' + i
} value
={d
.value
}>{d
.value
}</option
>;
219 const isValueSet
= leafRefPathValues
.filter(d
=> d
.isSelected
).length
> 0;
220 if (!isValueSet
|| property
.cardinality
=== '0..1') {
221 const noValueDisplayText
= changeCase
.title(property
.name
);
222 options
.unshift(<option key
={'(value-not-in-leafref)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
224 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
>;
227 if (property
['preserve-line-breaks']) {
228 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
} />;
231 return <input key
={fieldKey
.toString()}
232 id
={fieldKey
.toString()}
236 className
={className
}
237 placeholder
={placeholder
}
241 onMouseDown
={startEditing
}
242 onMouseOver
={startEditing
}
243 onMouseOut
={endEditing
}
244 onMouseLeave
={endEditing
}
245 readOnly
={!isEditable
}
250 function buildElement(container
, property
, valuePath
, value
) {
251 return property
.properties
.map((property
, index
) => {
253 const childPath
= valuePath
.slice();
254 if (typeof value
=== 'object') {
255 childValue
= value
[property
.name
];
257 if(property
.type
!= 'choice'){
258 childPath
.push(property
.name
);
260 return build(container
, property
, childPath
, childValue
);
265 function buildChoice(container
, property
, path
, value
, key
) {
267 function onFormFieldValueChanged(event
) {
268 if (DescriptorModelFactory
.isContainer(this)) {
270 event
.preventDefault();
272 let name
= event
.target
.name
;
273 const value
= event
.target
.value
;
277 Transient State is stored for convenience in the uiState field.
278 The choice yang type uses case elements to describe the "options".
279 A choice can only ever have one option selected which allows
280 the system to determine which type is selected by the name of
281 the element contained within the field.
284 const stateExample = {
299 const statePath
= ['uiState.choice'].concat(name
);
300 const stateObject
= utils
.resolvePath(this.model
, statePath
.join('.')) || {};
301 const selected
= stateObject
.selected
? stateObject
.selected
.split('.')[1] : undefined;
302 // write state back to the model so the new state objects are captured
303 utils
.assignPathValue(this.model
, statePath
.join('.'), stateObject
);
305 // write the current choice value into the state
306 let choiceObject
= utils
.resolvePath(this.model
, [name
, selected
].join('.'));
307 let isTopCase
= false;
310 choiceObject
= utils
.resolvePath(this.model
, [selected
].join('.'));
312 utils
.assignPathValue(stateObject
, [selected
].join('.'), _
.cloneDeep(choiceObject
));
315 if(this.model
.uiState
.choice
.hasOwnProperty(name
)) {
316 delete this.model
[selected
];
317 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
319 // remove the current choice value from the model
320 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
324 // get any state for the new selected choice
325 const newChoiceObject
= utils
.resolvePath(stateObject
, [value
].join('.')) || {};
327 // assign new choice value to the model
329 utils
.assignPathValue(this.model
, [name
, value
].join('.'), newChoiceObject
);
331 utils
.assignPathValue(this.model
, [value
].join('.'), newChoiceObject
)
335 // update the selected name
336 utils
.assignPathValue(this.model
, statePath
.concat('selected').join('.'), value
);
338 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
342 const caseByNameMap
= {};
344 const onChange
= onFormFieldValueChanged
.bind(container
);
346 const cases
= property
.properties
.map(d
=> {
347 if (d
.type
=== 'case') {
348 caseByNameMap
[d
.name
] = d
.properties
[0];
351 optionTitle
: d
.description
,
352 //represents case name and case element name
353 optionValue
: [d
.name
, d
.properties
[0].name
].join('.')
356 caseByNameMap
[d
.name
] = d
;
357 return {optionName
: d
.name
};
360 const options
= [{optionName
: '', optionValue
: false}].concat(cases
).map((d
, i
) => {
362 <option key
={i
} value
={d
.optionValue
} title
={d
.optionTitle
}>
364 {i
? null : changeCase
.title(property
.name
)}
369 const selectName
= path
.join('.');
370 let selectedOptionPath
= ['uiState.choice', selectName
, 'selected'].join('.');
371 //Currently selected choice/case statement on UI model
372 let selectedOptionValue
= utils
.resolvePath(container
.model
, selectedOptionPath
);
373 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
374 if(!selectedOptionValue
) {
375 //get field properties for choice on container model
376 let fieldProperties
= utils
.resolvePath(container
.model
, selectName
);
377 if(fieldProperties
) {
378 //Check each case statement in model and see if it is present in container model.
379 cases
.map(function(c
){
380 if(fieldProperties
.hasOwnProperty(c
.optionValue
.split('.')[1])) {
381 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), c
.optionValue
);
384 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
386 property
.properties
.map(function(p
) {
387 let pname
= p
.properties
[0].name
;
388 if(container
.model
.hasOwnProperty(pname
)) {
389 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), [p
.name
, pname
].join('.'));
392 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
395 //If selectedOptionValue is present, take first item in string which represents the case name.
396 const valueProperty
= caseByNameMap
[selectedOptionValue
? selectedOptionValue
.split('.')[0] : undefined] || {properties
: []};
397 const isLeaf
= Property
.isLeaf(valueProperty
);
398 const hasProperties
= _
.isArray(valueProperty
.properties
) && valueProperty
.properties
.length
;
399 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(valueProperty
);
400 //Some magic that prevents errors for arising
401 const valueResponse
= valueProperty
.properties
.length
? valueProperty
.properties
.map((d
, i
) => {
402 const childPath
= path
.concat(valueProperty
.name
, d
.name
);
403 const childValue
= utils
.resolvePath(container
.model
, childPath
.join('.'));
405 <div key
={childPath
.concat('info', i
).join(':')}>
406 {build(container
, d
, childPath
, childValue
, props
)}
409 }) : (!isMissingDescriptorMeta
) ? build(container
, valueProperty
, path
.concat(valueProperty
.name
), utils
.resolvePath(container
.model
, path
.concat(valueProperty
.name
).join('.')) || container
.model
[valueProperty
.name
]) : null
411 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
414 <div key
={key
} className
="choice">
415 <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
}>
424 function buildSimpleListItem(container
, property
, path
, value
, key
, index
) {
425 // todo need to abstract this better
426 const title
= getTitle(value
);
427 var req
= require
.context("../", true, /\.svg
/);
430 <a href
="#select-list-item" key
={Date
.now()} className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
431 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
434 {buildRemovePropertyAction(container
, property
, path
)}
439 function buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) {
440 const className
= ClassNames(property
.name
+ '-remove actions');
442 <div key
={fieldKey
.concat(index
).join(':')} className
={className
}>
444 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
445 <span className
="info">{index
+ 1}</span
>
446 {buildRemovePropertyAction(container
, property
, valuePath
)}
452 function buildLeafListItem(container
, property
, valuePath
, value
, key
, index
) {
453 // look at the type to determine how to parse the value
456 {buildRemoveListItem(container
, property
, valuePath
, key
, index
)}
457 {buildField(container
, property
, valuePath
, value
, key
)}
463 function build(container
, property
, path
, value
, props
= {}) {
466 const isLeaf
= Property
.isLeaf(property
);
467 const isArray
= Property
.isArray(property
);
468 const isObject
= Property
.isObject(property
);
469 const isLeafList
= Property
.isLeafList(property
);
470 const fieldKey
= [container
.id
].concat(path
);
471 const isRequired
= Property
.isRequired(property
);
472 const title
= changeCase
.titleCase(property
.name
);
473 const columnCount
= property
.properties
.length
|| 1;
474 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
475 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
477 if (!property
.properties
&& isObject
) {
478 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
479 property
.properties
= uiState
.properties
;
482 const hasProperties
= _
.isArray(property
.properties
) && property
.properties
.length
;
483 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
485 // ensure value is not undefined for non-leaf property types
487 if (typeof value
!== 'object') {
488 value
= isArray
? [] : {};
491 const valueAsArray
= _
.isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
493 const isMetaField
= property
.name
=== 'meta';
494 const isCVNFD
= property
.name
=== 'constituent-vnfd';
495 const isSimpleListView
= Property
.isSimpleList(property
);
497 valueAsArray
.forEach((value
, index
) => {
500 const key
= fieldKey
.slice();
501 const valuePath
= path
.slice();
504 valuePath
.push(index
);
509 if (typeof value
=== 'object') {
510 value
= JSON
.stringify(value
, undefined, 12);
511 } else if (typeof value
!== 'string') {
516 if (isMissingDescriptorMeta
) {
517 field
= <span key
={key
.concat('warning').join(':')} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
518 } else if (property
.type
=== 'choice') {
519 field
= buildChoice(container
, property
, valuePath
, value
, key
.join(':'));
520 } else if (isSimpleListView
) {
521 field
= buildSimpleListItem(container
, property
, valuePath
, value
, key
, index
);
522 } else if (isLeafList
) {
523 field
= buildLeafListItem(container
, property
, valuePath
, value
, key
, index
);
524 } else if (hasProperties
) {
525 field
= buildElement(container
, property
, valuePath
, value
, key
.join(':'))
527 field
= buildField(container
, property
, valuePath
, value
, key
.join(':'));
530 function onClickLeaf(property
, path
, value
, event
) {
531 if (event
.isDefaultPrevented()) {
534 event
.preventDefault();
535 event
.stopPropagation();
536 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
537 console
.log('property selected', path
.join('.'));
538 ComposerAppActions
.propertySelected([path
.join('.')]);
541 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
542 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
545 <div key
={fieldKey
.concat(['property-content', index
]).join(':')}
546 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
547 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
548 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) : null}
555 classNames
['-is-leaf'] = isLeaf
;
556 classNames
['-is-array'] = isArray
;
557 classNames
['cols-' + columnCount
] = isColumnar
;
559 if (property
.type
=== 'choice') {
560 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
562 property
.properties
.map(function(p
) {
563 let pname
= p
.properties
[0].name
;
564 if(container
.model
.hasOwnProperty(pname
)) {
565 value
= container
.model
[pname
];
571 let displayValue
= typeof value
=== 'object' ? '' : value
;
572 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
574 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
577 <div key
={fieldKey
.join(':')} className
={ClassNames(property
.type
+ '-property property', classNames
)} onFocus
={onFocus
}>
578 <h3 className
="property-label">
579 <label htmlFor
={fieldKey
.join(':')}>
580 <span className
={property
.type
+ '-name name'}>{title
}</span
>
582 <span className
={property
.type
+ '-info info'}>{displayValueInfo
}</span
>
583 <span className
={property
.type
+ '-value value'}>{displayValue
}</span
>
585 {isArray
? buildAddPropertyAction(container
, property
, path
.concat(valueAsArray
.length
)) : null}
588 <span className
={property
.type
+ '-description description'}>{property
.description
}</span
>
589 <val className
="property-value">
590 {isCVNFD
? <span className
={property
.type
+ '-tip tip'}>Drag a VNFD
from the Catalog to add more
.</span
> : null}
598 const containerType
= container
.uiState
['qualified-type'] || container
.uiState
.type
;
599 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
601 function buildBasicGroup() {
602 if (basicProperties
.length
=== 0) {
606 <div className
="basic-properties-group">
609 {basicProperties
.map(property
=> {
610 const path
= [property
.name
];
611 const value
= container
.model
[property
.name
];
612 return build(container
, property
, path
, value
);
619 function buildAdvancedGroup() {
620 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
621 if (properties
.length
=== 0) {
624 const hasBasicFields
= basicProperties
.length
> 0;
625 const closeGroup
= basicProperties
.length
> 0;
627 <div className
="advanced-properties-group">
628 <h1 data
-toggle
={closeGroup
? 'true' : 'false'} className
={ClassNames({'-is-toggled': closeGroup
})} onClick
={toggle
} style
={{display
: hasBasicFields
? 'block' : 'none'}}>
629 <a className
="toggle-show-more" href
="#show-more-properties">more
&hellip
;</a
>
630 <a className
="toggle-show-less" href
="#show-more-properties">less
&hellip
;</a
>
632 <div className
="toggleable">
633 {properties
.map(property
=> {
634 const path
= [property
.name
];
635 const value
= container
.model
[property
.name
];
636 return build(container
, property
, path
, value
, {toggle
: true, width
: props
.width
});
639 <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>
644 function buildMoreLess(d, i) {
646 <span key={'bread
-crumb
-part
-' + i}>
647 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
654 if (container.parent) {
655 path.push(container.parent);
657 path.push(container);
660 <div className="EditDescriptorModelProperties -is-tree-view">
661 <h1>{path.map(buildMoreLess)}</h1>
663 {buildAdvancedGroup()}