RIFT-15944 - editing an ‘id’ field is troublesome as focus is lost on each char typed
[osm/UI.git] / skyquake / plugins / composer / src / src / components / EditDescriptorModelProperties.js
index 59d2f95..f3b2c7a 100644 (file)
@@ -22,7 +22,9 @@
  */
 'use strict';
 
-import _ from 'lodash'
+import _includes from 'lodash/includes'
+import _isArray from 'lodash/isArray'
+import _cloneDeep from 'lodash/cloneDeep'
 import utils from '../libraries/utils'
 import React from 'react'
 import ClassNames from 'classnames'
@@ -39,6 +41,7 @@ import SelectionManager from '../libraries/SelectionManager'
 import DeletionManager from '../libraries/DeletionManager'
 import DescriptorModelIconFactory from '../libraries/model/IconFactory'
 import getEventPath from '../libraries/getEventPath'
+import CatalogDataStore from '../stores/CatalogDataStore'
 
 import imgAdd from '../../../node_modules/open-iconic/svg/plus.svg'
 import imgRemove from '../../../node_modules/open-iconic/svg/trash.svg'
@@ -46,12 +49,12 @@ import imgRemove from '../../../node_modules/open-iconic/svg/trash.svg'
 import '../styles/EditDescriptorModelProperties.scss'
 
 function getDescriptorMetaBasicForType(type) {
-       const basicPropertiesFilter = d => _.contains(DESCRIPTOR_MODEL_FIELDS[type], d.name);
+       const basicPropertiesFilter = d => _includes(DESCRIPTOR_MODEL_FIELDS[type], d.name);
        return DescriptorModelMetaFactory.getModelMetaForType(type, basicPropertiesFilter) || {properties: []};
 }
 
 function getDescriptorMetaAdvancedForType(type) {
-       const advPropertiesFilter = d => !_.contains(DESCRIPTOR_MODEL_FIELDS[type], d.name);
+       const advPropertiesFilter = d => !_includes(DESCRIPTOR_MODEL_FIELDS[type], d.name);
        return DescriptorModelMetaFactory.getModelMetaForType(type, advPropertiesFilter) || {properties: []};
 }
 
@@ -172,17 +175,21 @@ export default function EditDescriptorModelProperties(props) {
                }
        }
 
-       function buildField(container, property, path, value, fieldKey) {
+       function buildField(container, property, path, value, fieldId) {
+               let cds = CatalogDataStore;
+               let catalogs = cds.getTransientCatalogs();
 
-               const name = path.join('.');
+               const pathToProperty = path.join('.');
                const isEditable = true;
                const isGuid = Property.isGuid(property);
+               const isBoolean = Property.isBoolean(property);
                const onChange = onFormFieldValueChanged.bind(container);
                const isEnumeration = Property.isEnumeration(property);
+               const isLeafRef = Property.isLeafRef(property);
                const onFocus = onFocusPropertyFormInputElement.bind(container, property, path, value);
                const placeholder = changeCase.title(property.name);
                const className = ClassNames(property.name + '-input', {'-is-guid': isGuid});
-               const fieldValue = value ? value.constructor.name == "String" ? value : '' : undefined;
+               const fieldValue = value ? (value.constructor.name != "Object") ? value : '' : (isNaN(value) ? undefined : value);
                if (isEnumeration) {
                        const enumeration = Property.getEnumeration(property, value);
                        const options = enumeration.map((d, i) => {
@@ -190,36 +197,139 @@ export default function EditDescriptorModelProperties(props) {
                                // so we categorically ignore them
                                // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
                                //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
-                               return <option key={fieldKey.toString() + ':' + i} value={d.name}>{d.name}</option>;
+                               return <option key={':' + i} value={d.name}>{d.name}</option>;
                        });
                        const isValueSet = enumeration.filter(d => d.isSelected).length > 0;
                        if (!isValueSet || property.cardinality === '0..1') {
                                const noValueDisplayText = changeCase.title(property.name);
-                               options.unshift(<option key={'(value-not-in-enum)' + fieldKey.toString()} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
+                               options.unshift(<option key={'(value-not-in-enum)'} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
                        }
-                       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>;
+                       return (
+                               <select 
+                                       key={fieldId} 
+                                       id={fieldId}
+                                       name={pathToProperty} 
+                                       className={ClassNames({'-value-not-set': !isValueSet})} 
+                                       value={value} 
+                                       title={pathToProperty} 
+                                       onChange={onChange} 
+                                       onFocus={onFocus} 
+                                       onBlur={endEditing} 
+                                       onMouseDown={startEditing} 
+                                       onMouseOver={startEditing} 
+                                       readOnly={!isEditable}>
+                                               {options}
+                               </select>
+                       );
+               }
+
+               if (isLeafRef) {
+                       let fullPathString = container.key + ':' + path.join(':');
+                       let containerRef = container;
+                       while (containerRef.parent) {
+                               fullPathString = containerRef.parent.key + ':' + fullPathString;
+                               containerRef = containerRef.parent;
+                       }
+                       const leafRefPathValues = Property.getLeafRef(property, path, value, fullPathString, catalogs, container);
+
+                       const options = leafRefPathValues && leafRefPathValues.map((d, i) => {
+                               return <option key={':' + i} value={d.value}>{d.value}</option>;
+                       });
+                       const isValueSet = leafRefPathValues.filter(d => d.isSelected).length > 0;
+                       if (!isValueSet || property.cardinality === '0..1') {
+                               const noValueDisplayText = changeCase.title(property.name);
+                               options.unshift(<option key={'(value-not-in-leafref)'} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
+                       }
+                       return (
+                               <select 
+                                       key={fieldId} 
+                                       id={fieldId} 
+                                       name={pathToProperty}
+                                       className={ClassNames({'-value-not-set': !isValueSet})} 
+                                       value={value} 
+                                       title={pathToProperty} 
+                                       onChange={onChange} 
+                                       onFocus={onFocus} 
+                                       onBlur={endEditing} 
+                                       onMouseDown={startEditing} 
+                                       onMouseOver={startEditing} 
+                                       readOnly={!isEditable}>
+                                               {options}
+                               </select>
+                       );
+               }
+
+               if (isBoolean) {
+                       const options = [
+                               <option key={'true'} value="TRUE">TRUE</option>,
+                               <option key={'false'} value="FALSE">FALSE</option>
+                       ]
+
+                       // if (!isValueSet) {
+                               const noValueDisplayText = changeCase.title(property.name);
+                               options.unshift(<option key={'(value-not-in-leafref)'} value="" placeholder={placeholder}></option>);
+                       // }
+                       let val = value;
+                       if(typeof(val) == 'number') {
+                               val = value ? "TRUE" : "FALSE"
+                       }
+                       const isValueSet = (val != '' && val)
+                       return (
+                               <select 
+                                       key={fieldId} 
+                                       id={fieldId} 
+                                       name={pathToProperty}
+                                       className={ClassNames({'-value-not-set': !isValueSet})} 
+                                       value={val && val.toUpperCase()} title={pathToProperty} 
+                                       onChange={onChange} onFocus={onFocus} 
+                                       onBlur={endEditing} 
+                                       onMouseDown={startEditing} 
+                                       onMouseOver={startEditing} 
+                                       readOnly={!isEditable}>
+                                               {options}
+                               </select>
+                       );
                }
 
                if (property['preserve-line-breaks']) {
-                       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} />;
+                       return (
+                               <textarea 
+                                       key={fieldId} 
+                                       cols="5" 
+                                       id={fieldId} 
+                                       name={pathToProperty}
+                                       value={value} 
+                                       placeholder={placeholder} 
+                                       onChange={onChange} 
+                                       onFocus={onFocus} 
+                                       onBlur={endEditing} 
+                                       onMouseDown={startEditing} 
+                                       onMouseOver={startEditing} 
+                                       onMouseOut={endEditing} 
+                                       onMouseLeave={endEditing} 
+                                       readOnly={!isEditable} />
+                       );
                }
 
-               return <input key={fieldKey.toString()}
-                                         id={fieldKey.toString()}
-                                         type="text"
-                                         name={name}
-                                         value={fieldValue}
-                                         className={className}
-                                         placeholder={placeholder}
-                                         onChange={onChange}
-                                         onFocus={onFocus}
-                                         onBlur={endEditing}
-                                         onMouseDown={startEditing}
-                                         onMouseOver={startEditing}
-                                         onMouseOut={endEditing}
-                                         onMouseLeave={endEditing}
-                                         readOnly={!isEditable}
-               />;
+               return (
+                       <input 
+                               key={fieldId}
+                               id={fieldId}
+                               name={pathToProperty}
+                               type="text"
+                               value={fieldValue}
+                               className={className}
+                               placeholder={placeholder}
+                               onChange={onChange}
+                               onFocus={onFocus}
+                               onBlur={endEditing}
+                               onMouseDown={startEditing}
+                               onMouseOver={startEditing}
+                               onMouseOut={endEditing}
+                               onMouseLeave={endEditing}
+                               readOnly={!isEditable}
+                       />
+               );
 
        }
 
@@ -238,16 +348,16 @@ export default function EditDescriptorModelProperties(props) {
                });
        }
 
-       function buildChoice(container, property, path, value, key) {
-
+       function buildChoice(container, property, path, value, fieldId) {
                function onFormFieldValueChanged(event) {
                        if (DescriptorModelFactory.isContainer(this)) {
 
                                event.preventDefault();
 
-                               const name = event.target.name;
+                               let name = event.target.name;
                                const value = event.target.value;
 
+
                                /*
                                        Transient State is stored for convenience in the uiState field.
                                        The choice yang type uses case elements to describe the "options".
@@ -255,22 +365,22 @@ export default function EditDescriptorModelProperties(props) {
                                        the system to determine which type is selected by the name of
                                        the element contained within the field.
                                 */
-
-                               //const stateExample = {
-                               //      uiState: {
-                               //              choice: {
-                               //                      'conf-config': {
-                               //                              selected: 'rest',
-                               //                              'case': {
-                               //                                      rest: {},
-                               //                                      netconf: {},
-                               //                                      script: {}
-                               //                              }
-                               //                      }
-                               //              }
-                               //      }
-                               //};
-
+                               /*
+                                       const stateExample = {
+                                               uiState: {
+                                                       choice: {
+                                                               'conf-config': {
+                                                                       selected: 'rest',
+                                                                       'case': {
+                                                                               rest: {},
+                                                                               netconf: {},
+                                                                               script: {}
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       };
+                               */
                                const statePath = ['uiState.choice'].concat(name);
                                const stateObject = utils.resolvePath(this.model, statePath.join('.')) || {};
                                const selected = stateObject.selected ? stateObject.selected.split('.')[1] : undefined;
@@ -278,19 +388,34 @@ export default function EditDescriptorModelProperties(props) {
                                utils.assignPathValue(this.model, statePath.join('.'), stateObject);
 
                                // write the current choice value into the state
-                               const choiceObject = utils.resolvePath(this.model, [name, selected].join('.'));
-                               if (choiceObject) {
-                                       utils.assignPathValue(stateObject, ['case', selected].join('.'), _.cloneDeep(choiceObject));
+                               let choiceObject = utils.resolvePath(this.model, [name, selected].join('.'));
+                               let isTopCase = false;
+                               if (!choiceObject) {
+                                       isTopCase = true;
+                                       choiceObject = utils.resolvePath(this.model, [selected].join('.'));
+                               }
+                               utils.assignPathValue(stateObject, [selected].join('.'), _cloneDeep(choiceObject));
+
+                               if(selected) {
+                                       if(this.model.uiState.choice.hasOwnProperty(name)) {
+                                               delete this.model[selected];
+                                               utils.removePathValue(this.model, [name, selected].join('.'), isTopCase);
+                                       } else {
+                                               // remove the current choice value from the model
+                                               utils.removePathValue(this.model, [name, selected].join('.'), isTopCase);
+                                       }
                                }
-
-                               // remove the current choice value from the model
-                               utils.removePathValue(this.model, [name, selected].join('.'));
 
                                // get any state for the new selected choice
-                               const newChoiceObject = utils.resolvePath(stateObject, ['case', value].join('.')) || {};
+                               const newChoiceObject = utils.resolvePath(stateObject, [value].join('.')) || {};
 
                                // assign new choice value to the model
-                               utils.assignPathValue(this.model, [name, value].join('.'), newChoiceObject);
+                               if (isTopCase) {
+                                       utils.assignPathValue(this.model, [name, value].join('.'), newChoiceObject);
+                               } else {
+                                       utils.assignPathValue(this.model, [value].join('.'), newChoiceObject)
+                               }
+
 
                                // update the selected name
                                utils.assignPathValue(this.model, statePath.concat('selected').join('.'), value);
@@ -305,7 +430,8 @@ export default function EditDescriptorModelProperties(props) {
 
                const cases = property.properties.map(d => {
                        if (d.type === 'case') {
-                               caseByNameMap[d.name] = d.properties[0];
+                               //Previous it was assumed that a case choice would have only one property. Now we pass on either the only item or the
+                               caseByNameMap[d.name] = d.properties && (d.properties.length == 1 ? d.properties[0] : d.properties);
                                return {
                                        optionName: d.name,
                                        optionTitle: d.description,
@@ -317,7 +443,7 @@ export default function EditDescriptorModelProperties(props) {
                        return {optionName: d.name};
                });
 
-               const options = [{optionName: ''}].concat(cases).map((d, i) => {
+               const options = [{optionName: '', optionValue: false}].concat(cases).map((d, i) => {
                        return (
                                <option key={i} value={d.optionValue} title={d.optionTitle}>
                                        {d.optionName}
@@ -337,20 +463,30 @@ export default function EditDescriptorModelProperties(props) {
                        if(fieldProperties) {
                                //Check each case statement in model and see if it is present in container model.
                                cases.map(function(c){
-                                       if(fieldProperties.hasOwnProperty(c.optionName)) {
+                                       if(fieldProperties.hasOwnProperty(c.optionValue.split('.')[1])) {
                                                utils.assignPathValue(container.model, ['uiState.choice', selectName, 'selected'].join('.'), c.optionValue);
                                        }
                                });
                                selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', selectName, 'selected'].join('.'));
+                       } else {
+                               property.properties.map(function(p) {
+                                       let pname = p.properties[0].name;
+                                       if(container.model.hasOwnProperty(pname)) {
+                                               utils.assignPathValue(container.model, ['uiState.choice', selectName, 'selected'].join('.'), [p.name, pname].join('.'));
+                                       }
+                               })
+                               selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', selectName, 'selected'].join('.'));
                        }
                }
                //If selectedOptionValue is present, take first item in string which represents the case name.
                const valueProperty = caseByNameMap[selectedOptionValue ? selectedOptionValue.split('.')[0] : undefined] || {properties: []};
                const isLeaf = Property.isLeaf(valueProperty);
-               const hasProperties = _.isArray(valueProperty.properties) && valueProperty.properties.length;
+               const hasProperties = _isArray(valueProperty.properties) && valueProperty.properties.length;
                const isMissingDescriptorMeta = !hasProperties && !Property.isLeaf(valueProperty);
                //Some magic that prevents errors for arising
-               const valueResponse = valueProperty.properties.length ? valueProperty.properties.map((d, i) => {
+               const valueResponse = valueProperty.properties && valueProperty.properties.length ? valueProperty.properties.map(valuePropertyFn) : (!isMissingDescriptorMeta) ? build(container, valueProperty, path.concat(valueProperty.name), utils.resolvePath(container.model, path.concat(valueProperty.name).join('.')) || container.model[valueProperty.name]) :
+               valueProperty.map && valueProperty.map(valuePropertyFn);
+               function valuePropertyFn(d, i) {
                        const childPath = path.concat(valueProperty.name, d.name);
                        const childValue = utils.resolvePath(container.model, childPath.join('.'));
                        return (
@@ -358,13 +494,13 @@ export default function EditDescriptorModelProperties(props) {
                                        {build(container, d, childPath, childValue, props)}
                                </div>
                        );
-               }) : (!isMissingDescriptorMeta) ? build(container, valueProperty, path.concat(valueProperty.name), utils.resolvePath(container.model, path.concat(valueProperty.name).join('.'))) : null
+               }
                // end magic
                const onFocus = onFocusPropertyFormInputElement.bind(container, property, path, value);
 
                return (
-                       <div key={key} className="choice">
-                               <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}>
+                       <div key={fieldId} className="choice">
+                               <select id={fieldId} className={ClassNames({'-value-not-set': !selectedOptionValue})} name={selectName} value={selectedOptionValue} onChange={onChange} onFocus={onFocus} onBlur={endEditing} onMouseDown={startEditing} onMouseOver={startEditing} onMouseOut={endEditing} onMouseLeave={endEditing}>
                                        {options}
                                </select>
                                {valueResponse}
@@ -373,13 +509,13 @@ export default function EditDescriptorModelProperties(props) {
 
        }
 
-       function buildSimpleListItem(container, property, path, value, key, index) {
+       function buildSimpleListItem(container, property, path, value, uniqueId, index) {
                // todo need to abstract this better
                const title = getTitle(value);
                var req = require.context("../", true, /\.svg/);
                return (
-                       <div>
-                               <a href="#select-list-item" key={Date.now()} className={property.name + '-list-item simple-list-item '} onClick={onClickSelectItem.bind(container, property, path, value)}>
+                       <div key={uniqueId} >
+                               <a href="#select-list-item" className={property.name + '-list-item simple-list-item '} onClick={onClickSelectItem.bind(container, property, path, value)}>
                                        <img src={req('./' + DescriptorModelIconFactory.getUrlForType(property.name))} width="20px" />
                                        <span>{title}</span>
                                </a>
@@ -388,10 +524,10 @@ export default function EditDescriptorModelProperties(props) {
                );
        }
 
-       function buildRemoveListItem(container, property, valuePath, fieldKey, index) {
+       function buildRemoveListItem(container, property, valuePath, index) {
                const className = ClassNames(property.name + '-remove actions');
                return (
-                       <div key={fieldKey.concat(index).join(':')} className={className}>
+                       <div className={className}>
                                <h3>
                                        <span className={property.type + '-name name'}>{changeCase.title(property.name)}</span>
                                        <span className="info">{index + 1}</span>
@@ -401,12 +537,12 @@ export default function EditDescriptorModelProperties(props) {
                );
        }
 
-       function buildLeafListItem(container, property, valuePath, value, key, index) {
+       function buildLeafListItem(container, property, valuePath, value, index) {
                // look at the type to determine how to parse the value
                return (
-                       <div>
-                               {buildRemoveListItem(container, property, valuePath, key, index)}
-                               {buildField(container, property, valuePath, value, key)}
+                       <div key={uniqueId}>
+                               {buildRemoveListItem(container, property, valuePath, index)}
+                               {buildField(container, property, valuePath, value, uniqueId)}
                        </div>
 
                );
@@ -419,19 +555,28 @@ export default function EditDescriptorModelProperties(props) {
                const isArray = Property.isArray(property);
                const isObject = Property.isObject(property);
                const isLeafList = Property.isLeafList(property);
-               const fieldKey = [container.id].concat(path);
                const isRequired = Property.isRequired(property);
                const title = changeCase.titleCase(property.name);
                const columnCount = property.properties.length || 1;
                const isColumnar = isArray && (Math.round(props.width / columnCount) > 155);
                const classNames = {'-is-required': isRequired, '-is-columnar': isColumnar};
 
+               // create a unique Id for use as react component keys and html element ids
+               // use uid (from ui info) instead of id property (which is not stable)
+               let uniqueId = container.uid;
+               let containerRef = container;
+               while (containerRef.parent) {
+                       uniqueId = containerRef.parent.uid + ':' + uniqueId;
+                       containerRef = containerRef.parent;
+               }
+               uniqueId += ':' + path.join(':')
+
                if (!property.properties && isObject) {
                        const uiState = DescriptorModelMetaFactory.getModelMetaForType(property.name) || {};
                        property.properties = uiState.properties;
                }
 
-               const hasProperties = _.isArray(property.properties) && property.properties.length;
+               const hasProperties = _isArray(property.properties) && property.properties.length;
                const isMissingDescriptorMeta = !hasProperties && !Property.isLeaf(property);
 
                // ensure value is not undefined for non-leaf property types
@@ -440,7 +585,7 @@ export default function EditDescriptorModelProperties(props) {
                                value = isArray ? [] : {};
                        }
                }
-               const valueAsArray = _.isArray(value) ? value : isLeafList && typeof value === 'undefined' ? [] : [value];
+               const valueAsArray = _isArray(value) ? value : isLeafList && typeof value === 'undefined' ? [] : [value];
 
                const isMetaField = property.name === 'meta';
                const isCVNFD = property.name === 'constituent-vnfd';
@@ -449,12 +594,16 @@ export default function EditDescriptorModelProperties(props) {
                valueAsArray.forEach((value, index) => {
 
                        let field;
-                       const key = fieldKey.slice();
                        const valuePath = path.slice();
+                       // create a unique field Id for use as react component keys and html element ids
+                       // notes: 
+                       //   keys only need to be unique on components in the same array
+                       //   html element ids should be unique with the document (or form)
+                       let fieldId = uniqueId;
 
                        if (isArray) {
                                valuePath.push(index);
-                               key.push(index);
+                               fieldId += index;
                        }
 
                        if (isMetaField) {
@@ -466,17 +615,17 @@ export default function EditDescriptorModelProperties(props) {
                        }
 
                        if (isMissingDescriptorMeta) {
-                               field = <span key={key.concat('warning').join(':')} className="warning">No Descriptor Meta for {property.name}</span>;
+                               field = <span key={fieldId} className="warning">No Descriptor Meta for {property.name}</span>;
                        } else if (property.type === 'choice') {
-                               field = buildChoice(container, property, valuePath, value, key.join(':'));
+                               field = buildChoice(container, property, valuePath, value, fieldId);
                        } else if (isSimpleListView) {
-                               field = buildSimpleListItem(container, property, valuePath, value, key, index);
+                               field = buildSimpleListItem(container, property, valuePath, value, fieldId, index);
                        } else if (isLeafList) {
-                               field = buildLeafListItem(container, property, valuePath, value, key, index);
+                               field = buildLeafListItem(container, property, valuePath, value, fieldId, index);
                        } else if (hasProperties) {
-                               field = buildElement(container, property, valuePath, value, key.join(':'))
+                               field = buildElement(container, property, valuePath, value, fieldId)
                        } else {
-                               field = buildField(container, property, valuePath, value, key.join(':'));
+                               field = buildField(container, property, valuePath, value, fieldId);
                        }
 
                        function onClickLeaf(property, path, value, event) {
@@ -494,10 +643,10 @@ export default function EditDescriptorModelProperties(props) {
                        const isContainerList = isArray && !(isSimpleListView || isLeafList);
 
                        fields.push(
-                               <div key={fieldKey.concat(['property-content', index]).join(':')}
+                               <div key={fieldId}
                                         className={ClassNames('property-content', {'simple-list': isSimpleListView})}
                                         onClick={clickHandler.bind(container, property, valuePath, value)}>
-                                       {isContainerList ? buildRemoveListItem(container, property, valuePath, fieldKey, index) : null}
+                                       {isContainerList ? buildRemoveListItem(container, property, valuePath, index) : null}
                                        {field}
                                </div>
                        );
@@ -510,6 +659,14 @@ export default function EditDescriptorModelProperties(props) {
 
                if (property.type === 'choice') {
                        value = utils.resolvePath(container.model, ['uiState.choice'].concat(path, 'selected').join('.'));
+                       if(!value) {
+                               property.properties.map(function(p) {
+                                       let pname = p.properties[0].name;
+                                       if(container.model.hasOwnProperty(pname)) {
+                                               value = container.model[pname];
+                                       }
+                               })
+                       }
                }
 
                let displayValue = typeof value === 'object' ? '' : value;
@@ -518,9 +675,9 @@ export default function EditDescriptorModelProperties(props) {
                const onFocus = isLeaf ? event => event.target.classList.add('-is-focused') : false;
 
                return (
-                       <div key={fieldKey.join(':')} className={ClassNames(property.type + '-property property', classNames)} onFocus={onFocus}>
+                       <div key={uniqueId} className={ClassNames(property.type + '-property property', classNames)} onFocus={onFocus}>
                                <h3 className="property-label">
-                                       <label htmlFor={fieldKey.join(':')}>
+                                       <label htmlFor={uniqueId}>
                                                <span className={property.type + '-name name'}>{title}</span>
                                                <small>
                                                        <span className={property.type + '-info info'}>{displayValueInfo}</span>