1add58c4dd472be24b5a2e0119c20eba51833251
[osm/UI.git] / skyquake / plugins / composer / src / src / components / EditConfigParameterMap.js
1 /*
2 *
3 * Copyright 2016 RIFT.IO Inc
4 *
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
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
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.
16 *
17 */
18 /**
19 * Created by onvelocity on 1/18/16.
20 *
21 * This class generates the form fields used to edit the CONFD JSON model.
22 */
23 'use strict';
24
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'
43
44 import imgAdd from '../../../node_modules/open-iconic/svg/plus.svg'
45 import imgRemove from '../../../node_modules/open-iconic/svg/trash.svg'
46
47 import '../styles/EditDescriptorModelProperties.scss'
48
49
50
51 function getDescriptorMetaBasicForType(type) {
52 const basicPropertiesFilter = d => _.includes(DESCRIPTOR_MODEL_FIELDS[type], d.name);
53 return DescriptorModelMetaFactory.getModelMetaForType(type, basicPropertiesFilter) || {properties: []};
54 }
55
56 function getDescriptorMetaAdvancedForType(type) {
57 const advPropertiesFilter = d => !_.includes(DESCRIPTOR_MODEL_FIELDS[type], d.name);
58 return DescriptorModelMetaFactory.getModelMetaForType(type, advPropertiesFilter) || {properties: []};
59 }
60
61 function getTitle(model = {}) {
62 if (typeof model['short-name'] === 'string' && model['short-name']) {
63 return model['short-name'];
64 }
65 if (typeof model.name === 'string' && model.name) {
66 return model.name;
67 }
68 if (model.uiState && typeof model.uiState.displayName === 'string' && model.uiState.displayName) {
69 return model.uiState.displayName
70 }
71 if (typeof model.id === 'string') {
72 return model.id;
73 }
74 }
75 function startEditing() {
76 DeletionManager.removeEventListeners();
77 }
78
79 function endEditing() {
80 DeletionManager.addEventListeners();
81 }
82
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);
88 }
89 }
90
91 function onFocusPropertyFormInputElement(property, path, value, event) {
92
93 event.preventDefault();
94 startEditing();
95
96 function removeIsFocusedClass(event) {
97 event.target.removeEventListener('blur', removeIsFocusedClass);
98 Array.from(document.querySelectorAll('.-is-focused')).forEach(d => d.classList.remove('-is-focused'));
99 }
100
101 removeIsFocusedClass(event);
102
103 const propertyWrapper = getEventPath(event).reduce((parent, element) => {
104 if (parent) {
105 return parent;
106 }
107 if (!element.classList) {
108 return false;
109 }
110 if (element.classList.contains('property')) {
111 return element;
112 }
113 }, false);
114
115 if (propertyWrapper) {
116 propertyWrapper.classList.add('-is-focused');
117 event.target.addEventListener('blur', removeIsFocusedClass);
118 }
119
120 }
121
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);
127 if (create) {
128 const model = null;
129 create(model, path, property);
130 } else {
131 const name = path.join('.');
132 const value = Property.createModelInstance(property);
133 utils.assignPathValue(this.model, name, value);
134 }
135 CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
136 }
137 return (
138 <Button className="inline-hint" onClick={onClickAddProperty.bind(container, property, path)} label="Add" src={imgAdd} />
139 );
140 }
141
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');
147 if (removeMethod) {
148 removeMethod(utils.resolvePath(this.model, name));
149 } else {
150 utils.removePathValue(this.model, name);
151 }
152 CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
153 }
154 return (
155 <Button className="remove-property-action inline-hint" title="Remove" onClick={onClickRemoveProperty.bind(container, property, path)} label="Remove" src={imgRemove}/>
156 );
157 }
158
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());
166 }
167 }
168
169 function buildField(container, property, path, value, fieldKey) {
170 let cds = CatalogDataStore;
171 let catalogs = cds.getTransientCatalogs();
172
173 const name = path.join('.');
174 const isEditable = true;
175 const isGuid = Property.isGuid(property);
176 const onChange = onFormFieldValueChanged.bind(container);
177 const isEnumeration = Property.isEnumeration(property);
178 const isLeafRef = Property.isLeafRef(property);
179 const onFocus = onFocusPropertyFormInputElement.bind(container, property, path, value);
180 const placeholder = changeCase.title(property.name);
181 const className = ClassNames(property.name + '-input', {'-is-guid': isGuid});
182 const fieldValue = value ? (value.constructor.name != "Object") ? value : '' : undefined;
183 if (isEnumeration) {
184 const enumeration = Property.getEnumeration(property, value);
185 const options = enumeration.map((d, i) => {
186 // note yangforge generates values for enums but the system does not use them
187 // so we categorically ignore them
188 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
189 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
190 return <option key={fieldKey.toString() + ':' + i} value={d.name}>{d.name}</option>;
191 });
192 const isValueSet = enumeration.filter(d => d.isSelected).length > 0;
193 if (!isValueSet || property.cardinality === '0..1') {
194 const noValueDisplayText = changeCase.title(property.name);
195 options.unshift(<option key={'(value-not-in-enum)' + fieldKey.toString()} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
196 }
197 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>;
198 }
199
200 if (isLeafRef) {
201 let fullFieldKey = _.isArray(fieldKey) ? fieldKey.join(':') : fieldKey;
202 let containerRef = container;
203 while (containerRef.parent) {
204 fullFieldKey = containerRef.parent.key + ':' + fullFieldKey;
205 containerRef = containerRef.parent;
206 }
207 const leafRefPathValues = Property.getLeafRef(property, path, value, fullFieldKey, catalogs, container);
208
209 const options = leafRefPathValues && leafRefPathValues.map((d, i) => {
210 return <option key={fieldKey.toString() + ':' + i} value={d.value}>{d.value}</option>;
211 });
212 const isValueSet = leafRefPathValues.filter(d => d.isSelected).length > 0;
213 if (!isValueSet || property.cardinality === '0..1') {
214 const noValueDisplayText = changeCase.title(property.name);
215 options.unshift(<option key={'(value-not-in-leafref)' + fieldKey.toString()} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
216 }
217 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>;
218 }
219
220 if (property['preserve-line-breaks']) {
221 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} />;
222 }
223
224 return <input key={fieldKey.toString()}
225 id={fieldKey.toString()}
226 type="text"
227 name={name}
228 value={fieldValue}
229 className={className}
230 placeholder={placeholder}
231 onChange={onChange}
232 onFocus={onFocus}
233 onBlur={endEditing}
234 onMouseDown={startEditing}
235 onMouseOver={startEditing}
236 onMouseOut={endEditing}
237 onMouseLeave={endEditing}
238 readOnly={!isEditable}
239 />;
240
241 }
242
243 function buildElement(container, property, valuePath, value) {
244 return property.properties.map((property, index) => {
245 let childValue;
246 const childPath = valuePath.slice();
247 if (typeof value === 'object') {
248 childValue = value[property.name];
249 }
250 if(property.type != 'choice'){
251 childPath.push(property.name);
252 }
253 return build(container, property, childPath, childValue);
254
255 });
256 }
257
258 function buildChoice(container, property, path, value, key, props={}) {
259
260 function onFormFieldValueChanged(event) {
261 if (DescriptorModelFactory.isContainer(this)) {
262
263 event.preventDefault();
264
265 let name = event.target.name;
266 const value = event.target.value;
267
268
269 /*
270 Transient State is stored for convenience in the uiState field.
271 The choice yang type uses case elements to describe the "options".
272 A choice can only ever have one option selected which allows
273 the system to determine which type is selected by the name of
274 the element contained within the field.
275 */
276 /*
277 const stateExample = {
278 uiState: {
279 choice: {
280 'conf-config': {
281 selected: 'rest',
282 'case': {
283 rest: {},
284 netconf: {},
285 script: {}
286 }
287 }
288 }
289 }
290 };
291 */
292 const statePath = ['uiState.choice'].concat(name);
293 const stateObject = utils.resolvePath(this.model, statePath.join('.')) || {};
294 const selected = stateObject.selected ? stateObject.selected.split('.')[1] : undefined;
295 // write state back to the model so the new state objects are captured
296 utils.assignPathValue(this.model, statePath.join('.'), stateObject);
297
298 // write the current choice value into the state
299 let choiceObject = utils.resolvePath(this.model, [name, selected].join('.'));
300 let isTopCase = false;
301 if (!choiceObject) {
302 isTopCase = true;
303 choiceObject = utils.resolvePath(this.model, [selected].join('.'));
304 }
305 utils.assignPathValue(stateObject, [selected].join('.'), _.cloneDeep(choiceObject));
306
307 if(selected) {
308 if(this.model.uiState.choice.hasOwnProperty(name)) {
309 delete this.model[selected];
310 utils.removePathValue(this.model, [name, selected].join('.'), isTopCase);
311 } else {
312 // remove the current choice value from the model
313 utils.removePathValue(this.model, [name, selected].join('.'), isTopCase);
314 }
315 }
316
317 // get any state for the new selected choice
318 const newChoiceObject = utils.resolvePath(stateObject, [value].join('.')) || {};
319
320 // assign new choice value to the model
321 if (isTopCase) {
322 utils.assignPathValue(this.model, [name, value].join('.'), newChoiceObject);
323 } else {
324 utils.assignPathValue(this.model, [value].join('.'), newChoiceObject)
325 }
326
327
328 // update the selected name
329 utils.assignPathValue(this.model, statePath.concat('selected').join('.'), value);
330
331 CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
332 }
333 }
334
335 const caseByNameMap = {};
336
337 const onChange = onFormFieldValueChanged.bind(container);
338
339 const cases = property.properties.map(d => {
340 if (d.type === 'case') {
341 caseByNameMap[d.name] = d.properties[0];
342 return {
343 optionName: d.name,
344 optionTitle: d.description,
345 //represents case name and case element name
346 optionValue: [d.name, d.properties[0].name].join('.')
347 };
348 }
349 caseByNameMap[d.name] = d;
350 return {optionName: d.name};
351 });
352
353 const options = [{optionName: '', optionValue: false}].concat(cases).map((d, i) => {
354 return (
355 <option key={i} value={d.optionValue} title={d.optionTitle}>
356 {d.optionName}
357 {i ? null : changeCase.title(property.name)}
358 </option>
359 );
360 });
361
362 const selectName = path.join('.');
363 let selectedOptionPath = ['uiState.choice', selectName, 'selected'].join('.');
364 //Currently selected choice/case statement on UI model
365 let selectedOptionValue = utils.resolvePath(container.model, selectedOptionPath);
366 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
367 if(!selectedOptionValue) {
368 //get field properties for choice on container model
369 let fieldProperties = utils.resolvePath(container.model, selectName);
370 if(fieldProperties) {
371 //Check each case statement in model and see if it is present in container model.
372 cases.map(function(c){
373 if(fieldProperties.hasOwnProperty(c.optionValue.split('.')[1])) {
374 utils.assignPathValue(container.model, ['uiState.choice', selectName, 'selected'].join('.'), c.optionValue);
375 }
376 });
377 selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', selectName, 'selected'].join('.'));
378 } else {
379 property.properties.map(function(p) {
380 let pname = p.properties[0].name;
381 if(container.model.hasOwnProperty(pname)) {
382 utils.assignPathValue(container.model, ['uiState.choice', selectName, 'selected'].join('.'), [p.name, pname].join('.'));
383 }
384 })
385 selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', selectName, 'selected'].join('.'));
386 }
387 }
388 //If selectedOptionValue is present, take first item in string which represents the case name.
389 const valueProperty = caseByNameMap[selectedOptionValue ? selectedOptionValue.split('.')[0] : undefined] || {properties: []};
390 const isLeaf = Property.isLeaf(valueProperty);
391 const hasProperties = _.isArray(valueProperty.properties) && valueProperty.properties.length;
392 const isMissingDescriptorMeta = !hasProperties && !Property.isLeaf(valueProperty);
393 //Some magic that prevents errors for arising
394 const valueResponse = valueProperty.properties.length ? valueProperty.properties.map((d, i) => {
395 const childPath = path.concat(valueProperty.name, d.name);
396 const childValue = utils.resolvePath(container.model, childPath.join('.'));
397 return (
398 <div key={childPath.concat('info', i).join(':')}>
399 {build(container, d, childPath, childValue, props)}
400 </div>
401 );
402 }) : (!isMissingDescriptorMeta) ? build(container, valueProperty, path.concat(valueProperty.name), utils.resolvePath(container.model, path.concat(valueProperty.name).join('.')) || container.model[valueProperty.name]) : null
403 // end magic
404 const onFocus = onFocusPropertyFormInputElement.bind(container, property, path, value);
405
406 return (
407 <div key={key} className="choice">
408 <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}>
409 {options}
410 </select>
411 {valueResponse}
412 </div>
413 );
414
415 }
416
417 function buildSimpleListItem(container, property, path, value, key, index) {
418 // todo need to abstract this better
419 const title = getTitle(value);
420 var req = require.context("../", true, /\.svg/);
421 return (
422 <div>
423 <a href="#select-list-item" key={Date.now()} className={property.name + '-list-item simple-list-item '} onClick={onClickSelectItem.bind(container, property, path, value)}>
424 <img src={req('./' + DescriptorModelIconFactory.getUrlForType(property.name))} width="20px" />
425 <span>{title}</span>
426 </a>
427 {buildRemovePropertyAction(container, property, path)}
428 </div>
429 );
430 }
431
432 function buildRemoveListItem(container, property, valuePath, fieldKey, index) {
433 const className = ClassNames(property.name + '-remove actions');
434 return (
435 <div key={fieldKey.concat(index).join(':')} className={className}>
436 <h3>
437 <span className={property.type + '-name name'}>{changeCase.title(property.name)}</span>
438 <span className="info">{index + 1}</span>
439 {buildRemovePropertyAction(container, property, valuePath)}
440 </h3>
441 </div>
442 );
443 }
444
445 function buildLeafListItem(container, property, valuePath, value, key, index) {
446 // look at the type to determine how to parse the value
447 return (
448 <div>
449 {buildRemoveListItem(container, property, valuePath, key, index)}
450 {buildField(container, property, valuePath, value, key)}
451 </div>
452
453 );
454 }
455
456 function build(container, property, path, value, props = {}) {
457
458 const fields = [];
459 const isLeaf = Property.isLeaf(property);
460 const isArray = Property.isArray(property);
461 const isObject = Property.isObject(property);
462 const isLeafList = Property.isLeafList(property);
463 const fieldKey = [container.id].concat(path);
464 const isRequired = Property.isRequired(property);
465 const title = changeCase.titleCase(property.name);
466 const columnCount = property.properties.length || 1;
467 const isColumnar = isArray && (Math.round(props.width / columnCount) > 155);
468 const classNames = {'-is-required': isRequired, '-is-columnar': isColumnar};
469
470 if (!property.properties && isObject) {
471 const uiState = DescriptorModelMetaFactory.getModelMetaForType(property.name) || {};
472 property.properties = uiState.properties;
473 }
474
475 const hasProperties = _.isArray(property.properties) && property.properties.length;
476 const isMissingDescriptorMeta = !hasProperties && !Property.isLeaf(property);
477
478 // ensure value is not undefined for non-leaf property types
479 if (isObject) {
480 if (typeof value !== 'object') {
481 value = isArray ? [] : {};
482 }
483 }
484 const valueAsArray = _.isArray(value) ? value : isLeafList && typeof value === 'undefined' ? [] : [value];
485
486 const isMetaField = property.name === 'meta';
487 const isCVNFD = property.name === 'constituent-vnfd';
488 const isSimpleListView = Property.isSimpleList(property);
489
490 valueAsArray.forEach((value, index) => {
491
492 let field;
493 const key = fieldKey.slice();
494 const valuePath = path.slice();
495
496 if (isArray) {
497 valuePath.push(index);
498 key.push(index);
499 }
500
501 if (isMetaField) {
502 if (typeof value === 'object') {
503 value = JSON.stringify(value, undefined, 12);
504 } else if (typeof value !== 'string') {
505 value = '{}';
506 }
507 }
508
509 if (isMissingDescriptorMeta) {
510 field = <span key={key.concat('warning').join(':')} className="warning">No Descriptor Meta for {property.name}</span>;
511 } else if (property.type === 'choice') {
512 field = buildChoice(container, property, valuePath, value, key.join(':'), props);
513 } else if (isSimpleListView) {
514 field = buildSimpleListItem(container, property, valuePath, value, key, index, props);
515 } else if (isLeafList) {
516 field = buildLeafListItem(container, property, valuePath, value, key, index, props);
517 } else if (hasProperties) {
518 field = buildElement(container, property, valuePath, value, key.join(':'), props)
519 } else {
520 field = buildField(container, property, valuePath, value, key.join(':'), props);
521 }
522
523 function onClickLeaf(property, path, value, event) {
524 if (event.isDefaultPrevented()) {
525 return;
526 }
527 event.preventDefault();
528 event.stopPropagation();
529 this.getRoot().uiState.focusedPropertyPath = path.join('.');
530 console.log('property selected', path.join('.'));
531 ComposerAppActions.propertySelected([path.join('.')]);
532 }
533
534 const clickHandler = isLeaf ? onClickLeaf : () => {};
535 const isContainerList = isArray && !(isSimpleListView || isLeafList);
536
537 fields.push(
538 <div key={fieldKey.concat(['property-content', index]).join(':')}
539 className={ClassNames('property-content', {'simple-list': isSimpleListView})}
540 onClick={clickHandler.bind(container, property, valuePath, value)}>
541 {isContainerList ? buildRemoveListItem(container, property, valuePath, fieldKey, index) : null}
542 {field}
543 </div>
544 );
545
546 });
547
548 classNames['-is-leaf'] = isLeaf;
549 classNames['-is-array'] = isArray;
550 classNames['cols-' + columnCount] = isColumnar;
551
552 if (property.type === 'choice') {
553 value = utils.resolvePath(container.model, ['uiState.choice'].concat(path, 'selected').join('.'));
554 if(!value) {
555 property.properties.map(function(p) {
556 let pname = p.properties[0].name;
557 if(container.model.hasOwnProperty(pname)) {
558 value = container.model[pname];
559 }
560 })
561 }
562 }
563
564 let displayValue = typeof value === 'object' ? '' : value;
565 const displayValueInfo = isArray ? valueAsArray.filter(d => typeof d !== 'undefined').length + ' items' : '';
566
567 const onFocus = isLeaf ? event => event.target.classList.add('-is-focused') : false;
568
569 return (
570 <div key={fieldKey.join(':')} className={ClassNames(property.type + '-property property', classNames)} onFocus={onFocus}>
571 <h3 className="property-label">
572 <label htmlFor={fieldKey.join(':')}>
573 <span className={property.type + '-name name'}>{title}</span>
574 <small>
575 <span className={property.type + '-info info'}>{displayValueInfo}</span>
576 <span className={property.type + '-value value'}>{displayValue}</span>
577 </small>
578 {isArray ? buildAddPropertyAction(container, property, path.concat(valueAsArray.length)) : null}
579 </label>
580 </h3>
581 <span className={property.type + '-description description'}>{property.description}</span>
582 <val className="property-value">
583 {isCVNFD ? <span className={property.type + '-tip tip'}>Drag a VNFD from the Catalog to add more.</span> : null}
584 {fields}
585 </val>
586 </div>
587 );
588
589 }
590 export default function EditDescriptorModelProperties(props, type) {
591
592 const container = props.container;
593
594 if (!(DescriptorModelFactory.isContainer(container))) {
595 return
596 }
597
598
599
600 const containerType = (_.isEmpty(type) ? false : type)|| container.uiState['qualified-type'] || container.uiState.type;
601 const basicProperties = getDescriptorMetaBasicForType(containerType).properties;
602
603
604 function buildAdvancedGroup() {
605 const properties = getDescriptorMetaAdvancedForType(containerType).properties;
606 if (properties.length === 0) {
607 return null;
608 }
609 const hasBasicFields = basicProperties.length > 0;
610 const closeGroup = basicProperties.length > 0;
611 return (
612 <div className="advanced-properties-group">
613
614 <div className="toggleable">
615 {properties.map((property,i) => {
616 const path = [property.name];
617 const value = container.model[property.name];
618 if(path == 'id') {
619 return null
620 }
621 if(path == 'config-parameter-request') {
622 return (
623 <div className="container-property property" key={path + '-' + i}>
624 <h3 className="property-label">
625 <span className="container-name name">{`VNF Index: ${value['member-vnf-index-ref']}`}</span>
626 </h3>
627 <val className="property-value">
628 <div className="property-content">
629 <val className="property-value" style={{width: '100%'}}>
630 <div className="property-content">
631 <div className="leaf-property property -is-leaf">
632 <h3 className="property-label">
633 <label for={path + '-' + i}>
634 <span className="leaf-name name">
635 Parameter Request
636 </span>
637 </label>
638 </h3>
639 <input value={value['config-parameter-request-ref']} readonly placeholder="Parameter Request" name={path + '-' + i}>
640
641 </input>
642 </div>
643 </div>
644 </val>
645 </div>
646 </val>
647
648 </div>
649 )
650 } else {
651 return build(container, property, path, value, _.assign({toggle: true, width: props.width}, props));
652 }
653
654 })}
655 </div>
656 <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>
657 </div>
658 );
659 }
660
661 function buildMoreLess(d, i) {
662 return (
663 <span key={'bread-crumb-part-' + i}>
664 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
665 <i> / </i>
666 </span>
667 );
668 }
669
670 const path = [];
671 if (container.parent) {
672 path.push(container.parent);
673 }
674 path.push(container);
675
676 return (
677 <div className="EditDescriptorModelProperties -is-tree-view">
678 {buildAdvancedGroup()}
679 </div>
680 );
681
682 }
683 export {build}
684 // export buildElement;
685 // export buildChoice;