From 6028edd5dcbdea21a7d8d452bbb9fec011489338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Wed, 6 Jan 2016 18:26:42 +0100 Subject: [PATCH 1/4] Attempt to have a dynamic SchemaField component --- src/components/Form.js | 16 ++++++++++++++-- src/components/fields/ArrayField.js | 10 ++++++++-- src/components/fields/ObjectField.js | 6 +++++- src/utils.js | 8 ++++++++ test/index_test.js | 2 +- 5 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/components/Form.js b/src/components/Form.js index 4298769702..8916927f9b 100644 --- a/src/components/Form.js +++ b/src/components/Form.js @@ -1,8 +1,7 @@ import React, { Component, PropTypes } from "react"; import { Validator } from "jsonschema"; -import { getDefaultFormState } from "../utils"; -import SchemaField from "./fields/SchemaField"; +import { getDefaultFormState, getSchemaField } from "../utils"; import ErrorList from "./ErrorList"; @@ -31,6 +30,13 @@ export default class Form extends Component { }; } + getChildContext() { + const SchemaField = getSchemaField(this.props); + return { + schemaField: SchemaField + }; + } + validate(formData) { const validator = new Validator(); return validator.validate(formData, this.props.schema).errors; @@ -78,6 +84,8 @@ export default class Form extends Component { render() { const {schema, uiSchema} = this.props; const {formData} = this.state; + const SchemaField = getSchemaField(this.props, this.context); + console.log("THIS IS FORM", SchemaField); return (
{this.renderErrors()} @@ -103,4 +111,8 @@ if (process.env.NODE_ENV !== "production") { }; } +Form.childContextTypes = { + schemaField: PropTypes.oneOfType([Component, Function]) +}; + export default Form; diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js index 9b795b8ea0..06615f4064 100644 --- a/src/components/fields/ArrayField.js +++ b/src/components/fields/ArrayField.js @@ -1,7 +1,6 @@ import React, { Component, PropTypes } from "react"; import { getDefaultFormState } from "../../utils"; -import SchemaField from "./SchemaField"; class ArrayField extends Component { @@ -65,6 +64,8 @@ class ArrayField extends Component { const {schema, uiSchema, name} = this.props; const title = schema.title || name; const {items} = this.state; + const SchemaField = this.context.schemaField; + console.log("In ArrayField, got", SchemaField); return (
@@ -80,7 +81,8 @@ class ArrayField extends Component { uiSchema={uiSchema.items} formData={items[index]} required={this.isItemRequired(schema.items)} - onChange={this.onChange.bind(this, index)} /> + onChange={this.onChange.bind(this, index)} + schemaField={SchemaField}/>

@@ -105,4 +107,8 @@ if (process.env.NODE_ENV !== "production") { }; } +ArrayField.childContextTypes = { + schemaField: PropTypes.oneOfType([Component, Function]) +}; + export default ArrayField; diff --git a/src/components/fields/ObjectField.js b/src/components/fields/ObjectField.js index 3558890669..3b533eccd7 100644 --- a/src/components/fields/ObjectField.js +++ b/src/components/fields/ObjectField.js @@ -1,7 +1,6 @@ import React, { Component, PropTypes } from "react"; import { getDefaultFormState } from "../../utils"; -import SchemaField from "./SchemaField"; class ObjectField extends Component { @@ -40,6 +39,7 @@ class ObjectField extends Component { render() { const {schema, uiSchema, name} = this.props; const title = name || schema.title; + const SchemaField = this.context.schemaField; return (
{title ? {title} : null} @@ -72,4 +72,8 @@ if (process.env.NODE_ENV !== "production") { }; } +ObjectField.childContextTypes = { + schemaField: PropTypes.oneOfType([Component, Function]) +}; + export default ObjectField; diff --git a/src/utils.js b/src/utils.js index 94d2759201..08a6981134 100644 --- a/src/utils.js +++ b/src/utils.js @@ -4,6 +4,7 @@ import UpDownWidget from "./components/widgets/UpDownWidget"; import RangeWidget from "./components/widgets/RangeWidget"; import SelectWidget from "./components/widgets/SelectWidget"; import TextareaWidget from "./components/widgets/TextareaWidget"; +import SchemaField from "./components/fields/SchemaField"; const altWidgetMap = { @@ -80,3 +81,10 @@ export function asNumber(value) { const valid = typeof n === "number" && !Number.isNaN(n); return valid ? n : value; } + +export function getSchemaField(props, context) { + if (props == undefined) { + return SchemaField; + } + return props.hasOwnProperty("schemaField") ? props.schemaField : SchemaField; +} diff --git a/test/index_test.js b/test/index_test.js index 10e48c18de..cc9562ffb6 100644 --- a/test/index_test.js +++ b/test/index_test.js @@ -1252,7 +1252,7 @@ describe("Form", () => { }); describe("array level", () => { - it("should update form state from new formData prop value", () => { + it.only("should update form state from new formData prop value", () => { const schema = { type: "array", items: { From 6a645823c628a459b7a7826bbe63c53529bfd3f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Thu, 7 Jan 2016 11:09:57 +0100 Subject: [PATCH 2/4] Added support for custom schema types. In the previous commit, we tried to use the context concept from React and finally decided to pass it directly via the props as it seems clearer. This commit generates a warning when running the tests. Needs to be investigated. --- src/components/Form.js | 25 +++++++------------------ src/components/fields/ArrayField.js | 10 +++------- src/components/fields/ObjectField.js | 10 ++++------ src/utils.js | 8 -------- test/index_test.js | 21 ++++++++++++++++++++- 5 files changed, 34 insertions(+), 40 deletions(-) diff --git a/src/components/Form.js b/src/components/Form.js index 8916927f9b..615a76728e 100644 --- a/src/components/Form.js +++ b/src/components/Form.js @@ -1,10 +1,9 @@ import React, { Component, PropTypes } from "react"; import { Validator } from "jsonschema"; - -import { getDefaultFormState, getSchemaField } from "../utils"; +import SchemaField from "./fields/SchemaField"; +import { getDefaultFormState } from "../utils"; import ErrorList from "./ErrorList"; - export default class Form extends Component { static defaultProps = { uiSchema: {} @@ -30,13 +29,6 @@ export default class Form extends Component { }; } - getChildContext() { - const SchemaField = getSchemaField(this.props); - return { - schemaField: SchemaField - }; - } - validate(formData) { const validator = new Validator(); return validator.validate(formData, this.props.schema).errors; @@ -84,16 +76,16 @@ export default class Form extends Component { render() { const {schema, uiSchema} = this.props; const {formData} = this.state; - const SchemaField = getSchemaField(this.props, this.context); - console.log("THIS IS FORM", SchemaField); + const _SchemaField = this.props.SchemaField || SchemaField; return ( {this.renderErrors()} - + onChange={this.onChange.bind(this)} + SchemaField={_SchemaField}/>

); @@ -108,11 +100,8 @@ if (process.env.NODE_ENV !== "production") { onChange: PropTypes.func, onError: PropTypes.func, onSubmit: PropTypes.func, + SchemaField: PropTypes.oneOfType([Component, PropTypes.func]) }; } -Form.childContextTypes = { - schemaField: PropTypes.oneOfType([Component, Function]) -}; - export default Form; diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js index 06615f4064..b5c635aca1 100644 --- a/src/components/fields/ArrayField.js +++ b/src/components/fields/ArrayField.js @@ -64,8 +64,7 @@ class ArrayField extends Component { const {schema, uiSchema, name} = this.props; const title = schema.title || name; const {items} = this.state; - const SchemaField = this.context.schemaField; - console.log("In ArrayField, got", SchemaField); + const SchemaField = this.props.SchemaField; return (
@@ -82,7 +81,7 @@ class ArrayField extends Component { formData={items[index]} required={this.isItemRequired(schema.items)} onChange={this.onChange.bind(this, index)} - schemaField={SchemaField}/> + SchemaField={SchemaField}/>

@@ -104,11 +103,8 @@ if (process.env.NODE_ENV !== "production") { uiSchema: PropTypes.object, onChange: PropTypes.func.isRequired, formData: PropTypes.array, + SchemaField: PropTypes.oneOfType([Component, PropTypes.func]).isRequired }; } -ArrayField.childContextTypes = { - schemaField: PropTypes.oneOfType([Component, Function]) -}; - export default ArrayField; diff --git a/src/components/fields/ObjectField.js b/src/components/fields/ObjectField.js index 3b533eccd7..507f4d9431 100644 --- a/src/components/fields/ObjectField.js +++ b/src/components/fields/ObjectField.js @@ -39,7 +39,7 @@ class ObjectField extends Component { render() { const {schema, uiSchema, name} = this.props; const title = name || schema.title; - const SchemaField = this.context.schemaField; + const SchemaField = this.props.SchemaField; return (
{title ? {title} : null} @@ -54,7 +54,8 @@ class ObjectField extends Component { schema={schema.properties[name]} uiSchema={uiSchema[name]} formData={this.state[name]} - onChange={this.onChange.bind(this, name)} /> + onChange={this.onChange.bind(this, name)} + SchemaField={SchemaField}/> ); }) }
@@ -69,11 +70,8 @@ if (process.env.NODE_ENV !== "production") { onChange: PropTypes.func.isRequired, formData: PropTypes.object, required: PropTypes.bool, + SchemaField: PropTypes.oneOfType([Component, PropTypes.func]).isRequired }; } -ObjectField.childContextTypes = { - schemaField: PropTypes.oneOfType([Component, Function]) -}; - export default ObjectField; diff --git a/src/utils.js b/src/utils.js index 08a6981134..94d2759201 100644 --- a/src/utils.js +++ b/src/utils.js @@ -4,7 +4,6 @@ import UpDownWidget from "./components/widgets/UpDownWidget"; import RangeWidget from "./components/widgets/RangeWidget"; import SelectWidget from "./components/widgets/SelectWidget"; import TextareaWidget from "./components/widgets/TextareaWidget"; -import SchemaField from "./components/fields/SchemaField"; const altWidgetMap = { @@ -81,10 +80,3 @@ export function asNumber(value) { const valid = typeof n === "number" && !Number.isNaN(n); return valid ? n : value; } - -export function getSchemaField(props, context) { - if (props == undefined) { - return SchemaField; - } - return props.hasOwnProperty("schemaField") ? props.schemaField : SchemaField; -} diff --git a/test/index_test.js b/test/index_test.js index cc9562ffb6..2570c386be 100644 --- a/test/index_test.js +++ b/test/index_test.js @@ -5,6 +5,7 @@ import sinon from "sinon"; import React from "react"; import { Simulate, renderIntoDocument } from "react-addons-test-utils"; import { findDOMNode } from "react-dom"; +import SchemaField from "../src/components/fields/SchemaField"; import Form from "../src"; @@ -44,6 +45,24 @@ describe("Form", () => { }); }); + describe("Custom SchemaField", () => { + const CustomSchemaField = function(props) { + return (
); + }; + + it("should use the specified custom SchemaType property", () => { + const {node} = createComponent({ + schema: {type: "string"}, + SchemaField: CustomSchemaField + }); + + expect(node.querySelectorAll("#custom>.field input[type=text]")) + .to.have.length.of(1); + + }); + + }); + describe("StringField", () => { describe("TextWidget", () => { it("should render a string field", () => { @@ -1252,7 +1271,7 @@ describe("Form", () => { }); describe("array level", () => { - it.only("should update form state from new formData prop value", () => { + it("should update form state from new formData prop value", () => { const schema = { type: "array", items: { From 0f5e93b55c06494058ceab8754fd31224ec0ed86 Mon Sep 17 00:00:00 2001 From: Nicolas Perriault Date: Fri, 8 Jan 2016 09:14:00 +0100 Subject: [PATCH 3/4] Fixed propTypes for SchemaField prop. --- src/components/Form.js | 2 +- src/components/fields/ArrayField.js | 2 +- src/components/fields/BooleanField.js | 4 ++-- src/components/fields/ObjectField.js | 2 +- test/index_test.js | 4 +--- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/Form.js b/src/components/Form.js index 615a76728e..0df9cac4d6 100644 --- a/src/components/Form.js +++ b/src/components/Form.js @@ -100,7 +100,7 @@ if (process.env.NODE_ENV !== "production") { onChange: PropTypes.func, onError: PropTypes.func, onSubmit: PropTypes.func, - SchemaField: PropTypes.oneOfType([Component, PropTypes.func]) + SchemaField: PropTypes.func, }; } diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js index b5c635aca1..67b02ca07f 100644 --- a/src/components/fields/ArrayField.js +++ b/src/components/fields/ArrayField.js @@ -103,7 +103,7 @@ if (process.env.NODE_ENV !== "production") { uiSchema: PropTypes.object, onChange: PropTypes.func.isRequired, formData: PropTypes.array, - SchemaField: PropTypes.oneOfType([Component, PropTypes.func]).isRequired + SchemaField: PropTypes.func.isRequired, }; } diff --git a/src/components/fields/BooleanField.js b/src/components/fields/BooleanField.js index 74062e55c8..8a20ed7292 100644 --- a/src/components/fields/BooleanField.js +++ b/src/components/fields/BooleanField.js @@ -1,7 +1,7 @@ import React, { PropTypes } from "react"; import { defaultFieldValue, getAlternativeWidget } from "../../utils"; -import CheckboxField from "./../widgets/CheckboxWidget"; +import CheckboxWidget from "./../widgets/CheckboxWidget"; function BooleanField({schema, name, uiSchema, formData, required, onChange}) { const {title, description} = schema; @@ -19,7 +19,7 @@ function BooleanField({schema, name, uiSchema, formData, required, onChange}) { const Widget = getAlternativeWidget(schema.type, widget); return ; } - return ; + return ; } if (process.env.NODE_ENV !== "production") { diff --git a/src/components/fields/ObjectField.js b/src/components/fields/ObjectField.js index 507f4d9431..1db56f55b9 100644 --- a/src/components/fields/ObjectField.js +++ b/src/components/fields/ObjectField.js @@ -70,7 +70,7 @@ if (process.env.NODE_ENV !== "production") { onChange: PropTypes.func.isRequired, formData: PropTypes.object, required: PropTypes.bool, - SchemaField: PropTypes.oneOfType([Component, PropTypes.func]).isRequired + SchemaField: PropTypes.func.isRequired, }; } diff --git a/test/index_test.js b/test/index_test.js index 2570c386be..85f3c6fa32 100644 --- a/test/index_test.js +++ b/test/index_test.js @@ -56,11 +56,9 @@ describe("Form", () => { SchemaField: CustomSchemaField }); - expect(node.querySelectorAll("#custom>.field input[type=text]")) + expect(node.querySelectorAll("#custom > .field input[type=text]")) .to.have.length.of(1); - }); - }); describe("StringField", () => { From 556032c634fd8a5c6ba725f08dc900e25bc10d6c Mon Sep 17 00:00:00 2001 From: Nicolas Perriault Date: Sat, 9 Jan 2016 10:41:56 +0100 Subject: [PATCH 4/4] Updated README. --- README.md | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2e4c652844..f348979bd5 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ A default, very basic CSS stylesheet is provided, though you're encouraged to bu ## Usage -```js +```jsx import React, { Component } from "react"; import { render } from "react-dom"; @@ -79,7 +79,7 @@ JSONSchema is limited for describing how a given data type should be rendered as Example: -```js +```jsx const uiSchema =  { done: { widget: "radio" // could also be "select" @@ -119,7 +119,7 @@ Here's a list of supported alternative widgets for different JSONSchema data typ The UISchema object accepts a `classNames` property for each field of the schema: -```js +```jsx const uiSchema = { title: { classNames: "task-title foo-bar" @@ -148,7 +148,7 @@ You can provide your own custom widgets to a uiSchema for the following json dat - `boolean` - `date-time` -```js +```jsx const schema = { type: "string" }; @@ -179,6 +179,36 @@ The following props are passed to the widget component: - `placeholder`: The placeholder value, if any; - `options`: The list of options for `enum` fields; +## Custom SchemaField + +**Warning:** This is a powerful feature as you can override the whole form behavior and easily mess it up. Handle with care. + +You can provide your own implementation of the `SchemaField` base React component for rendering any JSONSchema field type, including objects and arrays. This is useful when you want to augment a given field type with supplementary powers. + +To proceed so, you can pass a `SchemaField` prop to the `Form` component instance; here's a rather silly example wrapping the standard `SchemaField` lib component: + +```jsx +import SchemaField from "react-jsonschema-form/lib/components/fields/SchemaField"; + +const CustomSchemaField = function(props) { + return ( +
+

Yeah, I'm pretty dumb.

+ +
+ ); +}; + +render(( +
+), document.getElementById("app")); +``` + +If you're curious how this could ever be useful, have a look at the [Kinto formbuilder](https://github.com/Kinto/formbuilder) repository to see how it's used to provide editing capabilities to any form field. + ## Development server ```