3 * Copyright 2016 RIFT.IO Inc
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 * Created by onvelocity on 1/18/16.
21 * This class generates the form fields used to edit the CONFD JSON model.
24 import _uniqueId from 'lodash/uniqueId';
25 import _set from 'lodash/set';
26 import _get from 'lodash/get';
27 import _has from 'lodash/has';
28 import _keys from 'lodash/keys';
29 import _isObject from 'lodash/isObject';
30 import _isArray from 'lodash/isArray';
31 import _isNumber from 'lodash/isNumber';
32 import utils from '../libraries/utils'
33 import React from 'react'
34 import changeCase from 'change-case'
35 import toggle from '../libraries/ToggleElementHandler'
36 import Property from '../libraries/model/DescriptorModelMetaProperty'
37 import SelectionManager from '../libraries/SelectionManager'
38 import ComposerAppActions from '../actions/ComposerAppActions'
39 import CatalogItemsActions from '../actions/CatalogItemsActions'
40 import DescriptorEditorActions from '../actions/DescriptorEditorActions'
41 import DescriptorModelFactory from '../libraries/model/DescriptorModelFactory'
42 import DescriptorModelMetaFactory from '../libraries/model/DescriptorModelMetaFactory'
43 import PropertyNavigate from './model/PropertyNavigate'
46 import '../styles/EditDescriptorModelProperties.scss'
48 function selectModel(container, model) {
49 const root = container.getRoot();
50 if (SelectionManager.select(model)) {
51 CatalogItemsActions.catalogItemMetaDataChanged(root.model);
55 function isDataProperty(property) {
56 return property.type === 'leaf' || property.type === 'leaf_list' || property.type === 'choice';
59 function checkIfValueEmpty(value) {
60 if (value === null || typeof value === 'undefined') {
62 } else if (_isArray(value) && !value.length) {
64 } else if (_isObject(value)) {
65 const keys = _keys(value);
66 if (keys.length < 2) {
67 return !keys.length || (keys[0] === 'uiState')
73 function makeOption(path, value) {
74 let labelPath = path.map(node => _isNumber(node) ? node + 1: node);
77 label: labelPath.join(' . ') + (value ? ' : ' + value : '')
81 export default function NavigateDescriptorModel(props) {
82 const { container, idMaker, style } = props;
83 const uiState = container.uiState;
85 function buildField(property, path, value) {
86 return [makeOption(path, value)];
89 function buildLeafList(property, path, value) {
90 const searchValue = Array.isArray(value) ? value.join(' ') : value;
91 return [makeOption(path, searchValue)];
94 function buildChoice(property, path, value) {
95 const uiStatePath = path.concat(['uiState']);
96 const choiceStatePath = ['choice', property.name];
97 const fullChoiceStatePath = uiStatePath.concat(choiceStatePath);
99 function determineSelectedChoice(model) {
100 let choiceState = utils.resolvePath(container.model, fullChoiceStatePath.join('.'));
102 return property.properties.find(c => c.name === choiceState.selected);
104 const selectedCase = property.properties.find(c =>
105 c.properties && c.properties.find(p => _has(model, path.concat([p.name])))
110 const selectedCase = determineSelectedChoice(container.model);
111 return [makeOption(path)].concat(selectedCase ?
112 buildComponentsForProperties(
113 selectedCase.properties, path, path.length ? _get(container.model, path) : container.model) :
117 function buildList(property, path, value, uniqueId) {
118 if (value && !Array.isArray(value)) {
122 function getListItemSummary(index, value) {
123 const keys = property.key.map((key) => value[key]);
124 const summary = keys.join(' ');
125 return summary.length > 1 ? summary : '' + (index + 1);
127 const children = value ? value.reduce((a, itemValue, i) => {
128 const itemPath = path.concat([i]);
129 return a.concat(buildComponentsForProperties(property.properties, itemPath, itemValue));
130 }, [makeOption(path)])
131 : [makeOption(path)];
135 function buildSimpleList(property, path, value, uniqueId) {
136 return [makeOption(path)];
139 function buildContainer(property, path, value, uniqueId, readOnly) {
140 return buildComponentsForProperties(property.properties, path, value);
144 * buiid and return an array of components representing an editor for each property.
146 * @param {any} container the master document being edited
147 * @param {[property]} properties
148 * @param {string} pathToProperties path within the container to the properties
149 * @param {Object} data source for each property
150 * which may be useful/necessary to a components rendering.
151 * @returns an array of react components
153 function buildComponentsForProperties(properties, pathToProperties, data) {
154 return properties.reduce((a, property) => {
156 let propertyPath = pathToProperties.slice();
157 if (property.type != 'choice') {
158 propertyPath.push(property.name);
160 if (data && typeof data === 'object') {
161 value = _get(data, property.name);
165 result = buildPropertyComponent(property, propertyPath, value);
169 return a.concat(result);
173 function buildPropertyComponent(property, path, value) {
176 const isObject = Property.isObject(property);
177 const title = changeCase.titleCase(property.name);
179 // create a unique Id for use as react component keys and html element ids
180 // use uid (from ui info) instead of id property (which is not stable)
181 let uniqueId = container.uid;
182 let containerRef = container;
183 while (containerRef.parent) {
184 uniqueId = containerRef.parent.uid + ':' + uniqueId;
185 containerRef = containerRef.parent;
187 uniqueId += ':' + path.join(':')
189 if (!property.properties && isObject) {
190 console.debug('no properties', property);
191 const uiState = DescriptorModelMetaFactory.getModelMetaForType(property.name) || {};
192 property.properties = uiState.properties;
195 if (property.type === 'leaf') {
196 return buildField(property, path, value, uniqueId);
197 } else if (property.type === 'leaf_list') {
198 return buildLeafList(property, path, value, uniqueId);
199 } else if (property.type === 'list') {
200 return Property.isSimpleList(property) ?
201 buildSimpleList(property, path, value, uniqueId) :
202 buildList(property, path, value, uniqueId);
203 } else if (property.type === 'container') {
204 return buildContainer(property, path, value, uniqueId);
205 } else if (property.type === 'choice') {
206 return buildChoice(property, path, value, uniqueId);
213 if (!(DescriptorModelFactory.isContainer(container))) {
217 const containerType = container.uiState['qualified-type'] || container.uiState.type;
218 let properties = DescriptorModelMetaFactory.getModelMetaForType(containerType).properties;
219 // bubble all data properties to top of list
220 let twoLists = properties.reduce((o, property) => {
221 const value = _get(container.model, [property.name]);
222 if (isDataProperty(property)) {
223 o.listOne.push(property);
225 o.listTwo.push(property);
232 properties = twoLists.listOne.concat(twoLists.listTwo);
233 const options = buildComponentsForProperties(properties, [], container.model);
234 return options.length ?
235 (<PropertyNavigate container={container} idMaker={idMaker} options={options}
236 style={style} placeholder="Select to navigate" />)