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 _isInt from 'validator/lib/isInt'
24 import _toInt from 'validator/lib/toInt'
25 import _isFloat from 'validator/lib/isFloat'
26 import _toFloat from 'validator/lib/toFloat'
27 import _trim from 'validator/lib/trim'
28 import _isIP from 'validator/lib/isIP'
29 import yang from '../../yang/leaf-utils.js'
30 import resolveLeafRefPaths from './resolveLeafRef'
31 import Select from './Select'
33 function validateRequired(isRequired, value) {
35 return isRequired && !value ? { success: false, message: "A value is required." } : { success: true, value: null };
38 function editorExitHandler(isValueRequired, onExit, onError, event) {
39 const value = event.target.value;
40 const result = validateRequired(isValueRequired, value);
41 onExit && onExit(result);
44 function Enumeration(props) {
45 const { id, property, title, readOnly, onChange, onError, onExit } = props;
46 let value = props.value;
47 const enumDef = property['data-type'].enumeration.enum;
48 const enumeration = typeof enumDef === 'string' ?
49 [{ name: enumDef, value: enumDef, isSelected: String(value) === enumDef }]
50 : Object.keys(enumDef).map(enumName => {
51 let enumValue = enumName;
52 return { name: enumName, value: enumValue, isSelected: String(enumValue) === String(value) };
54 const hasDefaultValue = !!yang.getDefaultValue(property);
55 const required = yang.isRequired(property) || hasDefaultValue;
56 if (!value && hasDefaultValue) {
57 value = yang.getDefaultValue(property);
65 placeholder={property.name}
67 onExit={editorExitHandler.bind(null, required, onExit, onError)}
69 readOnly={readOnly} />
73 function Reference(props) {
74 const { id, property, title, value, path, model, readOnly, onChange, onError, onExit } = props;
76 function getLeafRef(property = {}, path, value, model) {
77 const leafRefPath = property['data-type']['leafref']['path'];
79 let leafRefPathValues = []; //resolveLeafRefPath(model, path, leafRefPath);
81 let leafRefObjects = [];
83 leafRefPathValues && leafRefPathValues.map((leafRefPathValue) => {
85 name: leafRefPathValue,
86 value: leafRefPathValue,
87 isSelected: String(leafRefPathValue) === String(value)
91 return leafRefObjects;
93 const leafRefPathValues = getLeafRef(property, path, value, model);
94 const required = yang.isRequired(property);
100 options={leafRefPathValues}
102 placeholder={property.name}
104 onExit={editorExitHandler.bind(null, required, onExit, onError)}
106 readOnly={readOnly} />
110 function Boolean(props) {
111 const { id, property, title, readOnly, onChange, onError, onExit } = props;
112 let value = props.value;
113 const typeOfValue = typeof value;
114 if (typeOfValue === 'number' || typeOfValue === 'boolean') {
115 value = value ? 'TRUE' : 'FALSE';
117 value = value.toUpperCase();
120 { name: "TRUE", value: 'TRUE' },
121 { name: "FALSE", value: 'FALSE' }
123 const hasDefaultValue = !!yang.getDefaultValue(property);
124 const required = yang.isRequired(property) || hasDefaultValue;
125 if (!value && hasDefaultValue) {
126 value = yang.getDefaultValue(property);
134 placeholder={property.name}
136 onExit={editorExitHandler.bind(null, required, onExit, onError)}
138 readOnly={readOnly} />
142 function Empty(props) {
143 // A null value indicates the leaf exists (as opposed to undefined).
144 // We stick in a string when the user actually sets it to simplify things
145 // but the correct thing happens when we serialize to user data
146 const EMPTY_LEAF_PRESENT = '--empty-leaf-set--';
147 const { id, property, value, title, readOnly, onChange } = props;
148 let isEmptyLeafPresent = !!value;
149 let present = isEmptyLeafPresent ? EMPTY_LEAF_PRESENT : "";
151 { name: "Enabled", value: EMPTY_LEAF_PRESENT }
158 placeholder={"Not Enabled"}
162 readOnly={readOnly} />
166 function getValidator(property) {
167 function validateInteger(constraints, value) {
168 return _isInt(value, constraints) ? { success: true, value: _toInt(value) } :
169 { success: false, value, message: "The value is not an integer or does not meet the property constraints." };
171 function validateDecimal(constraints, value) {
172 return _isFloat(value, constraints) ? { success: true, value: _toFloat(value) } :
173 { success: false, value, message: "The value is not a decimal number or does not meet the property constraints." };
175 function validateProperty(validator, errorMessage, value) {
176 return validator(value) ? { success: true, value } :
177 { success: false, value, message: errorMessage };
179 const name = property.name;
180 if (name === 'ip-address' || name.endsWith('-ip-address')) {
181 return validateProperty.bind(null, _isIP, "The value is not a valid ip address.")
183 switch (property['data-type']) {
185 return validateInteger.bind(null, { min: -128, max: 127 });
187 return validateInteger.bind(null, { min: -32768, max: 32767 });
189 return validateInteger.bind(null, { min: -2147483648, max: 2147483647 });
191 return validateInteger.bind(null, null);
193 return validateInteger.bind(null, { min: 0, max: 255 });
195 return validateInteger.bind(null, { min: 0, max: 65535 });
197 return validateInteger.bind(null, { min: 0, max: 4294967295 });
199 return validateInteger.bind(null, { min: 0 });
201 return validateDecimal.bind(null, null)
204 return function (value) { return { success: true, value } };
208 function messageTemplate(strings, ...keys) {
209 return (function (...vars) {
210 let helpInfo = vars.reduce((o, info) => Object.assign(o, info), {});
211 return keys.reduce((s, key, i) => {
212 return s + helpInfo[key] + strings[i + 1];
217 const errorMessage = messageTemplate`"${'value'}" is ${'error'}. ${'message'}`;
219 const inputDemensionStyle = { maxWidth: '100%', minWidth: '100%' };
221 class Input extends React.Component {
224 let originalValue = yang.isValueSet(props.property, props.value) ? props.value : null; // normalize empty value
225 this.state = { originalValue };
228 componentWillReceiveProps(nextProps) {
229 const { value } = nextProps
230 if (value !== this.state.originalValue) {
231 let originalValue = value ? value : null; // normalize empty value
232 this.setState({ originalValue })
237 const { id, property, value, title, readOnly, onChange, onError, onExit } = this.props;
238 const { originalValue } = this.state;
239 const placeholder = property.name;
240 const required = yang.isRequired(property);
241 const className = ClassNames(property.name + '-input', { '-is-required': required });
243 const validator = getValidator(property);
244 function handleValueChanged(newValue) {
245 newValue = newValue.trim();
246 const result = !newValue ? validateRequired(required, newValue) : validator(newValue);
247 result.success ? onChange(result.value) : onError(result.message);
249 const changeHandler = _debounce(handleValueChanged, 2000);
250 function onInputChange(e) {
252 changeHandler(_trim(e.target.value));
255 changeHandler.cancel();
256 const value = _trim(e.target.value);
257 const result = !value ? validateRequired(required, value) : validator(value);
258 // just in case we missed it by cancelling the debouncer
259 result.success ? onChange(result.value) : onError(result.message);
262 // if (!yang.isKey(property) && yang.isString(property)) {
267 // defaultValue={value}
268 // placeholder={placeholder}
269 // className={className}
270 // onChange={onInputChange}
272 // required={required}
273 // readOnly={readOnly}
274 // style={inputDemensionStyle}/>
282 className={className}
283 placeholder={placeholder}
284 onChange={onInputChange}
288 style={inputDemensionStyle}