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
=> _
.contains(DESCRIPTOR_MODEL_FIELDS
[type
], d
.name
);
53 return DescriptorModelMetaFactory
.getModelMetaForType(type
, basicPropertiesFilter
) || {properties
: []};
56 function getDescriptorMetaAdvancedForType(type
) {
57 const advPropertiesFilter
= d
=> !_
.contains(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') {
76 export default function EditDescriptorModelProperties(props
) {
78 const container
= props
.container
;
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());
147 <Button className
="inline-hint" onClick
={onClickAddProperty
.bind(container
, property
, path
)} label
="Add" src
={imgAdd
} />
151 function buildRemovePropertyAction(container
, property
, path
) {
152 function onClickRemoveProperty(property
, path
, event
) {
153 event
.preventDefault();
154 const name
= path
.join('.');
155 const removeMethod
= Property
.getContainerMethod(property
, this, 'remove');
157 removeMethod(utils
.resolvePath(this.model
, name
));
159 utils
.removePathValue(this.model
, name
);
161 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
164 <Button className
="remove-property-action inline-hint" title
="Remove" onClick
={onClickRemoveProperty
.bind(container
, property
, path
)} label
="Remove" src
={imgRemove
}/>
168 function onFormFieldValueChanged(event
) {
169 if (DescriptorModelFactory
.isContainer(this)) {
170 event
.preventDefault();
171 const name
= event
.target
.name
;
172 const value
= event
.target
.value
;
173 utils
.assignPathValue(this.model
, name
, value
);
174 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
178 function buildField(container
, property
, path
, value
, fieldKey
) {
179 let cds
= CatalogDataStore
;
180 let catalogs
= cds
.getTransientCatalogs();
182 const name
= path
.join('.');
183 const isEditable
= true;
184 const isGuid
= Property
.isGuid(property
);
185 const onChange
= onFormFieldValueChanged
.bind(container
);
186 const isEnumeration
= Property
.isEnumeration(property
);
187 const isLeafRef
= Property
.isLeafRef(property
);
188 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
189 const placeholder
= changeCase
.title(property
.name
);
190 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
191 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : undefined;
193 const enumeration
= Property
.getEnumeration(property
, value
);
194 const options
= enumeration
.map((d
, i
) => {
195 // note yangforge generates values for enums but the system does not use them
196 // so we categorically ignore them
197 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
198 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
199 return <option key
={fieldKey
.toString() + ':' + i
} value
={d
.name
}>{d
.name
}</option
>;
201 const isValueSet
= enumeration
.filter(d
=> d
.isSelected
).length
> 0;
202 if (!isValueSet
|| property
.cardinality
=== '0..1') {
203 const noValueDisplayText
= changeCase
.title(property
.name
);
204 options
.unshift(<option key
={'(value-not-in-enum)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
206 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
>;
210 let fullFieldKey
= fieldKey
;
211 let containerRef
= container
;
212 while (containerRef
.parent
) {
213 fullFieldKey
= containerRef
.parent
.key
+ ':' + fullFieldKey
;
214 containerRef
= containerRef
.parent
;
216 const leafRefPathValues
= Property
.getLeafRef(property
, path
, value
, fullFieldKey
, catalogs
, container
);
218 const options
= leafRefPathValues
&& leafRefPathValues
.map((d
, i
) => {
219 return <option key
={fieldKey
.toString() + ':' + i
} value
={d
.value
}>{d
.value
}</option
>;
221 const isValueSet
= leafRefPathValues
.filter(d
=> d
.isSelected
).length
> 0;
222 if (!isValueSet
|| property
.cardinality
=== '0..1') {
223 const noValueDisplayText
= changeCase
.title(property
.name
);
224 options
.unshift(<option key
={'(value-not-in-leafref)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
226 return <select key
={fieldKey
.toString()} id
={fieldKey
.toString()} className
={ClassNames({'-value-not-set': !isValueSet
})} name
={name
} value
={value
} title
={name
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} readOnly
={!isEditable
}>{options
}</select
>;
229 if (property
['preserve-line-breaks']) {
230 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
} />;
233 return <input key
={fieldKey
.toString()}
234 id
={fieldKey
.toString()}
238 className
={className
}
239 placeholder
={placeholder
}
243 onMouseDown
={startEditing
}
244 onMouseOver
={startEditing
}
245 onMouseOut
={endEditing
}
246 onMouseLeave
={endEditing
}
247 readOnly
={!isEditable
}
252 function buildElement(container
, property
, valuePath
, value
) {
253 return property
.properties
.map((property
, index
) => {
255 const childPath
= valuePath
.slice();
256 if (typeof value
=== 'object') {
257 childValue
= value
[property
.name
];
259 if(property
.type
!= 'choice'){
260 childPath
.push(property
.name
);
262 return build(container
, property
, childPath
, childValue
);
267 function buildChoice(container
, property
, path
, value
, key
) {
269 function onFormFieldValueChanged(event
) {
270 if (DescriptorModelFactory
.isContainer(this)) {
272 event
.preventDefault();
274 let name
= event
.target
.name
;
275 const value
= event
.target
.value
;
279 Transient State is stored for convenience in the uiState field.
280 The choice yang type uses case elements to describe the "options".
281 A choice can only ever have one option selected which allows
282 the system to determine which type is selected by the name of
283 the element contained within the field.
286 const stateExample = {
301 const statePath
= ['uiState.choice'].concat(name
);
302 const stateObject
= utils
.resolvePath(this.model
, statePath
.join('.')) || {};
303 const selected
= stateObject
.selected
? stateObject
.selected
.split('.')[1] : undefined;
304 // write state back to the model so the new state objects are captured
305 utils
.assignPathValue(this.model
, statePath
.join('.'), stateObject
);
307 // write the current choice value into the state
308 let choiceObject
= utils
.resolvePath(this.model
, [name
, selected
].join('.'));
309 let isTopCase
= false;
312 choiceObject
= utils
.resolvePath(this.model
, [selected
].join('.'));
314 utils
.assignPathValue(stateObject
, [selected
].join('.'), _
.cloneDeep(choiceObject
));
317 if(this.model
.uiState
.choice
.hasOwnProperty(name
)) {
318 delete this.model
[selected
];
319 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
321 // remove the current choice value from the model
322 utils
.removePathValue(this.model
, [name
, selected
].join('.'), isTopCase
);
326 // get any state for the new selected choice
327 const newChoiceObject
= utils
.resolvePath(stateObject
, [value
].join('.')) || {};
329 // assign new choice value to the model
331 utils
.assignPathValue(this.model
, [name
, value
].join('.'), newChoiceObject
);
333 utils
.assignPathValue(this.model
, [value
].join('.'), newChoiceObject
)
337 // update the selected name
338 utils
.assignPathValue(this.model
, statePath
.concat('selected').join('.'), value
);
340 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
344 const caseByNameMap
= {};
346 const onChange
= onFormFieldValueChanged
.bind(container
);
348 const cases
= property
.properties
.map(d
=> {
349 if (d
.type
=== 'case') {
350 caseByNameMap
[d
.name
] = d
.properties
[0];
353 optionTitle
: d
.description
,
354 //represents case name and case element name
355 optionValue
: [d
.name
, d
.properties
[0].name
].join('.')
358 caseByNameMap
[d
.name
] = d
;
359 return {optionName
: d
.name
};
362 const options
= [{optionName
: '', optionValue
: false}].concat(cases
).map((d
, i
) => {
364 <option key
={i
} value
={d
.optionValue
} title
={d
.optionTitle
}>
366 {i
? null : changeCase
.title(property
.name
)}
371 const selectName
= path
.join('.');
372 let selectedOptionPath
= ['uiState.choice', selectName
, 'selected'].join('.');
373 //Currently selected choice/case statement on UI model
374 let selectedOptionValue
= utils
.resolvePath(container
.model
, selectedOptionPath
);
375 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
376 if(!selectedOptionValue
) {
377 //get field properties for choice on container model
378 let fieldProperties
= utils
.resolvePath(container
.model
, selectName
);
379 if(fieldProperties
) {
380 //Check each case statement in model and see if it is present in container model.
381 cases
.map(function(c
){
382 if(fieldProperties
.hasOwnProperty(c
.optionValue
.split('.')[1])) {
383 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), c
.optionValue
);
386 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
388 property
.properties
.map(function(p
) {
389 let pname
= p
.properties
[0].name
;
390 if(container
.model
.hasOwnProperty(pname
)) {
391 utils
.assignPathValue(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'), [p
.name
, pname
].join('.'));
394 selectedOptionValue
= utils
.resolvePath(container
.model
, ['uiState.choice', selectName
, 'selected'].join('.'));
397 //If selectedOptionValue is present, take first item in string which represents the case name.
398 const valueProperty
= caseByNameMap
[selectedOptionValue
? selectedOptionValue
.split('.')[0] : undefined] || {properties
: []};
399 const isLeaf
= Property
.isLeaf(valueProperty
);
400 const hasProperties
= _
.isArray(valueProperty
.properties
) && valueProperty
.properties
.length
;
401 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(valueProperty
);
402 //Some magic that prevents errors for arising
403 const valueResponse
= valueProperty
.properties
.length
? valueProperty
.properties
.map((d
, i
) => {
404 const childPath
= path
.concat(valueProperty
.name
, d
.name
);
405 const childValue
= utils
.resolvePath(container
.model
, childPath
.join('.'));
407 <div key
={childPath
.concat('info', i
).join(':')}>
408 {build(container
, d
, childPath
, childValue
, props
)}
411 }) : (!isMissingDescriptorMeta
) ? build(container
, valueProperty
, path
.concat(valueProperty
.name
), utils
.resolvePath(container
.model
, path
.concat(valueProperty
.name
).join('.')) || container
.model
[valueProperty
.name
]) : null
413 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
416 <div key
={key
} className
="choice">
417 <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
}>
426 function buildSimpleListItem(container
, property
, path
, value
, key
, index
) {
427 // todo need to abstract this better
428 const title
= getTitle(value
);
429 var req
= require
.context("../", true, /\.svg
/);
432 <a href
="#select-list-item" key
={Date
.now()} className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
433 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
436 {buildRemovePropertyAction(container
, property
, path
)}
441 function buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) {
442 const className
= ClassNames(property
.name
+ '-remove actions');
444 <div key
={fieldKey
.concat(index
).join(':')} className
={className
}>
446 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
447 <span className
="info">{index
+ 1}</span
>
448 {buildRemovePropertyAction(container
, property
, valuePath
)}
454 function buildLeafListItem(container
, property
, valuePath
, value
, key
, index
) {
455 // look at the type to determine how to parse the value
458 {buildRemoveListItem(container
, property
, valuePath
, key
, index
)}
459 {buildField(container
, property
, valuePath
, value
, key
)}
465 function build(container
, property
, path
, value
, props
= {}) {
468 const isLeaf
= Property
.isLeaf(property
);
469 const isArray
= Property
.isArray(property
);
470 const isObject
= Property
.isObject(property
);
471 const isLeafList
= Property
.isLeafList(property
);
472 const fieldKey
= [container
.id
].concat(path
);
473 const isRequired
= Property
.isRequired(property
);
474 const title
= changeCase
.titleCase(property
.name
);
475 const columnCount
= property
.properties
.length
|| 1;
476 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
477 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
479 if (!property
.properties
&& isObject
) {
480 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
481 property
.properties
= uiState
.properties
;
484 const hasProperties
= _
.isArray(property
.properties
) && property
.properties
.length
;
485 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
487 // ensure value is not undefined for non-leaf property types
489 if (typeof value
!== 'object') {
490 value
= isArray
? [] : {};
493 const valueAsArray
= _
.isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
495 const isMetaField
= property
.name
=== 'meta';
496 const isCVNFD
= property
.name
=== 'constituent-vnfd';
497 const isSimpleListView
= Property
.isSimpleList(property
);
499 valueAsArray
.forEach((value
, index
) => {
502 const key
= fieldKey
.slice();
503 const valuePath
= path
.slice();
506 valuePath
.push(index
);
511 if (typeof value
=== 'object') {
512 value
= JSON
.stringify(value
, undefined, 12);
513 } else if (typeof value
!== 'string') {
518 if (isMissingDescriptorMeta
) {
519 field
= <span key
={key
.concat('warning').join(':')} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
520 } else if (property
.type
=== 'choice') {
521 field
= buildChoice(container
, property
, valuePath
, value
, key
.join(':'));
522 } else if (isSimpleListView
) {
523 field
= buildSimpleListItem(container
, property
, valuePath
, value
, key
, index
);
524 } else if (isLeafList
) {
525 field
= buildLeafListItem(container
, property
, valuePath
, value
, key
, index
);
526 } else if (hasProperties
) {
527 field
= buildElement(container
, property
, valuePath
, value
, key
.join(':'))
529 field
= buildField(container
, property
, valuePath
, value
, key
.join(':'));
532 function onClickLeaf(property
, path
, value
, event
) {
533 if (event
.isDefaultPrevented()) {
536 event
.preventDefault();
537 event
.stopPropagation();
538 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
539 console
.log('property selected', path
.join('.'));
540 ComposerAppActions
.propertySelected([path
.join('.')]);
543 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
544 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
547 <div key
={fieldKey
.concat(['property-content', index
]).join(':')}
548 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
549 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
550 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) : null}
557 classNames
['-is-leaf'] = isLeaf
;
558 classNames
['-is-array'] = isArray
;
559 classNames
['cols-' + columnCount
] = isColumnar
;
561 if (property
.type
=== 'choice') {
562 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
564 property
.properties
.map(function(p
) {
565 let pname
= p
.properties
[0].name
;
566 if(container
.model
.hasOwnProperty(pname
)) {
567 value
= container
.model
[pname
];
573 let displayValue
= typeof value
=== 'object' ? '' : value
;
574 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
576 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
579 <div key
={fieldKey
.join(':')} className
={ClassNames(property
.type
+ '-property property', classNames
)} onFocus
={onFocus
}>
580 <h3 className
="property-label">
581 <label htmlFor
={fieldKey
.join(':')}>
582 <span className
={property
.type
+ '-name name'}>{title
}</span
>
584 <span className
={property
.type
+ '-info info'}>{displayValueInfo
}</span
>
585 <span className
={property
.type
+ '-value value'}>{displayValue
}</span
>
587 {isArray
? buildAddPropertyAction(container
, property
, path
.concat(valueAsArray
.length
)) : null}
590 <span className
={property
.type
+ '-description description'}>{property
.description
}</span
>
591 <val className
="property-value">
592 {isCVNFD
? <span className
={property
.type
+ '-tip tip'}>Drag a VNFD
from the Catalog to add more
.</span
> : null}
600 const containerType
= container
.uiState
['qualified-type'] || container
.uiState
.type
;
601 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
603 function buildBasicGroup() {
604 if (basicProperties
.length
=== 0) {
608 <div className
="basic-properties-group">
611 {basicProperties
.map(property
=> {
612 const path
= [property
.name
];
613 const value
= container
.model
[property
.name
];
614 return build(container
, property
, path
, value
);
621 function buildAdvancedGroup() {
622 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
623 if (properties
.length
=== 0) {
626 const hasBasicFields
= basicProperties
.length
> 0;
627 const closeGroup
= basicProperties
.length
> 0;
629 <div className
="advanced-properties-group">
630 <h1 data
-toggle
={closeGroup
? 'true' : 'false'} className
={ClassNames({'-is-toggled': closeGroup
})} onClick
={toggle
} style
={{display
: hasBasicFields
? 'block' : 'none'}}>
631 <a className
="toggle-show-more" href
="#show-more-properties">more
&hellip
;</a
>
632 <a className
="toggle-show-less" href
="#show-more-properties">less
&hellip
;</a
>
634 <div className
="toggleable">
635 {properties
.map(property
=> {
636 const path
= [property
.name
];
637 const value
= container
.model
[property
.name
];
638 return build(container
, property
, path
, value
, {toggle
: true, width
: props
.width
});
641 <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>
646 function buildMoreLess(d, i) {
648 <span key={'bread
-crumb
-part
-' + i}>
649 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
656 if (container.parent) {
657 path.push(container.parent);
659 path.push(container);
662 <div className="EditDescriptorModelProperties -is-tree-view">
663 <h1>{path.map(buildMoreLess)}</h1>
665 {buildAdvancedGroup()}
670 // export buildElement;
671 // export buildChoice;