From 32e2f3c2ccca13549deddb33e39985f0d52db4ed Mon Sep 17 00:00:00 2001 From: Nicolas Perriault Date: Tue, 29 Mar 2016 23:14:08 +0200 Subject: [PATCH] Fixes #102: Make live form validation an option. --- README.md | 9 ++++ src/components/Form.js | 11 ++-- test/Form_test.js | 119 +++++++++++++++++++++++++---------------- 3 files changed, 88 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index fb3fd7f87f..8bbacfb3ef 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ A [live playground](https://mozilla-services.github.io/react-jsonschema-form/) i - [Custom field components](#custom-field-components) - [Custom SchemaField](#custom-schemafield) - [Custom titles](#custom-titles) + - [Live form data validation](#live-form-data-validation) - [Styling your forms](#styling-your-forms) - [Schema definitions and references](#schema-definitions-and-references) - [Contributing](#contributing) @@ -471,6 +472,14 @@ render(( ), document.getElementById("app")); ``` +## Live form data validation + +By default, form data are only validated when the form is submitted or when a new `formData` prop is passed to the `Form` component. + +You can enable live form data validation by passing a `liveValidate` prop to the `Form` component, and set it to `true`. Then, everytime a value changes within the form data tree (eg. the user entering a character in a field), a validation operation is performed, and the validation results are reflected into the form state. + +Be warned that this is an expensive strategy, with possibly strong impact on performances. + ## Styling your forms This library renders form fields and widgets leveraging the [Bootstrap](http://getbootstrap.com/) semantics. That means your forms will be beautiful by default if you're loading its stylesheet in your page. diff --git a/src/components/Form.js b/src/components/Form.js index 22a5008915..610a80b503 100644 --- a/src/components/Form.js +++ b/src/components/Form.js @@ -14,7 +14,8 @@ import ErrorList from "./ErrorList"; export default class Form extends Component { static defaultProps = { - uiSchema: {} + uiSchema: {}, + liveValidate: false, } constructor(props) { @@ -55,9 +56,10 @@ export default class Form extends Component { return null; } - onChange = (formData, options={validate: true}) => { - const errors = options.validate ? this.validate(formData) : - this.state.errors; + onChange = (formData, options={validate: false}) => { + const liveValidate = this.props.liveValidate || options.validate; + const errors = liveValidate ? this.validate(formData) : + this.state.errors; const errorSchema = toErrorSchema(errors); this.setState({ status: "editing", @@ -142,6 +144,7 @@ if (process.env.NODE_ENV !== "production") { onChange: PropTypes.func, onError: PropTypes.func, onSubmit: PropTypes.func, + liveValidate: PropTypes.bool, }; } diff --git a/test/Form_test.js b/test/Form_test.js index 63936fecbc..ab64ea9b80 100644 --- a/test/Form_test.js +++ b/test/Form_test.js @@ -500,29 +500,54 @@ describe("Form", () => { minLength: 8 }; - it("should update the errorSchema when the formData changes", () => { - const {comp, node} = createFormComponent({schema}); + describe("Lazy validation", () => { + it("should not update the errorSchema when the formData changes", () => { + const {comp, node} = createFormComponent({schema}); - Simulate.change(node.querySelector("input[type=text]"), { - target: {value: "short"} + Simulate.change(node.querySelector("input[type=text]"), { + target: {value: "short"} + }); + + expect(comp.state.errorSchema).eql({}); }); - expect(comp.state.errorSchema).eql({ - errors: ["does not meet minimum length of 8"] + it("should not denote an error in the field", () => { + const {node} = createFormComponent({schema}); + + Simulate.change(node.querySelector("input[type=text]"), { + target: {value: "short"} + }); + + expect(node.querySelectorAll(".field-error")) + .to.have.length.of(0); }); }); - it("should denote the new error in the field", () => { - const {node} = createFormComponent({schema}); + describe("Live validation", () => { + it("should update the errorSchema when the formData changes", () => { + const {comp, node} = createFormComponent({schema, liveValidate: true}); - Simulate.change(node.querySelector("input[type=text]"), { - target: {value: "short"} + Simulate.change(node.querySelector("input[type=text]"), { + target: {value: "short"} + }); + + expect(comp.state.errorSchema).eql({ + errors: ["does not meet minimum length of 8"] + }); }); - expect(node.querySelectorAll(".field-error")) - .to.have.length.of(1); - expect(node.querySelector(".field-string .error-detail").textContent) - .eql("does not meet minimum length of 8"); + it("should denote the new error in the field", () => { + const {node} = createFormComponent({schema, liveValidate: true}); + + Simulate.change(node.querySelector("input[type=text]"), { + target: {value: "short"} + }); + + expect(node.querySelectorAll(".field-error")) + .to.have.length.of(1); + expect(node.querySelector(".field-string .error-detail").textContent) + .eql("does not meet minimum length of 8"); + }); }); }); @@ -734,49 +759,49 @@ describe("Form", () => { .eql(["does not meet minimum length of 4"]); }); }); - }); - describe("array nested items", () => { - const schema = { - type: "array", - items: { - type: "object", - properties: { - foo: { - type: "string", - minLength: 4 + describe("array nested items", () => { + const schema = { + type: "array", + items: { + type: "object", + properties: { + foo: { + type: "string", + minLength: 4 + } } } - } - }; + }; - it("should contextualize the error for array nested items", () => { - const {comp} = createFormComponent({schema, formData: [ - {foo: "good"}, {foo: "bad"}, {foo: "good"} - ]}); + it("should contextualize the error for array nested items", () => { + const {comp} = createFormComponent({schema, formData: [ + {foo: "good"}, {foo: "bad"}, {foo: "good"} + ]}); - expect(comp.state.errorSchema).eql({ - 1: { - foo: { - errors: ["does not meet minimum length of 4"] + expect(comp.state.errorSchema).eql({ + 1: { + foo: { + errors: ["does not meet minimum length of 4"] + } } - } + }); }); - }); - it("should denote the error in the array nested item", () => { - const {node} = createFormComponent({schema, formData: [ - {foo: "good"}, {foo: "bad"}, {foo: "good"} - ]}); - const fieldNodes = node.querySelectorAll(".field-string"); + it("should denote the error in the array nested item", () => { + const {node} = createFormComponent({schema, formData: [ + {foo: "good"}, {foo: "bad"}, {foo: "good"} + ]}); + const fieldNodes = node.querySelectorAll(".field-string"); - const liNodes = fieldNodes[1] - .querySelectorAll(".field-string .error-detail li"); - const errors = [].map.call(liNodes, li => li.textContent); + const liNodes = fieldNodes[1] + .querySelectorAll(".field-string .error-detail li"); + const errors = [].map.call(liNodes, li => li.textContent); - expect(fieldNodes[1].classList.contains("field-error")).eql(true); - expect(errors) - .eql(["does not meet minimum length of 4"]); + expect(fieldNodes[1].classList.contains("field-error")).eql(true); + expect(errors) + .eql(["does not meet minimum length of 4"]); + }); }); }); });