3 * Copyright 2016-2017 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 import _debounce from 'lodash/debounce';
20 import React from 'react'
21 import ClassNames from 'classnames'
22 import changeCase from 'change-case'
23 import Property from '../../libraries/model/DescriptorModelMetaProperty'
24 import CatalogDataStore from '../../stores/CatalogDataStore'
25 import _isInt from 'validator/lib/isInt'
26 import _toInt from 'validator/lib/toInt'
27 import _isFloat from 'validator/lib/isFloat'
28 import _toFloat from 'validator/lib/toFloat'
29 import _trim from 'validator/lib/trim'
30 import _isIP from 'validator/lib/isIP'
32 import { startEditing, endEditing, onFocusPropertyFormInputElement } from './EditDescriptorUtils'
33 import Select from './Select'
35 function validateRequired(isRequired, value) {
37 return isRequired && !value ? { success: false, message: "A value is required." } : { success: true, value: null };
40 function editorExitHandler(isValueRequired, onExit, onError, event) {
41 const value = event.target.value;
42 const result = validateRequired(isValueRequired, value);
43 onExit && onExit(result);
47 function Enumeration(props) {
48 const { id, property, title, readOnly, onChange, onError, onExit } = props;
49 let value = props.value;
50 const enumeration = Property.getEnumeration(property, value);
51 const hasDefaultValue = !!property['default-value'];
52 const required = property.mandatory || hasDefaultValue;
53 if (!value && hasDefaultValue) {
54 value = property['default-value'];
62 placeholder={property.name}
64 onExit={editorExitHandler.bind(null, required, onExit, onError)}
66 readOnly={readOnly} />
70 function Reference(props) {
71 const { id, property, title, path, container, readOnly, onChange, onError, onExit } = props;
72 let value = props.value;
73 const catalogs = props.catalogs || CatalogDataStore.getTransientCatalogs();
74 let fullPathString = container.key + ':' + path.join(':');
75 let containerRef = container;
76 while (containerRef.parent) {
77 fullPathString = containerRef.parent.key + ':' + fullPathString;
78 containerRef = containerRef.parent;
80 const leafRefPathValues = Property.getLeafRef(property, path, value, fullPathString, catalogs, container);
81 const required = property.mandatory;
82 if (value && !leafRefPathValues.find(option => option.isSelected)) {
90 options={leafRefPathValues}
92 placeholder={property.name}
94 onExit={editorExitHandler.bind(null, required, onExit, onError)}
96 readOnly={readOnly} />
100 function Boolean(props) {
101 const { id, property, title, readOnly, onChange, onError, onExit } = props;
102 let value = props.value;
103 const hasDefaultValue = !!property['default-value'];
104 const required = property.mandatory || hasDefaultValue;
105 const typeOfValue = typeof value;
106 if (typeOfValue === 'number' || typeOfValue === 'boolean') {
107 value = value ? 'TRUE' : 'FALSE';
109 value = value.toUpperCase();
111 if (hasDefaultValue) {
112 value = ('' + property['default-value']).toUpperCase();
116 { name: "TRUE", value: 'TRUE' },
117 { name: "FALSE", value: 'FALSE' }
125 placeholder={property.name}
127 onExit={editorExitHandler.bind(null, required, onExit, onError)}
129 readOnly={readOnly} />
133 function Empty(props) {
134 // A null value indicates the leaf exists (as opposed to undefined).
135 // We stick in a string when the user actually sets it to simplify things
136 // but the correct thing happens when we serialize to user data
137 const EMPTY_LEAF_PRESENT = '--empty-leaf-set--';
138 const { id, property, value, title, readOnly, onChange } = props;
139 let isEmptyLeafPresent = !!value;
140 let present = isEmptyLeafPresent ? EMPTY_LEAF_PRESENT : "";
142 { name: "Enabled", value: EMPTY_LEAF_PRESENT }
149 placeholder={"Not Enabled"}
153 readOnly={readOnly} />
157 function getValidator(property) {
158 function validateInteger(constraints, value) {
159 return _isInt(value, constraints) ? { success: true, value: _toInt(value) } :
160 { success: false, value, message: "The value is not an integer or does not meet the property constraints." };
162 function validateDecimal(constraints, value) {
163 return _isFloat(value, constraints) ? { success: true, value: _toFloat(value) } :
164 { success: false, value, message: "The value is not a decimal number or does not meet the property constraints." };
166 function validateProperty(validator, errorMessage, value) {
167 return validator(value) ? { success: true, value } :
168 { success: false, value, message: errorMessage };
170 const name = property.name;
171 if (name === 'ip-address' || name.endsWith('-ip-address')) {
172 return validateProperty.bind(null, _isIP, "The value is not a valid ip address.")
174 switch (property['data-type']) {
176 return validateInteger.bind(null, { min: -128, max: 127 });
178 return validateInteger.bind(null, { min: -32768, max: 32767 });
180 return validateInteger.bind(null, { min: -2147483648, max: 2147483647 });
182 return validateInteger.bind(null, null);
184 return validateInteger.bind(null, { min: 0, max: 255 });
186 return validateInteger.bind(null, { min: 0, max: 65535 });
188 return validateInteger.bind(null, { min: 0, max: 4294967295 });
190 return validateInteger.bind(null, { min: 0 });
192 return validateDecimal.bind(null, null)
195 return function (value) { return { success: true, value } };
199 function messageTemplate(strings, ...keys) {
200 return (function (...vars) {
201 let helpInfo = vars.reduce((o, info) => Object.assign(o, info), {});
202 return keys.reduce((s, key, i) => {
203 return s + helpInfo[key] + strings[i + 1];
208 const errorMessage = messageTemplate`"${'value'}" is ${'error'}. ${'message'}`;
210 class Input extends React.Component {
213 let originalValue = props.value ? props.value : null; // normalize empty value
214 this.state = { originalValue };
217 componentWillReceiveProps(nextProps) {
218 const { value } = nextProps
219 if (value !== this.state.originalValue) {
220 let originalValue = value ? value : null; // normalize empty value
221 this.setState({ originalValue })
226 const { id, property, value, title, readOnly, onChange, onError, onExit } = this.props;
227 const { originalValue } = this.state;
228 const isGuid = Property.isGuid(property);
229 const className = ClassNames(property.name + '-input', { '-is-guid': isGuid, '-is-required': required });
230 const placeholder = property.name;
231 const required = property.mandatory;
233 const validator = getValidator(property);
234 function handleValueChanged(newValue) {
235 newValue = newValue.trim();
236 const result = !newValue ? validateRequired(required, newValue) : validator(newValue);
237 result.success ? (originalValue !== result.value) && onChange(result.value) : onError(result.message);
239 const changeHandler = _debounce(handleValueChanged, 2000);
240 function onInputChange(e) {
242 changeHandler(_trim(e.target.value));
245 changeHandler.cancel();
246 const value = _trim(e.target.value);
247 const result = !value ? validateRequired(required, value) : validator(value);
248 if (result.success) {
249 // just in case we missed it by cancelling the debouncer
250 (originalValue !== result.value) && onChange(result.value);
254 if (property['preserve-line-breaks']) {
260 placeholder={placeholder}
261 className={className}
262 onChange={readOnly ? null : onInputChange}
263 onFocus={onFocusPropertyFormInputElement}
264 onBlur={readOnly ? null : editorExitHandler.bind(null, required, onExit, onError)}
265 onMouseDown={startEditing}
266 onMouseOver={startEditing}
267 onMouseOut={endEditing}
268 onMouseLeave={endEditing}
269 required={required} readOnly={readOnly} />
277 className={className}
278 placeholder={placeholder}
279 onChange={readOnly ? null : onInputChange}
280 onFocus={onFocusPropertyFormInputElement}
281 onBlur={readOnly ? null : onBlur}
282 onMouseDown={startEditing}
283 onMouseOver={startEditing}
284 onMouseOut={endEditing}
285 onMouseLeave={endEditing}
286 required={required} readOnly={readOnly}