X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;ds=inline;f=skyquake%2Fplugins%2Fcomposer%2Fsrc%2Fsrc%2Fcomponents%2FEditDescriptorModelProperties.js;h=651c40d7b11fb4132cab9ab0b82418ad9dbfd975;hb=15999cf835be1475fd72fddcfb34831f4173eb92;hp=4dc4b28f59925dc82d3b3f2a0ae3d4fcb391c80a;hpb=a6690e0878612ca0ffb7119c95a033dd1d7d1246;p=osm%2FUI.git diff --git a/skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js b/skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js index 4dc4b28f5..651c40d7b 100644 --- a/skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js +++ b/skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js @@ -20,9 +20,15 @@ * * This class generates the form fields used to edit the CONFD JSON model. */ -'use strict'; -import _ from 'lodash' +import _includes from 'lodash/includes' +import _isArray from 'lodash/isArray' +import _cloneDeep from 'lodash/cloneDeep' +import _debounce from 'lodash/debounce'; +import _uniqueId from 'lodash/uniqueId'; +import _set from 'lodash/set'; +import _get from 'lodash/get'; +import _has from 'lodash/has'; import utils from '../libraries/utils' import React from 'react' import ClassNames from 'classnames' @@ -39,19 +45,30 @@ 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' import '../styles/EditDescriptorModelProperties.scss' +const EMPTY_LEAF_PRESENT = '--empty-leaf-set--'; + +function resolveReactKey(value) { + const keyPath = ['uiState', 'fieldKey']; + if (!_has(value, keyPath)) { + _set(value, keyPath, _uniqueId()); + } + return _get(value, keyPath); +} + 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: []}; } @@ -135,7 +152,10 @@ export default function EditDescriptorModelProperties(props) { create(model, path, property); } else { const name = path.join('.'); - const value = Property.createModelInstance(property); + // get a unique name for the new list item based on the current list content + // some lists, based on the key, may not get a uniqueName generated here + const uniqueName = DescriptorModelMetaFactory.generateItemUniqueName(container.model[property.name], property); + const value = Property.createModelInstance(property, uniqueName); utils.assignPathValue(this.model, name, value); } CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot()); @@ -162,27 +182,43 @@ export default function EditDescriptorModelProperties(props) { ); } - function onFormFieldValueChanged(event) { - if (DescriptorModelFactory.isContainer(this)) { - event.preventDefault(); - const name = event.target.name; - const value = event.target.value; - utils.assignPathValue(this.model, name, value); - CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot()); - } - } - function buildField(container, property, path, value, fieldKey) { + let cds = CatalogDataStore; + let catalogs = cds.getTransientCatalogs(); - const name = path.join('.'); + const pathToProperty = path.join('.'); const isEditable = true; const isGuid = Property.isGuid(property); - const onChange = onFormFieldValueChanged.bind(container); + const isBoolean = Property.isBoolean(property); 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); + + // process the named field value change + function processFieldValueChange(name, value) { + console.debug('processed change for -- ' + name + ' -- with value -- ' + value); + // this = the container being edited + if (DescriptorModelFactory.isContainer(this)) { + utils.assignPathValue(this.model, name, value); + CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot()); + } + } + + // change handler used for onChange event + const changeHandler = (handleValueChange, event) => { + event.preventDefault(); + console.debug(event.target.value); + handleValueChange(event.target.value); + }; + // create an onChange event handler for a text field for the specified field path (debounced to accumulate chars) + const onTextChange = changeHandler.bind(null, _debounce( + processFieldValueChange.bind(container, pathToProperty), 2000, {maxWait: 5000})); // max wait for short-name + // create an onChange event handler for a select field for the specified field path + const onSelectChange = changeHandler.bind(null, processFieldValueChange.bind(container, pathToProperty)); + if (isEnumeration) { const enumeration = Property.getEnumeration(property, value); const options = enumeration.map((d, i) => { @@ -190,65 +226,202 @@ 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 ; - return ; + return ; }); const isValueSet = enumeration.filter(d => d.isSelected).length > 0; if (!isValueSet || property.cardinality === '0..1') { const noValueDisplayText = changeCase.title(property.name); - options.unshift(); + options.unshift(); } - return ; + return ( + + ); + } + + 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 ; + }); + const isValueSet = leafRefPathValues.filter(d => d.isSelected).length > 0; + if (!isValueSet || property.cardinality === '0..1') { + const noValueDisplayText = changeCase.title(property.name); + options.unshift(); + } + return ( + + ); + } + + if (isBoolean) { + const options = [ + , + + ] + + // if (!isValueSet) { + const noValueDisplayText = changeCase.title(property.name); + options.unshift(); + // } + let val = value; + if(typeof(val) == 'number') { + val = value ? "TRUE" : "FALSE" + } + const isValueSet = (val != '' && val) + return ( + + ); + } + + if (Property.isLeafEmpty(property)) { + // A null value indicates the leaf exists (as opposed to undefined). + // We stick in a string when the user actually sets it to simplify things + // but the correct thing happens when we serialize to user data + let isEmptyLeafPresent = (value === EMPTY_LEAF_PRESENT || value === null); + let present = isEmptyLeafPresent ? EMPTY_LEAF_PRESENT : [null]; + const options = [ + , + + ] + + return ( + + ); } if (property['preserve-line-breaks']) { - return ; + return ( + + ); } - return ; + return ( + + ); } - function buildElement(container, property, valuePath, value) { - return property.properties.map((property, index) => { - let childValue; - const childPath = valuePath.slice(); - if (typeof value === 'object') { - childValue = value[property.name]; + /** + * buiid and return an array of components representing an editor for each property. + * + * @param {any} container the master document being edited + * @param {[property]} properties + * @param {string} pathToProperties path within the container to the properties + * @param {Object} data source for each property + * @param {any} props object containing main data panel information, e.g. panel width {width: 375} + * which may be useful/necessary to a components rendering. + * @returns an array of react components + */ + function buildComponentsForProperties(container, properties, pathToProperties, data, props) { + return properties.map((property) => { + let value; + let propertyPath = pathToProperties.slice(); + if (data && typeof data === 'object') { + value = data[property.name]; } if(property.type != 'choice'){ - childPath.push(property.name); + propertyPath.push(property.name); } - return build(container, property, childPath, childValue); - + return build(container, property, propertyPath, value, props); }); } + function buildElement(container, property, valuePath, value) { + return buildComponentsForProperties(container, property.properties, valuePath, value); + } + function buildChoice(container, property, path, value, key) { - function onFormFieldValueChanged(event) { + function processChoiceChange(name, value) { if (DescriptorModelFactory.isContainer(this)) { - event.preventDefault(); - - 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". @@ -285,7 +458,7 @@ export default function EditDescriptorModelProperties(props) { isTopCase = true; choiceObject = utils.resolvePath(this.model, [selected].join('.')); } - utils.assignPathValue(stateObject, [selected].join('.'), _.cloneDeep(choiceObject)); + utils.assignPathValue(stateObject, [selected].join('.'), _cloneDeep(choiceObject)); if(selected) { if(this.model.uiState.choice.hasOwnProperty(name)) { @@ -307,7 +480,6 @@ export default function EditDescriptorModelProperties(props) { utils.assignPathValue(this.model, [value].join('.'), newChoiceObject) } - // update the selected name utils.assignPathValue(this.model, statePath.concat('selected').join('.'), value); @@ -315,13 +487,20 @@ export default function EditDescriptorModelProperties(props) { } } + const pathToChoice = path.join('.'); const caseByNameMap = {}; - const onChange = onFormFieldValueChanged.bind(container); + const choiceChangeHandler = processChoiceChange.bind(container, pathToChoice); + const onChange = ((handleChoiceChange, event) => { + event.preventDefault(); + handleChoiceChange(event.target.value); + }).bind(null, choiceChangeHandler); + 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, @@ -342,39 +521,47 @@ export default function EditDescriptorModelProperties(props) { ); }); - const selectName = path.join('.'); - let selectedOptionPath = ['uiState.choice', selectName, 'selected'].join('.'); + let selectedOptionPath = ['uiState.choice', pathToChoice, 'selected'].join('.'); //Currently selected choice/case statement on UI model let selectedOptionValue = utils.resolvePath(container.model, selectedOptionPath); //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model if(!selectedOptionValue) { //get field properties for choice on container model - let fieldProperties = utils.resolvePath(container.model, selectName); + let fieldProperties = utils.resolvePath(container.model, pathToChoice); 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.optionValue.split('.')[1])) { - utils.assignPathValue(container.model, ['uiState.choice', selectName, 'selected'].join('.'), c.optionValue); + if(c.optionValue && fieldProperties.hasOwnProperty(c.optionValue.split('.')[1])) { + utils.assignPathValue(container.model, ['uiState.choice', pathToChoice, 'selected'].join('.'), c.optionValue); } }); - selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', selectName, 'selected'].join('.')); + selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', pathToChoice, 'selected'].join('.')); } else { property.properties.map(function(p) { - let pname = p.properties[0].name; + let pname = p.properties[0] && p.properties[0].name; if(container.model.hasOwnProperty(pname)) { - utils.assignPathValue(container.model, ['uiState.choice', selectName, 'selected'].join('.'), [p.name, pname].join('.')); + utils.assignPathValue(container.model, ['uiState.choice', pathToChoice, 'selected'].join('.'), [p.name, pname].join('.')); } }) - selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', selectName, 'selected'].join('.')); + selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', pathToChoice, '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) => { + let valueResponse = null; + if (valueProperty.properties && valueProperty.properties.length) { + valueResponse = valueProperty.properties.map(valuePropertyFn); + } else if (!isMissingDescriptorMeta) { + let value = utils.resolvePath(container.model, path.concat(valueProperty.name).join('.')) || container.model[valueProperty.name]; + valueResponse = build(container, valueProperty, path.concat(valueProperty.name), value) + } else { + valueResponse = 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 ( @@ -382,13 +569,24 @@ export default function EditDescriptorModelProperties(props) { {build(container, d, childPath, childValue, props)} ); - }) : (!isMissingDescriptorMeta) ? build(container, valueProperty, path.concat(valueProperty.name), utils.resolvePath(container.model, path.concat(valueProperty.name).join('.')) || container.model[valueProperty.name]) : null + } // end magic const onFocus = onFocusPropertyFormInputElement.bind(container, property, path, value); return (