*/
'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'
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'
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: []};
}
}
}
- 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 != "Object") ? 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) => {
// 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}
+ />
+ );
}
});
}
- 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".
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;
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);
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,
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}
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 (
{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}
}
- 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>
);
}
- 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>
);
}
- 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>
);
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
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';
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) {
}
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) {
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>
);
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;
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>