diff --git a/README.md b/README.md index d13dd26d99..5dc2daccff 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,29 @@ A [live demo](https://mozilla-services.github.io/react-jsonschema-form/) is host ![](http://i.imgur.com/oxBlg96.png) +## Table of Contents + + - [Installation](#installation) + - [Usage](#usage) + - [Custom labels for enum fields](#custom-labels-for-enum-fields) + - [Alternative widgets](#alternative-widgets) + - [For boolean fields](#for-boolean-fields) + - [For string fields](#for-string-fields) + - [For number and integer fields](#for-number-and-integer-fields) + - [Object fields ordering](#object-fields-ordering) + - [Multiple choices list](#multiple-choices-list) + - [Custom styles](#custom-styles) + - [Custom widgets](#custom-widgets) + - [Custom SchemaField](#custom-schemafield) + - [Custom titles](#custom-titles) + - [Custom buttons](#custom-buttons) + - [Development server](#development-server) + - [Tests](#tests) + - [TDD](#tdd) + - [License](#license) + +--- + ## Installation Requires React 0.14+. @@ -119,19 +142,19 @@ render(( Here's a list of supported alternative widgets for different JSONSchema data types: -#### `boolean`: +#### For `boolean` fields * `radio`: a radio button group with `true` and `false` as selectable values; * `select`: a select box with `true` and `false` as options; * by default, a checkbox is used -#### `string`: +#### For `string` fields * `textarea`: a `textarea` element; * `password`: an `input[type=password]` element; * by default, a regular `input[type=text]` element is used. -#### `number` and `integer`: +#### For `number` and `integer` fields * `updown`: an `input[type=number]` updown selector; * `range`: an `input[type=range]` slider; @@ -221,6 +244,36 @@ const uiSchema = { render(
); ``` +Alternatively, you can register them all at once by passing the `widgets` prop to the `Form` component, and reference their identifier from the `uiSchema`: + +```jsx +const MyCustomWidget = (props) => { + return ( + props.onChange(event.target.value)} /> + ); +}; + +const widgets = { + myCustomWidget: MyCustomWidget +}; + +const uiSchema = { + "ui:widget": "myCustomWidget" +} + +render(); +``` + +This is useful if you expose the `uiSchema` as pure JSON, which can't carry functions. + The following props are passed to the widget component: - `schema`: The JSONSchema subschema object for this field; diff --git a/package.json b/package.json index a9852d50de..754da13875 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.14.0", "description": "A simple React component capable of building HTML forms out of a JSON schema.", "scripts": { + "build:readme": "toctoc README.md -w", "build:css": "cp css/react-jsonschema-form.css dist/", "build:lib": "rimraf lib && babel -d lib/ src/", "build:dist": "rimraf dist && webpack --config webpack.config.dist.js --optimize-minimize", @@ -10,7 +11,7 @@ "dist": "npm run build:lib && npm run build:dist && npm run build:css", "lint": "eslint src test", "publish-to-gh-pages": "npm run build:playground && gh-pages --dist build/", - "publish-to-npm": "npm run dist && npm publish", + "publish-to-npm": "npm run build:readme && npm run dist && npm publish", "start": "node devServer.js", "tdd": "npm run test -- -w", "test": "NODE_ENV=test mocha --compilers js:babel/register --recursive --require ./test/setup-jsdom.js $(find test -name '*_test.js')" @@ -22,8 +23,8 @@ ], "engineStrict": false, "engines": { - "npm": "^2.14.7", - "node": ">=4" + "npm": "^2.14.7", + "node": ">=4" }, "peerDependencies": { "react": "^0.14.3", @@ -56,6 +57,7 @@ "rimraf": "^2.4.4", "sinon": "^1.17.2", "style-loader": "^0.13.0", + "toctoc": "^0.0.6", "webpack": "^1.10.5", "webpack-dev-middleware": "^1.4.0", "webpack-hot-middleware": "^2.6.0" diff --git a/src/components/Form.js b/src/components/Form.js index 98fed2f7ee..fd15052332 100644 --- a/src/components/Form.js +++ b/src/components/Form.js @@ -79,11 +79,12 @@ export default class Form extends Component { return { SchemaField: this.props.SchemaField || SchemaField, TitleField: this.props.TitleField || TitleField, + widgets: this.props.widgets || {}, }; } render() { - const {children, schema, uiSchema} = this.props; + const {children, schema, uiSchema, widgets} = this.props; const {formData} = this.state; const registry = this.getRegistry(); const _SchemaField = registry.SchemaField; @@ -94,6 +95,7 @@ export default class Form extends Component { schema={schema} uiSchema={uiSchema} formData={formData} + widgets={widgets} onChange={this.onChange.bind(this)} registry={registry}/> { children ? children :

} @@ -107,6 +109,7 @@ if (process.env.NODE_ENV !== "production") { schema: PropTypes.object.isRequired, uiSchema: PropTypes.object, formData: PropTypes.any, + widgets: PropTypes.object, onChange: PropTypes.func, onError: PropTypes.func, onSubmit: PropTypes.func, diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js index 837aeea7a1..b231c7e2eb 100644 --- a/src/components/fields/ArrayField.js +++ b/src/components/fields/ArrayField.js @@ -69,7 +69,7 @@ class ArrayField extends Component { const {schema, uiSchema, name} = this.props; const title = schema.title || name; const {items} = this.state; - const SchemaField = this.props.registry.SchemaField; + const {SchemaField} = this.props.registry; if (isMultiSelect(schema)) { return ( ; } return ; diff --git a/src/components/fields/ObjectField.js b/src/components/fields/ObjectField.js index 7b12f047d5..4bcc77512c 100644 --- a/src/components/fields/ObjectField.js +++ b/src/components/fields/ObjectField.js @@ -67,7 +67,7 @@ class ObjectField extends Component { formData={this.state[name]} onChange={this.onChange.bind(this, name)} registry={this.props.registry} /> - ); + ); }) } ); diff --git a/src/components/fields/SchemaField.js b/src/components/fields/SchemaField.js index ffa3f2399a..491accf131 100644 --- a/src/components/fields/SchemaField.js +++ b/src/components/fields/SchemaField.js @@ -98,6 +98,7 @@ if (process.env.NODE_ENV !== "production") { SchemaField.propTypes = { schema: PropTypes.object.isRequired, uiSchema: PropTypes.object, + registry: PropTypes.object, }; } diff --git a/src/components/fields/StringField.js b/src/components/fields/StringField.js index d496a34a93..a58c39c742 100644 --- a/src/components/fields/StringField.js +++ b/src/components/fields/StringField.js @@ -5,7 +5,8 @@ import TextWidget from "./../widgets/TextWidget"; import SelectWidget from "./../widgets/SelectWidget"; -function StringField({schema, name, uiSchema, formData, required, onChange}) { +function StringField(props) { + const {schema, name, uiSchema, formData, widgets, required, onChange} = props; const {title, description} = schema; const widget = uiSchema["ui:widget"]; const commonProps = { @@ -19,13 +20,13 @@ function StringField({schema, name, uiSchema, formData, required, onChange}) { }; if (Array.isArray(schema.enum)) { if (widget) { - const Widget = getAlternativeWidget(schema.type, widget); + const Widget = getAlternativeWidget(schema.type, widget, widgets); return ; } return ; } if (widget) { - const Widget = getAlternativeWidget(schema.type, widget); + const Widget = getAlternativeWidget(schema.type, widget, widgets); return ; } return ; diff --git a/src/utils.js b/src/utils.js index 6ad084ce30..6513f22d38 100644 --- a/src/utils.js +++ b/src/utils.js @@ -43,13 +43,16 @@ export function defaultFieldValue(formData, schema) { return formData === null ? defaultTypeValue(schema.type) : formData; } -export function getAlternativeWidget(type, widget) { +export function getAlternativeWidget(type, widget, registeredWidgets={}) { if (typeof widget === "function") { return widget; } if (typeof widget !== "string") { throw new Error(`Unsupported widget definition: ${typeof widget}`); } + if (widget in registeredWidgets) { + return registeredWidgets[widget]; + } if (!altWidgetMap.hasOwnProperty(type)) { throw new Error(`No alternative widget for type ${type}`); } diff --git a/test/index_test.js b/test/index_test.js index fd31aae7ab..0a49186981 100644 --- a/test/index_test.js +++ b/test/index_test.js @@ -91,6 +91,26 @@ describe("Form", () => { }); }); + describe("Custom widgets", () => { + it("should support custom StringField widgets", () => { + const schema = {type: "string"}; + const uiSchema = {"ui:widget": "foo"}; + const foo = () =>
yo
; + const {node} = createComponent({schema, uiSchema, widgets: {foo}}); + + expect(node.querySelector("#custom").textContent).eql("yo"); + }); + + it("should support custom BooleanField widgets", () => { + const schema = {type: "boolean"}; + const uiSchema = {"ui:widget": "foo"}; + const foo = () =>
yo
; + const {node} = createComponent({schema, uiSchema, widgets: {foo}}); + + expect(node.querySelector("#custom").textContent).eql("yo"); + }); + }); + describe("Object fields ordering", () => { const schema = { type: "object",