Skip to content

Commit

Permalink
Merge pull request #103 from mozilla-services/optional-live-validation
Browse files Browse the repository at this point in the history
Fixes #102: Make live form validation an option.
  • Loading branch information
n1k0 committed Mar 30, 2016
2 parents a304c69 + 32e2f3c commit 7d533ed
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 51 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
11 changes: 7 additions & 4 deletions src/components/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import ErrorList from "./ErrorList";

export default class Form extends Component {
static defaultProps = {
uiSchema: {}
uiSchema: {},
liveValidate: false,
}

constructor(props) {
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -142,6 +144,7 @@ if (process.env.NODE_ENV !== "production") {
onChange: PropTypes.func,
onError: PropTypes.func,
onSubmit: PropTypes.func,
liveValidate: PropTypes.bool,
};
}

Expand Down
119 changes: 72 additions & 47 deletions test/Form_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});
});

Expand Down Expand Up @@ -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"]);
});
});
});
});

0 comments on commit 7d533ed

Please sign in to comment.