Skip to content

Commit e3d7634

Browse files
committed
Major rewrite of some internal parts.
These changes will allow for this library to be used in a node project as well as in the browser. - Create a dedicated class for managing state (EditorState) - Provide some JS APIs to control the form from outside of react - Remove JSONForm function from renderer.js - Rename the Form component to ReactJSONForm - Provide functions for validating schema - Fix #34: Make multiselect widget work in top level arrays
1 parent 15d0b7e commit e3d7634

File tree

9 files changed

+481
-113
lines changed

9 files changed

+481
-113
lines changed

src/components/containers.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import React from 'react';
12
import Button from './buttons';
23

34

src/components/widgets.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import React from 'react';
12
import Button from './buttons';
23
import Icon from './icons';
34

src/editorState.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import {getBlankData, getSyncedData} from './data';
2+
import {validateSchema} from './schemaValidation';
3+
4+
5+
export default class EditorState {
6+
/* Not for public consumption */
7+
constructor(state) {
8+
this.state = state;
9+
}
10+
11+
static create(schema, data) {
12+
/*
13+
schema and data can be either a JSON string or a JS object.
14+
data is optional.
15+
*/
16+
17+
if (typeof schema === 'string')
18+
schema = JSON.parse(schema);
19+
20+
let validation = validateSchema(schema);
21+
22+
if (!validation.isValid)
23+
throw new Error('Error while creating EditorState: Invalid schema: ' + validation.msg);
24+
25+
if (typeof data === 'string' && data !== '')
26+
data = JSON.parse(data);
27+
28+
if (!data) {
29+
// create empty data from schema
30+
data = getBlankData(schema, (ref) => EditorState.getRef(ref, schema));
31+
} else {
32+
// data might be stale if schema has new keys, so add them to data
33+
try {
34+
data = getSyncedData(data, schema, (ref) => EditorState.getRef(ref, schema));
35+
} catch (error) {
36+
console.error("Error while creating EditorState: Schema and data structure don't match");
37+
console.error(error);
38+
}
39+
}
40+
41+
return new EditorState({schema: schema, data: data});
42+
}
43+
44+
static getRef(ref, schema) {
45+
/* Returns schema reference. Nothing to do with React's refs.
46+
47+
This will not normalize keywords, i.e. it won't convert 'keys'
48+
to 'properties', etc. Because what if there's an actual key called
49+
'keys'? Substituting the keywords will lead to unexpected lookup.
50+
51+
*/
52+
53+
let refSchema;
54+
let tokens = ref.split('/');
55+
56+
for (let i = 0; i < tokens.length; i++) {
57+
let token = tokens[i];
58+
59+
if (token === '#')
60+
refSchema = schema;
61+
else
62+
refSchema = refSchema[token];
63+
}
64+
65+
return {...refSchema};
66+
}
67+
68+
static update(editorState, data) {
69+
/* Only for updating data.
70+
For updating schema, create new state.
71+
*/
72+
return new EditorState({...editorState._getState(), data: data});
73+
}
74+
75+
_getState() {
76+
return this.state;
77+
}
78+
79+
getData() {
80+
let state = this._getState();
81+
return state.data;
82+
}
83+
84+
getSchema() {
85+
let state = this._getState();
86+
return state.schema;
87+
}
88+
}

src/form.js

Lines changed: 67 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,10 @@
1-
import {getBlankData, getSyncedData} from './data';
1+
import React from 'react';
22
import {getArrayFormRow, getObjectFormRow} from './ui';
33
import {EditorContext} from './util';
4+
import EditorState from './editorState';
45

56

6-
export default class Form extends React.Component {
7-
constructor(props) {
8-
super(props);
9-
10-
this.dataInput = document.getElementById(this.props.dataInputId);
11-
this.schema = props.schema;
12-
13-
let data = props.data;
14-
15-
if (!data) {
16-
// create empty data from schema
17-
data = getBlankData(this.schema, this.getRef);
18-
} else {
19-
// data might be stale if schema has new keys, so add them to data
20-
try {
21-
data = getSyncedData(data, this.schema, this.getRef);
22-
} catch (error) {
23-
console.error("Error: Schema and data structure don't match");
24-
console.error(error);
25-
}
26-
}
27-
28-
this.state = {
29-
value: '',
30-
data: data
31-
};
32-
33-
// update data in the input
34-
this.populateDataInput();
35-
}
36-
37-
componentDidUpdate(prevProps, prevState) {
38-
if (this.state.data !== prevState.data) {
39-
this.populateDataInput();
40-
}
41-
}
42-
43-
populateDataInput = () => {
44-
this.dataInput.value = JSON.stringify(this.state.data);
45-
}
46-
7+
export default class ReactJSONForm extends React.Component {
478
handleChange = (coords, value) => {
489
/*
4910
e.target.name is a chain of indices and keys:
@@ -57,50 +18,25 @@ export default class Form extends React.Component {
5718

5819
coords.shift(); // remove first coord
5920

60-
function setDataUsingCoords(coords, data, value) {
61-
let coord = coords.shift();
62-
if (!isNaN(Number(coord)))
63-
coord = Number(coord);
21+
// :TODO: use immutable JS instead of JSON-ising the data
22+
let data = setDataUsingCoords(coords, JSON.parse(JSON.stringify(this.props.editorState.getData())), value);
6423

65-
if (coords.length) {
66-
setDataUsingCoords(coords, data[coord], value);
67-
} else {
68-
data[coord] = value;
69-
}
70-
}
71-
72-
let _data = JSON.parse(JSON.stringify(this.state.data));
73-
74-
setDataUsingCoords(coords, _data, value);
75-
76-
this.setState({data: _data});
24+
this.props.onChange(EditorState.update(this.props.editorState, data));
7725
}
7826

7927
getRef = (ref) => {
8028
/* Returns schema reference. Nothing to do with React's refs.*/
8129

82-
let refSchema;
83-
let tokens = ref.split('/');
84-
85-
for (let i = 0; i < tokens.length; i++) {
86-
let token = tokens[i];
87-
88-
if (token === '#')
89-
refSchema = this.schema;
90-
else
91-
refSchema = refSchema[token];
92-
}
93-
94-
95-
return {...refSchema};
30+
return EditorState.getRef(ref, this.props.editorState.getSchema());
9631
}
9732

9833
getFields = () => {
99-
let data = this.state.data;
34+
let data = this.props.editorState.getData();
35+
let schema = this.props.editorState.getSchema();
10036
let formGroups = [];
10137

10238
try {
103-
let type = this.schema.type;
39+
let type = schema.type;
10440

10541
if (type === 'list')
10642
type = 'array';
@@ -109,11 +45,12 @@ export default class Form extends React.Component {
10945

11046
let args = {
11147
data: data,
112-
schema: this.schema,
48+
schema: schema,
11349
name: 'rjf',
11450
onChange: this.handleChange,
11551
onAdd: this.addFieldset,
11652
onRemove: this.removeFieldset,
53+
onEdit: this.editFieldset,
11754
onMove: this.moveFieldset,
11855
level: 0,
11956
getRef: this.getRef,
@@ -125,6 +62,8 @@ export default class Form extends React.Component {
12562
return getObjectFormRow(args);
12663
}
12764
} catch (error) {
65+
console.log(error);
66+
12867
formGroups = (
12968
<p style={{color: '#f00'}}>
13069
<strong>(!) Error:</strong> Schema and data structure do not match.
@@ -139,26 +78,40 @@ export default class Form extends React.Component {
13978
coords = coords.split('-');
14079
coords.shift();
14180

142-
this.setState((state) => {
143-
let _data = JSON.parse(JSON.stringify(state.data));
81+
// :TODO: use immutable JS instead of JSON-ising the data
82+
let data = addDataUsingCoords(coords, JSON.parse(JSON.stringify(this.props.editorState.getData())), blankData);
14483

145-
addDataUsingCoords(coords, _data, blankData);
146-
147-
return {data: _data};
148-
});
84+
this.props.onChange(EditorState.update(this.props.editorState, data));
14985
}
15086

15187
removeFieldset = (coords) => {
15288
coords = coords.split('-');
15389
coords.shift();
90+
91+
// :TODO: use immutable JS instead of JSON-ising the data
92+
let data = removeDataUsingCoords(coords, JSON.parse(JSON.stringify(this.props.editorState.getData())));
93+
94+
this.props.onChange(EditorState.update(this.props.editorState, data));
95+
}
96+
97+
editFieldset = (value, newCoords, oldCoords) => {
98+
/* Add and remove in a single state update
99+
100+
newCoords will be added
101+
oldCoords willbe removed
102+
*/
103+
104+
newCoords = newCoords.split('-');
105+
newCoords.shift();
106+
107+
oldCoords = oldCoords.split('-');
108+
oldCoords.shift();
154109

155-
this.setState((state) => {
156-
let _data = JSON.parse(JSON.stringify(state.data));
110+
let data = addDataUsingCoords(newCoords, JSON.parse(JSON.stringify(this.props.editorState.getData())), value);
157111

158-
removeDataUsingCoords(coords, _data);
112+
data = removeDataUsingCoords(oldCoords, data);
159113

160-
return {data: _data};
161-
});
114+
this.props.onChange(EditorState.update(this.props.editorState, data));
162115
}
163116

164117
moveFieldset = (oldCoords, newCoords) => {
@@ -168,13 +121,10 @@ export default class Form extends React.Component {
168121
newCoords = newCoords.split("-");
169122
newCoords.shift();
170123

171-
this.setState((state) => {
172-
let _data = JSON.parse(JSON.stringify(state.data));
173-
174-
moveDataUsingCoords(oldCoords, newCoords, _data);
124+
// :TODO: use immutable JS instead of JSON-ising the data
125+
let data = moveDataUsingCoords(oldCoords, newCoords, JSON.parse(JSON.stringify(this.props.editorState.getData())));
175126

176-
return {data: _data};
177-
});
127+
this.props.onChange(EditorState.update(this.props.editorState, data));
178128
}
179129

180130
render() {
@@ -196,26 +146,44 @@ export default class Form extends React.Component {
196146
}
197147
}
198148

149+
function setDataUsingCoords(coords, data, value) {
150+
let coord = coords.shift();
151+
152+
if (!isNaN(Number(coord)))
153+
coord = Number(coord);
154+
155+
if (coords.length) {
156+
data[coord] = setDataUsingCoords(coords, data[coord], value);
157+
} else {
158+
if (coord === undefined) // top level array with multiselect widget
159+
data = value;
160+
else
161+
data[coord] = value;
162+
}
163+
164+
return data;
165+
}
199166

200167
function addDataUsingCoords(coords, data, value) {
201168
let coord = coords.shift();
202169
if (!isNaN(Number(coord)))
203170
coord = Number(coord);
204171

205172
if (coords.length) {
206-
addDataUsingCoords(coords, data[coord], value);
173+
data[coord] = addDataUsingCoords(coords, data[coord], value);
207174
} else {
208175
if (Array.isArray(data[coord])) {
209176
data[coord].push(value);
210-
}
211-
else {
177+
} else {
212178
if (Array.isArray(data)) {
213179
data.push(value);
214180
} else {
215181
data[coord] = value;
216182
}
217183
}
218184
}
185+
186+
return data;
219187
}
220188

221189
function removeDataUsingCoords(coords, data) {
@@ -231,6 +199,8 @@ function removeDataUsingCoords(coords, data) {
231199
else
232200
delete data[coord];
233201
}
202+
203+
return data;
234204
}
235205

236206

@@ -257,4 +227,6 @@ function moveDataUsingCoords(oldCoords, newCoords, data) {
257227
data.splice(newCoord, 0, item);
258228
}
259229
}
230+
231+
return data;
260232
}

src/index.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
import JSONForm from './renderer';
1+
import ReactJSONForm from './form.js';
2+
import EditorState from './editorState.js';
3+
import {createForm, getFormInstance} from './renderer.js';
24

35

4-
export default {
5-
JSONForm,
6+
export {
7+
ReactJSONForm,
8+
EditorState,
9+
createForm,
10+
getFormInstance,
611
};

0 commit comments

Comments
 (0)