Compare commits

...

6 Commits
master ... beta

Author SHA1 Message Date
Vsevolod Ivanov db6e68ad4c fix resetmodel binding 2017-10-21 21:44:01 -07:00
Vsevolod Ivanov c7926cf355 Update package.json 2017-09-28 01:10:46 +03:00
Aesop Wolf 42e39cbc8f Chore/tweaks for v1 (#473)
* Rename HOC/Wrapper export to `withFormsy`

This is more in alignment with community practices.

* Migrate from npm to yarn

* Update package.json

Use order listed on https://yarnpkg.com/en/docs/package-json

* Update README.md

New examples, and small copy/whitespace changes

* Remove CHANGES.md

We will use github release from now on

* Fix test suite

Replace all references to  with

* Add ESLint

* Update dependencies

* Upgrade babel dependencies

* Upgrade jsdom

* Upgrade nodeunit and sinon

* Upgrade webpack and webpack-dev-server

* Fix examples

* Convert to ES6 classes

* Fix ESLint errors and warnings (WIP)

* Fix more ESLint errors and warnings (WIP)

* Move runRules to utils.js

* Fix more ESLint errors and warnings (WIP)

* Fix more ESLint errors and warnings (WIP)

* Fix more ESLint errors and warnings (WIP)

* Use less complex regex's for url and email

* Change grammar in README

* Change export pattern

* Use ES6 export for utils and validationRules

* Fix login example

* Reorder methods alphabetically and remove "magical" `validate` feature

* Remove `validate` from API docs (and general cleanup of file)

* Update examples (removes `validate` feature)

* Rename webpack file and remove json loader

* Fix code samples in README

* Update reset-values example (WIP)

* Cleanup reset-values example

* Fix prop type for Wrapper value

* Handle onReset event

* Update reset-value example to support `<button type="reset">`

* Update dynamic form fields example
2017-08-29 16:35:17 -07:00
Tom B b413f9db77 A few changes to API for better Wrapped component support (#472)
* Add propTypes to API docs; move innerRef doc to match location in table of contents

* Change HOC to Wrapper (be consistent everywhere)

* Inject defaultProps.value from wrapped component into Formsy.Wrapper

This way users can provide a default value for their form field.
Previously, this was possible because mixins were on the same level as the actual component.

* Add optional parameter to setState to disable validation

* Update prepublish script to clean `lib`; rebuild release folder
2017-08-11 11:53:00 -07:00
Aesop Wolf 998e2be44f Update package.json version to 1.0.0 2017-08-08 22:14:00 -07:00
Tom B 98525a4b64 Major update/cleanup to Formsy API; React v15+; No more mixins (#470)
* Up node to 8.2.1; only allow React v15+

* Update HOC to ES6 component; kill Mixin and decorator

* Update all tests; ES6ify everything!

* Update examples to ES6 classes

* Update API docs

* Fix broken/commented test case

* Move peer dep to regular dep since peer is deprecated
2017-08-08 22:09:11 -07:00
60 changed files with 7258 additions and 2019 deletions

3
.eslintrc Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "airbnb"
}

View File

@ -2,7 +2,7 @@
.editorconfig
.travis.yml
testrunner.js
webpack.production.config.js
webpack.config.js
examples/
release/
tests/

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
8.2.1

View File

@ -1,4 +1,4 @@
language: node_js
sudo: false
node_js:
- '4.1'
- '8.2.1'

789
API.md

File diff suppressed because it is too large Load Diff

View File

@ -1,81 +0,0 @@
This is the old CHANGES file. Please look at [releases](https://github.com/christianalfoni/formsy-react/releases) for latest changes.
**0.8.0**
- Fixed bug where dynamic form elements gave "not mounted" error (Thanks @sdemjanenko)
- React is now a peer dependency (Thanks @snario)
- Dynamically updated values should now work with initial "undefined" value (Thanks @sdemjanenko)
- Validations are now dynamic. Change the prop and existing values are re-validated (thanks @bryannaegele)
- You can now set a "disabled" prop on the form and check "isFormDisabled()" in form elements
- Refactored some code and written a couple of tests
**0.7.2**:
- isNumber validation now supports float (Thanks @hahahana)
- Form XHR calls now includes CSRF headers, if exists (Thanks @hahahana)
**0.7.1**
- Fixed bug where external update of value on pristine form element did not update the form model (Thanks @sdemjanenko)
- Fixed bug where children are null/undefined (Thanks @sdemjanenko)
**0.7.0**
- Dynamic form elements. Add them at any point and they will be registered with the form
- **onChange()** handler is called whenever an form element has changed its value or a new form element is added to the form
- isNumeric validator now also handles actual numbers, not only strings
- Some more tests
**0.6.0**
- **onSubmit()** now has the same signature regardless of passing url attribute or not
- **isPristine()** is a new method to handle "touched" form elements (thanks @FoxxMD)
- Mapping attributes to pass a function that maps input values to new structure. The new structure is either passed to *onSubmit* and/or to the server when using a url attribute (thanks for feedback @MattAitchison)
- Added default "equalsField" validation rule
- Lots of tests!
**0.5.2**
- Fixed bug with handlers in ajax requests (Thanks @smokku)
**0.5.1**
- Fixed bug with empty validations
**0.5.0**
- Added [cross input validation](#formsyaddvalidationrule)
- Fixed bug where validation rule refers to a string
- Added "invalidateForm" function when manually submitting the form
**0.4.1**
- Fixed bug where form element is required, but no validations
**0.4.0**:
- Possibility to handle form data manually using "onSubmit"
- Added two more default rules. *isWords* and *isSpecialWords*
**0.3.0**:
- Deprecated everything related to buttons automatically added
- Added onValid and onInvalid handlers, use those to manipulate submit buttons etc.
**0.2.3**:
- Fixed bug where child does not have props property
**0.2.2**:
- Fixed bug with updating the props
**0.2.1**:
- Cancel button displays if onCancel handler is defined
**0.2.0**:
- Implemented hasValue() method
**0.1.3**:
- Fixed resetValue bug
**0.1.2**:
- Fixed isValue check to empty string, needs to support false and 0
**0.1.1**:
- Added resetValue method
- Changed value check of showRequired

210
README.md
View File

@ -1,135 +1,131 @@
formsy-react [![GitHub release](https://img.shields.io/github/release/christianalfoni/formsy-react.svg)](https://github.com/christianalfoni/formsy-react/releases) [![Build Status](https://travis-ci.org/christianalfoni/formsy-react.svg?branch=master)](https://travis-ci.org/christianalfoni/formsy-react)
============
# formsy-react [![GitHub release](https://img.shields.io/github/release/christianalfoni/formsy-react.svg)](https://github.com/christianalfoni/formsy-react/releases) [![Build Status](https://travis-ci.org/christianalfoni/formsy-react.svg?branch=master)](https://travis-ci.org/christianalfoni/formsy-react)
A form input builder and validator for React JS
A form input builder and validator for React.
| [How to use](#how-to-use) | [API](/API.md) | [Examples](/examples) |
|---|---|---|
| [Quick Start](#quick-start) | [API](/API.md) | [Examples](/examples) |
| --------------------------- | -------------- | --------------------- |
## <a name="background">Background</a>
I wrote an article on forms and validation with React JS, [Nailing that validation with React JS](http://christianalfoni.github.io/javascript/2014/10/22/nailing-that-validation-with-reactjs.html), the result of that was this extension.
## Background
The main concept is that forms, inputs and validation is done very differently across developers and projects. This extension to React JS aims to be that "sweet spot" between flexibility and reusability.
I wrote an article on forms and validation with React, [Nailing that validation with React JS](http://christianalfoni.github.io/javascript/2014/10/22/nailing-that-validation-with-reactjs.html), the result of that was this component.
## What you can do
The main concept is that forms, inputs, and validation are done very differently across developers and projects. This React component aims to be that “sweet spot” between flexibility and reusability.
1. Build any kind of form element components. Not just traditional inputs, but anything you want and get that validation for free
## What You Can Do
2. Add validation rules and use them with simple syntax
3. Use handlers for different states of your form. Ex. "onSubmit", "onError", "onValid" etc.
4. Pass external errors to the form to invalidate elements
5. You can dynamically add form elements to your form and they will register/unregister to the form
## Default elements
You can look at examples in this repo or use the [formsy-react-components](https://github.com/twisty/formsy-react-components) project to use bootstrap with formsy-react, or use [formsy-material-ui](https://github.com/mbrookes/formsy-material-ui) to use [Material-UI](http://material-ui.com/) with formsy-react.
1. Build any kind of form element components. Not just traditional inputs, but anything you want, and get that validation for free
2. Add validation rules and use them with simple syntax
3. Use handlers for different states of your form. (`onError`, `onSubmit`, `onValid`, etc.)
4. Pass external errors to the form to invalidate elements (E.g. a response from a server)
5. Dynamically add form elements to your form and they will register/unregister to the form
## Install
1. Download from this REPO and use globally (Formsy) or with requirejs
2. Install with `npm install formsy-react` and use with browserify etc.
3. Install with `bower install formsy-react`
`yarn add formsy-react react react-dom` and use with webpack, browserify, etc.
## Changes
## Quick Start
[Check out releases](https://github.com/christianalfoni/formsy-react/releases)
[Older changes](CHANGES.md)
## How to use
See [`examples` folder](/examples) for examples. [Codepen demo](http://codepen.io/semigradsky/pen/dYYpwv?editors=001).
Complete API reference is available [here](/API.md).
#### Formsy gives you a form straight out of the box
### 1. Build a Formsy element
```jsx
import Formsy from 'formsy-react';
// MyInput.js
import { withFormsy } from 'formsy-react';
import React from 'react';
const MyAppForm = React.createClass({
getInitialState() {
return {
canSubmit: false
}
},
enableButton() {
this.setState({
canSubmit: true
});
},
disableButton() {
this.setState({
canSubmit: false
});
},
submit(model) {
someDep.saveEmail(model.email);
},
render() {
return (
<Formsy.Form onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}>
<MyOwnInput name="email" validations="isEmail" validationError="This is not a valid email" required/>
<button type="submit" disabled={!this.state.canSubmit}>Submit</button>
</Formsy.Form>
);
}
});
```
This code results in a form with a submit button that will run the `submit` method when the submit button is clicked with a valid email. The submit button is disabled as long as the input is empty ([required](/API.md#required)) or the value is not an email ([isEmail](/API.md#validators)). On validation error it will show the message: "This is not a valid email".
#### Building a form element (required)
```jsx
import Formsy from 'formsy-react';
const MyOwnInput = React.createClass({
// Add the Formsy Mixin
mixins: [Formsy.Mixin],
class MyInput extends React.Component {
constructor(props) {
super(props);
this.changeValue = this.changeValue.bind(this);
}
changeValue(event) {
// setValue() will set the value of the component, which in
// turn will validate it and the rest of the form
changeValue(event) {
this.setValue(event.currentTarget.value);
},
// Important: Don't skip this step. This pattern is required
// for Formsy to work.
this.props.setValue(event.currentTarget.value);
}
render() {
// Set a specific className based on the validation
// state of this component. showRequired() is true
// when the value is empty and the required prop is
// passed to the input. showError() is true when the
// value typed is invalid
const className = this.showRequired() ? 'required' : this.showError() ? 'error' : null;
render() {
// An error message is returned only if the component is invalid
const errorMessage = this.props.getErrorMessage();
// An error message is returned ONLY if the component is invalid
// or the server has returned an error message
const errorMessage = this.getErrorMessage();
return (
<div>
<input
onChange={this.changeValue}
type="text"
value={this.props.getValue() || ''}
/>
<span>{errorMessage}</span>
</div>
);
}
}
return (
<div className={className}>
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
<span>{errorMessage}</span>
</div>
);
}
});
export default withFormsy(MyInput);
```
The form element component is what gives the form validation functionality to whatever you want to put inside this wrapper. You do not have to use traditional inputs, it can be anything you want and the value of the form element can also be anything you want. As you can see it is very flexible, you just have a small API to help you identify the state of the component and set its value.
## Related projects
- [formsy-material-ui](https://github.com/mbrookes/formsy-material-ui) - A formsy-react compatibility wrapper for [Material-UI](http://material-ui.com/) form components.
- [formsy-react-components](https://github.com/twisty/formsy-react-components) - A set of React JS components for use in a formsy-react form.
- ...
- Send PR for adding your project to this list!
`withFormsy` is a [Higher-Order Component](https://facebook.github.io/react/docs/higher-order-components.html) that exposes additional props to `MyInput`. See the [API](/API.md#withFormsy) documentation to view a complete list of the props.
### 2. Use your Formsy element
```jsx
import Formsy from 'formsy-react';
import React from 'react';
import MyInput from './MyInput';
export default class App extends React.Component {
constructor(props) {
super(props);
this.disableButton = this.disableButton.bind(this);
this.enableButton = this.enableButton.bind(this);
this.state = { canSubmit: false };
}
disableButton() {
this.setState({ canSubmit: false });
}
enableButton() {
this.setState({ canSubmit: true });
}
submit(model) {
fetch('http://example.com/', {
method: 'post',
body: JSON.stringify(model)
});
}
render() {
return (
<Formsy onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}>
<MyInput
name="email"
validations="isEmail"
validationError="This is not a valid email"
required
/>
<button type="submit" disabled={!this.state.canSubmit}>Submit</button>
</Formsy>
);
}
}
```
This code results in a form with a submit button that will run the `submit` method when the form is submitted with a valid email. The submit button is disabled as long as the input is empty ([required](/API.md#required)) and the value is not an email ([isEmail](/API.md#validators)). On validation error it will show the message: "This is not a valid email".
## Contribute
- Fork repo
- `npm install`
- `npm run examples` runs the development server on `localhost:8080`
- `npm test` runs the tests
- Fork repo
- `yarn`
- `yarn examples` runs the development server on `localhost:8080`
- `yarn test` runs the tests
## Changelog
[Check out releases](https://github.com/christianalfoni/formsy-react/releases)
## License

View File

@ -1,25 +0,0 @@
{
"name": "formsy-react",
"version": "0.18.0",
"description": "A form input builder and validator for React JS",
"repository": {
"type": "git",
"url": "https://github.com/christianalfoni/formsy-react.git"
},
"main": "src/main.js",
"license": "MIT",
"ignore": [
"build/",
"Gulpfile.js"
],
"dependencies": {
"react": "^0.14.7 || ^15.0.0"
},
"keywords": [
"react",
"form",
"forms",
"validation",
"react-component"
]
}

10
examples/.eslintrc Normal file
View File

@ -0,0 +1,10 @@
{
"env": {
"browser": true
},
"extends": "eslint:recommended",
"rules": {
"react/jsx-filename-extension": 0,
"react/no-multi-comp": 0
}
}

View File

@ -29,7 +29,7 @@ If it is not helped try update your node.js and npm.
2. [**Custom Validation**](custom-validation)
One field with added validation rule (`Formsy.addValidationRule`) and one field with dynamically added validation and error messages.
One field with added validation rule (`addValidationRule`) and one field with dynamically added validation and error messages.
3. [**Reset Values**](reset-values)

View File

@ -0,0 +1,48 @@
import React from 'react';
import { propTypes, withFormsy } from 'formsy-react';
class MyCheckbox extends React.Component {
constructor(props) {
super(props);
this.changeValue = this.changeValue.bind(this);
this.state = {
value: true,
};
}
changeValue(event) {
// setValue() will set the value of the component, which in
// turn will validate it and the rest of the form
this.props.setValue(event.target.checked)
}
render() {
// Set a specific className based on the validation
// state of this component. showRequired() is true
// when the value is empty and the required prop is
// passed to the input. showError() is true when the
// value typed is invalid
const className = `form-group ${this.props.className} ${this.props.showRequired() ? 'required' : ''} ${this.props.showError() ? 'error' : ''}`;
const value = this.props.getValue();
return (
<div
className={className}
>
<label htmlFor={this.props.name}>{this.props.title}</label>
<input
onChange={this.changeValue}
id={this.props.name}
type='checkbox'
checked={value}
data-checked={value}
/>
</div>
);
}
}
MyCheckbox.propTypes = {
...propTypes,
};
export default withFormsy(MyCheckbox);

View File

@ -1,44 +1,47 @@
import React from 'react';
import Formsy from 'formsy-react';
import { propTypes, withFormsy } from 'formsy-react';
const MyInput = React.createClass({
class MyInput extends React.Component {
constructor(props) {
super(props);
this.changeValue = this.changeValue.bind(this);
}
// Add the Formsy Mixin
mixins: [Formsy.Mixin],
// setValue() will set the value of the component, which in
// turn will validate it and the rest of the form
changeValue(event) {
this.setValue(event.currentTarget[this.props.type === 'checkbox' ? 'checked' : 'value']);
},
render() {
// setValue() will set the value of the component, which in
// turn will validate it and the rest of the form
this.props.setValue(event.currentTarget[this.props.type === 'checkbox' ? 'checked' : 'value']);
}
render() {
// Set a specific className based on the validation
// state of this component. showRequired() is true
// when the value is empty and the required prop is
// passed to the input. showError() is true when the
// value typed is invalid
const className = 'form-group' + (this.props.className || ' ') +
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
const className = `form-group ${this.props.className} ${this.props.showRequired() ? 'required' : ''} ${this.props.showError() ? 'error' : ''}`;
// An error message is returned ONLY if the component is invalid
// or the server has returned an error message
const errorMessage = this.getErrorMessage();
const errorMessage = this.props.getErrorMessage();
return (
<div className={className}>
<label htmlFor={this.props.name}>{this.props.title}</label>
<input
type={this.props.type || 'text'}
name={this.props.name}
onChange={this.changeValue}
value={this.getValue()}
checked={this.props.type === 'checkbox' && this.getValue() ? 'checked' : null}
name={this.props.name}
type={this.props.type || 'text'}
value={this.props.getValue() || ''}
/>
<span className='validation-error'>{errorMessage}</span>
</div>
);
}
});
}
export default MyInput;
MyInput.propTypes = {
...propTypes,
};
export default withFormsy(MyInput);

View File

@ -1,5 +1,5 @@
import React from 'react';
import Formsy from 'formsy-react';
import { withFormsy } from 'formsy-react';
function contains(container, item, cmp) {
for (const it of container) {
@ -10,18 +10,16 @@ function contains(container, item, cmp) {
return false;
}
const MyRadioGroup = React.createClass({
mixins: [Formsy.Mixin],
getInitialState() {
return { value: [], cmp: (a, b) => a === b };
},
class MyRadioGroup extends React.Component {
state = { value: [], cmp: (a, b) => a === b };
componentDidMount() {
const value = this.props.value || [];
this.setValue(value);
this.props.setValue(value);
this.setState({ value: value, cmp: this.props.cmp || this.state.cmp });
},
}
changeValue(value, event) {
changeValue = (value, event) => {
const checked = event.currentTarget.checked;
let newValue = [];
@ -31,14 +29,14 @@ const MyRadioGroup = React.createClass({
newValue = this.state.value.filter(it => !this.state.cmp(it, value));
}
this.setValue(newValue);
this.props.setValue(newValue);
this.setState({ value: newValue });
},
}
render() {
const className = 'form-group' + (this.props.className || ' ') +
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
const errorMessage = this.getErrorMessage();
(this.props.showRequired() ? 'required' : this.props.showError() ? 'error' : '');
const errorMessage = this.props.getErrorMessage();
const { name, title, items } = this.props;
return (
@ -61,6 +59,6 @@ const MyRadioGroup = React.createClass({
);
}
});
}
export default MyRadioGroup;
export default withFormsy(MyRadioGroup);

View File

@ -1,24 +1,24 @@
import React from 'react';
import Formsy from 'formsy-react';
import { withFormsy } from 'formsy-react';
const MyRadioGroup = React.createClass({
mixins: [Formsy.Mixin],
class MyRadioGroup extends React.Component {
state = {};
componentDidMount() {
const value = this.props.value;
this.setValue(value);
this.props.setValue(value);
this.setState({ value });
},
}
changeValue(value) {
this.setValue(value);
changeValue = (value) => {
this.props.setValue(value);
this.setState({ value });
},
}
render() {
const className = 'form-group' + (this.props.className || ' ') +
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
const errorMessage = this.getErrorMessage();
(this.props.showRequired() ? 'required' : this.props.showError() ? 'error' : '');
const errorMessage = this.props.getErrorMessage();
const { name, title, items } = this.props;
return (
@ -40,7 +40,6 @@ const MyRadioGroup = React.createClass({
</div>
);
}
}
});
export default MyRadioGroup;
export default withFormsy(MyRadioGroup);

View File

@ -1,17 +1,15 @@
import React from 'react';
import Formsy from 'formsy-react';
import { withFormsy } from 'formsy-react';
const MySelect = React.createClass({
mixins: [Formsy.Mixin],
changeValue(event) {
this.setValue(event.currentTarget.value);
},
class MySelect extends React.Component {
changeValue = (event) => {
this.props.setValue(event.currentTarget.value);
}
render() {
const className = 'form-group' + (this.props.className || ' ') +
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
const errorMessage = this.getErrorMessage();
(this.props.showRequired() ? 'required' : this.props.showError() ? 'error' : '');
const errorMessage = this.props.getErrorMessage();
const options = this.props.options.map((option, i) => (
<option key={option.title+option.value} value={option.value}>
@ -22,14 +20,13 @@ const MySelect = React.createClass({
return (
<div className={className}>
<label htmlFor={this.props.name}>{this.props.title}</label>
<select name={this.props.name} onChange={this.changeValue} value={this.getValue()}>
<select name={this.props.name} onChange={this.changeValue} value={this.props.getValue()}>
{options}
</select>
<span className='validation-error'>{errorMessage}</span>
</div>
);
}
}
});
export default MySelect;
export default withFormsy(MySelect);

View File

@ -1,105 +1,124 @@
import Formsy, { addValidationRule, propTypes, withFormsy } from 'formsy-react';
import PropTypes from 'prop-types';
import React from 'react';
import ReactDOM from 'react-dom';
import Formsy from 'formsy-react';
import MyInput from './../components/Input';
const currentYear = new Date().getFullYear();
const validators = {
time: {
regexp: /^(([0-1]?[0-9])|([2][0-3])):([0-5]?[0-9])(:([0-5]?[0-9]))?$/,
message: 'Not valid time'
},
decimal: {
regexp: /(^\d*\.?\d*[0-9]+\d*$)|(^[0-9]+\d*\.\d*$)/,
message: 'Please type decimal value'
},
binary: {
regexp: /^([0-1])*$/,
message: '10101000'
}
};
Formsy.addValidationRule('isYearOfBirth', (values, value) => {
value = parseInt(value);
if (typeof value !== 'number') {
addValidationRule('time', (values, value) => {
return /^(([0-1]?[0-9])|([2][0-3])):([0-5]?[0-9])(:([0-5]?[0-9]))?$/.test(value);
});
addValidationRule('decimal', (values, value) => {
return /(^\d*\.?\d*[0-9]+\d*$)|(^[0-9]+\d*\.\d*$)/.test(value);
});
addValidationRule('binary', (values, value) => {
return /^([0-1])*$/.test(value);
});
addValidationRule('isYearOfBirth', (values, value) => {
const parsedValue = parseInt(value, 10);
if (typeof parsedValue !== 'number') {
return false;
}
return value < currentYear && value > currentYear - 130;
return parsedValue < currentYear && parsedValue > currentYear - 130;
});
const App = React.createClass({
class App extends React.Component {
constructor(props) {
super(props);
this.submit = this.submit.bind(this);
}
submit(data) {
alert(JSON.stringify(data, null, 4));
},
}
render() {
return (
<Formsy.Form onSubmit={this.submit} className="custom-validation">
<Formsy onSubmit={this.submit} className="custom-validation">
<MyInput name="year" title="Year of Birth" type="number" validations="isYearOfBirth" validationError="Please type your year of birth" />
<DynamicInput name="dynamic" title="..." />
<FormsyDynamicInput name="dynamic" title="..." />
<button type="submit">Submit</button>
</Formsy.Form>
</Formsy>
);
}
});
}
class DynamicInput extends React.Component {
constructor(props) {
super(props);
this.state = { validationType: 'time' };
this.changeValidation = this.changeValidation.bind(this);
this.changeValue = this.changeValue.bind(this);
}
const DynamicInput = React.createClass({
mixins: [Formsy.Mixin],
getInitialState() {
return { validationType: 'time' };
},
changeValue(event) {
this.setValue(event.currentTarget.value);
},
changeValidation(validationType) {
this.setState({ validationType: validationType });
this.setValue(this.getValue());
},
validate() {
const value = this.getValue();
console.log(value, this.state.validationType);
return value ? validators[this.state.validationType].regexp.test(value) : true;
},
getCustomErrorMessage() {
return this.showError() ? validators[this.state.validationType].message : '';
},
this.props.setValue(this.props.getValue());
}
changeValue(event) {
this.props.setValue(event.currentTarget.value);
}
render() {
const className = 'form-group' + (this.props.className || ' ') + (this.showRequired() ? 'required' : this.showError() ? 'error' : null);
const errorMessage = this.getCustomErrorMessage();
const errorMessage = {
time: 'Not a valid time format',
decimal: "Not a valid decimal value",
binary: "Not a valid binary number"
}
return (
<div className={className}>
<div>
<label htmlFor={this.props.name}>{this.props.title}</label>
<input type='text' name={this.props.name} onChange={this.changeValue} value={this.getValue() || ''}/>
<span className='validation-error'>{errorMessage}</span>
<Validations validationType={this.state.validationType} changeValidation={this.changeValidation}/>
<MyInput validations={this.state.validationType} validationError={errorMessage[this.state.validationType]} type='text' name={this.props.name} onChange={this.changeValue} value={this.props.getValue() || ''} />
<Validations validationType={this.state.validationType} changeValidation={this.changeValidation} />
</div>
);
}
});
}
DynamicInput.displayName = "dynamic input";
DynamicInput.propTypes = {
...propTypes,
};
const FormsyDynamicInput = withFormsy(DynamicInput);
class Validations extends React.Component {
constructor(props) {
super(props);
this.changeValidation = this.changeValidation.bind(this);
}
const Validations = React.createClass({
changeValidation(e) {
this.props.changeValidation(e.target.value);
},
}
render() {
const { validationType } = this.props;
return (
<fieldset onChange={this.changeValidation}>
<fieldset>
<legend>Validation Type</legend>
<div>
<input name='validationType' type='radio' value='time' defaultChecked={validationType === 'time'}/>Time
<input onChange={this.changeValidation} name='validationType' type='radio' value='time' checked={validationType === 'time'} />Time
</div>
<div>
<input name='validationType' type='radio' value='decimal' defaultChecked={validationType === 'decimal'}/>Decimal
<input onChange={this.changeValidation} name='validationType' type='radio' value='decimal' checked={validationType === 'decimal'} />Decimal
</div>
<div>
<input name='validationType' type='radio' value='binary' defaultChecked={validationType === 'binary'}/>Binary
<input onChange={this.changeValidation} name='validationType' type='radio' value='binary' checked={validationType === 'binary'} />Binary
</div>
</fieldset>
);
}
});
}
ReactDOM.render(<App/>, document.getElementById('example'));
Validations.propTypes = {
changeValidation: PropTypes.func.isRequired,
validationType: PropTypes.string.isRequired,
};
ReactDOM.render(<App />, document.getElementById('example'));

View File

@ -8,7 +8,6 @@
<body>
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Custom Validation</h1>
<div id="example"/>
<script src="/__build__/shared.js"></script>
<script src="/__build__/custom-validation.js"></script>
</body>
</html>
</html>

View File

@ -9,7 +9,8 @@
}
.field {
overflow: hidden;
display: flex;
align-items: center;
}
.many-fields .form-group {
@ -17,8 +18,5 @@
float: left;
}
.many-fields .remove-field {
margin-top: 30px;
margin-left: 8px;
display: inline-block;
text-decoration: none;
max-height: 2rem;
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Form } from 'formsy-react';
import Formsy from 'formsy-react';
import MyInput from './../components/Input';
import MySelect from './../components/Select';
@ -14,7 +14,7 @@ const Fields = props => {
props.onRemove(pos);
};
}
const foo = 'required';
return (
<div className="fields">
{props.data.map((field, i) => (
@ -46,7 +46,7 @@ const Fields = props => {
/>
)
}
<a href="#" className="remove-field" onClick={onRemove(i)}>X</a>
<button className="remove-field" onClick={onRemove(i)}>X</button>
</div>
))
}
@ -54,36 +54,49 @@ const Fields = props => {
);
};
const App = React.createClass({
getInitialState() {
return { fields: [], canSubmit: false };
},
class App extends React.Component {
constructor(props) {
super(props);
this.state = { fields: [], canSubmit: false };
this.addField = this.addField.bind(this);
this.removeField = this.removeField.bind(this);
this.enableButton = this.enableButton.bind(this);
this.disableButton = this.disableButton.bind(this);
}
submit(data) {
alert(JSON.stringify(data, null, 4));
},
}
addField(fieldData) {
fieldData.validations = fieldData.validations.length ?
fieldData.validations.reduce((a, b) => Object.assign({}, a, b)) :
null;
fieldData.id = Date.now();
this.setState({ fields: this.state.fields.concat(fieldData) });
},
}
removeField(pos) {
const fields = this.state.fields;
this.setState({ fields: fields.slice(0, pos).concat(fields.slice(pos+1)) })
},
}
enableButton() {
this.setState({ canSubmit: true });
},
}
disableButton() {
this.setState({ canSubmit: false });
},
}
render() {
const { fields, canSubmit } = this.state;
return (
<div>
<Form onSubmit={this.addField} className="many-fields-conf">
<MyMultiCheckboxSet name="validations" title="Validations"
<Formsy onSubmit={this.addField} className="many-fields-conf">
<MyMultiCheckboxSet
name="validations"
title="Validations"
cmp={(a, b) => JSON.stringify(a) === JSON.stringify(b)}
items={[
{isEmail: true},
@ -95,19 +108,27 @@ const App = React.createClass({
{maxLength: 7}
]}
/>
<MyRadioGroup name="required" value={false} title="Required"
items={[true, false]} />
<MyRadioGroup name="type" value="input" title="Type"
items={['input', 'select']} />
<MyRadioGroup
name="required"
value={false}
title="Required"
items={[true, false]}
/>
<MyRadioGroup
name="type"
value="input"
title="Type"
items={['input', 'select']}
/>
<button type="submit">Add</button>
</Form>
<Form onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="many-fields">
</Formsy>
<Formsy onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="many-fields">
<Fields data={fields} onRemove={this.removeField} />
<button type="submit" disabled={!canSubmit}>Submit</button>
</Form>
</Formsy>
</div>
);
}
});
}
ReactDOM.render(<App/>, document.getElementById('example'));
ReactDOM.render(<App />, document.getElementById('example'));

View File

@ -8,7 +8,6 @@
<body>
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Dynamic Form Fields</h1>
<div id="example"/>
<script src="/__build__/shared.js"></script>
<script src="/__build__/dynamic-form-fields.js"></script>
</body>
</html>

View File

@ -1,31 +1,38 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Form } from 'formsy-react';
import Formsy from 'formsy-react';
import MyInput from './../components/Input';
const App = React.createClass({
getInitialState() {
return { canSubmit: false };
},
class App extends React.Component {
constructor(props) {
super(props);
this.state = { canSubmit: false };
this.disableButton = this.disableButton.bind(this);
this.enableButton = this.enableButton.bind(this);
}
submit(data) {
alert(JSON.stringify(data, null, 4));
},
}
enableButton() {
this.setState({ canSubmit: true });
},
}
disableButton() {
this.setState({ canSubmit: false });
},
}
render() {
return (
<Form onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="login">
<MyInput value="" name="email" title="Email" validations="isEmail" validationError="This is not a valid email" required />
<MyInput value="" name="password" title="Password" type="password" required />
<Formsy onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="login">
<MyInput name="email" title="Email" validations="isEmail" validationError="This is not a valid email" required />
<MyInput name="password" title="Password" type="password" required />
<button type="submit" disabled={!this.state.canSubmit}>Submit</button>
</Form>
</Formsy>
);
}
});
}
ReactDOM.render(<App/>, document.getElementById('example'));

View File

@ -8,7 +8,6 @@
<body>
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Login</h1>
<div id="example"/>
<script src="/__build__/shared.js"></script>
<script src="/__build__/login.js"></script>
</body>
</html>
</html>

View File

@ -1,7 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Form } from 'formsy-react';
import Formsy from 'formsy-react';
import MyCheckbox from './../components/Checkbox';
import MyInput from './../components/Input';
import MySelect from './../components/Select';
@ -11,19 +12,45 @@ const user = {
hair: 'brown'
};
const App = React.createClass({
const randomNames = ['Christian', 'Dmitry', 'Aesop'];
const randomFree = [true, false];
const randomHair = ['brown', 'black', 'blonde', 'red'];
class App extends React.Component {
constructor(props) {
super(props);
this.randomize = this.randomize.bind(this);
this.submit = this.submit.bind(this);
}
randomize() {
const random = {
name: randomNames[Math.floor(Math.random()*randomNames.length)],
free: randomFree[Math.floor(Math.random()*randomFree.length)],
hair: randomHair[Math.floor(Math.random()*randomHair.length)],
};
this.form.reset(random);
}
submit(data) {
alert(JSON.stringify(data, null, 4));
},
resetForm() {
this.refs.form.reset();
},
}
render() {
return (
<Formsy.Form ref="form" onSubmit={this.submit} className="form">
<Formsy
ref={(c) => this.form = c}
onSubmit={this.submit}
onReset={this.reset}
className="form"
>
<MyInput name="name" title="Name" value={user.name} />
<MyInput name="free" title="Free to hire" type="checkbox" value={user.free} />
<MySelect name="hair" title="Hair" value={user.hair}
<MyCheckbox name="free" title="Free to hire" value={user.free} />
<MySelect
name="hair"
title="Hair"
value={user.hair}
options={[
{ value: "black", title: "Black" },
{ value: "brown", title: "Brown" },
@ -33,12 +60,13 @@ const App = React.createClass({
/>
<div className="buttons">
<button type="reset" onClick={this.resetForm}>Reset</button>
<button type="reset">Reset</button>
<button type="button" onClick={this.randomize}>Randomize</button>
<button type="submit">Submit</button>
</div>
</Formsy.Form>
</Formsy>
);
}
});
}
ReactDOM.render(<App/>, document.getElementById('example'));
ReactDOM.render(<App />, document.getElementById('example'));

View File

@ -8,7 +8,6 @@
<body>
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Reset Values</h1>
<div id="example"/>
<script src="/__build__/shared.js"></script>
<script src="/__build__/reset-values.js"></script>
</body>
</html>

View File

@ -1,49 +1,41 @@
var fs = require('fs');
var path = require('path');
var webpack = require('webpack');
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
function isDirectory(dir) {
return fs.lstatSync(dir).isDirectory();
}
module.exports = {
devtool: 'inline-source-map',
entry: fs.readdirSync(__dirname).reduce(function (entries, dir) {
var isDraft = dir.charAt(0) === '_' || dir.indexOf('components') >= 0;
entry: fs.readdirSync(__dirname).reduce((entries, dir) => {
const isDraft = dir.charAt(0) === '_' || dir.indexOf('components') >= 0;
if (!isDraft && isDirectory(path.join(__dirname, dir))) {
entries[dir] = path.join(__dirname, dir, 'app.js');
}
return entries;
}, {}),
output: {
path: 'examples/__build__',
path: path.resolve(__dirname, 'examples/__build__'),
filename: '[name].js',
chunkFilename: '[id].chunk.js',
publicPath: '/__build__/'
publicPath: '/__build__/',
},
module: {
loaders: [
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel' }
]
loaders: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
}],
},
resolve: {
alias: {
'formsy-react': '../../src/main'
}
'formsy-react': '../../src/index',
},
},
plugins: [
new webpack.optimize.CommonsChunkPlugin('shared.js'),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
})
]
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
}),
],
};

View File

@ -1,49 +1,61 @@
{
"name": "formsy-react",
"version": "0.19.5",
"description": "A form input builder and validator for React JS",
"version": "1.0.0",
"description": "A form input builder and validator for React",
"keywords": [
"form",
"forms",
"formsy",
"react",
"react-component",
"validation"
],
"license": "MIT",
"homepage": "https://github.com/christianalfoni/formsy-react",
"bugs": "https://github.com/christianalfoni/formsy-react/issues",
"repository": {
"type": "git",
"url": "https://github.com/christianalfoni/formsy-react.git"
},
"main": "lib/main.js",
"scripts": {
"build": "NODE_ENV=production webpack -p --config webpack.production.config.js",
"test": "babel-node testrunner",
"examples": "webpack-dev-server --config examples/webpack.config.js --content-base examples",
"prepublish": "babel ./src/ -d ./lib/"
},
"author": "Christian Alfoni",
"license": "MIT",
"keywords": [
"react",
"form",
"forms",
"validation",
"react-component"
"files": [
"lib"
],
"main": "release/formsy-react.js",
"scripts": {
"build": "NODE_ENV=production webpack -p --config webpack.config.js && yarn run prepublish",
"examples": "webpack-dev-server --config examples/webpack.config.js --content-base examples",
"lint": "eslint src/**/*.js",
"prepublish": "rm -Rf ./lib && babel ./src/ -d ./lib/",
"test": "babel-node testrunner"
},
"dependencies": {
"form-data-to-object": "^0.2.0"
"form-data-to-object": "^0.2.0",
"prop-types": "^15.5.10"
},
"devDependencies": {
"babel-cli": "^6.6.5",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-2": "^6.5.0",
"create-react-class": "^15.6.0",
"jsdom": "^6.5.1",
"nodeunit": "^0.9.1",
"prop-types": "^15.5.10",
"react": "^15.0.0",
"react-addons-pure-render-mixin": "^15.0.0",
"react-addons-test-utils": "^15.0.0",
"react-dom": "^15.0.0",
"sinon": "^1.17.3",
"webpack": "^1.12.14",
"webpack-dev-server": "^1.14.1"
"babel-cli": "^6.24.1",
"babel-loader": "^7.1.1",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"eslint": "^3.19.0 || ^4.3.0",
"eslint-config-airbnb": "^15.1.0",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-jsx-a11y": "^5.1.1",
"eslint-plugin-react": "^7.1.0",
"jsdom": "^11.1.0",
"nodeunit": "^0.11.1",
"react": "^15.6.1",
"react-addons-pure-render-mixin": "^15.6.0",
"react-addons-test-utils": "^15.6.0",
"react-dom": "^15.6.1",
"sinon": "^3.2.0",
"webpack": "^3.5.4",
"webpack-dev-server": "^2.7.1"
},
"peerDependencies": {
"react": "^0.14.0 || ^15.0.0"
"react": "^15.6.1",
"react-dom": "^15.6.1"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,30 +0,0 @@
var React = global.React || require('react');
var createReactClass = require('create-react-class');
var Mixin = require('./Mixin.js');
module.exports = function () {
return function (Component) {
return createReactClass({
mixins: [Mixin],
render: function () {
return React.createElement(Component, {
setValidations: this.setValidations,
setValue: this.setValue,
resetValue: this.resetValue,
getValue: this.getValue,
hasValue: this.hasValue,
getErrorMessage: this.getErrorMessage,
getErrorMessages: this.getErrorMessages,
isFormDisabled: this.isFormDisabled,
isValid: this.isValid,
isPristine: this.isPristine,
isFormSubmitted: this.isFormSubmitted,
isRequired: this.isRequired,
showRequired: this.showRequired,
showError: this.showError,
isValidValue: this.isValidValue,
...this.props
});
}
});
};
};

View File

@ -1,44 +0,0 @@
var React = global.React || require('react');
var createReactClass = require('create-react-class');
var Mixin = require('./Mixin.js');
module.exports = function (Component) {
return createReactClass({
displayName: 'Formsy(' + getDisplayName(Component) + ')',
mixins: [Mixin],
render: function () {
const { innerRef } = this.props;
const propsForElement = {
setValidations: this.setValidations,
setValue: this.setValue,
resetValue: this.resetValue,
getValue: this.getValue,
hasValue: this.hasValue,
getErrorMessage: this.getErrorMessage,
getErrorMessages: this.getErrorMessages,
isFormDisabled: this.isFormDisabled,
isValid: this.isValid,
isPristine: this.isPristine,
isFormSubmitted: this.isFormSubmitted,
isRequired: this.isRequired,
showRequired: this.showRequired,
showError: this.showError,
isValidValue: this.isValidValue,
...this.props
};
if (innerRef) {
propsForElement.ref = innerRef;
}
return React.createElement(Component, propsForElement);
}
});
};
function getDisplayName(Component) {
return (
Component.displayName ||
Component.name ||
(typeof Component === 'string' ? Component : 'Component')
);
}

View File

@ -1,176 +0,0 @@
var PropTypes = require('prop-types');
var utils = require('./utils.js');
var React = global.React || require('react');
var convertValidationsToObject = function (validations) {
if (typeof validations === 'string') {
return validations.split(/\,(?![^{\[]*[}\]])/g).reduce(function (validations, validation) {
var args = validation.split(':');
var validateMethod = args.shift();
args = args.map(function (arg) {
try {
return JSON.parse(arg);
} catch (e) {
return arg; // It is a string if it can not parse it
}
});
if (args.length > 1) {
throw new Error('Formsy does not support multiple args on string validations. Use object format of validations instead.');
}
validations[validateMethod] = args.length ? args[0] : true;
return validations;
}, {});
}
return validations || {};
};
module.exports = {
getInitialState: function () {
return {
_value: this.props.value,
_isRequired: false,
_isValid: true,
_isPristine: true,
_pristineValue: this.props.value,
_validationError: [],
_externalError: null,
_formSubmitted: false
};
},
contextTypes: {
formsy: PropTypes.object // What about required?
},
getDefaultProps: function () {
return {
validationError: '',
validationErrors: {}
};
},
componentWillMount: function () {
var configure = function () {
this.setValidations(this.props.validations, this.props.required);
// Pass a function instead?
this.context.formsy.attachToForm(this);
//this.props._attachToForm(this);
}.bind(this);
if (!this.props.name) {
throw new Error('Form Input requires a name property when used');
}
/*
if (!this.props._attachToForm) {
return setTimeout(function () {
if (!this.isMounted()) return;
if (!this.props._attachToForm) {
throw new Error('Form Mixin requires component to be nested in a Form');
}
configure();
}.bind(this), 0);
}
*/
configure();
},
// We have to make the validate method is kept when new props are added
componentWillReceiveProps: function (nextProps) {
this.setValidations(nextProps.validations, nextProps.required);
},
componentDidUpdate: function (prevProps) {
// If the value passed has changed, set it. If value is not passed it will
// internally update, and this will never run
if (!utils.isSame(this.props.value, prevProps.value)) {
this.setValue(this.props.value);
}
// If validations or required is changed, run a new validation
if (!utils.isSame(this.props.validations, prevProps.validations) || !utils.isSame(this.props.required, prevProps.required)) {
this.context.formsy.validate(this);
}
},
// Detach it when component unmounts
componentWillUnmount: function () {
this.context.formsy.detachFromForm(this);
//this.props._detachFromForm(this);
},
setValidations: function (validations, required) {
// Add validations to the store itself as the props object can not be modified
this._validations = convertValidationsToObject(validations) || {};
this._requiredValidations = required === true ? {isDefaultRequiredValue: true} : convertValidationsToObject(required);
},
// We validate after the value has been set
setValue: function (value) {
this.setState({
_value: value,
_isPristine: false
}, function () {
this.context.formsy.validate(this);
//this.props._validate(this);
}.bind(this));
},
resetValue: function () {
this.setState({
_value: this.state._pristineValue,
_isPristine: true
}, function () {
this.context.formsy.validate(this);
//this.props._validate(this);
});
},
getValue: function () {
return this.state._value;
},
hasValue: function () {
return this.state._value !== '';
},
getErrorMessage: function () {
var messages = this.getErrorMessages();
return messages.length ? messages[0] : null;
},
getErrorMessages: function () {
return !this.isValid() || this.showRequired() ? (this.state._externalError || this.state._validationError || []) : [];
},
isFormDisabled: function () {
return this.context.formsy.isFormDisabled();
//return this.props._isFormDisabled();
},
isValid: function () {
return this.state._isValid;
},
isPristine: function () {
return this.state._isPristine;
},
isFormSubmitted: function () {
return this.state._formSubmitted;
},
isRequired: function () {
return !!this.props.required;
},
showRequired: function () {
return this.state._isRequired;
},
showError: function () {
return !this.showRequired() && !this.isValid();
},
isValidValue: function (value) {
return this.context.formsy.isValidValue.call(null, this, value);
//return this.props._isValidValue.call(null, this, value);
}
};

255
src/Wrapper.js Normal file
View File

@ -0,0 +1,255 @@
import PropTypes from 'prop-types';
import React from 'react';
import utils from './utils';
const convertValidationsToObject = (validations) => {
if (typeof validations === 'string') {
return validations.split(/,(?![^{[]*[}\]])/g).reduce((validationsAccumulator, validation) => {
let args = validation.split(':');
const validateMethod = args.shift();
args = args.map((arg) => {
try {
return JSON.parse(arg);
} catch (e) {
return arg; // It is a string if it can not parse it
}
});
if (args.length > 1) {
throw new Error('Formsy does not support multiple args on string validations. Use object format of validations instead.');
}
// Avoid parameter reassignment
const validationsAccumulatorCopy = Object.assign({}, validationsAccumulator);
validationsAccumulatorCopy[validateMethod] = args.length ? args[0] : true;
return validationsAccumulatorCopy;
}, {});
}
return validations || {};
};
const propTypes = {
innerRef: PropTypes.func,
name: PropTypes.string.isRequired,
required: PropTypes.bool,
validations: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string,
]),
value: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.string,
]),
};
export {
propTypes,
};
export default (Component) => {
class WrappedComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
value: props.value,
isRequired: false,
isValid: true,
isPristine: true,
pristineValue: props.value,
validationError: [],
externalError: null,
formSubmitted: false,
};
this.getErrorMessage = this.getErrorMessage.bind(this);
this.getErrorMessages = this.getErrorMessages.bind(this);
this.getValue = this.getValue.bind(this);
this.isFormDisabled = this.isFormDisabled.bind(this);
this.isPristine = this.isPristine.bind(this);
this.isRequired = this.isRequired.bind(this);
this.isValid = this.isValid.bind(this);
this.resetValue = this.resetValue.bind(this);
this.setValue = this.setValue.bind(this);
this.showRequired = this.showRequired.bind(this);
}
componentWillMount() {
const configure = () => {
this.setValidations(this.props.validations, this.props.required);
// Pass a function instead?
this.context.formsy.attachToForm(this);
};
if (!this.props.name) {
throw new Error('Form Input requires a name property when used');
}
configure();
}
// We have to make sure the validate method is kept when new props are added
componentWillReceiveProps(nextProps) {
this.setValidations(nextProps.validations, nextProps.required);
}
componentDidUpdate(prevProps) {
// If the value passed has changed, set it. If value is not passed it will
// internally update, and this will never run
if (!utils.isSame(this.props.value, prevProps.value)) {
this.setValue(this.props.value);
}
// If validations or required is changed, run a new validation
if (!utils.isSame(this.props.validations, prevProps.validations) ||
!utils.isSame(this.props.required, prevProps.required)) {
this.context.formsy.validate(this);
}
}
// Detach it when component unmounts
componentWillUnmount() {
this.context.formsy.detachFromForm(this);
}
getErrorMessage() {
const messages = this.getErrorMessages();
return messages.length ? messages[0] : null;
}
getErrorMessages() {
return !this.isValid() || this.showRequired() ?
(this.state.externalError || this.state.validationError || []) : [];
}
getValue() {
return this.state.value;
}
hasValue() {
return this.state.value !== '';
}
isFormDisabled() {
return this.context.formsy.isFormDisabled();
}
isFormSubmitted() {
return this.state.formSubmitted;
}
isPristine() {
return this.state.isPristine;
}
isRequired() {
return !!this.props.required;
}
isValid() {
return this.state.isValid;
}
isValidValue(value) {
return this.context.formsy.isValidValue.call(null, this, value);
// return this.props.isValidValue.call(null, this, value);
}
resetValue() {
this.setState({
value: this.state.pristineValue,
isPristine: true,
}, () => {
this.context.formsy.validate(this);
});
}
setValidations(validations, required) {
// Add validations to the store itself as the props object can not be modified
this.validations = convertValidationsToObject(validations) || {};
this.requiredValidations = required === true ? { isDefaultRequiredValue: true } :
convertValidationsToObject(required);
}
// By default, we validate after the value has been set.
// A user can override this and pass a second parameter of `false` to skip validation.
setValue(value, validate = true) {
if (!validate) {
this.setState({
value,
});
} else {
this.setState({
value,
isPristine: false,
}, () => {
this.context.formsy.validate(this);
});
}
}
showError() {
return !this.showRequired() && !this.isValid();
}
showRequired() {
return this.state.isRequired;
}
render() {
const { innerRef } = this.props;
const propsForElement = {
getErrorMessage: this.getErrorMessage,
getErrorMessages: this.getErrorMessages,
getValue: this.getValue,
hasValue: this.hasValue,
isFormDisabled: this.isFormDisabled,
isValid: this.isValid,
isPristine: this.isPristine,
isFormSubmitted: this.isFormSubmitted,
isRequired: this.isRequired,
isValidValue: this.isValidValue,
resetValue: this.resetValue,
setValidations: this.setValidations,
setValue: this.setValue,
showRequired: this.showRequired,
showError: this.showError,
...this.props,
};
if (innerRef) {
propsForElement.ref = innerRef;
}
return React.createElement(Component, propsForElement);
}
}
function getDisplayName(component) {
return (
component.displayName ||
component.name ||
(typeof component === 'string' ? component : 'Component')
);
}
WrappedComponent.displayName = `Formsy(${getDisplayName(Component)})`;
WrappedComponent.contextTypes = {
formsy: PropTypes.object, // What about required?
};
WrappedComponent.defaultProps = {
innerRef: () => {},
required: false,
validationError: '',
validationErrors: {},
validations: null,
value: Component.defaultValue,
};
WrappedComponent.propTypes = propTypes;
return WrappedComponent;
};

468
src/index.js Normal file
View File

@ -0,0 +1,468 @@
import formDataToObject from 'form-data-to-object';
import PropTypes from 'prop-types';
import React from 'react';
import utils from './utils';
import validationRules from './validationRules';
import Wrapper, { propTypes } from './Wrapper';
class Formsy extends React.Component {
constructor(props) {
super(props);
this.state = {
isValid: true,
isSubmitting: false,
canChange: false,
};
this.inputs = [];
this.attachToForm = this.attachToForm.bind(this);
this.detachFromForm = this.detachFromForm.bind(this);
this.getCurrentValues = this.getCurrentValues.bind(this);
this.getPristineValues = this.getPristineValues.bind(this);
this.isChanged = this.isChanged.bind(this);
this.isFormDisabled = this.isFormDisabled.bind(this);
this.reset = this.reset.bind(this);
this.resetModel = this.resetModel.bind(this);
this.resetInternal = this.resetInternal.bind(this);
this.runValidation = this.runValidation.bind(this);
this.submit = this.submit.bind(this);
this.updateInputsWithError = this.updateInputsWithError.bind(this);
this.validate = this.validate.bind(this);
this.validateForm = this.validateForm.bind(this);
}
getChildContext() {
return {
formsy: {
attachToForm: this.attachToForm,
detachFromForm: this.detachFromForm,
validate: this.validate,
isFormDisabled: this.isFormDisabled,
isValidValue: (component, value) => this.runValidation(component, value).isValid,
},
};
}
componentDidMount() {
this.validateForm();
}
componentWillUpdate() {
// Keep a reference to input names before form updates,
// to check if inputs has changed after render
this.prevInputNames = this.inputs.map(component => component.props.name);
}
componentDidUpdate() {
if (this.props.validationErrors && typeof this.props.validationErrors === 'object' && Object.keys(this.props.validationErrors).length > 0) {
this.setInputValidationErrors(this.props.validationErrors);
}
const newInputNames = this.inputs.map(component => component.props.name);
if (utils.arraysDiffer(this.prevInputNames, newInputNames)) {
this.validateForm();
}
}
// Method put on each input component to register
// itself to the form
attachToForm(component) {
if (this.inputs.indexOf(component) === -1) {
this.inputs.push(component);
}
this.validate(component);
}
// Method put on each input component to unregister
// itself from the form
detachFromForm(component) {
const componentPos = this.inputs.indexOf(component);
if (componentPos !== -1) {
this.inputs = this.inputs.slice(0, componentPos).concat(this.inputs.slice(componentPos + 1));
}
this.validateForm();
}
getCurrentValues() {
return this.inputs.reduce((data, component) => {
const name = component.props.name;
const dataCopy = Object.assign({}, data); // avoid param reassignment
dataCopy[name] = component.state.value;
return dataCopy;
}, {});
}
getModel() {
const currentValues = this.getCurrentValues();
return this.mapModel(currentValues);
}
getPristineValues() {
return this.inputs.reduce((data, component) => {
const name = component.props.name;
const dataCopy = Object.assign({}, data); // avoid param reassignment
dataCopy[name] = component.props.value;
return dataCopy;
}, {});
}
// Checks if the values have changed from their initial value
isChanged() {
return !utils.isSame(this.getPristineValues(), this.getCurrentValues());
}
isFormDisabled() {
return this.props.disabled;
}
mapModel(model) {
if (this.props.mapping) {
return this.props.mapping(model);
}
return formDataToObject.toObj(Object.keys(model).reduce((mappedModel, key) => {
const keyArray = key.split('.');
let base = mappedModel;
while (keyArray.length) {
const currentKey = keyArray.shift();
base[currentKey] = (keyArray.length ? base[currentKey] || {} : model[key]);
base = base[currentKey];
}
return mappedModel;
}, {}));
}
reset(data) {
this.setFormPristine(true);
this.resetModel(data);
}
resetInternal(event) {
event.preventDefault();
this.reset();
if (this.props.onReset) {
this.props.onReset();
}
}
// Reset each key in the model to the original / initial / specified value
resetModel(data) {
this.inputs.forEach((component) => {
const name = component.props.name;
if (data && Object.prototype.hasOwnProperty.call(data, name)) {
component.setValue(data[name]);
} else {
component.resetValue();
}
});
this.validateForm();
}
// Checks validation on current value or a passed value
runValidation(component, value = component.state.value) {
const currentValues = this.getCurrentValues();
const validationErrors = component.props.validationErrors;
const validationError = component.props.validationError;
const validationResults = utils.runRules(
value, currentValues, component.validations, validationRules,
);
const requiredResults = utils.runRules(
value, currentValues, component.requiredValidations, validationRules,
);
const isRequired = Object.keys(component.requiredValidations).length ?
!!requiredResults.success.length : false;
const isValid = !validationResults.failed.length &&
!(this.props.validationErrors && this.props.validationErrors[component.props.name]);
return {
isRequired,
isValid: isRequired ? false : isValid,
error: (() => {
if (isValid && !isRequired) {
return [];
}
if (validationResults.errors.length) {
return validationResults.errors;
}
if (this.props.validationErrors && this.props.validationErrors[component.props.name]) {
return typeof this.props.validationErrors[component.props.name] === 'string' ? [this.props.validationErrors[component.props.name]] : this.props.validationErrors[component.props.name];
}
if (isRequired) {
const error = validationErrors[requiredResults.success[0]];
return error ? [error] : null;
}
if (validationResults.failed.length) {
return validationResults.failed.map(failed =>
(validationErrors[failed] ? validationErrors[failed] : validationError))
.filter((x, pos, arr) => arr.indexOf(x) === pos); // remove duplicates
}
return undefined;
})(),
};
}
setInputValidationErrors(errors) {
this.inputs.forEach((component) => {
const name = component.props.name;
const args = [{
isValid: !(name in errors),
validationError: typeof errors[name] === 'string' ? [errors[name]] : errors[name],
}];
component.setState(...args);
});
}
setFormPristine(isPristine) {
this.setState({
formSubmitted: !isPristine,
});
// Iterate through each component and set it as pristine
// or "dirty".
this.inputs.forEach((component) => {
component.setState({
formSubmitted: !isPristine,
isPristine,
});
});
}
// Update model, submit to url prop and send the model
submit(event) {
if (event && event.preventDefault) {
event.preventDefault();
}
// Trigger form as not pristine.
// If any inputs have not been touched yet this will make them dirty
// so validation becomes visible (if based on isPristine)
this.setFormPristine(false);
const model = this.getModel();
this.props.onSubmit(model, this.resetModel, this.updateInputsWithError);
if (this.state.isValid) {
this.props.onValidSubmit(model, this.resetModel, this.updateInputsWithError);
} else {
this.props.onInvalidSubmit(model, this.resetModel, this.updateInputsWithError);
}
}
// Go through errors from server and grab the components
// stored in the inputs map. Change their state to invalid
// and set the serverError message
updateInputsWithError(errors) {
Object.keys(errors).forEach((name) => {
const component = utils.find(this.inputs, input => input.props.name === name);
if (!component) {
throw new Error(`You are trying to update an input that does not exist. Verify errors object with input names. ${JSON.stringify(errors)}`);
}
const args = [{
isValid: this.props.preventExternalInvalidation,
externalError: typeof errors[name] === 'string' ? [errors[name]] : errors[name],
}];
component.setState(...args);
});
}
// Use the binded values and the actual input value to
// validate the input and set its state. Then check the
// state of the form itself
validate(component) {
// Trigger onChange
if (this.state.canChange) {
this.props.onChange(this.getCurrentValues(), this.isChanged());
}
const validation = this.runValidation(component);
// Run through the validations, split them up and call
// the validator IF there is a value or it is required
component.setState({
isValid: validation.isValid,
isRequired: validation.isRequired,
validationError: validation.error,
externalError: null,
}, this.validateForm);
}
// Validate the form by going through all child input components
// and check their state
validateForm() {
// We need a callback as we are validating all inputs again. This will
// run when the last component has set its state
const onValidationComplete = () => {
const allIsValid = this.inputs.every(component => component.state.isValid);
this.setState({
isValid: allIsValid,
});
if (allIsValid) {
this.props.onValid();
} else {
this.props.onInvalid();
}
// Tell the form that it can start to trigger change events
this.setState({
canChange: true,
});
};
// Run validation again in case affected by other inputs. The
// last component validated will run the onValidationComplete callback
this.inputs.forEach((component, index) => {
const validation = this.runValidation(component);
if (validation.isValid && component.state.externalError) {
validation.isValid = false;
}
component.setState({
isValid: validation.isValid,
isRequired: validation.isRequired,
validationError: validation.error,
externalError: !validation.isValid && component.state.externalError ?
component.state.externalError : null,
}, index === this.inputs.length - 1 ? onValidationComplete : null);
});
// If there are no inputs, set state where form is ready to trigger
// change event. New inputs might be added later
if (!this.inputs.length) {
this.setState({
canChange: true,
});
}
}
render() {
const {
getErrorMessage,
getErrorMessages,
getValue,
hasValue,
isFormDisabled,
isFormSubmitted,
isPristine,
isRequired,
isValid,
isValidValue,
mapping,
onChange,
// onError,
onInvalidSubmit,
onInvalid,
onReset,
onSubmit,
onValid,
onValidSubmit,
preventExternalInvalidation,
// reset,
resetValue,
setValidations,
setValue,
showError,
showRequired,
validationErrors,
...nonFormsyProps
} = this.props;
return React.createElement(
'form',
{
onReset: this.resetInternal,
onSubmit: this.submit,
...nonFormsyProps,
},
this.props.children,
);
}
}
Formsy.displayName = 'Formsy';
Formsy.defaultProps = {
children: null,
disabled: false,
getErrorMessage: () => {},
getErrorMessages: () => {},
getValue: () => {},
hasValue: () => {},
isFormDisabled: () => {},
isFormSubmitted: () => {},
isPristine: () => {},
isRequired: () => {},
isValid: () => {},
isValidValue: () => {},
mapping: null,
onChange: () => {},
onError: () => {},
onInvalid: () => {},
onInvalidSubmit: () => {},
onReset: () => {},
onSubmit: () => {},
onValid: () => {},
onValidSubmit: () => {},
preventExternalInvalidation: false,
resetValue: () => {},
setValidations: () => {},
setValue: () => {},
showError: () => {},
showRequired: () => {},
validationErrors: null,
};
Formsy.propTypes = {
children: PropTypes.node,
disabled: PropTypes.bool,
getErrorMessage: PropTypes.func,
getErrorMessages: PropTypes.func,
getValue: PropTypes.func,
hasValue: PropTypes.func,
isFormDisabled: PropTypes.func,
isFormSubmitted: PropTypes.func,
isPristine: PropTypes.func,
isRequired: PropTypes.func,
isValid: PropTypes.func,
isValidValue: PropTypes.func,
mapping: PropTypes.object, // eslint-disable-line
preventExternalInvalidation: PropTypes.bool,
onChange: PropTypes.func,
onInvalid: PropTypes.func,
onInvalidSubmit: PropTypes.func,
onReset: PropTypes.func,
onSubmit: PropTypes.func,
onValid: PropTypes.func,
onValidSubmit: PropTypes.func,
resetValue: PropTypes.func,
setValidations: PropTypes.func,
setValue: PropTypes.func,
showError: PropTypes.func,
showRequired: PropTypes.func,
validationErrors: PropTypes.object, // eslint-disable-line
};
Formsy.childContextTypes = {
formsy: PropTypes.object,
};
const addValidationRule = (name, func) => {
validationRules[name] = func;
};
const withFormsy = Wrapper;
export {
addValidationRule,
propTypes,
withFormsy,
};
export default Formsy;

View File

@ -1,457 +0,0 @@
var PropTypes = require('prop-types');
var React = global.React || require('react');
var createReactClass = require('create-react-class');
var Formsy = {};
var validationRules = require('./validationRules.js');
var formDataToObject = require('form-data-to-object');
var utils = require('./utils.js');
var Mixin = require('./Mixin.js');
var HOC = require('./HOC.js');
var Decorator = require('./Decorator.js');
var options = {};
var emptyArray = [];
Formsy.Mixin = Mixin;
Formsy.HOC = HOC;
Formsy.Decorator = Decorator;
Formsy.defaults = function (passedOptions) {
options = passedOptions;
};
Formsy.addValidationRule = function (name, func) {
validationRules[name] = func;
};
Formsy.Form = createReactClass({
displayName: 'Formsy',
getInitialState: function () {
return {
isValid: true,
isSubmitting: false,
canChange: false
};
},
getDefaultProps: function () {
return {
onSuccess: function () {},
onError: function () {},
onSubmit: function () {},
onValidSubmit: function () {},
onInvalidSubmit: function () {},
onValid: function () {},
onInvalid: function () {},
onChange: function () {},
validationErrors: null,
preventExternalInvalidation: false
};
},
childContextTypes: {
formsy: PropTypes.object
},
getChildContext: function () {
return {
formsy: {
attachToForm: this.attachToForm,
detachFromForm: this.detachFromForm,
validate: this.validate,
isFormDisabled: this.isFormDisabled,
isValidValue: (component, value) => {
return this.runValidation(component, value).isValid;
}
}
}
},
// Add a map to store the inputs of the form, a model to store
// the values of the form and register child inputs
componentWillMount: function () {
this.inputs = [];
},
componentDidMount: function () {
this.validateForm();
},
componentWillUpdate: function () {
// Keep a reference to input names before form updates,
// to check if inputs has changed after render
this.prevInputNames = this.inputs.map(component => component.props.name);
},
componentDidUpdate: function () {
if (this.props.validationErrors && typeof this.props.validationErrors === 'object' && Object.keys(this.props.validationErrors).length > 0) {
this.setInputValidationErrors(this.props.validationErrors);
}
var newInputNames = this.inputs.map(component => component.props.name);
if (utils.arraysDiffer(this.prevInputNames, newInputNames)) {
this.validateForm();
}
},
// Allow resetting to specified data
reset: function (data) {
this.setFormPristine(true);
this.resetModel(data);
},
// Update model, submit to url prop and send the model
submit: function (event) {
event && event.preventDefault();
// Trigger form as not pristine.
// If any inputs have not been touched yet this will make them dirty
// so validation becomes visible (if based on isPristine)
this.setFormPristine(false);
var model = this.getModel();
this.props.onSubmit(model, this.resetModel, this.updateInputsWithError);
this.state.isValid ? this.props.onValidSubmit(model, this.resetModel, this.updateInputsWithError) : this.props.onInvalidSubmit(model, this.resetModel, this.updateInputsWithError);
},
mapModel: function (model) {
if (this.props.mapping) {
return this.props.mapping(model)
} else {
return formDataToObject.toObj(Object.keys(model).reduce((mappedModel, key) => {
var keyArray = key.split('.');
var base = mappedModel;
while (keyArray.length) {
var currentKey = keyArray.shift();
base = (base[currentKey] = keyArray.length ? base[currentKey] || {} : model[key]);
}
return mappedModel;
}, {}));
}
},
getModel: function () {
var currentValues = this.getCurrentValues();
return this.mapModel(currentValues);
},
// Reset each key in the model to the original / initial / specified value
resetModel: function (data) {
this.inputs.forEach(component => {
var name = component.props.name;
if (data && data.hasOwnProperty(name)) {
component.setValue(data[name]);
} else {
component.resetValue();
}
});
this.validateForm();
},
setInputValidationErrors: function (errors) {
this.inputs.forEach(component => {
var name = component.props.name;
var args = [{
_isValid: !(name in errors),
_validationError: typeof errors[name] === 'string' ? [errors[name]] : errors[name]
}];
component.setState.apply(component, args);
});
},
// Checks if the values have changed from their initial value
isChanged: function() {
return !utils.isSame(this.getPristineValues(), this.getCurrentValues());
},
getPristineValues: function() {
return this.inputs.reduce((data, component) => {
var name = component.props.name;
data[name] = component.props.value;
return data;
}, {});
},
// Go through errors from server and grab the components
// stored in the inputs map. Change their state to invalid
// and set the serverError message
updateInputsWithError: function (errors) {
Object.keys(errors).forEach((name, index) => {
var component = utils.find(this.inputs, component => component.props.name === name);
if (!component) {
throw new Error('You are trying to update an input that does not exist. ' +
'Verify errors object with input names. ' + JSON.stringify(errors));
}
var args = [{
_isValid: this.props.preventExternalInvalidation || false,
_externalError: typeof errors[name] === 'string' ? [errors[name]] : errors[name]
}];
component.setState.apply(component, args);
});
},
isFormDisabled: function () {
return this.props.disabled;
},
getCurrentValues: function () {
return this.inputs.reduce((data, component) => {
var name = component.props.name;
data[name] = component.state._value;
return data;
}, {});
},
setFormPristine: function (isPristine) {
this.setState({
_formSubmitted: !isPristine
});
// Iterate through each component and set it as pristine
// or "dirty".
this.inputs.forEach((component, index) => {
component.setState({
_formSubmitted: !isPristine,
_isPristine: isPristine
});
});
},
// Use the binded values and the actual input value to
// validate the input and set its state. Then check the
// state of the form itself
validate: function (component) {
// Trigger onChange
if (this.state.canChange) {
this.props.onChange(this.getCurrentValues(), this.isChanged());
}
var validation = this.runValidation(component);
// Run through the validations, split them up and call
// the validator IF there is a value or it is required
component.setState({
_isValid: validation.isValid,
_isRequired: validation.isRequired,
_validationError: validation.error,
_externalError: null
}, this.validateForm);
},
// Checks validation on current value or a passed value
runValidation: function (component, value) {
var currentValues = this.getCurrentValues();
var validationErrors = component.props.validationErrors;
var validationError = component.props.validationError;
value = arguments.length === 2 ? value : component.state._value;
var validationResults = this.runRules(value, currentValues, component._validations);
var requiredResults = this.runRules(value, currentValues, component._requiredValidations);
// the component defines an explicit validate function
if (typeof component.validate === "function") {
validationResults.failed = component.validate() ? [] : ['failed'];
}
var isRequired = Object.keys(component._requiredValidations).length ? !!requiredResults.success.length : false;
var isValid = !validationResults.failed.length && !(this.props.validationErrors && this.props.validationErrors[component.props.name]);
return {
isRequired: isRequired,
isValid: isRequired ? false : isValid,
error: (function () {
if (isValid && !isRequired) {
return emptyArray;
}
if (validationResults.errors.length) {
return validationResults.errors;
}
if (this.props.validationErrors && this.props.validationErrors[component.props.name]) {
return typeof this.props.validationErrors[component.props.name] === 'string' ? [this.props.validationErrors[component.props.name]] : this.props.validationErrors[component.props.name];
}
if (isRequired) {
var error = validationErrors[requiredResults.success[0]];
return error ? [error] : null;
}
if (validationResults.failed.length) {
return validationResults.failed.map(function(failed) {
return validationErrors[failed] ? validationErrors[failed] : validationError;
}).filter(function(x, pos, arr) {
// Remove duplicates
return arr.indexOf(x) === pos;
});
}
}.call(this))
};
},
runRules: function (value, currentValues, validations) {
var results = {
errors: [],
failed: [],
success: []
};
if (Object.keys(validations).length) {
Object.keys(validations).forEach(function (validationMethod) {
if (validationRules[validationMethod] && typeof validations[validationMethod] === 'function') {
throw new Error('Formsy does not allow you to override default validations: ' + validationMethod);
}
if (!validationRules[validationMethod] && typeof validations[validationMethod] !== 'function') {
throw new Error('Formsy does not have the validation rule: ' + validationMethod);
}
if (typeof validations[validationMethod] === 'function') {
var validation = validations[validationMethod](currentValues, value);
if (typeof validation === 'string') {
results.errors.push(validation);
results.failed.push(validationMethod);
} else if (!validation) {
results.failed.push(validationMethod);
}
return;
} else if (typeof validations[validationMethod] !== 'function') {
var validation = validationRules[validationMethod](currentValues, value, validations[validationMethod]);
if (typeof validation === 'string') {
results.errors.push(validation);
results.failed.push(validationMethod);
} else if (!validation) {
results.failed.push(validationMethod);
} else {
results.success.push(validationMethod);
}
return;
}
return results.success.push(validationMethod);
});
}
return results;
},
// Validate the form by going through all child input components
// and check their state
validateForm: function () {
// We need a callback as we are validating all inputs again. This will
// run when the last component has set its state
var onValidationComplete = function () {
var allIsValid = this.inputs.every(component => {
return component.state._isValid;
});
this.setState({
isValid: allIsValid
});
if (allIsValid) {
this.props.onValid();
} else {
this.props.onInvalid();
}
// Tell the form that it can start to trigger change events
this.setState({
canChange: true
});
}.bind(this);
// Run validation again in case affected by other inputs. The
// last component validated will run the onValidationComplete callback
this.inputs.forEach((component, index) => {
var validation = this.runValidation(component);
if (validation.isValid && component.state._externalError) {
validation.isValid = false;
}
component.setState({
_isValid: validation.isValid,
_isRequired: validation.isRequired,
_validationError: validation.error,
_externalError: !validation.isValid && component.state._externalError ? component.state._externalError : null
}, index === this.inputs.length - 1 ? onValidationComplete : null);
});
// If there are no inputs, set state where form is ready to trigger
// change event. New inputs might be added later
if (!this.inputs.length) {
this.setState({
canChange: true
});
}
},
// Method put on each input component to register
// itself to the form
attachToForm: function (component) {
if (this.inputs.indexOf(component) === -1) {
this.inputs.push(component);
}
this.validate(component);
},
// Method put on each input component to unregister
// itself from the form
detachFromForm: function (component) {
var componentPos = this.inputs.indexOf(component);
if (componentPos !== -1) {
this.inputs = this.inputs.slice(0, componentPos)
.concat(this.inputs.slice(componentPos + 1));
}
this.validateForm();
},
render: function () {
var {
mapping,
validationErrors,
onSubmit,
onValid,
onValidSubmit,
onInvalid,
onInvalidSubmit,
onChange,
reset,
preventExternalInvalidation,
onSuccess,
onError,
...nonFormsyProps
} = this.props;
return (
<form {...nonFormsyProps} onSubmit={this.submit}>
{this.props.children}
</form>
);
}
});
if (!global.exports && !global.module && (!global.define || !global.define.amd)) {
global.Formsy = Formsy;
}
module.exports = Formsy;

View File

@ -1,10 +1,10 @@
module.exports = {
arraysDiffer: function (a, b) {
var isDifferent = false;
export default {
arraysDiffer(a, b) {
let isDifferent = false;
if (a.length !== b.length) {
isDifferent = true;
} else {
a.forEach(function (item, index) {
a.forEach((item, index) => {
if (!this.isSame(item, b[index])) {
isDifferent = true;
}
@ -13,12 +13,12 @@ module.exports = {
return isDifferent;
},
objectsDiffer: function (a, b) {
var isDifferent = false;
objectsDiffer(a, b) {
let isDifferent = false;
if (Object.keys(a).length !== Object.keys(b).length) {
isDifferent = true;
} else {
Object.keys(a).forEach(function (key) {
Object.keys(a).forEach((key) => {
if (!this.isSame(a[key], b[key])) {
isDifferent = true;
}
@ -27,7 +27,7 @@ module.exports = {
return isDifferent;
},
isSame: function (a, b) {
isSame(a, b) {
if (typeof a !== typeof b) {
return false;
} else if (Array.isArray(a) && Array.isArray(b)) {
@ -41,13 +41,61 @@ module.exports = {
return a === b;
},
find: function (collection, fn) {
for (var i = 0, l = collection.length; i < l; i++) {
var item = collection[i];
find(collection, fn) {
for (let i = 0, l = collection.length; i < l; i += 1) {
const item = collection[i];
if (fn(item)) {
return item;
}
}
return null;
}
},
runRules(value, currentValues, validations, validationRules) {
const results = {
errors: [],
failed: [],
success: [],
};
if (Object.keys(validations).length) {
Object.keys(validations).forEach((validationMethod) => {
if (validationRules[validationMethod] && typeof validations[validationMethod] === 'function') {
throw new Error(`Formsy does not allow you to override default validations: ${validationMethod}`);
}
if (!validationRules[validationMethod] && typeof validations[validationMethod] !== 'function') {
throw new Error(`Formsy does not have the validation rule: ${validationMethod}`);
}
if (typeof validations[validationMethod] === 'function') {
const validation = validations[validationMethod](currentValues, value);
if (typeof validation === 'string') {
results.errors.push(validation);
results.failed.push(validationMethod);
} else if (!validation) {
results.failed.push(validationMethod);
}
return;
} else if (typeof validations[validationMethod] !== 'function') {
const validation = validationRules[validationMethod](
currentValues, value, validations[validationMethod],
);
if (typeof validation === 'string') {
results.errors.push(validation);
results.failed.push(validationMethod);
} else if (!validation) {
results.failed.push(validationMethod);
} else {
results.success.push(validationMethod);
}
return;
}
results.success.push(validationMethod);
});
}
return results;
},
};

View File

@ -1,78 +1,73 @@
var isExisty = function (value) {
return value !== null && value !== undefined;
};
const isExisty = value => value !== null && value !== undefined;
const isEmpty = value => value === '';
var isEmpty = function (value) {
return value === '';
};
var validations = {
isDefaultRequiredValue: function (values, value) {
const validations = {
isDefaultRequiredValue(values, value) {
return value === undefined || value === '';
},
isExisty: function (values, value) {
isExisty(values, value) {
return isExisty(value);
},
matchRegexp: function (values, value, regexp) {
matchRegexp(values, value, regexp) {
return !isExisty(value) || isEmpty(value) || regexp.test(value);
},
isUndefined: function (values, value) {
isUndefined(values, value) {
return value === undefined;
},
isEmptyString: function (values, value) {
isEmptyString(values, value) {
return isEmpty(value);
},
isEmail: function (values, value) {
return validations.matchRegexp(values, value, /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i);
isEmail(values, value) {
return validations.matchRegexp(values, value, /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i);
},
isUrl: function (values, value) {
return validations.matchRegexp(values, value, /^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i);
isUrl(values, value) {
return validations.matchRegexp(values, value, /^(?:\w+:)?\/\/([^\s.]+\.\S{2}|localhost[:?\d]*)\S*$/i);
},
isTrue: function (values, value) {
isTrue(values, value) {
return value === true;
},
isFalse: function (values, value) {
isFalse(values, value) {
return value === false;
},
isNumeric: function (values, value) {
isNumeric(values, value) {
if (typeof value === 'number') {
return true;
}
return validations.matchRegexp(values, value, /^[-+]?(?:\d*[.])?\d+$/);
},
isAlpha: function (values, value) {
isAlpha(values, value) {
return validations.matchRegexp(values, value, /^[A-Z]+$/i);
},
isAlphanumeric: function (values, value) {
isAlphanumeric(values, value) {
return validations.matchRegexp(values, value, /^[0-9A-Z]+$/i);
},
isInt: function (values, value) {
isInt(values, value) {
return validations.matchRegexp(values, value, /^(?:[-+]?(?:0|[1-9]\d*))$/);
},
isFloat: function (values, value) {
return validations.matchRegexp(values, value, /^(?:[-+]?(?:\d+))?(?:\.\d*)?(?:[eE][\+\-]?(?:\d+))?$/);
isFloat(values, value) {
return validations.matchRegexp(values, value, /^(?:[-+]?(?:\d+))?(?:\.\d*)?(?:[eE][+-]?(?:\d+))?$/);
},
isWords: function (values, value) {
isWords(values, value) {
return validations.matchRegexp(values, value, /^[A-Z\s]+$/i);
},
isSpecialWords: function (values, value) {
isSpecialWords(values, value) {
return validations.matchRegexp(values, value, /^[A-Z\s\u00C0-\u017F]+$/i);
},
isLength: function (values, value, length) {
isLength(values, value, length) {
return !isExisty(value) || isEmpty(value) || value.length === length;
},
equals: function (values, value, eql) {
return !isExisty(value) || isEmpty(value) || value == eql;
equals(values, value, eql) {
return !isExisty(value) || isEmpty(value) || value === eql;
},
equalsField: function (values, value, field) {
return value == values[field];
equalsField(values, value, field) {
return value === values[field];
},
maxLength: function (values, value, length) {
maxLength(values, value, length) {
return !isExisty(value) || value.length <= length;
},
minLength: function (values, value, length) {
minLength(values, value, length) {
return !isExisty(value) || isEmpty(value) || value.length >= length;
}
},
};
module.exports = validations;
export default validations;

View File

@ -1,22 +1,21 @@
import path from 'path';
import testrunner from 'nodeunit/lib/reporters/default.js';
import {jsdom} from 'jsdom';
import testrunner from 'nodeunit/lib/reporters/default';
import { jsdom } from 'jsdom/lib/old-api';
global.document = jsdom();
global.window = document.defaultView;
global.window = global.document.defaultView;
global.navigator = global.window.navigator;
testrunner.run(['tests'], {
"error_prefix": "\u001B[31m",
"error_suffix": "\u001B[39m",
"ok_prefix": "\u001B[32m",
"ok_suffix": "\u001B[39m",
"bold_prefix": "\u001B[1m",
"bold_suffix": "\u001B[22m",
"assertion_prefix": "\u001B[35m",
"assertion_suffix": "\u001B[39m"
}, function(err) {
if (err) {
process.exit(1);
}
error_prefix: '\u001B[31m',
error_suffix: '\u001B[39m',
ok_prefix: '\u001B[32m',
ok_suffix: '\u001B[39m',
bold_prefix: '\u001B[1m',
bold_suffix: '\u001B[22m',
assertion_prefix: '\u001B[35m',
assertion_suffix: '\u001B[39m',
}, (err) => {
if (err) {
process.exit(1);
}
});

View File

@ -1,9 +1,9 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TestUtils from 'react-dom/test-utils';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import sinon from 'sinon';
import Formsy from './..';
import Formsy, { withFormsy } from './..';
import TestInput, { InputFactory } from './utils/TestInput';
import immediate from './utils/immediate';
@ -12,9 +12,9 @@ export default {
'should return passed and setValue() value when using getValue()': function (test) {
const form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Formsy>
<TestInput name="foo" value="foo"/>
</Formsy.Form>
</Formsy>
);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
@ -26,18 +26,45 @@ export default {
},
'should only set the value and not validate when calling setValue(val, false)': function (test) {
const Input = withFormsy(class TestInput extends React.Component {
updateValue = (event) => {
this.props.setValue(event.target.value, false);
}
render() {
return <input type="text" value={this.props.getValue()} onChange={this.updateValue}/>;
}
})
const form = TestUtils.renderIntoDocument(
<Formsy>
<Input name="foo" value="foo" innerRef="comp" />
</Formsy>
);
const inputComponent = TestUtils.findRenderedComponentWithType(form, Input);
const setStateSpy = sinon.spy(inputComponent, 'setState');
const inputElement = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
test.equal(setStateSpy.called, false);
TestUtils.Simulate.change(inputElement, {target: {value: 'foobar'}});
test.equal(setStateSpy.calledOnce, true);
test.equal(setStateSpy.calledWithExactly({ value: 'foobar' }), true);
test.done();
},
'should set back to pristine value when running reset': function (test) {
let reset = null;
const Input = InputFactory({
componentDidMount() {
reset = this.resetValue;
componentDidMount: function() {
reset = this.props.resetValue;
}
});
const form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Formsy>
<Input name="foo" value="foo"/>
</Formsy.Form>
</Formsy>
);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
@ -53,14 +80,14 @@ export default {
let getErrorMessage = null;
const Input = InputFactory({
componentDidMount() {
getErrorMessage = this.getErrorMessage;
componentDidMount: function() {
getErrorMessage = this.props.getErrorMessage;
}
});
TestUtils.renderIntoDocument(
<Formsy.Form>
<Formsy>
<Input name="foo" value="foo" validations="isEmail" validationError="Has to be email"/>
</Formsy.Form>
</Formsy>
);
test.equal(getErrorMessage(), 'Has to be email');
@ -73,14 +100,14 @@ export default {
let isValid = null;
const Input = InputFactory({
componentDidMount() {
isValid = this.isValid;
componentDidMount: function() {
isValid = this.props.isValid;
}
});
const form = TestUtils.renderIntoDocument(
<Formsy.Form url="/users">
<Formsy action="/users">
<Input name="foo" value="foo" validations="isEmail"/>
</Formsy.Form>
</Formsy>
);
test.equal(isValid(), false);
@ -96,16 +123,16 @@ export default {
const isRequireds = [];
const Input = InputFactory({
componentDidMount() {
isRequireds.push(this.isRequired);
componentDidMount: function() {
isRequireds.push(this.props.isRequired);
}
});
TestUtils.renderIntoDocument(
<Formsy.Form url="/users">
<Formsy action="/users">
<Input name="foo" value=""/>
<Input name="foo" value="" required/>
<Input name="foo" value="foo" required="isLength:3"/>
</Formsy.Form>
</Formsy>
);
test.equal(isRequireds[0](), false);
@ -120,16 +147,16 @@ export default {
const showRequireds = [];
const Input = InputFactory({
componentDidMount() {
showRequireds.push(this.showRequired);
componentDidMount: function() {
showRequireds.push(this.props.showRequired);
}
});
TestUtils.renderIntoDocument(
<Formsy.Form url="/users">
<Formsy action="/users">
<Input name="A" value="foo"/>
<Input name="B" value="" required/>
<Input name="C" value=""/>
</Formsy.Form>
</Formsy>
);
test.equal(showRequireds[0](), false);
@ -144,14 +171,14 @@ export default {
let isPristine = null;
const Input = InputFactory({
componentDidMount() {
isPristine = this.isPristine;
componentDidMount: function() {
isPristine = this.props.isPristine;
}
});
const form = TestUtils.renderIntoDocument(
<Formsy.Form url="/users">
<Formsy action="/users">
<Input name="A" value="foo"/>
</Formsy.Form>
</Formsy>
);
test.equal(isPristine(), true);
@ -165,23 +192,21 @@ export default {
'should allow an undefined value to be updated to a value': function (test) {
const TestForm = React.createClass({
getInitialState() {
return {value: undefined};
},
changeValue() {
class TestForm extends React.Component {
state = {value: undefined};
changeValue = () => {
this.setState({
value: 'foo'
});
},
}
render() {
return (
<Formsy.Form url="/users">
<TestInput name="A" value={this.state.value}/>
</Formsy.Form>
<Formsy action="/users">
<TestInput name="A" value={this.state.value} />
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
form.changeValue();
@ -195,15 +220,15 @@ export default {
'should be able to test a values validity': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="A" validations="isEmail"/>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -215,17 +240,17 @@ export default {
'should be able to use an object as validations property': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="A" validations={{
isEmail: true
}}/>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -238,17 +263,17 @@ export default {
'should be able to pass complex values to a validation rule': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="A" validations={{
matchRegexp: /foo/
}} value="foo"/>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -263,26 +288,26 @@ export default {
'should be able to run a function to validate': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
customValidationA(values, value) {
return value === 'foo';
},
}
customValidationB(values, value) {
return value === 'foo' && values.A === 'foo';
},
}
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="A" validations={{
custom: this.customValidationA
}} value="foo"/>
<TestInput name="B" validations={{
custom: this.customValidationB
}} value="foo"/>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.scryRenderedComponentsWithType(form, TestInput);
@ -299,17 +324,17 @@ export default {
'should not override error messages with error messages passed by form if passed eror messages is an empty object': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form validationErrors={{}}>
<Formsy validationErrors={{}}>
<TestInput name="A" validations={{
isEmail: true
}} validationError="bar2" validationErrors={{isEmail: 'bar3'}} value="foo"/>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -319,20 +344,19 @@ export default {
},
'should override all error messages with error messages passed by form': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form validationErrors={{A: 'bar'}}>
<Formsy validationErrors={{A: 'bar'}}>
<TestInput name="A" validations={{
isEmail: true
}} validationError="bar2" validationErrors={{isEmail: 'bar3'}} value="foo"/>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -344,10 +368,10 @@ export default {
'should override validation rules with required rules': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="A"
validations={{
isEmail: true
@ -359,10 +383,10 @@ export default {
isLength: 1
}}
/>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -374,26 +398,26 @@ export default {
'should fall back to default error message when non exist in validationErrors map': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="A"
validations={{
isEmail: true
}}
validationError="bar"
validationErrors={{foo: 'bar'}}
validationError="bar1"
validationErrors={{foo: 'bar2'}}
value="foo"
/>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.getErrorMessage(), 'bar');
test.equal(inputComponent.getErrorMessage(), 'bar1');
test.done();
@ -401,17 +425,17 @@ export default {
'should not be valid if it is required and required rule is true': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="A"
required
/>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -423,22 +447,20 @@ export default {
'should handle objects and arrays as values': function (test) {
const TestForm = React.createClass({
getInitialState() {
return {
foo: {foo: 'bar'},
bar: ['foo']
};
},
class TestForm extends React.Component {
state = {
foo: {foo: 'bar'},
bar: ['foo']
}
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" value={this.state.foo}/>
<TestInput name="bar" value={this.state.bar}/>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
form.setState({
@ -456,28 +478,24 @@ export default {
'should handle isFormDisabled with dynamic inputs': function (test) {
const TestForm = React.createClass({
getInitialState() {
return {
bool: true
};
},
flip() {
class TestForm extends React.Component {
state = { bool: true }
flip = () => {
this.setState({
bool: !this.state.bool
});
},
}
render() {
return (
<Formsy.Form disabled={this.state.bool}>
<Formsy disabled={this.state.bool}>
{this.state.bool ?
<TestInput name="foo" /> :
<TestInput name="bar" />
}
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -491,19 +509,19 @@ export default {
'should allow for dot notation in name which maps to a deep object': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
onSubmit(model) {
test.deepEqual(model, {foo: {bar: 'foo', test: 'test'}});
},
}
render() {
return (
<Formsy.Form onSubmit={this.onSubmit}>
<Formsy onSubmit={this.onSubmit}>
<TestInput name="foo.bar" value="foo"/>
<TestInput name="foo.test" value="test"/>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
test.expect(1);
@ -517,19 +535,19 @@ export default {
'should allow for application/x-www-form-urlencoded syntax and convert to object': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
onSubmit(model) {
test.deepEqual(model, {foo: ['foo', 'bar']});
},
}
render() {
return (
<Formsy.Form onSubmit={this.onSubmit}>
<Formsy onSubmit={this.onSubmit}>
<TestInput name="foo[0]" value="foo"/>
<TestInput name="foo[1]" value="bar"/>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
test.expect(1);
@ -546,17 +564,17 @@ export default {
var renderSpy = sinon.spy();
const Input = InputFactory({
mixins: [Formsy.Mixin, PureRenderMixin],
render() {
shouldComponentUpdate: function() { return false },
render: function() {
renderSpy();
return <input type={this.props.type} value={this.getValue()} onChange={this.updateValue}/>;
return <input type={this.props.type} value={this.props.getValue()} onChange={this.updateValue}/>;
}
});
const form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Formsy>
<Input name="foo" value="foo"/>
</Formsy.Form>
</Formsy>
);
test.equal(renderSpy.calledOnce, true);

View File

@ -1,26 +1,25 @@
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-addons-test-utils';
import TestUtils from 'react-dom/test-utils';
import Formsy from './..';
import Formsy, { addValidationRule } from './..';
import TestInput from './utils/TestInput';
import TestInputHoc from './utils/TestInputHoc';
import immediate from './utils/immediate';
import sinon from 'sinon';
export default {
'Setting up a form': {
'should expose the users DOM node through an innerRef prop': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInputHoc name="name" innerRef={(c) => { this.name = c; }} />
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = form.name;
@ -31,7 +30,7 @@ export default {
'should render a form into the document': function (test) {
const form = TestUtils.renderIntoDocument(<Formsy.Form></Formsy.Form>);
const form = TestUtils.renderIntoDocument(<Formsy></Formsy>);
test.equal(ReactDOM.findDOMNode(form).tagName, 'FORM');
test.done();
@ -40,7 +39,7 @@ export default {
'should set a class name if passed': function (test) {
const form = TestUtils.renderIntoDocument( <Formsy.Form className="foo"></Formsy.Form>);
const form = TestUtils.renderIntoDocument( <Formsy className="foo"></Formsy>);
test.equal(ReactDOM.findDOMNode(form).className, 'foo');
test.done();
@ -50,18 +49,18 @@ export default {
'should allow for null/undefined children': function (test) {
let model = null;
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
<Formsy onSubmit={(formModel) => (model = formModel)}>
<h1>Test</h1>
{ null }
{ undefined }
<TestInput name="name" value={ 'foo' } />
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
immediate(() => {
@ -77,17 +76,17 @@ export default {
const inputs = [];
let forceUpdate = null;
let model = null;
const TestForm = React.createClass({
class TestForm extends React.Component {
componentWillMount() {
forceUpdate = this.forceUpdate.bind(this);
},
}
render() {
return (
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
<Formsy onSubmit={(formModel) => (model = formModel)}>
{inputs}
</Formsy.Form>);
</Formsy>);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
// Wait before adding the input
@ -113,17 +112,17 @@ export default {
const inputs = [];
let forceUpdate = null;
let model = null;
const TestForm = React.createClass({
class TestForm extends React.Component {
componentWillMount() {
forceUpdate = this.forceUpdate.bind(this);
},
}
render() {
return (
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
<Formsy onSubmit={(formModel) => (model = formModel)}>
{inputs}
</Formsy.Form>);
</Formsy>);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
// Wait before adding the input
@ -151,19 +150,19 @@ export default {
let forceUpdate = null;
let model = null;
const TestForm = React.createClass({
class TestForm extends React.Component {
componentWillMount() {
forceUpdate = this.forceUpdate.bind(this);
},
}
render() {
const input = <TestInput name="test" value={this.props.value} />;
return (
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
<Formsy onSubmit={(formModel) => (model = formModel)}>
{input}
</Formsy.Form>);
</Formsy>);
}
});
}
let form = TestUtils.renderIntoDocument(<TestForm value="foo"/>);
// Wait before changing the input
@ -193,13 +192,13 @@ export default {
const runRule = sinon.spy();
const notRunRule = sinon.spy();
Formsy.addValidationRule('runRule', runRule);
Formsy.addValidationRule('notRunRule', notRunRule);
addValidationRule('runRule', runRule);
addValidationRule('notRunRule', notRunRule);
const form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Formsy>
<TestInput name="one" validations="runRule" value="foo"/>
</Formsy.Form>
</Formsy>
);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
@ -215,24 +214,24 @@ export default {
const ruleA = sinon.spy();
const ruleB = sinon.spy();
Formsy.addValidationRule('ruleA', ruleA);
Formsy.addValidationRule('ruleB', ruleB);
addValidationRule('ruleA', ruleA);
addValidationRule('ruleB', ruleB);
class TestForm extends React.Component {
constructor(props) {
super(props);
this.state = {rule: 'ruleA'};
}
changeRule() {
changeRule = () => {
this.setState({
rule: 'ruleB'
});
}
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="one" validations={this.state.rule} value="foo"/>
</Formsy.Form>
</Formsy>
);
}
}
@ -256,14 +255,14 @@ export default {
super(props);
this.state = {showSecondInput: false};
}
addInput() {
addInput = () => {
this.setState({
showSecondInput: true
});
}
render() {
return (
<Formsy.Form ref="formsy" onInvalid={isInValidSpy}>
<Formsy ref="formsy" onInvalid={isInValidSpy}>
<TestInput name="one" validations="isEmail" value="foo@bar.com"/>
{
this.state.showSecondInput ?
@ -271,7 +270,7 @@ export default {
:
null
}
</Formsy.Form>
</Formsy>
);
}
}
@ -304,7 +303,7 @@ export default {
}
render() {
return (
<Formsy.Form ref="formsy" onValid={isValidSpy}>
<Formsy ref="formsy" onValid={isValidSpy}>
<TestInput name="one" validations="isEmail" value="foo@bar.com"/>
{
this.state.showSecondInput ?
@ -312,7 +311,7 @@ export default {
:
null
}
</Formsy.Form>
</Formsy>
);
}
}
@ -334,13 +333,13 @@ export default {
const ruleA = sinon.spy();
const ruleB = sinon.spy();
Formsy.addValidationRule('ruleA', ruleA);
Formsy.addValidationRule('ruleB', ruleB);
addValidationRule('ruleA', ruleA);
addValidationRule('ruleB', ruleB);
const form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Formsy>
<TestInput name="one" validations="ruleA,ruleB" value="foo" />
</Formsy.Form>
</Formsy>
);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
@ -357,11 +356,11 @@ export default {
const hasChanged = sinon.spy();
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return <Formsy.Form onChange={hasChanged}></Formsy.Form>;
return <Formsy onChange={hasChanged}></Formsy>;
}
});
}
TestUtils.renderIntoDocument(<TestForm/>);
test.equal(hasChanged.called, false);
test.done();
@ -372,9 +371,9 @@ export default {
const hasChanged = sinon.spy();
const form = TestUtils.renderIntoDocument(
<Formsy.Form onChange={hasChanged}>
<Formsy onChange={hasChanged}>
<TestInput name="foo"/>
</Formsy.Form>
</Formsy>
);
TestUtils.Simulate.change(TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'), {target: {value: 'bar'}});
test.equal(hasChanged.calledOnce, true);
@ -385,29 +384,27 @@ export default {
'should trigger onChange once when new input is added to form': function (test) {
const hasChanged = sinon.spy();
const TestForm = React.createClass({
getInitialState() {
return {
showInput: false
};
},
class TestForm extends React.Component {
state = {
showInput: false
}
addInput() {
this.setState({
showInput: true
})
},
}
render() {
return (
<Formsy.Form onChange={hasChanged}>
<Formsy onChange={hasChanged}>
{
this.state.showInput ?
<TestInput name="test"/>
:
null
}
</Formsy.Form>);
</Formsy>);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
form.addInput();
@ -422,16 +419,16 @@ export default {
'should allow elements to check if the form is disabled': function (test) {
const TestForm = React.createClass({
getInitialState() { return { disabled: true }; },
enableForm() { this.setState({ disabled: false }); },
class TestForm extends React.Component {
state = { disabled: true };
enableForm() { this.setState({ disabled: false }); }
render() {
return (
<Formsy.Form disabled={this.state.disabled}>
<Formsy disabled={this.state.disabled}>
<TestInput name="foo"/>
</Formsy.Form>);
</Formsy>);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -447,18 +444,18 @@ export default {
'should be possible to pass error state of elements by changing an errors attribute': function (test) {
const TestForm = React.createClass({
getInitialState() { return { validationErrors: { foo: 'bar' } }; },
onChange(values) {
class TestForm extends React.Component {
state = { validationErrors: { foo: 'bar' } };
onChange = (values) => {
this.setState(values.foo ? { validationErrors: {} } : { validationErrors: {foo: 'bar'} });
},
}
render() {
return (
<Formsy.Form onChange={this.onChange} validationErrors={this.state.validationErrors}>
<Formsy onChange={this.onChange} validationErrors={this.state.validationErrors}>
<TestInput name="foo"/>
</Formsy.Form>);
</Formsy>);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
// Wait for update
@ -479,14 +476,14 @@ export default {
'should trigger an onValidSubmit when submitting a valid form': function (test) {
let isCalled = sinon.spy();
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form onValidSubmit={isCalled}>
<Formsy onValidSubmit={isCalled}>
<TestInput name="foo" validations="isEmail" value="foo@bar.com"/>
</Formsy.Form>);
</Formsy>);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const FoundForm = TestUtils.findRenderedComponentWithType(form, TestForm);
TestUtils.Simulate.submit(ReactDOM.findDOMNode(FoundForm));
@ -498,14 +495,14 @@ export default {
'should trigger an onInvalidSubmit when submitting an invalid form': function (test) {
let isCalled = sinon.spy();
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form onInvalidSubmit={isCalled}>
<Formsy onInvalidSubmit={isCalled}>
<TestInput name="foo" validations="isEmail" value="foo@bar"/>
</Formsy.Form>);
</Formsy>);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const FoundForm = TestUtils.findRenderedComponentWithType(form, TestForm);
@ -523,16 +520,16 @@ export default {
'should call onSubmit correctly': function (test) {
const onSubmit = sinon.spy();
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form onSubmit={onSubmit}>
<Formsy onSubmit={onSubmit}>
<TestInput name="foo" value={false} type="checkbox" />
<button type="submit">Save</button>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
@ -544,26 +541,24 @@ export default {
'should allow dynamic changes to false': function (test) {
const onSubmit = sinon.spy();
const TestForm = React.createClass({
getInitialState() {
return {
value: true
};
},
class TestForm extends React.Component {
state = {
value: true
}
changeValue() {
this.setState({
value: false
});
},
}
render() {
return (
<Formsy.Form onSubmit={onSubmit}>
<Formsy onSubmit={onSubmit}>
<TestInput name="foo" value={this.state.value} type="checkbox" />
<button type="submit">Save</button>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
form.changeValue();
@ -575,16 +570,16 @@ export default {
'should say the form is submitted': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" value={true} type="checkbox" />
<button type="submit">Save</button>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(input.isFormSubmitted(), false);
@ -596,29 +591,27 @@ export default {
'should be able to reset the form to its pristine state': function (test) {
const TestForm = React.createClass({
getInitialState() {
return {
value: true
};
},
class TestForm extends React.Component {
state = {
value: true
}
changeValue() {
this.setState({
value: false
});
},
}
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" value={this.state.value} type="checkbox" />
<button type="submit">Save</button>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy.Form);
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy);
test.equal(input.getValue(), true);
form.changeValue();
test.equal(input.getValue(), false);
@ -631,29 +624,27 @@ export default {
'should be able to reset the form using custom data': function (test) {
const TestForm = React.createClass({
getInitialState() {
return {
value: true
};
},
class TestForm extends React.Component {
state = {
value: true
}
changeValue() {
this.setState({
value: false
});
},
}
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" value={this.state.value} type="checkbox" />
<button type="submit">Save</button>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy.Form);
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy);
test.equal(input.getValue(), true);
form.changeValue();
@ -670,19 +661,19 @@ export default {
'should be able to reset the form to empty values': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" value="42" type="checkbox" />
<button type="submit">Save</button>
</Formsy.Form>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy.Form);
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy);
formsyForm.reset({
foo: ''
@ -698,9 +689,9 @@ export default {
const hasOnChanged = sinon.spy();
const form = TestUtils.renderIntoDocument(
<Formsy.Form onChange={hasOnChanged}>
<Formsy onChange={hasOnChanged}>
<TestInput name="one" value="foo" />
</Formsy.Form>
</Formsy>
);
test.equal(form.isChanged(), false);
test.equal(hasOnChanged.called, false);
@ -712,9 +703,9 @@ export default {
const hasOnChanged = sinon.spy();
const form = TestUtils.renderIntoDocument(
<Formsy.Form onChange={hasOnChanged}>
<Formsy onChange={hasOnChanged}>
<TestInput name="one" value="foo" />
</Formsy.Form>
</Formsy>
);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}});
@ -728,9 +719,9 @@ export default {
const hasOnChanged = sinon.spy();
const form = TestUtils.renderIntoDocument(
<Formsy.Form onChange={hasOnChanged}>
<Formsy onChange={hasOnChanged}>
<TestInput name="one" value="foo" />
</Formsy.Form>
</Formsy>
);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}});

View File

@ -1,25 +1,25 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TestUtils from 'react-dom/test-utils';
import immediate from './utils/immediate';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
render: function() {
return <input value={this.props.getValue()} readOnly />;
}
});
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="equals:foo" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
});
}
export default {

View File

@ -1,24 +1,24 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TestUtils from 'react-dom/test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
render: function() {
return <input value={this.props.getValue()} readOnly />;
}
});
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isAlpha" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
});
}
export default {

View File

@ -1,24 +1,24 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TestUtils from 'react-dom/test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
return <input value={this.props.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isAlphanumeric" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
});
}
export default {

View File

@ -1,24 +1,24 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TestUtils from 'react-dom/test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
return <input value={this.props.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isEmail" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
});
}
export default {

View File

@ -1,24 +1,24 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TestUtils from 'react-dom/test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
return <input value={this.props.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isEmptyString" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
});
}
export default {

View File

@ -1,24 +1,24 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TestUtils from 'react-dom/test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
return <input value={this.props.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isExisty" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
});
}
export default {

View File

@ -1,24 +1,24 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TestUtils from 'react-dom/test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
return <input value={this.props.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isFloat" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
});
}
export default {

View File

@ -1,24 +1,24 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TestUtils from 'react-dom/test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
return <input value={this.props.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isInt" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
});
}
export default {

View File

@ -1,24 +1,24 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TestUtils from 'react-dom/test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
return <input value={this.props.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations={this.props.rule} value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
});
}
export default {

View File

@ -1,24 +1,24 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TestUtils from 'react-dom/test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
return <input value={this.props.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isNumeric" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
});
}
export default {

View File

@ -1,24 +1,24 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TestUtils from 'react-dom/test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
return <input value={this.props.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isUrl" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
});
}
export default {

View File

@ -1,24 +1,24 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TestUtils from 'react-dom/test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
return <input value={this.props.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isWords" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
});
}
export default {

View File

@ -1,24 +1,24 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TestUtils from 'react-dom/test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
return <input value={this.props.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="maxLength:3" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
});
}
export default {

View File

@ -1,24 +1,24 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TestUtils from 'react-dom/test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
return <input value={this.props.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations={this.props.rule} value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
});
}
export default {

View File

@ -1,24 +1,37 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TestUtils from 'react-dom/test-utils';
import Formsy from './..';
import TestInput, {InputFactory} from './utils/TestInput';
import Formsy, { withFormsy } from './..';
import { InputFactory } from './utils/TestInput';
import immediate from './utils/immediate';
import sinon from 'sinon';
class MyTest extends React.Component {
static defaultProps = { type: 'text' };
handleChange = (event) => {
this.props.setValue(event.target.value);
}
render() {
return <input type={this.props.type} value={this.props.getValue()} onChange={this.handleChange}/>;
}
}
const FormsyTest = withFormsy(MyTest);
export default {
'should reset only changed form element when external error is passed': function (test) {
const form = TestUtils.renderIntoDocument(
<Formsy.Form onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar', bar: 'foo' })}>
<TestInput name="foo"/>
<TestInput name="bar"/>
</Formsy.Form>
<Formsy onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar', bar: 'foo' })}>
<FormsyTest name="foo"/>
<FormsyTest name="bar"/>
</Formsy>
);
const input = TestUtils.scryRenderedDOMComponentsWithTag(form, 'INPUT')[0];
const inputComponents = TestUtils.scryRenderedComponentsWithType(form, TestInput);
const inputComponents = TestUtils.scryRenderedComponentsWithType(form, FormsyTest);
form.submit();
test.equal(inputComponents[0].isValid(), false);
@ -36,13 +49,13 @@ export default {
'should let normal validation take over when component with external error is changed': function (test) {
const form = TestUtils.renderIntoDocument(
<Formsy.Form onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<TestInput name="foo" validations="isEmail"/>
</Formsy.Form>
<Formsy onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<FormsyTest name="foo" validations="isEmail"/>
</Formsy>
);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
const inputComponent = TestUtils.findRenderedComponentWithType(form, FormsyTest);
form.submit();
test.equal(inputComponent.isValid(), false);
@ -62,9 +75,9 @@ export default {
const onInvalid = sinon.spy();
TestUtils.renderIntoDocument(
<Formsy.Form onValid={onValid} onInvalid={onInvalid}>
<TestInput name="foo" value="bar" required/>
</Formsy.Form>
<Formsy onValid={onValid} onInvalid={onInvalid}>
<FormsyTest name="foo" value="bar" required/>
</Formsy>
);
test.equal(onValid.called, true);
@ -79,9 +92,9 @@ export default {
const onInvalid = sinon.spy();
TestUtils.renderIntoDocument(
<Formsy.Form onValid={onValid} onInvalid={onInvalid}>
<TestInput name="foo" required />
</Formsy.Form>
<Formsy onValid={onValid} onInvalid={onInvalid}>
<FormsyTest name="foo" required />
</Formsy>
);
test.equal(onValid.called, false);
@ -94,14 +107,14 @@ export default {
let isValid = false;
const CustomInput = InputFactory({
componentDidMount() {
isValid = this.isValid();
componentDidMount: function() {
isValid = this.props.isValid();
}
});
const form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Formsy>
<CustomInput name="foo" value="foo" required/>
</Formsy.Form>
</Formsy>
);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
@ -110,22 +123,22 @@ export default {
},
'should provide invalidate callback on onValiSubmit': function (test) {
'should provide invalidate callback on onValidSubmit': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form onValidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<TestInput name="foo" value="foo"/>
</Formsy.Form>
<Formsy onValidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<FormsyTest name="foo" value="foo"/>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
const input = TestUtils.findRenderedComponentWithType(form, FormsyTest);
TestUtils.Simulate.submit(formEl);
test.equal(input.isValid(), false);
test.done();
@ -134,19 +147,19 @@ export default {
'should provide invalidate callback on onInvalidSubmit': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form onInvalidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<TestInput name="foo" value="foo" validations="isEmail"/>
</Formsy.Form>
<Formsy onInvalidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<FormsyTest name="foo" value="foo" validations="isEmail"/>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
const input = TestUtils.findRenderedComponentWithType(form, FormsyTest);
TestUtils.Simulate.submit(formEl);
test.equal(input.getErrorMessage(), 'bar');
@ -156,21 +169,21 @@ export default {
'should not invalidate inputs on external errors with preventExternalInvalidation prop': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form
<Formsy
preventExternalInvalidation
onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<TestInput name="foo" value="foo"/>
</Formsy.Form>
<FormsyTest name="foo" value="foo"/>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
const input = TestUtils.findRenderedComponentWithType(form, FormsyTest);
TestUtils.Simulate.submit(formEl);
test.equal(input.isValid(), true);
test.done();
@ -179,19 +192,19 @@ export default {
'should invalidate inputs on external errors without preventExternalInvalidation prop': function (test) {
const TestForm = React.createClass({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<TestInput name="foo" value="foo"/>
</Formsy.Form>
<Formsy onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<FormsyTest name="foo" value="foo"/>
</Formsy>
);
}
});
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
const input = TestUtils.findRenderedComponentWithType(form, FormsyTest);
TestUtils.Simulate.submit(formEl);
test.equal(input.isValid(), false);
test.done();

View File

@ -1,21 +1,25 @@
import React from 'react';
import Formsy from './../..';
import Formsy, { withFormsy } from './../..';
const defaultProps = {
mixins: [Formsy.Mixin],
getDefaultProps() {
return { type: 'text' };
},
updateValue(event) {
this.setValue(event.target[this.props.type === 'checkbox' ? 'checked' : 'value']);
},
render() {
return <input type={this.props.type} value={this.getValue()} onChange={this.updateValue}/>;
}
};
class TestInput extends React.Component {
static defaultProps = { type: 'text' };
export function InputFactory(props) {
return React.createClass(Object.assign(defaultProps, props));
updateValue = (event) => {
this.props.setValue(event.target[this.props.type === 'checkbox' ? 'checked' : 'value']);
}
render() {
return <input type={this.props.type} value={this.props.getValue()} onChange={this.updateValue}/>;
}
}
export default React.createClass(defaultProps);
export function InputFactory(methods) {
for (let method in methods) {
if (methods.hasOwnProperty(method)) {
TestInput.prototype[method] = methods[method];
}
}
return withFormsy(TestInput);
}
export default withFormsy(TestInput);

View File

@ -1,13 +1,14 @@
import React from 'react';
import { HOC as formsyHoc } from './../..';
import Formsy, { withFormsy } from './../..';
const defaultProps = {
methodOnWrappedInstance(param) {
return param;
},
render() {
return (<input />);
},
};
class TestComponent extends React.Component {
methodOnWrappedInstance = (param) => {
return param;
}
export default formsyHoc(React.createClass(defaultProps));
render() {
return (<input />);
}
}
export default withFormsy(TestComponent);

View File

@ -1,3 +1,3 @@
export default function (fn) {
setTimeout(fn, 0);
setTimeout(fn, 0);
}

20
webpack.config.js Normal file
View File

@ -0,0 +1,20 @@
const path = require('path');
module.exports = {
devtool: 'source-map',
entry: path.resolve(__dirname, 'src', 'index.js'),
externals: 'react',
output: {
path: path.resolve(__dirname, 'release'),
filename: 'formsy-react.js',
libraryTarget: 'umd',
library: 'Formsy',
},
module: {
loaders: [{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
}],
},
};

View File

@ -1,21 +0,0 @@
var path = require('path');
module.exports = {
devtool: 'source-map',
entry: path.resolve(__dirname, 'src', 'main.js'),
externals: 'react',
output: {
path: path.resolve(__dirname, 'release'),
filename: 'formsy-react.js',
libraryTarget: 'umd',
library: 'Formsy'
},
module: {
loaders: [
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel' },
{ test: /\.json$/, loader: 'json' }
]
}
};

5063
yarn.lock Normal file

File diff suppressed because it is too large Load Diff