diff --git a/README.md b/README.md index aeb3048c08..02b5e85981 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ A [live playground](https://mozilla-services.github.io/react-jsonschema-form/) i - [Form error event handler](#form-error-event-handler) - [Form data changes](#form-data-changes) - [Form field blur events](#form-field-blur-events) + - [Form field focus events](#form-field-focus-events) - [Form customization](#form-customization) - [The uiSchema object](#the-uischema-object) - [Alternative widgets](#alternative-widgets) @@ -42,7 +43,8 @@ A [live playground](https://mozilla-services.github.io/react-jsonschema-form/) i - [removable option](#removable-option) - [Custom CSS class names](#custom-css-class-names) - [Custom labels for enum fields](#custom-labels-for-enum-fields) - - [Disabled enum fields](#disabled-attribute-for-enum-fields) + - [Alternative JSON-Schema compliant approach](#alternative-json-schema-compliant-approach) + - [Disabled attribute for enum fields](#disabled-attribute-for-enum-fields) - [Multiple choices list](#multiple-choices-list) - [Autogenerated widget ids](#autogenerated-widget-ids) - [Form action buttons](#form-action-buttons) @@ -60,11 +62,12 @@ A [live playground](https://mozilla-services.github.io/react-jsonschema-form/) i - [Array Field Template](#array-field-template) - [Object Field Template](#object-field-template) - [Error List template](#error-list-template) + - [Id prefix](#id-prefix) - [Custom widgets and fields](#custom-widgets-and-fields) - [Custom widget components](#custom-widget-components) - [Custom component registration](#custom-component-registration) - [Custom widget options](#custom-widget-options) - - [Customizing widgets' text input](#customizing-widgets-text-input) + - [Customizing widgets text input](#customizing-widgets-text-input) - [Custom field components](#custom-field-components) - [Field props](#field-props) - [The registry object](#the-registry-object) @@ -84,11 +87,11 @@ A [live playground](https://mozilla-services.github.io/react-jsonschema-form/) i - [Styling your forms](#styling-your-forms) - [Schema definitions and references](#schema-definitions-and-references) - [Property dependencies](#property-dependencies) - - [Unidirectional](#unidirectional) - - [Bidirectional](#bidirectional) + - [Unidirectional](#unidirectional) + - [Bidirectional](#bidirectional) - [Schema dependencies](#schema-dependencies) - - [Conditional](#conditional) - - [Dynamic](#dynamic) + - [Conditional](#conditional) + - [Dynamic](#dynamic) - [JSON Schema supporting status](#json-schema-supporting-status) - [Tips and tricks](#tips-and-tricks) - [Contributing](#contributing) @@ -96,7 +99,10 @@ A [live playground](https://mozilla-services.github.io/react-jsonschema-form/) i - [Development server](#development-server) - [Tests](#tests) - [TDD](#tdd) + - [Releasing](#releasing) - [FAQ](#faq) + - [Q: Does rjsf support oneOf, anyOf, multiple types in an array, etc.?](#q-does-rjsf-support-oneof-anyof-multiple-types-in-an-array-etc) + - [Q: Will react-jsonschema-form support Material, Ant-Design, Foundation, or [some other specific widget library or frontend style]?](#q-will-react-jsonschema-form-support-material-ant-design-foundation-or-some-other-specific-widget-library-or-frontend-style) - [License](#license) --- @@ -1184,7 +1190,7 @@ render(( > Note: Since v0.41.0, the `ui:widget` object API, where a widget and options were specified with `"ui:widget": {component, options}` shape, is deprecated. It will be removed in a future release. -#### Customizing widgets' text input +#### Customizing widgets text input All the widgets that render a text input use the `BaseInput` component internally. If you need to customize all text inputs without customizing all widgets individially, you can provide a `BaseInput` component in the `widgets` property of `Form` (see [Custom component registration](#custom-component-registration). diff --git a/package.json b/package.json index c4f1c74cd2..0137af3c36 100644 --- a/package.json +++ b/package.json @@ -8,17 +8,18 @@ "build:dist": "rimraf dist && cross-env NODE_ENV=production webpack --config webpack.config.dist.js --optimize-minimize", "build:playground": "rimraf build && cross-env NODE_ENV=production webpack --config webpack.config.prod.js --optimize-minimize && cp playground/index.prod.html build/index.html", "cs-check": "prettier -l $npm_package_prettierOptions '{playground,src,test}/**/*.js'", - "cs-format": "prettier $npm_package_prettierOptions '{playground,src,test}/**/*.js' --write", + "cs-format": "prettier --jsx-bracket-same-line --trailing-comma es5 --use-tabs false --semi --tab-width 2 '{playground,src,test}/**/*.js' --write", "dist": "npm run build:lib && npm run build:dist", "lint": "eslint src test playground", "precommit": "lint-staged", "publish-to-gh-pages": "npm run build:playground && gh-pages --dist build/", "publish-to-npm": "npm run build:readme && npm run dist && npm publish", + "preversion": "npm run build:playground && npm run dist && npm run build:readme && npm run cs-check && npm run lint", "start": "node devServer.js", "tdd": "cross-env NODE_ENV=test mocha --compilers js:babel-register --watch --require ./test/setup-jsdom.js test/**/*_test.js", "test": "cross-env NODE_ENV=test mocha --compilers js:babel-register --require ./test/setup-jsdom.js test/**/*_test.js" }, - "prettierOptions": "--jsx-bracket-same-line --trailing-comma es5 --semi", + "prettierOptions": "--jsx-bracket-same-line --trailing-comma es5 --semi --tab-width 2", "lint-staged": { "{playground,src,test}/**/*.js": [ "npm run lint", diff --git a/src/components/Form.js b/src/components/Form.js index e32a7b239c..c1b053786e 100644 --- a/src/components/Form.js +++ b/src/components/Form.js @@ -173,6 +173,7 @@ export default class Form extends Component { children, safeRenderCompletion, id, + idPrefix, className, name, method, @@ -207,6 +208,7 @@ export default class Form extends Component { uiSchema={uiSchema} errorSchema={errorSchema} idSchema={idSchema} + idPrefix={idPrefix} formData={formData} onChange={this.onChange} onBlur={this.onBlur} diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js index 94fbabac6d..dd71f85642 100644 --- a/src/components/fields/ArrayField.js +++ b/src/components/fields/ArrayField.js @@ -64,7 +64,10 @@ function DefaultArrayItem(props) {
+ style={{ + display: "flex", + justifyContent: "space-around", + }}> {(props.hasMoveUp || props.hasMoveDown) && ( { }, }, }; - let form = createFormComponent({ schema: complexSchema, formData: {} }); + let form = createFormComponent({ + schema: complexSchema, + formData: {}, + }); let inputs = form.node.querySelectorAll("input[type=text]"); expect(inputs[0].value).eql("Default name"); expect(inputs[1].value).eql("Default name"); @@ -726,7 +729,10 @@ describe("ArrayField", () => { }); it("should handle a change event", () => { - const { comp, node } = createFormComponent({ schema, uiSchema }); + const { comp, node } = createFormComponent({ + schema, + uiSchema, + }); Simulate.change(node.querySelectorAll("[type=checkbox]")[0], { target: { checked: true }, @@ -1058,7 +1064,10 @@ describe("ArrayField", () => { }); it("should fill fields with data", () => { - const { node } = createFormComponent({ schema, formData: ["foo", 42] }); + const { node } = createFormComponent({ + schema, + formData: ["foo", 42], + }); const strInput = node.querySelector( "fieldset .field-string input[type=text]" ); diff --git a/test/Form_test.js b/test/Form_test.js index ec26583948..fd84eea2cf 100644 --- a/test/Form_test.js +++ b/test/Form_test.js @@ -68,6 +68,85 @@ describe("Form", () => { }); }); + describe("Changing idPrefix", function() { + it("should work with simple example", function() { + const schema = { + type: "object", + title: "root object", + required: ["foo"], + properties: { + count: { + type: "number", + }, + }, + }; + const comp = renderIntoDocument(
); + const node = findDOMNode(comp); + const inputs = node.querySelectorAll("input"); + const ids = []; + for (var i = 0, len = inputs.length; i < len; i++) { + const input = inputs[i]; + ids.push(input.getAttribute("id")); + } + expect(ids).to.eql(["rjsf_count"]); + }); + + it("should work with oneOf", function() { + const schema = { + $schema: "http://json-schema.org/draft-06/schema#", + type: "object", + properties: { + connector: { + type: "string", + enum: ["aws", "gcp"], + title: "Provider", + default: "aws", + }, + }, + dependencies: { + connector: { + oneOf: [ + { + type: "object", + properties: { + connector: { + type: "string", + enum: ["aws"], + }, + key_aws: { + type: "string", + }, + }, + }, + { + type: "object", + properties: { + connector: { + type: "string", + enum: ["gcp"], + }, + key_gcp: { + type: "string", + }, + }, + }, + ], + }, + }, + }; + + const comp = renderIntoDocument(); + const node = findDOMNode(comp); + const inputs = node.querySelectorAll("input"); + const ids = []; + for (var i = 0, len = inputs.length; i < len; i++) { + const input = inputs[i]; + ids.push(input.getAttribute("id")); + } + expect(ids).to.eql(["rjsf_key_aws"]); + }); + }); + describe("Custom field template", () => { const schema = { type: "object", @@ -592,7 +671,11 @@ describe("Form", () => { foo: "", }; const onChange = sandbox.spy(); - const { node } = createFormComponent({ schema, formData, onChange }); + const { node } = createFormComponent({ + schema, + formData, + onChange, + }); Simulate.change(node.querySelector("[type=text]"), { target: { value: "new" }, @@ -835,7 +918,10 @@ describe("Form", () => { }); it("should denote the new error in the field", () => { - const { node } = createFormComponent({ schema, liveValidate: true }); + const { node } = createFormComponent({ + schema, + liveValidate: true, + }); Simulate.change(node.querySelector("input[type=text]"), { target: { value: "short" }, @@ -1056,7 +1142,9 @@ describe("Form", () => { const { comp } = createFormComponent(formProps); expect(comp.state.errorSchema).eql({ - 1: { __errors: ["should NOT be shorter than 4 characters"] }, + 1: { + __errors: ["should NOT be shorter than 4 characters"], + }, }); }); @@ -1108,8 +1196,12 @@ describe("Form", () => { expect(comp.state.errorSchema).eql({ level1: { - 1: { __errors: ["should NOT be shorter than 4 characters"] }, - 3: { __errors: ["should NOT be shorter than 4 characters"] }, + 1: { + __errors: ["should NOT be shorter than 4 characters"], + }, + 3: { + __errors: ["should NOT be shorter than 4 characters"], + }, }, }); }); @@ -1158,10 +1250,14 @@ describe("Form", () => { expect(comp.state.errorSchema).eql({ outer: { 0: { - 1: { __errors: ["should NOT be shorter than 4 characters"] }, + 1: { + __errors: ["should NOT be shorter than 4 characters"], + }, }, 1: { - 0: { __errors: ["should NOT be shorter than 4 characters"] }, + 0: { + __errors: ["should NOT be shorter than 4 characters"], + }, }, }, }); diff --git a/test/NumberField_test.js b/test/NumberField_test.js index b5eee0fe3a..f1f5fd4824 100644 --- a/test/NumberField_test.js +++ b/test/NumberField_test.js @@ -51,7 +51,9 @@ describe("NumberField", () => { }); it("should default state value to undefined", () => { - const { comp } = createFormComponent({ schema: { type: "number" } }); + const { comp } = createFormComponent({ + schema: { type: "number" }, + }); expect(comp.state.formData).eql(undefined); }); diff --git a/test/SchemaField_test.js b/test/SchemaField_test.js index 6376b4fd51..fa503d767e 100644 --- a/test/SchemaField_test.js +++ b/test/SchemaField_test.js @@ -22,7 +22,9 @@ describe("SchemaField", () => { describe("Unsupported field", () => { it("should warn on invalid field type", () => { - const { node } = createFormComponent({ schema: { type: "invalid" } }); + const { node } = createFormComponent({ + schema: { type: "invalid" }, + }); expect(node.querySelector(".unsupported-field").textContent).to.contain( "Unknown field type invalid" @@ -240,7 +242,9 @@ describe("SchemaField", () => { }, }, }; - const { node } = createFormComponent({ schema: schemaWithReference }); + const { node } = createFormComponent({ + schema: schemaWithReference, + }); const matches = node.querySelectorAll("#root_foo__description"); expect(matches).to.have.length.of(1); @@ -301,7 +305,11 @@ describe("SchemaField", () => { } it("should render it's own errors", () => { - const { node } = createFormComponent({ schema, uiSchema, validate }); + const { node } = createFormComponent({ + schema, + uiSchema, + validate, + }); submit(node); const matches = node.querySelectorAll( @@ -312,7 +320,11 @@ describe("SchemaField", () => { }); it("should pass errors to child component", () => { - const { node } = createFormComponent({ schema, uiSchema, validate }); + const { node } = createFormComponent({ + schema, + uiSchema, + validate, + }); submit(node); const matches = node.querySelectorAll( diff --git a/test/StringField_test.js b/test/StringField_test.js index af02df60ba..8bb9d2b079 100644 --- a/test/StringField_test.js +++ b/test/StringField_test.js @@ -66,7 +66,9 @@ describe("StringField", () => { }); it("should default state value to undefined", () => { - const { comp } = createFormComponent({ schema: { type: "string" } }); + const { comp } = createFormComponent({ + schema: { type: "string" }, + }); expect(comp.state.formData).eql(undefined); }); @@ -368,7 +370,10 @@ describe("StringField", () => { it("should handle an empty string change event with custom ui:defaultValue", () => { const { comp, node } = createFormComponent({ schema: { type: "string" }, - uiSchema: { "ui:widget": "textarea", "ui:emptyValue": "default" }, + uiSchema: { + "ui:widget": "textarea", + "ui:emptyValue": "default", + }, formData: "x", }); @@ -1535,7 +1540,9 @@ describe("StringField", () => { }); Simulate.change(node.querySelector("[type=file]"), { - target: { files: [{ name: "file1.txt", size: 1, type: "type" }] }, + target: { + files: [{ name: "file1.txt", size: 1, type: "type" }], + }, }); return new Promise(setImmediate).then(() => diff --git a/test/uiSchema_test.js b/test/uiSchema_test.js index 68d8123eb4..01848d32c8 100644 --- a/test/uiSchema_test.js +++ b/test/uiSchema_test.js @@ -200,7 +200,11 @@ describe("uiSchema", () => { }); it("should render merged ui:widget options for widget referenced as function", () => { - const { node } = createFormComponent({ schema, uiSchema, widgets }); + const { node } = createFormComponent({ + schema, + uiSchema, + widgets, + }); const widget = node.querySelector("#funcAll"); expect(widget.style.background).to.equal("purple"); @@ -210,7 +214,11 @@ describe("uiSchema", () => { }); it("should render ui:widget default options for widget referenced as function", () => { - const { node } = createFormComponent({ schema, uiSchema, widgets }); + const { node } = createFormComponent({ + schema, + uiSchema, + widgets, + }); const widget = node.querySelector("#funcNone"); expect(widget.style.background).to.equal("yellow"); @@ -220,7 +228,11 @@ describe("uiSchema", () => { }); it("should render merged ui:widget options for widget referenced as string", () => { - const { node } = createFormComponent({ schema, uiSchema, widgets }); + const { node } = createFormComponent({ + schema, + uiSchema, + widgets, + }); const widget = node.querySelector("#stringAll"); expect(widget.style.background).to.equal("blue"); @@ -230,7 +242,11 @@ describe("uiSchema", () => { }); it("should render ui:widget default options for widget referenced as string", () => { - const { node } = createFormComponent({ schema, uiSchema, widgets }); + const { node } = createFormComponent({ + schema, + uiSchema, + widgets, + }); const widget = node.querySelector("#stringNone"); expect(widget.style.background).to.equal("yellow"); @@ -240,7 +256,11 @@ describe("uiSchema", () => { }); it("should ui:option inputType for html5 input types", () => { - const { node } = createFormComponent({ schema, uiSchema, widgets }); + const { node } = createFormComponent({ + schema, + uiSchema, + widgets, + }); const widget = node.querySelector("input[type='tel']"); expect(widget).to.not.be.null; }); @@ -280,7 +300,11 @@ describe("uiSchema", () => { }; it("should render a nested custom widget", () => { - const { node } = createFormComponent({ schema, uiSchema, widgets }); + const { node } = createFormComponent({ + schema, + uiSchema, + widgets, + }); expect(node.querySelectorAll(".custom")).to.have.length.of(1); }); @@ -335,7 +359,11 @@ describe("uiSchema", () => { }; it("should render a custom widget with options", () => { - const { node } = createFormComponent({ schema, uiSchema, widgets }); + const { node } = createFormComponent({ + schema, + uiSchema, + widgets, + }); expect(node.querySelectorAll(".custom")).to.have.length.of(1); }); @@ -1766,7 +1794,11 @@ describe("uiSchema", () => { }; const formData = ["a", "b"]; - let rendered = createFormComponent({ schema, uiSchema, formData }); + let rendered = createFormComponent({ + schema, + uiSchema, + formData, + }); node = rendered.node; }); @@ -2050,7 +2082,11 @@ describe("uiSchema", () => { }; const formData = ["a", "b"]; - let rendered = createFormComponent({ schema, uiSchema, formData }); + let rendered = createFormComponent({ + schema, + uiSchema, + formData, + }); node = rendered.node; }); diff --git a/test/utils_test.js b/test/utils_test.js index 1e3b0141e3..2ada075147 100644 --- a/test/utils_test.js +++ b/test/utils_test.js @@ -145,7 +145,14 @@ describe("utils", () => { properties: { level1: { type: "object", - default: { level2: { leaf1: 1, leaf2: 1, leaf3: 1, leaf4: 1 } }, + default: { + level2: { + leaf1: 1, + leaf2: 1, + leaf3: 1, + leaf4: 1, + }, + }, properties: { level2: { type: "object", @@ -166,9 +173,13 @@ describe("utils", () => { }, }; expect( - getDefaultFormState(schema, { level1: { level2: { leaf4: 4 } } }) + getDefaultFormState(schema, { + level1: { level2: { leaf4: 4 } }, + }) ).eql({ - level1: { level2: { leaf1: 1, leaf2: 2, leaf3: 3, leaf4: 4 } }, + level1: { + level2: { leaf1: 1, leaf2: 2, leaf3: 3, leaf4: 4 }, + }, }); }); @@ -183,7 +194,9 @@ describe("utils", () => { }, }, }; - expect(getDefaultFormState(schema, {})).eql({ level1: [1, 2, 3] }); + expect(getDefaultFormState(schema, {})).eql({ + level1: [1, 2, 3], + }); }); it("should use parent defaults for ArrayFields if declared in parent", () => { @@ -197,7 +210,9 @@ describe("utils", () => { }, }, }; - expect(getDefaultFormState(schema, {})).eql({ level1: [1, 2, 3] }); + expect(getDefaultFormState(schema, {})).eql({ + level1: [1, 2, 3], + }); }); it("should map item defaults to fixed array default", () => { @@ -318,7 +333,10 @@ describe("utils", () => { describe("uniqueItems is true", () => { describe("schema items enum is an array", () => { it("should be true", () => { - let schema = { items: { enum: ["foo", "bar"] }, uniqueItems: true }; + let schema = { + items: { enum: ["foo", "bar"] }, + uniqueItems: true, + }; expect(isMultiSelect(schema)).to.be.true; }); }); @@ -359,13 +377,18 @@ describe("utils", () => { items: { $ref: "#/definitions/FooItem" }, uniqueItems: true, }; - const definitions = { FooItem: { type: "string", enum: ["foo"] } }; + const definitions = { + FooItem: { type: "string", enum: ["foo"] }, + }; expect(isMultiSelect(schema, definitions)).to.be.true; }); }); it("should be false if uniqueItems is false", () => { - const schema = { items: { enum: ["foo", "bar"] }, uniqueItems: false }; + const schema = { + items: { enum: ["foo", "bar"] }, + uniqueItems: false, + }; expect(isMultiSelect(schema)).to.be.false; }); }); @@ -450,7 +473,9 @@ describe("utils", () => { const obj1 = { a: { b: [1] } }; const obj2 = { a: { b: [2] } }; - expect(mergeObjects(obj1, obj2, true)).eql({ a: { b: [1, 2] } }); + expect(mergeObjects(obj1, obj2, true)).eql({ + a: { b: [1, 2] }, + }); }); }); });