*
* This class generates the form fields used to edit the CONFD JSON model.
*/
-'use strict';
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'
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 => _includes(DESCRIPTOR_MODEL_FIELDS[type], d.name);
return DescriptorModelMetaFactory.getModelMetaForType(type, basicPropertiesFilter) || {properties: []};
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());
);
}
- 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, fieldId) {
+ function buildField(container, property, path, value, fieldKey) {
let cds = CatalogDataStore;
let catalogs = cds.getTransientCatalogs();
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 : '' : (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) => {
}
return (
<select
- key={fieldId}
- id={fieldId}
- name={pathToProperty}
+ key={fieldKey}
+ id={fieldKey}
className={ClassNames({'-value-not-set': !isValueSet})}
- value={value}
+ defaultValue={value}
title={pathToProperty}
- onChange={onChange}
+ onChange={onSelectChange}
onFocus={onFocus}
onBlur={endEditing}
onMouseDown={startEditing}
}
return (
<select
- key={fieldId}
- id={fieldId}
- name={pathToProperty}
+ key={fieldKey}
+ id={fieldKey}
className={ClassNames({'-value-not-set': !isValueSet})}
- value={value}
+ defaultValue={value}
title={pathToProperty}
- onChange={onChange}
+ onChange={onSelectChange}
onFocus={onFocus}
onBlur={endEditing}
onMouseDown={startEditing}
const isValueSet = (val != '' && val)
return (
<select
- key={fieldId}
- id={fieldId}
- name={pathToProperty}
+ key={fieldKey}
+ id={fieldKey}
className={ClassNames({'-value-not-set': !isValueSet})}
- value={val && val.toUpperCase()} title={pathToProperty}
- onChange={onChange} onFocus={onFocus}
+ defaultValue={val && val.toUpperCase()}
+ title={pathToProperty}
+ onChange={onSelectChange}
+ onFocus={onFocus}
+ onBlur={endEditing}
+ onMouseDown={startEditing}
+ onMouseOver={startEditing}
+ readOnly={!isEditable}>
+ {options}
+ </select>
+ );
+ }
+
+ 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 : "";
+ const options = [
+ <option key={'true'} value={EMPTY_LEAF_PRESENT}>Enabled</option>,
+ <option key={'false'} value="">Not Enabled</option>
+ ]
+
+ return (
+ <select
+ key={fieldKey}
+ id={fieldKey}
+ className={ClassNames({'-value-not-set': !isEmptyLeafPresent})}
+ defaultValue={present}
+ title={pathToProperty}
+ onChange={onSelectChange}
+ onFocus={onFocus}
onBlur={endEditing}
onMouseDown={startEditing}
onMouseOver={startEditing}
if (property['preserve-line-breaks']) {
return (
<textarea
- key={fieldId}
+ key={fieldKey}
cols="5"
- id={fieldId}
- name={pathToProperty}
- value={value}
+ id={fieldKey}
+ defaultValue={value}
placeholder={placeholder}
- onChange={onChange}
+ onChange={onTextChange}
onFocus={onFocus}
onBlur={endEditing}
onMouseDown={startEditing}
return (
<input
- key={fieldId}
- id={fieldId}
- name={pathToProperty}
+ key={fieldKey}
+ id={fieldKey}
type="text"
- value={fieldValue}
+ defaultValue={fieldValue}
className={className}
placeholder={placeholder}
- onChange={onChange}
+ onChange={onTextChange}
onFocus={onFocus}
onBlur={endEditing}
onMouseDown={startEditing}
}
- 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 buildChoice(container, property, path, value, fieldId) {
- function onFormFieldValueChanged(event) {
- if (DescriptorModelFactory.isContainer(this)) {
-
- event.preventDefault();
+ function buildElement(container, property, valuePath, value) {
+ return buildComponentsForProperties(container, property.properties, valuePath, value);
+ }
- let name = event.target.name;
- const value = event.target.value;
+ function buildChoice(container, property, path, value, key) {
+ function processChoiceChange(name, value) {
+ if (DescriptorModelFactory.isContainer(this)) {
/*
Transient State is stored for convenience in the uiState field.
utils.assignPathValue(this.model, [value].join('.'), newChoiceObject)
}
-
// update the selected name
utils.assignPathValue(this.model, statePath.concat('selected').join('.'), value);
}
}
+ 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') {
);
});
- 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 hasProperties = _isArray(valueProperty.properties) && valueProperty.properties.length;
const isMissingDescriptorMeta = !hasProperties && !Property.isLeaf(valueProperty);
//Some magic that prevents errors for arising
- 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);
+ 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('.'));
const onFocus = onFocusPropertyFormInputElement.bind(container, property, path, value);
return (
- <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}>
+ <div key={key} className="choice">
+ <select
+ key={Date.now()}
+ className={ClassNames({'-value-not-set': !selectedOptionValue})}
+ defaultValue={selectedOptionValue}
+ onChange={onChange}
+ onFocus={onFocus}
+ onBlur={endEditing}
+ onMouseDown={startEditing}
+ onMouseOver={startEditing}
+ onMouseOut={endEditing}
+ onMouseLeave={endEditing}
+ >
{options}
</select>
{valueResponse}
);
}
- function buildLeafListItem(container, property, valuePath, value, index) {
+ function buildLeafListItem(container, property, valuePath, value, uniqueId, index) {
// look at the type to determine how to parse the value
return (
<div key={uniqueId}>
if (isArray) {
valuePath.push(index);
- fieldId += index;
+ fieldId = isLeafList ? fieldId + index + value : resolveReactKey(value);
}
if (isMetaField) {
event.preventDefault();
event.stopPropagation();
this.getRoot().uiState.focusedPropertyPath = path.join('.');
- console.log('property selected', path.join('.'));
+ console.debug('property selected', path.join('.'));
ComposerAppActions.propertySelected([path.join('.')]);
}
value = utils.resolvePath(container.model, ['uiState.choice'].concat(path, 'selected').join('.'));
if(!value) {
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)) {
value = container.model[pname];
}
<div className="basic-properties-group">
<h2>Basic</h2>
<div>
- {basicProperties.map(property => {
- const path = [property.name];
- const value = container.model[property.name];
- return build(container, property, path, value);
- })}
+ {buildComponentsForProperties(container, basicProperties, [], container.model)}
</div>
</div>
);
<a className="toggle-show-less" href="#show-more-properties">less…</a>
</h1>
<div className="toggleable">
- {properties.map(property => {
- const path = [property.name];
- const value = container.model[property.name];
- return build(container, property, path, value, {toggle: true, width: props.width});
- })}
+ {buildComponentsForProperties(container, properties, [], container.model, {toggle: true, width: props.width})}
</div>
<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>
</div>
{buildAdvancedGroup()}
</div>
);
+};
-}