Compare commits

..

No commits in common. "beta" and "master" have entirely different histories.
beta ... master

60 changed files with 2034 additions and 7273 deletions

View File

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

View File

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

1
.nvmrc
View File

@ -1 +0,0 @@
8.2.1

View File

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

797
API.md

File diff suppressed because it is too large Load Diff

81
CHANGES.md Normal file
View File

@ -0,0 +1,81 @@
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

234
README.md
View File

@ -1,132 +1,136 @@
# 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.
A form input builder and validator for React JS
| [Quick Start](#quick-start) | [API](/API.md) | [Examples](/examples) |
| --------------------------- | -------------- | --------------------- |
| [How to use](#how-to-use) | [API](/API.md) | [Examples](/examples) |
|---|---|---|
## Background
## <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.
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.
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.
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.
## What you can do
## What You Can Do
1. Build any kind of form element components. Not just traditional inputs, but anything you want and get that validation for free
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
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.
## Install
`yarn add formsy-react react react-dom` and use with webpack, browserify, etc.
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`
## Quick Start
### 1. Build a Formsy element
```jsx
// MyInput.js
import { withFormsy } from 'formsy-react';
import React from 'react';
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
// Important: Don't skip this step. This pattern is required
// for Formsy to work.
this.props.setValue(event.currentTarget.value);
}
render() {
// An error message is returned only if the component is invalid
const errorMessage = this.props.getErrorMessage();
return (
<div>
<input
onChange={this.changeValue}
type="text"
value={this.props.getValue() || ''}
/>
<span>{errorMessage}</span>
</div>
);
}
}
export default withFormsy(MyInput);
```
`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
- `yarn`
- `yarn examples` runs the development server on `localhost:8080`
- `yarn test` runs the tests
## Changelog
## Changes
[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
```jsx
import Formsy from 'formsy-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],
// 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);
},
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;
// 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 className={className}>
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
<span>{errorMessage}</span>
</div>
);
}
});
```
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!
## Contribute
- Fork repo
- `npm install`
- `npm run examples` runs the development server on `localhost:8080`
- `npm test` runs the tests
## License
[The MIT License (MIT)](/LICENSE)

25
bower.json Normal file
View File

@ -0,0 +1,25 @@
{
"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"
]
}

View File

@ -1,10 +0,0 @@
{
"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 (`addValidationRule`) and one field with dynamically added validation and error messages.
One field with added validation rule (`Formsy.addValidationRule`) and one field with dynamically added validation and error messages.
3. [**Reset Values**](reset-values)

View File

@ -1,48 +0,0 @@
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,47 +1,44 @@
import React from 'react';
import { propTypes, withFormsy } from 'formsy-react';
import Formsy from 'formsy-react';
class MyInput extends React.Component {
constructor(props) {
super(props);
this.changeValue = this.changeValue.bind(this);
}
const MyInput = React.createClass({
// 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) {
// 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']);
}
this.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.props.showRequired() ? 'required' : ''} ${this.props.showError() ? 'error' : ''}`;
const className = 'form-group' + (this.props.className || ' ') +
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
// An error message is returned ONLY if the component is invalid
// or the server has returned an error message
const errorMessage = this.props.getErrorMessage();
const errorMessage = this.getErrorMessage();
return (
<div className={className}>
<label htmlFor={this.props.name}>{this.props.title}</label>
<input
onChange={this.changeValue}
name={this.props.name}
type={this.props.type || 'text'}
value={this.props.getValue() || ''}
name={this.props.name}
onChange={this.changeValue}
value={this.getValue()}
checked={this.props.type === 'checkbox' && this.getValue() ? 'checked' : null}
/>
<span className='validation-error'>{errorMessage}</span>
</div>
);
}
}
});
MyInput.propTypes = {
...propTypes,
};
export default withFormsy(MyInput);
export default MyInput;

View File

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

View File

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

View File

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

View File

@ -1,124 +1,105 @@
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();
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') {
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') {
return false;
}
return parsedValue < currentYear && parsedValue > currentYear - 130;
return value < currentYear && value > currentYear - 130;
});
class App extends React.Component {
constructor(props) {
super(props);
this.submit = this.submit.bind(this);
}
const App = React.createClass({
submit(data) {
alert(JSON.stringify(data, null, 4));
}
},
render() {
return (
<Formsy onSubmit={this.submit} className="custom-validation">
<Formsy.Form onSubmit={this.submit} className="custom-validation">
<MyInput name="year" title="Year of Birth" type="number" validations="isYearOfBirth" validationError="Please type your year of birth" />
<FormsyDynamicInput name="dynamic" title="..." />
<DynamicInput name="dynamic" title="..." />
<button type="submit">Submit</button>
</Formsy>
</Formsy.Form>
);
}
}
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.props.setValue(this.props.getValue());
}
changeValue(event) {
this.props.setValue(event.currentTarget.value);
}
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 : '';
},
render() {
const errorMessage = {
time: 'Not a valid time format',
decimal: "Not a valid decimal value",
binary: "Not a valid binary number"
}
const className = 'form-group' + (this.props.className || ' ') + (this.showRequired() ? 'required' : this.showError() ? 'error' : null);
const errorMessage = this.getCustomErrorMessage();
return (
<div>
<div className={className}>
<label htmlFor={this.props.name}>{this.props.title}</label>
<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} />
<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}/>
</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>
<fieldset onChange={this.changeValidation}>
<legend>Validation Type</legend>
<div>
<input onChange={this.changeValidation} name='validationType' type='radio' value='time' checked={validationType === 'time'} />Time
<input name='validationType' type='radio' value='time' defaultChecked={validationType === 'time'}/>Time
</div>
<div>
<input onChange={this.changeValidation} name='validationType' type='radio' value='decimal' checked={validationType === 'decimal'} />Decimal
<input name='validationType' type='radio' value='decimal' defaultChecked={validationType === 'decimal'}/>Decimal
</div>
<div>
<input onChange={this.changeValidation} name='validationType' type='radio' value='binary' checked={validationType === 'binary'} />Binary
<input name='validationType' type='radio' value='binary' defaultChecked={validationType === 'binary'}/>Binary
</div>
</fieldset>
);
}
}
});
Validations.propTypes = {
changeValidation: PropTypes.func.isRequired,
validationType: PropTypes.string.isRequired,
};
ReactDOM.render(<App />, document.getElementById('example'));
ReactDOM.render(<App/>, document.getElementById('example'));

View File

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

View File

@ -1,6 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Formsy from 'formsy-react';
import { Form } 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 => {
/>
)
}
<button className="remove-field" onClick={onRemove(i)}>X</button>
<a href="#" className="remove-field" onClick={onRemove(i)}>X</a>
</div>
))
}
@ -54,49 +54,36 @@ const Fields = props => {
);
};
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);
}
const App = React.createClass({
getInitialState() {
return { fields: [], canSubmit: false };
},
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>
<Formsy onSubmit={this.addField} className="many-fields-conf">
<MyMultiCheckboxSet
name="validations"
title="Validations"
<Form onSubmit={this.addField} className="many-fields-conf">
<MyMultiCheckboxSet name="validations" title="Validations"
cmp={(a, b) => JSON.stringify(a) === JSON.stringify(b)}
items={[
{isEmail: true},
@ -108,27 +95,19 @@ class App extends React.Component {
{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>
</Formsy>
<Formsy onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="many-fields">
</Form>
<Form 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>
</Formsy>
</Form>
</div>
);
}
}
});
ReactDOM.render(<App />, document.getElementById('example'));
ReactDOM.render(<App/>, document.getElementById('example'));

View File

@ -8,6 +8,7 @@
<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,38 +1,31 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Formsy from 'formsy-react';
import { Form } from 'formsy-react';
import MyInput from './../components/Input';
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);
}
const App = React.createClass({
getInitialState() {
return { canSubmit: false };
},
submit(data) {
alert(JSON.stringify(data, null, 4));
}
},
enableButton() {
this.setState({ canSubmit: true });
}
},
disableButton() {
this.setState({ canSubmit: false });
}
},
render() {
return (
<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 />
<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 />
<button type="submit" disabled={!this.state.canSubmit}>Submit</button>
</Formsy>
</Form>
);
}
}
});
ReactDOM.render(<App/>, document.getElementById('example'));

View File

@ -8,6 +8,7 @@
<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,8 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Formsy from 'formsy-react';
import { Form } from 'formsy-react';
import MyCheckbox from './../components/Checkbox';
import MyInput from './../components/Input';
import MySelect from './../components/Select';
@ -12,45 +11,19 @@ const user = {
hair: 'brown'
};
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);
}
const App = React.createClass({
submit(data) {
alert(JSON.stringify(data, null, 4));
}
},
resetForm() {
this.refs.form.reset();
},
render() {
return (
<Formsy
ref={(c) => this.form = c}
onSubmit={this.submit}
onReset={this.reset}
className="form"
>
<Formsy.Form ref="form" onSubmit={this.submit} className="form">
<MyInput name="name" title="Name" value={user.name} />
<MyCheckbox name="free" title="Free to hire" value={user.free} />
<MySelect
name="hair"
title="Hair"
value={user.hair}
<MyInput name="free" title="Free to hire" type="checkbox" value={user.free} />
<MySelect name="hair" title="Hair" value={user.hair}
options={[
{ value: "black", title: "Black" },
{ value: "brown", title: "Brown" },
@ -60,13 +33,12 @@ class App extends React.Component {
/>
<div className="buttons">
<button type="reset">Reset</button>
<button type="button" onClick={this.randomize}>Randomize</button>
<button type="reset" onClick={this.resetForm}>Reset</button>
<button type="submit">Submit</button>
</div>
</Formsy>
</Formsy.Form>
);
}
}
});
ReactDOM.render(<App />, document.getElementById('example'));
ReactDOM.render(<App/>, document.getElementById('example'));

View File

@ -8,6 +8,7 @@
<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,41 +1,49 @@
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
var fs = require('fs');
var path = require('path');
var webpack = require('webpack');
function isDirectory(dir) {
return fs.lstatSync(dir).isDirectory();
}
module.exports = {
devtool: 'inline-source-map',
entry: fs.readdirSync(__dirname).reduce((entries, dir) => {
const isDraft = dir.charAt(0) === '_' || dir.indexOf('components') >= 0;
entry: fs.readdirSync(__dirname).reduce(function (entries, dir) {
var 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: path.resolve(__dirname, 'examples/__build__'),
path: 'examples/__build__',
filename: '[name].js',
chunkFilename: '[id].chunk.js',
publicPath: '/__build__/',
publicPath: '/__build__/'
},
module: {
loaders: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
}],
loaders: [
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel' }
]
},
resolve: {
alias: {
'formsy-react': '../../src/index',
},
'formsy-react': '../../src/main'
}
},
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,61 +1,49 @@
{
"name": "formsy-react",
"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",
"version": "0.19.5",
"description": "A form input builder and validator for React JS",
"repository": {
"type": "git",
"url": "https://github.com/christianalfoni/formsy-react.git"
},
"author": "Christian Alfoni",
"files": [
"lib"
],
"main": "release/formsy-react.js",
"main": "lib/main.js",
"scripts": {
"build": "NODE_ENV=production webpack -p --config webpack.config.js && yarn run prepublish",
"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",
"lint": "eslint src/**/*.js",
"prepublish": "rm -Rf ./lib && babel ./src/ -d ./lib/",
"test": "babel-node testrunner"
"prepublish": "babel ./src/ -d ./lib/"
},
"author": "Christian Alfoni",
"license": "MIT",
"keywords": [
"react",
"form",
"forms",
"validation",
"react-component"
],
"dependencies": {
"form-data-to-object": "^0.2.0",
"prop-types": "^15.5.10"
"form-data-to-object": "^0.2.0"
},
"devDependencies": {
"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"
"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"
},
"peerDependencies": {
"react": "^15.6.1",
"react-dom": "^15.6.1"
"react": "^0.14.0 || ^15.0.0"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

30
src/Decorator.js Normal file
View File

@ -0,0 +1,30 @@
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
});
}
});
};
};

44
src/HOC.js Normal file
View File

@ -0,0 +1,44 @@
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')
);
}

176
src/Mixin.js Normal file
View File

@ -0,0 +1,176 @@
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);
}
};

View File

@ -1,255 +0,0 @@
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;
};

View File

@ -1,468 +0,0 @@
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;

457
src/main.js Normal file
View File

@ -0,0 +1,457 @@
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 @@
export default {
arraysDiffer(a, b) {
let isDifferent = false;
module.exports = {
arraysDiffer: function (a, b) {
var isDifferent = false;
if (a.length !== b.length) {
isDifferent = true;
} else {
a.forEach((item, index) => {
a.forEach(function (item, index) {
if (!this.isSame(item, b[index])) {
isDifferent = true;
}
@ -13,12 +13,12 @@ export default {
return isDifferent;
},
objectsDiffer(a, b) {
let isDifferent = false;
objectsDiffer: function (a, b) {
var isDifferent = false;
if (Object.keys(a).length !== Object.keys(b).length) {
isDifferent = true;
} else {
Object.keys(a).forEach((key) => {
Object.keys(a).forEach(function (key) {
if (!this.isSame(a[key], b[key])) {
isDifferent = true;
}
@ -27,7 +27,7 @@ export default {
return isDifferent;
},
isSame(a, b) {
isSame: function (a, b) {
if (typeof a !== typeof b) {
return false;
} else if (Array.isArray(a) && Array.isArray(b)) {
@ -41,61 +41,13 @@ export default {
return a === b;
},
find(collection, fn) {
for (let i = 0, l = collection.length; i < l; i += 1) {
const item = collection[i];
find: function (collection, fn) {
for (var i = 0, l = collection.length; i < l; i++) {
var 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,73 +1,78 @@
const isExisty = value => value !== null && value !== undefined;
const isEmpty = value => value === '';
var isExisty = function (value) {
return value !== null && value !== undefined;
};
const validations = {
isDefaultRequiredValue(values, value) {
var isEmpty = function (value) {
return value === '';
};
var validations = {
isDefaultRequiredValue: function (values, value) {
return value === undefined || value === '';
},
isExisty(values, value) {
isExisty: function (values, value) {
return isExisty(value);
},
matchRegexp(values, value, regexp) {
matchRegexp: function (values, value, regexp) {
return !isExisty(value) || isEmpty(value) || regexp.test(value);
},
isUndefined(values, value) {
isUndefined: function (values, value) {
return value === undefined;
},
isEmptyString(values, value) {
isEmptyString: function (values, value) {
return isEmpty(value);
},
isEmail(values, value) {
return validations.matchRegexp(values, value, /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i);
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);
},
isUrl(values, value) {
return validations.matchRegexp(values, value, /^(?:\w+:)?\/\/([^\s.]+\.\S{2}|localhost[:?\d]*)\S*$/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);
},
isTrue(values, value) {
isTrue: function (values, value) {
return value === true;
},
isFalse(values, value) {
isFalse: function (values, value) {
return value === false;
},
isNumeric(values, value) {
isNumeric: function (values, value) {
if (typeof value === 'number') {
return true;
}
return validations.matchRegexp(values, value, /^[-+]?(?:\d*[.])?\d+$/);
},
isAlpha(values, value) {
isAlpha: function (values, value) {
return validations.matchRegexp(values, value, /^[A-Z]+$/i);
},
isAlphanumeric(values, value) {
isAlphanumeric: function (values, value) {
return validations.matchRegexp(values, value, /^[0-9A-Z]+$/i);
},
isInt(values, value) {
isInt: function (values, value) {
return validations.matchRegexp(values, value, /^(?:[-+]?(?:0|[1-9]\d*))$/);
},
isFloat(values, value) {
return validations.matchRegexp(values, value, /^(?:[-+]?(?:\d+))?(?:\.\d*)?(?:[eE][+-]?(?:\d+))?$/);
isFloat: function (values, value) {
return validations.matchRegexp(values, value, /^(?:[-+]?(?:\d+))?(?:\.\d*)?(?:[eE][\+\-]?(?:\d+))?$/);
},
isWords(values, value) {
isWords: function (values, value) {
return validations.matchRegexp(values, value, /^[A-Z\s]+$/i);
},
isSpecialWords(values, value) {
isSpecialWords: function (values, value) {
return validations.matchRegexp(values, value, /^[A-Z\s\u00C0-\u017F]+$/i);
},
isLength(values, value, length) {
isLength: function (values, value, length) {
return !isExisty(value) || isEmpty(value) || value.length === length;
},
equals(values, value, eql) {
return !isExisty(value) || isEmpty(value) || value === eql;
equals: function (values, value, eql) {
return !isExisty(value) || isEmpty(value) || value == eql;
},
equalsField(values, value, field) {
return value === values[field];
equalsField: function (values, value, field) {
return value == values[field];
},
maxLength(values, value, length) {
maxLength: function (values, value, length) {
return !isExisty(value) || value.length <= length;
},
minLength(values, value, length) {
minLength: function (values, value, length) {
return !isExisty(value) || isEmpty(value) || value.length >= length;
},
}
};
export default validations;
module.exports = validations;

View File

@ -1,21 +1,22 @@
import testrunner from 'nodeunit/lib/reporters/default';
import { jsdom } from 'jsdom/lib/old-api';
import path from 'path';
import testrunner from 'nodeunit/lib/reporters/default.js';
import {jsdom} from 'jsdom';
global.document = jsdom();
global.window = global.document.defaultView;
global.window = 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',
}, (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"
}, function(err) {
if (err) {
process.exit(1);
}
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,25 +1,21 @@
import React from 'react';
import Formsy, { withFormsy } from './../..';
import Formsy from './../..';
class TestInput extends React.Component {
static defaultProps = { type: 'text' };
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}/>;
}
};
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 function InputFactory(props) {
return React.createClass(Object.assign(defaultProps, props));
}
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);
export default React.createClass(defaultProps);

View File

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

View File

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

View File

@ -1,20 +0,0 @@
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

@ -0,0 +1,21 @@
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

File diff suppressed because it is too large Load Diff