diff --git a/README.md b/README.md
index c11f4e4..e1e7360 100644
--- a/README.md
+++ b/README.md
@@ -8,9 +8,9 @@ Base Form component for building convenient forms for [React](https://facebook.g
## Features Overview
-- Controlled Form, i.e. it works with input values passed in props as JS object.
+- Controlled Form, i.e. it accepts input values as a JSON object.
- Simple API that handles deeply nested values and collections.
-- Flexible and conventient validation that allows to validate inputs as user types.
+- Flexible and convenient validation that allows to validate inputs as user types.
- Allows to easily turn any existing component into a Form Input component.
## Installation
@@ -26,177 +26,69 @@ npm install --save react-form-base
`react-form-base` provides a `Form` base class which expects to work together
with **Input** components. An **Input** is any component that consumes three
properties: `value`, `error` and `onChange`. It also has to provide it's
-`value` as first argument to `onChange` function supplied in props. For
-existing ready-for-use input bindings take a look on [react-form-js](https://github.com/akuzko/react-form-js)
-and [react-form-material-ui](https://github.com/akuzko/react-form-material-ui).
+`value` as first argument to `onChange` function supplied in props.
+*For existing ready-for-use input bindings take a look on:*
+- [react-form-js](https://github.com/akuzko/react-form-js)
+- [react-form-material-ui](https://github.com/akuzko/react-form-material-ui)
### Form Usage
Most of form use-cases with examples are revealed in [**Demo Application**](https://akuzko.github.io/react-form-base/).
Details on how to run it locally are at the end of README.
-Bellow you can take a glance on main aspects of form usage: general API,
-custom on-change handlers, validation and `$render` helper function.
+#### Dedicated Forms
-#### Basic example
+Most of forms developers deal with are quite complicated and encapsulate
+vast amount of validation and rendering logic. After some basic setup described
+in the [Wiki](https://github.com/akuzko/react-form-base/wiki) your form may
+look like following:
```js
-import Form from 'react-form-base';
-import { TextField } from 'your-inputs'; // read on inputs in the beginning of README
-
-class MyForm extends Form {
- render() {
- return (
-
-
-
-
-
-
- );
- }
-}
-```
-
-#### Nested fields example
-
-```js
-import Form from 'react-form-base';
-import { TextField, Select } from 'your-inputs'; // read on inputs in the beginning of README
-import countries from 'utils/countries'; // it's just a stub
-
-class MyForm extends Form {
- render() {
- return (
-
-
-
-
-
-
-
-
-
- );
- }
-}
-```
-
-#### Custom on-change handler
-
-```js
-import Form from 'react-form-base';
-import { Select } from 'your-inputs'; // read on inputs in the beginning of README
-
-class MyForm extends Form {
- changeItem(value) {
- this.set({
- item: value,
- amount: null
- });
- }
-
- render() {
- return (
-
-
-
-
-
-
- );
- }
-}
-```
-
-#### Validation
-
-```js
-import Form from 'react-form-base';
-import { TextField } from 'your-inputs'; // read on inputs in the beginning of README
-
-class MyForm extends Form {
- // static validations are common validation rules. it's best to define them
- // in your top-level application form that is a base class for other forms.
- static validations = {
- presence: function(value) {
- if (!value) return 'cannot be blank';
- },
- numericality: function(value, options) {
- const { greaterThan } = options;
- const fValue = parseFloat(value);
-
- if (isNaN(fValue)) return 'should be a number';
- if (greaterThan != undefined && fValue <= greaterThan) {
- return `should be greater than ${greaterThan}`;
- }
- }
- };
-
- // per-form input validations
+class UserForm extends Form {
validations = {
- // firstName: 'presence' from static validation rules
- firstName: 'presence',
- // email: 'presence' validation from rules and custom regexp validation
- // for this specific form
- email: ['presence', function(value) {
- if (!/^[\w\d\.]+@[\w\d]+\.[\w\d]{2,}$/.test(value)) {
- return 'should be an email';
- }
- }],
- // validation with options
- amount: { presence: true, numericality: { greaterThan: 10 } }
+ 'email': ['presence', 'email'],
+ 'fullName': 'presence',
+ 'address.city': 'presence',
+ 'address.line': { presence: true, format: /^[\w\s\d\.,]+$/ }
};
- render() {
+ $render($) {
return (
-
-
-
-
-
-
- );
- }
-}
-```
+
+
-#### $render($) method
+
+
+
-If you don't have extra logic based on render method (such as implementing
-rendering in base form and calling `super.render(someContent)` from child
-forms), and you want to make things a little bit more DRY, you may declare
-your form's rendering using `$render` method that accepts input-generation
-function as argument. Thus, removing the `this.` prefix in inputs:
-
-```js
-class MyForm extends Form {
- $render($) {
- return (
-
-
-
-
+
);
}
}
```
-This form of rendering declaration is also very useful when working with
-nested forms, since it has a special `nested` method that will generate
-onChange handler for nested form for you:
+#### Inline Forms
+
+If your form is small enough, you might want to render it inline instead of
+defining separate form component. In this case you may pass renderer function
+as only form's child. This function takes form's `$` function as argument for
+convenience. Note that you still need to define static `validation` rules
+for the Form to be able to use validations.
```js
-{this.map('items', (_item, i) =>
-
-)}
+
```
-Of course, since `$` is argument in this method, you may use any name for
-this variable that you find suitable.
-
#### API and helper methods
- `$(name)`, `input(name)` - returns a set of properties for input with a given
diff --git a/demo/src/App.jsx b/demo/src/App.jsx
index b671633..ff4ac73 100644
--- a/demo/src/App.jsx
+++ b/demo/src/App.jsx
@@ -1,3 +1,4 @@
+/* global Promise */
import React, { PureComponent } from 'react';
import { Intro, InputPrerequisites } from './components';
import * as Forms from './forms';
diff --git a/demo/src/components/Source.jsx b/demo/src/components/Source.jsx
index c4babcd..769197a 100644
--- a/demo/src/components/Source.jsx
+++ b/demo/src/components/Source.jsx
@@ -1,3 +1,4 @@
+/* global Prism */
import React, { PropTypes, PureComponent } from 'react';
export default class Source extends PureComponent {
diff --git a/src/Form.jsx b/src/Form.jsx
index 7cc0dab..914cae6 100644
--- a/src/Form.jsx
+++ b/src/Form.jsx
@@ -14,7 +14,9 @@ export default class Form extends PureComponent {
clearErrorsOnChange: PropTypes.bool,
validateOnChange: PropTypes.bool,
validateOnSave: PropTypes.bool,
- onRequestSave: PropTypes.func
+ onRequestSave: PropTypes.func,
+ validations: PropTypes.object,
+ children: PropTypes.func
};
static defaultProps = {
@@ -128,6 +130,10 @@ export default class Form extends PureComponent {
return this.props.validateOnChange && this.state.hadErrors;
}
+ get _validations() {
+ return this.props.validations || this.validations;
+ }
+
ifValid(callback) {
const errors = this.getValidationErrors();
@@ -150,7 +156,7 @@ export default class Form extends PureComponent {
}
validate(validate) {
- for (const name in this.validations) {
+ for (const name in this._validations) {
validate(name);
}
@@ -213,6 +219,17 @@ export default class Form extends PureComponent {
}
render() {
+ const $bound = this._bind$();
+ const { children: renderer } = this.props;
+
+ if (typeof renderer === 'function') {
+ return renderer($bound);
+ }
+
+ return this.$render($bound);
+ }
+
+ _bind$() {
const $bound = this.$.bind(this);
Object.defineProperty($bound, 'nested', {
@@ -226,7 +243,7 @@ export default class Form extends PureComponent {
enumerable: false
});
- return this.$render($bound);
+ return $bound;
}
$render() {
diff --git a/src/utils.js b/src/utils.js
index 570d806..926d53e 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -19,7 +19,7 @@ function wildcard(name) {
export function buildFormValidator(form) {
function validate(name, options = {}) {
const value = options.hasOwnProperty('value') ? options.value : form.get(name);
- const validator = options['with'] || form.validations[name] || form.validations[wildcard(name)];
+ const validator = options['with'] || form._validations[name] || form._validations[wildcard(name)];
if (!validator) return null;
diff --git a/test/Form.test.js b/test/Form.test.js
index b906686..f727586 100644
--- a/test/Form.test.js
+++ b/test/Form.test.js
@@ -559,4 +559,17 @@ describe('', function() {
expect(this.test.wrapper.state('form').foo).toEqual('new value');
});
});
+
+ describe('render', function() {
+ context('when function is passed', function() {
+ it('uses it as renderer function', function() {
+ const wrapper = shallow(
+
+ );
+ expect(wrapper.containsMatchingElement()).toEqual(true);
+ });
+ });
+ });
});