@bhch/react-json-form
Version:
Create forms using JSON Schema
1,046 lines (858 loc) • 34.4 kB
JavaScript
import React from 'react';
import {getBlankData, findMatchingSubschemaIndex, dataObjectMatchesSchema,
dataArrayMatchesSchema} from './data';
import {Button, FormInput, FormCheckInput, FormRadioInput, FormSelectInput,
FormFileInput, FormRow, FormGroup, GroupTitle, GroupDescription, FormRowControls, FormTextareaInput,
FormDateTimeInput, FormMultiSelectInput, FileUploader, AutoCompleteInput, FormURLInput} from './components';
import {getVerboseName, convertType, getCoordsFromName, getKeyword, normalizeKeyword,
joinCoords, splitCoords, actualType, getSchemaType, isEqualset, isSubset} from './util';
function handleChange(e, fieldType, callback) {
let type = e.target.type
let value;
if (type === 'checkbox') {
value = e.target.checked;
} else {
value = e.target.value;
}
if (Array.isArray(value)) { /* multiselect widget values are arrays */
value = value.map((item) => convertType(item, fieldType));
} else {
value = convertType(value, fieldType);
}
callback(e.target.name, value);
}
function FormField(props) {
let inputProps = {
name: props.name,
value: props.data,
readOnly: getKeyword(props.schema, 'readOnly', 'readonly'),
help_text: getKeyword(props.schema, 'help_text', 'helpText'),
error: props.errorMap[getCoordsFromName(props.name)],
required: props.schema.required || false,
};
if (typeof inputProps.error === 'string')
inputProps.error = [inputProps.error];
if (props.schema.placeholder)
inputProps.placeholder = props.schema.placeholder;
if (props.schema.handler)
inputProps.handler = props.schema.handler;
let schemaType = normalizeKeyword(props.schema.type);
let type;
if (props.schema.hasOwnProperty('const')) {
type = actualType(props.schema.const);
inputProps.readOnly = true;
} else {
type = schemaType;
}
let choices = getKeyword(props.schema, 'choices', 'enum');
if (choices) {
inputProps.options = choices;
type = 'select';
}
if (props.schema.widget) {
if (props.schema.widget === 'multiselect' && props.parentType !== 'array') {
// pass
} else if (props.schema.widget === 'hidden') {
type = 'string';
} else {
type = props.schema.widget;
}
}
let InputField;
switch (type) {
case 'string':
InputField = FormInput;
if (props.schema.format) {
let format = props.schema.format;
if (format === 'data-url') {
InputField = FormFileInput;
} else if (format === 'file-url') {
InputField = FileUploader;
} else if (normalizeKeyword(format) === 'date-time') {
InputField = FormDateTimeInput;
} else if (format === 'uri' || format === 'uri-reference') {
InputField = FormURLInput;
}
inputProps.type = format;
} else if (props.schema.widget === 'hidden') {
inputProps.type = 'hidden';
} else {
inputProps.type = 'text';
}
if (props.schema.minLength || props.schema.minLength === 0)
inputProps.minLength = props.schema.minLength;
if (props.schema.maxLength || props.schema.maxLength === 0)
inputProps.maxLength = props.schema.maxLength;
break;
case 'fileinput':
InputField = FormFileInput;
if (props.schema.format)
inputProps.type = props.schema.format;
break;
case 'range':
case 'integer':
case 'number':
if (type === 'range')
inputProps.type = 'range';
else
inputProps.type = 'number';
if (schemaType === 'integer')
inputProps.step = '1';
else
inputProps.step = 'any';
InputField = FormInput;
if (props.schema.minimum || props.schema.minimum === 0)
inputProps.min = props.schema.minimum;
if (props.schema.maximum || props.schema.maximum === 0)
inputProps.max = props.schema.maximum;
break;
case 'boolean':
inputProps.type = 'checkbox';
InputField = FormCheckInput;
break;
case 'checkbox':
inputProps.type = 'checkbox';
InputField = FormCheckInput;
break;
case 'radio':
inputProps.type = 'radio';
InputField = FormRadioInput;
break;
case 'select':
InputField = FormSelectInput;
break;
case 'multiselect':
inputProps.valueType = props.schema.type;
InputField = FormMultiSelectInput;
break;
case 'autocomplete':
InputField = AutoCompleteInput;
break;
case 'multiselect-autocomplete':
InputField = AutoCompleteInput;
inputProps.multiselect = true;
break;
case 'textarea':
InputField = FormTextareaInput;
if (props.schema.minLength || props.schema.minLength === 0)
inputProps.minLength = props.schema.minLength;
if (props.schema.maxLength || props.schema.maxLength === 0)
inputProps.maxLength = props.schema.maxLength;
break;
default:
inputProps.type = 'text';
InputField = FormInput;
}
return (
<InputField
{...inputProps}
label={
props.editable ? <span>{props.schema.title} <Button className="edit" onClick={props.onEdit} title="Edit">Edit</Button></span>
:
props.schema.title
}
onChange={(e) => handleChange(e, props.schema.type, props.onChange)}
/>
);
}
export function getStringFormRow(args) {
let {
data, schema, name, onChange, onRemove, removable, onEdit, onKeyEdit, editable,
onMoveUp, onMoveDown, parentType, errorMap, ...fieldProps
} = args;
return (
<FormRow
key={name}
onRemove={removable ? (e) => onRemove(name) : null}
onMoveUp={onMoveUp}
onMoveDown={onMoveDown}
hidden={schema.widget === 'hidden'}
>
<FormField
data={data}
schema={schema}
name={name}
onChange={onChange}
onEdit={onKeyEdit}
editable={editable}
parentType={parentType}
errorMap={errorMap}
{...fieldProps}
/>
</FormRow>
);
}
export function getArrayFormRow(args) {
let {data, schema, name, onChange, onAdd, onRemove, onMove, onEdit, level} = args;
let rows = [];
let groups = [];
let isReadonly = getKeyword(schema, 'readonly', 'readOnly', false);
let removable = true;
let min_items = getKeyword(schema, 'min_items', 'minItems') || 0;
if (data.length <= min_items || isReadonly)
removable = false;
let addable = true;
let max_items = getKeyword(schema, 'max_items', 'maxItems') || 100;
if (data.length >= max_items || isReadonly)
addable = false;
let isRef = schema.items.hasOwnProperty('$ref');
if (isRef) {
schema.items = {...args.getRef(schema.items['$ref']), ...schema.items};
delete schema.items['$ref'];
}
let type = normalizeKeyword(schema.items.type);
let nextArgs = {
schema: schema.items,
onChange: onChange,
onAdd: onAdd,
onRemove: onRemove,
level: level + 1,
removable: removable,
onMove: onMove,
onEdit: onEdit,
onKeyEdit: args.onKeyEdit,
parentType: 'array',
getRef: args.getRef,
errorMap: args.errorMap,
};
if (isReadonly)
nextArgs.schema.readOnly = true;
if (nextArgs.schema.widget === 'multiselect' || nextArgs.schema.widget === 'multiselect-autocomplete') {
nextArgs.data = data;
nextArgs.name = name;
nextArgs.removable = false;
nextArgs.onMoveUp = null;
nextArgs.onMoveDown = null;
addable = false;
rows.push(getStringFormRow(nextArgs));
} else {
for (let i = 0; i < data.length; i++) {
nextArgs.data = data[i];
nextArgs.name = joinCoords(name, i);
if (i === 0 || isReadonly)
nextArgs.onMoveUp = null;
else
nextArgs.onMoveUp = (e) => onMove(joinCoords(name, i), joinCoords(name, i - 1));
if (i === data.length - 1 || isReadonly)
nextArgs.onMoveDown = null;
else
nextArgs.onMoveDown = (e) => onMove(joinCoords(name, i), joinCoords(name, i + 1));
if (type === 'array') {
groups.push(getArrayFormRow(nextArgs));
} else if (type === 'object') {
groups.push(getObjectFormRow(nextArgs));
} else {
// oneOf/anyOf
if (schema.items.hasOwnProperty('oneOf')) {
groups.push(<OneOf parentArgs={args} nextArgs={{...nextArgs}} key={"oneOf_" + name + '_' + i} />);
} else if (schema.items.hasOwnProperty('anyOf')) {
groups.push(<AnyOf parentArgs={args} nextArgs={{...nextArgs}} key={"anyOf_" + name + '_' + i} />);
} else {
rows.push(getStringFormRow(nextArgs));
}
}
}
}
let coords = name; // coordinates for insertion and deletion
if (rows.length || (!rows.length && !groups.length)) {
let rowError;
if (!groups.length) {
rowError = args.errorMap[getCoordsFromName(coords)];
if (typeof rowError === 'string')
rowError = [rowError];
}
rows = (
<FormGroup
level={level}
schema={schema}
addable={addable}
onAdd={() => onAdd(getBlankData(schema.items, args.getRef), coords)}
editable={args.editable}
onEdit={args.onKeyEdit}
key={'row_group_' + name}
collapsible={data.length > 0}
childrenType="rows"
>
{rowError && rowError.map((error, i) => <div className="rjf-error-text" key={i}>{error}</div>)}
{rows}
</FormGroup>
);
if (args.parentType === 'object' && args.removable) {
rows = (
<div className="rjf-form-group-wrapper" key={'row_group_wrapper_' + name}>
<FormRowControls
onRemove={(e) => onRemove(name)}
/>
{rows}
</div>
);
}
}
if (groups.length) {
let groupError = args.errorMap[getCoordsFromName(coords)];
if (typeof groupError === 'string')
groupError = [groupError];
groups = (
<div key={'group_' + name} className="rjf-form-group-wrapper">
{args.parentType === 'object' && args.removable &&
<FormRowControls
onRemove={(e) => onRemove(name)}
/>
}
<FormGroup
level={level}
schema={schema}
addable={addable}
onAdd={() => onAdd(getBlankData(schema.items, args.getRef), coords)}
editable={args.editable}
onEdit={args.onKeyEdit}
collapsible={data.length > 0}
childrenType="groups"
>
{groupError && groupError.map((error, i) => <div className="rjf-error-text" key={i}>{error}</div>)}
{groups.map((i, index) => (
<div className="rjf-form-group-wrapper" key={'group_wrapper_' + name + '_' + index}>
<FormRowControls
onRemove={removable ? (e) => onRemove(joinCoords(name, index)) : null}
onMoveUp={index > 0 && !isReadonly ? (e) => onMove(joinCoords(name, index), joinCoords(name, index - 1)) : null}
onMoveDown={index < groups.length - 1 && !isReadonly ? (e) => onMove(joinCoords(name, index), joinCoords(name, index + 1)) : null}
/>
{i}
</div>
)
)}
</FormGroup>
</div>
);
}
return [...rows, ...groups];
}
export function getObjectFormRow(args) {
let {data, schema, name, onChange, onAdd, onRemove, onMove, onEdit, level} = args;
let rows = [];
let isReadonly = getKeyword(schema, 'readonly', 'readOnly', false);
let schema_keys = getKeyword(schema, 'keys', 'properties', {});
if (schema.hasOwnProperty('allOf')) {
for (let i = 0; i < schema.allOf.length; i++) {
schema_keys = {...schema_keys, ...getKeyword(schema.allOf[i], 'keys', 'properties', {})};
}
}
let keys = [...Object.keys(schema_keys)];
if (schema.additionalProperties)
keys = [...keys, ...Object.keys(data).filter((k) => keys.indexOf(k) === -1)];
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
let value = data[key];
let childName = joinCoords(name, key);
let schemaValue = schema_keys.hasOwnProperty(key) ? {...schema_keys[key]} : undefined;
let isAdditionalProperty = schema_keys.hasOwnProperty(key) ? false : true;
if (typeof schemaValue === 'undefined') {
// for keys added through additionalProperties
if (typeof schema.additionalProperties === 'boolean')
schemaValue = {type: 'string'};
else
schemaValue = {...schema.additionalProperties};
}
let isRef = schemaValue.hasOwnProperty('$ref');
if (isRef) {
schemaValue = {...args.getRef(schemaValue['$ref']), ...schemaValue};
delete schemaValue['$ref'];
}
if (isReadonly)
schemaValue.readOnly = true;
let type = normalizeKeyword(schemaValue.type);
if (!schemaValue.title) {
if (isAdditionalProperty)
schemaValue.title = key;
else
schemaValue.title = getVerboseName(key);
}
let removable = false;
if (schema_keys[key] === undefined)
removable = true;
if (schema.hasOwnProperty('required') && Array.isArray(schema.required)) {
if (schema.required.indexOf(key) > -1)
schemaValue['required'] = true;
}
let nextArgs = {
data: value,
schema: schemaValue,
name: childName,
onChange: onChange,
onAdd: onAdd,
onRemove: onRemove,
level: level + 1,
removable: removable,
onMove: onMove,
onEdit: onEdit,
parentType: 'object',
getRef: args.getRef,
errorMap: args.errorMap,
};
nextArgs.onKeyEdit = () => handleKeyEdit(data, key, value, childName, onEdit);
nextArgs.editable = removable;
if (type === 'array') {
rows.push(getArrayFormRow(nextArgs));
} else if (type === 'object') {
rows.push(getObjectFormRow(nextArgs));
} else {
// oneOf/anyOf
if (nextArgs.schema.hasOwnProperty('oneOf')) {
rows.push(<OneOf parentArgs={args} nextArgs={{...nextArgs}} key={"oneOf_" + name + '_' + i} />);
} else if (nextArgs.schema.hasOwnProperty('anyOf')) {
rows.push(<AnyOf parentArgs={args} nextArgs={{...nextArgs}} key={"anyOf_" + name + '_' + i} />);
} else {
rows.push(getStringFormRow(nextArgs));
}
}
}
// oneOf
if (schema.hasOwnProperty('oneOf')) {
rows.push(<OneOf parentArgs={args} key={"oneOf_" + name} />);
}
// anyOf
if (schema.hasOwnProperty('anyOf')) {
rows.push(<AnyOf parentArgs={args} key={"anyOf_" + name} />);
}
if (rows.length || schema.additionalProperties) {
let coords = name;
let groupError = args.errorMap[getCoordsFromName(coords)];
if (typeof groupError === 'string')
groupError = [groupError];
rows = (
<FormGroup
level={level}
schema={schema}
addable={schema.additionalProperties && !isReadonly}
onAdd={() => handleKeyValueAdd(data, coords, onAdd, schema.additionalProperties, args.getRef)}
editable={args.editable}
onEdit={args.onKeyEdit}
key={'row_group_' + name}
collapsible={keys.length > 0}
childrenType="rows"
>
{groupError && groupError.map((error, i) => <div className="rjf-error-text" key={i}>{error}</div>)}
{rows}
</FormGroup>
);
if (args.parentType === 'object' && args.removable) {
rows = (
<div className="rjf-form-group-wrapper" key={'row_group_wrapper_' + name}>
<FormRowControls
onRemove={(e) => onRemove(name)}
/>
{rows}
</div>
);
}
}
return rows;
}
export function getOneOfFormRow(args) {
/* For top-level oneOf when type is not provided.
This will try to find appropriate option for the given data.
*/
return <OneOfTopLevel args={args} />;
}
export function getAnyOfFormRow(args) {
/* For top-level oneOf when type is not provided */
return <OneOfTopLevel args={args} schemaName="anyOf" />;
}
export function getAllOfFormRow(args) {
/* For top-level oneOf when type is not provided */
// currently we only suuport allOf inside an object.
// so we'll render it as an object
return getObjectFormRow(args);
}
class OneOfTopLevel extends React.Component {
constructor(props) {
super(props);
this.schemaName = this.props.schemaName || 'oneOf';
// Uncomment when caching is implemented
//
// this.state = {
// option: this.findSelectedOption(),
// };
}
findSelectedOption = () => {
/* Returns index of currently selected option.
* It's a hard problem to reliably find the selected option for
* the given data.
*/
let dataType = actualType(this.props.args.data);
let subschemas = this.props.args.schema[this.schemaName];
return findMatchingSubschemaIndex(
this.props.args.data,
this.props.args.schema,
this.props.args.getRef,
this.schemaName
);
}
getOptions = () => {
return Array.from(
this.props.args.schema[this.schemaName].keys(),
(index) => ({label: this.getSchema(index).title || 'Option ' + (index + 1), value: index})
);
}
getSchema = (index) => {
if (index === undefined)
index = this.state.option;
let schema = this.props.args.schema[this.schemaName][index];
let isRef = schema.hasOwnProperty('$ref');
if (isRef) {
schema = {...this.props.args.getRef(schema['$ref']), schema};
delete schema['$ref'];
}
return schema;
}
handleOptionChange = (e) => {
this.updateData(this.getSchema(e.target.value));
// Uncomment when caching is reimplemented
//
// this.setState({
// option: e.target.value
// });
}
updateData(newSchema) {
this.props.args.onChange(
this.props.args.name,
getBlankData(newSchema, this.props.args.getRef)
);
}
render() {
/* Perfomance note:
*
* In order to resolve https://github.com/bhch/react-json-form/issues/67,
* we will not cache the selected option. Instead, we'll recalculate the
* selected option on every render.
*
* If there're serious performance issues, we'll reconsider caching.
*/
let selectedOption = this.findSelectedOption();
let schema = this.getSchema(selectedOption);
let type = getSchemaType(schema);
let args = this.props.args;
let rowFunc;
if (type === 'object') {
rowFunc = getObjectFormRow;
} else if (type === 'array') {
rowFunc = getArrayFormRow;
} else {
rowFunc = getStringFormRow;
args.removable = false;
args.onMoveUp = null;
args.onMoveDown = null;
if (Array.isArray(args.data) || typeof args.data === 'object')
args.data = null;
}
let rows = rowFunc({...args, schema: schema});
let selectorLabel = this.props.args.schema.title || null;
return (
<div className="rjf-form-group rjf-oneof-group rjf-oneof-group-top-level">
<div className="rjf-oneof-selector">
<FormSelectInput
value={selectedOption}
options={this.getOptions()}
onChange={this.handleOptionChange}
className="rjf-oneof-selector-input"
label={selectorLabel}
/>
</div>
{rows}
</div>
);
}
}
class OneOf extends React.Component {
constructor(props) {
super(props);
this.schemaName = this.props.schemaName || 'oneOf';
// Uncomment when caching is implemented
//
// this.state = {
// option: this.findSelectedOption(),
// };
}
/* Uncomment when caching is implemente
componentDidUpdate(prevProps, prevState) {
if (prevProps.nextArgs || this.props.nextArgs) {
let prevDataType = 'string';
let newDataType = 'string';
if (prevProps.nextArgs)
prevDataType = actualType(prevProps.nextArgs.data);
if (this.props.nextArgs)
newDataType = actualType(this.props.nextArgs.data);
if (prevDataType !== newDataType)
this.setState({option: this.findSelectedOption()});
}
}
*/
findSelectedOption = () => {
/* Returns index of currently selected option.
* It's a hard problem to reliably find the selected option for
* the given data.
*/
let index = 0;
if (this.props.nextArgs) {
let dataType = actualType(this.props.nextArgs.data);
let subschemas = this.props.nextArgs.schema[this.schemaName];
for (let i = 0; i < subschemas.length; i++) {
let subschema = subschemas[i];
let isRef = subschema.hasOwnProperty('$ref');
if (isRef) {
subschema = {...this.props.parentArgs.getRef(subschema['$ref']), ...subschema};
delete subschema['$ref'];
}
let subType = getSchemaType(subschema);
if (subschema.hasOwnProperty('const')) {
if (subschema.const === this.props.nextArgs.data) {
index = 1;
break;
} else {
continue;
}
}
if (dataType === 'number') {
if (subType === 'number' || subType === 'integer') {
index = i;
break;
}
} else if (dataType === 'null' && ['boolean', 'integer', 'number'].indexOf(subType) > -1) {
index = i;
break;
} else if (dataType === 'object') {
// check if all keys match
if (dataObjectMatchesSchema(this.props.nextArgs.data, subschema)) {
index = i;
break;
}
} else if (dataType === 'array') {
// check if item types match
if (dataArrayMatchesSchema(this.props.nextArgs.data, subschema)) {
index = i;
break;
}
} else if (dataType === subType) {
index = i;
break;
}
}
} else {
let data = this.props.parentArgs.data;
let dataType = actualType(data);
let subschemas = this.props.parentArgs.schema[this.schemaName];
if (subschemas === undefined)
return index;
for (let i = 0; i < subschemas.length; i++) {
let subschema = subschemas[i];
let subType = getSchemaType(subschema);
if (subType !== dataType)
continue;
if (dataType === 'object') {
if (dataObjectMatchesSchema(data, subschema)) {
index = i;
break;
}
} else if (dataType === 'array') {
// check if all items in data match the items type
// strangely enough, haven't found a schema case
// which triggers this condition.
// for the time being, let's just throw an error if
// this runs and ask the user to report the error
throw new Error(
"Unexpected block (#1) tirggered. " +
"If you see this error, you've found a rare schema. " +
"Please report this issue on our Github."
);
}
}
}
return index;
}
getOptions = () => {
let parentType = this.getParentType();
if (parentType === 'object') {
let schema;
if (this.props.nextArgs) {
// this is an object key which has oneOf keyword
schema = this.props.nextArgs.schema;
} else {
schema = this.props.parentArgs.schema;
}
return schema[this.schemaName].map((option, index) => {
return {label: option.title || 'Option ' + (index + 1), value: index};
});
} else if (parentType === 'array') {
return this.props.parentArgs.schema.items[this.schemaName].map((option, index) => {
return {label: option.title || 'Option ' + (index + 1), value: index};
});
}
return [];
}
getSchema = (index) => {
if (index === undefined)
index = this.state.option;
let parentType = this.getParentType();
let isReadonly = false;
let schema;
if (parentType === 'object') {
if (this.props.nextArgs) {
// this is an object key which has oneOf keyword
schema = {...this.props.nextArgs.schema[this.schemaName][index]};
if (!schema.title)
schema.title = this.props.nextArgs.schema.title;
isReadonly = getKeyword(this.props.nextArgs.schema, 'readOnly', 'readonly', isReadonly);
} else {
schema = this.props.parentArgs.schema[this.schemaName][index];
isReadonly = getKeyword(this.props.parentArgs.schema, 'readOnly', 'readonly', isReadonly);
}
} else if (parentType === 'array') {
schema = this.props.parentArgs.schema.items[this.schemaName][index];
isReadonly = getKeyword(this.props.parentArgs.schema, 'readOnly', 'readonly', isReadonly);
} else {
schema = {'type': 'string'};
}
let isRef = schema.hasOwnProperty('$ref');
if (isRef) {
schema = {...this.props.parentArgs.getRef(schema['$ref']), ...schema};
delete schema['$ref'];
}
if (isReadonly)
schema.readOnly = true;
return schema;
}
getParentType = () => {
return getSchemaType(this.props.parentArgs.schema);
}
handleOptionChange = (e, selectedOption) => {
this.updateData(this.getSchema(selectedOption), this.getSchema(e.target.value));
// Uncomment when caching is implemented
//
// this.setState({
// option: e.target.value
// });
}
updateData(oldSchema, newSchema) {
let parentType = this.getParentType();
/*
If parent is an object,
then all subschemas in oneOf must be objects containing properties
of the parent object
otherwise, it's an error
If parent is an array,
then the subschemas in oneOf can be anything.
*/
if (parentType === 'object' && !this.props.nextArgs) {
let name = this.props.parentArgs.name;
let schema = newSchema;
let data = this.props.parentArgs.data;
let schemaProperties = getKeyword(schema, 'properties', 'keys', {});
// keys to remove
let remove = [...Object.keys(getKeyword(oldSchema, 'properties', 'keys'))];
// keys to add
let add = [...Object.keys(getKeyword(schema, 'properties', 'keys'))];
let newData = {};
for (let key in data) {
if (!data.hasOwnProperty(key))
continue;
if (remove.indexOf(key) > -1)
continue;
newData[key] = data[key];
}
add.forEach((key, index) => {
newData[key] = getBlankData(schemaProperties[key], this.props.parentArgs.getRef);
});
this.props.parentArgs.onChange(name, newData);
} else if (parentType === 'array' || this.props.nextArgs) {
let name = this.props.nextArgs.name;
let schema = newSchema;
let data = this.props.nextArgs.data;
this.props.parentArgs.onChange(name, getBlankData(schema, this.props.parentArgs.getRef));
}
}
render() {
/* Perfomance note:
*
* In order to resolve https://github.com/bhch/react-json-form/issues/67,
* we will not cache the selected option. Instead, we'll recalculate the
* selected option on every render.
*
* If there're serious performance issues, we'll reconsider caching.
*/
let selectedOption = this.findSelectedOption();
let schema = this.getSchema(selectedOption);
let isReadonly = getKeyword(schema, 'readOnly', 'readonly');
let type = getSchemaType(schema);
let args = this.props.nextArgs ? this.props.nextArgs : this.props.parentArgs;
let rowFunc;
if (type === 'object') {
rowFunc = getObjectFormRow;
if (typeof args.data != 'object' || args.data === null)
args.data = {};
} else if (type === 'array') {
rowFunc = getArrayFormRow;
if (!Array.isArray(args.data))
args.data = [];
} else {
rowFunc = getStringFormRow;
args.removable = false;
args.onMoveUp = null;
args.onMoveDown = null;
if (Array.isArray(args.data) || typeof args.data === 'object')
args.data = null;
}
let rows = rowFunc({...args, schema: schema});
let selectorLabel = null;
if (this.props.nextArgs)
selectorLabel = this.props.nextArgs.schema.title || null;
return (
<div className="rjf-form-group rjf-oneof-group">
<div className="rjf-oneof-selector">
<FormSelectInput
value={selectedOption}
options={this.getOptions()}
onChange={(e) => this.handleOptionChange(e, selectedOption)}
className="rjf-oneof-selector-input"
label={selectorLabel}
readOnly={isReadonly}
/>
</div>
{rows}
</div>
);
}
}
function AnyOf(props) {
return <OneOf {...props} schemaName="anyOf" />;
};
function handleKeyValueAdd(data, coords, onAdd, newSchema, getRef) {
let key = prompt("Add new key");
if (key === null) // clicked cancel
return;
if (newSchema === true)
newSchema = {type: 'string'};
key = key.trim();
if (!key)
alert("(!) Can't add empty key.\r\n\r\n");
else if (data.hasOwnProperty(key))
alert("(!) Duplicate keys not allowed. This key already exists.\r\n\r\n");
else
onAdd(getBlankData(newSchema, getRef), joinCoords(coords, key));
}
function handleKeyEdit(data, key, value, coords, onEdit) {
let newKey = prompt("Rename key", key);
if (newKey === null) // clicked cancel
return;
newKey = newKey.trim();
if (newKey === key) // same keys
return;
if (!newKey)
return alert("(!) Key name can't be empty.\r\n\r\n");
else if (data.hasOwnProperty(newKey))
return alert("(!) Duplicate keys not allowed. This key already exists.\r\n\r\n");
let newCoords = splitCoords(coords);
newCoords.pop();
newCoords.push(newKey);
newCoords = joinCoords.apply(null, newCoords);
onEdit(value, newCoords, coords);
}