Compare commits
6 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
db6e68ad4c | |
|
|
c7926cf355 | |
|
|
42e39cbc8f | |
|
|
b413f9db77 | |
|
|
998e2be44f | |
|
|
98525a4b64 |
|
|
@ -2,7 +2,7 @@
|
|||
.editorconfig
|
||||
.travis.yml
|
||||
testrunner.js
|
||||
webpack.production.config.js
|
||||
webpack.config.js
|
||||
examples/
|
||||
release/
|
||||
tests/
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
language: node_js
|
||||
sudo: false
|
||||
node_js:
|
||||
- '4.1'
|
||||
- '8.2.1'
|
||||
|
|
|
|||
81
CHANGES.md
81
CHANGES.md
|
|
@ -1,81 +0,0 @@
|
|||
This is the old CHANGES file. Please look at [releases](https://github.com/christianalfoni/formsy-react/releases) for latest changes.
|
||||
|
||||
**0.8.0**
|
||||
- Fixed bug where dynamic form elements gave "not mounted" error (Thanks @sdemjanenko)
|
||||
- React is now a peer dependency (Thanks @snario)
|
||||
- Dynamically updated values should now work with initial "undefined" value (Thanks @sdemjanenko)
|
||||
- Validations are now dynamic. Change the prop and existing values are re-validated (thanks @bryannaegele)
|
||||
- You can now set a "disabled" prop on the form and check "isFormDisabled()" in form elements
|
||||
- Refactored some code and written a couple of tests
|
||||
|
||||
**0.7.2**:
|
||||
- isNumber validation now supports float (Thanks @hahahana)
|
||||
- Form XHR calls now includes CSRF headers, if exists (Thanks @hahahana)
|
||||
|
||||
**0.7.1**
|
||||
- Fixed bug where external update of value on pristine form element did not update the form model (Thanks @sdemjanenko)
|
||||
- Fixed bug where children are null/undefined (Thanks @sdemjanenko)
|
||||
|
||||
**0.7.0**
|
||||
- Dynamic form elements. Add them at any point and they will be registered with the form
|
||||
- **onChange()** handler is called whenever an form element has changed its value or a new form element is added to the form
|
||||
- isNumeric validator now also handles actual numbers, not only strings
|
||||
- Some more tests
|
||||
|
||||
**0.6.0**
|
||||
- **onSubmit()** now has the same signature regardless of passing url attribute or not
|
||||
- **isPristine()** is a new method to handle "touched" form elements (thanks @FoxxMD)
|
||||
- Mapping attributes to pass a function that maps input values to new structure. The new structure is either passed to *onSubmit* and/or to the server when using a url attribute (thanks for feedback @MattAitchison)
|
||||
- Added default "equalsField" validation rule
|
||||
- Lots of tests!
|
||||
|
||||
**0.5.2**
|
||||
- Fixed bug with handlers in ajax requests (Thanks @smokku)
|
||||
|
||||
**0.5.1**
|
||||
- Fixed bug with empty validations
|
||||
|
||||
**0.5.0**
|
||||
- Added [cross input validation](#formsyaddvalidationrule)
|
||||
- Fixed bug where validation rule refers to a string
|
||||
- Added "invalidateForm" function when manually submitting the form
|
||||
|
||||
**0.4.1**
|
||||
- Fixed bug where form element is required, but no validations
|
||||
|
||||
**0.4.0**:
|
||||
- Possibility to handle form data manually using "onSubmit"
|
||||
- Added two more default rules. *isWords* and *isSpecialWords*
|
||||
|
||||
**0.3.0**:
|
||||
- Deprecated everything related to buttons automatically added
|
||||
- Added onValid and onInvalid handlers, use those to manipulate submit buttons etc.
|
||||
|
||||
**0.2.3**:
|
||||
|
||||
- Fixed bug where child does not have props property
|
||||
|
||||
**0.2.2**:
|
||||
|
||||
- Fixed bug with updating the props
|
||||
|
||||
**0.2.1**:
|
||||
|
||||
- Cancel button displays if onCancel handler is defined
|
||||
|
||||
**0.2.0**:
|
||||
|
||||
- Implemented hasValue() method
|
||||
|
||||
**0.1.3**:
|
||||
|
||||
- Fixed resetValue bug
|
||||
|
||||
**0.1.2**:
|
||||
|
||||
- Fixed isValue check to empty string, needs to support false and 0
|
||||
|
||||
**0.1.1**:
|
||||
|
||||
- Added resetValue method
|
||||
- Changed value check of showRequired
|
||||
210
README.md
210
README.md
|
|
@ -1,135 +1,131 @@
|
|||
formsy-react [](https://github.com/christianalfoni/formsy-react/releases) [](https://travis-ci.org/christianalfoni/formsy-react)
|
||||
============
|
||||
# formsy-react [](https://github.com/christianalfoni/formsy-react/releases) [](https://travis-ci.org/christianalfoni/formsy-react)
|
||||
|
||||
A form input builder and validator for React JS
|
||||
A form input builder and validator for React.
|
||||
|
||||
| [How to use](#how-to-use) | [API](/API.md) | [Examples](/examples) |
|
||||
|---|---|---|
|
||||
| [Quick Start](#quick-start) | [API](/API.md) | [Examples](/examples) |
|
||||
| --------------------------- | -------------- | --------------------- |
|
||||
|
||||
## <a name="background">Background</a>
|
||||
I wrote an article on forms and validation with React JS, [Nailing that validation with React JS](http://christianalfoni.github.io/javascript/2014/10/22/nailing-that-validation-with-reactjs.html), the result of that was this extension.
|
||||
## Background
|
||||
|
||||
The main concept is that forms, inputs and validation is done very differently across developers and projects. This extension to React JS aims to be that "sweet spot" between flexibility and reusability.
|
||||
I wrote an article on forms and validation with React, [Nailing that validation with React JS](http://christianalfoni.github.io/javascript/2014/10/22/nailing-that-validation-with-reactjs.html), the result of that was this component.
|
||||
|
||||
## What you can do
|
||||
The main concept is that forms, inputs, and validation are done very differently across developers and projects. This React component aims to be that “sweet spot” between flexibility and reusability.
|
||||
|
||||
1. Build any kind of form element components. Not just traditional inputs, but anything you want and get that validation for free
|
||||
## What You Can Do
|
||||
|
||||
2. Add validation rules and use them with simple syntax
|
||||
|
||||
3. Use handlers for different states of your form. Ex. "onSubmit", "onError", "onValid" etc.
|
||||
|
||||
4. Pass external errors to the form to invalidate elements
|
||||
|
||||
5. You can dynamically add form elements to your form and they will register/unregister to the form
|
||||
|
||||
## Default elements
|
||||
You can look at examples in this repo or use the [formsy-react-components](https://github.com/twisty/formsy-react-components) project to use bootstrap with formsy-react, or use [formsy-material-ui](https://github.com/mbrookes/formsy-material-ui) to use [Material-UI](http://material-ui.com/) with formsy-react.
|
||||
1. Build any kind of form element components. Not just traditional inputs, but anything you want, and get that validation for free
|
||||
2. Add validation rules and use them with simple syntax
|
||||
3. Use handlers for different states of your form. (`onError`, `onSubmit`, `onValid`, etc.)
|
||||
4. Pass external errors to the form to invalidate elements (E.g. a response from a server)
|
||||
5. Dynamically add form elements to your form and they will register/unregister to the form
|
||||
|
||||
## Install
|
||||
|
||||
1. Download from this REPO and use globally (Formsy) or with requirejs
|
||||
2. Install with `npm install formsy-react` and use with browserify etc.
|
||||
3. Install with `bower install formsy-react`
|
||||
`yarn add formsy-react react react-dom` and use with webpack, browserify, etc.
|
||||
|
||||
## Changes
|
||||
## Quick Start
|
||||
|
||||
[Check out releases](https://github.com/christianalfoni/formsy-react/releases)
|
||||
|
||||
[Older changes](CHANGES.md)
|
||||
|
||||
## How to use
|
||||
|
||||
See [`examples` folder](/examples) for examples. [Codepen demo](http://codepen.io/semigradsky/pen/dYYpwv?editors=001).
|
||||
|
||||
Complete API reference is available [here](/API.md).
|
||||
|
||||
#### Formsy gives you a form straight out of the box
|
||||
### 1. Build a Formsy element
|
||||
|
||||
```jsx
|
||||
import Formsy from 'formsy-react';
|
||||
// MyInput.js
|
||||
import { withFormsy } from 'formsy-react';
|
||||
import React from 'react';
|
||||
|
||||
const MyAppForm = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
canSubmit: false
|
||||
}
|
||||
},
|
||||
enableButton() {
|
||||
this.setState({
|
||||
canSubmit: true
|
||||
});
|
||||
},
|
||||
disableButton() {
|
||||
this.setState({
|
||||
canSubmit: false
|
||||
});
|
||||
},
|
||||
submit(model) {
|
||||
someDep.saveEmail(model.email);
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}>
|
||||
<MyOwnInput name="email" validations="isEmail" validationError="This is not a valid email" required/>
|
||||
<button type="submit" disabled={!this.state.canSubmit}>Submit</button>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
This code results in a form with a submit button that will run the `submit` method when the submit button is clicked with a valid email. The submit button is disabled as long as the input is empty ([required](/API.md#required)) or the value is not an email ([isEmail](/API.md#validators)). On validation error it will show the message: "This is not a valid email".
|
||||
|
||||
#### Building a form element (required)
|
||||
```jsx
|
||||
import Formsy from 'formsy-react';
|
||||
|
||||
const MyOwnInput = React.createClass({
|
||||
|
||||
// Add the Formsy Mixin
|
||||
mixins: [Formsy.Mixin],
|
||||
class MyInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.changeValue = this.changeValue.bind(this);
|
||||
}
|
||||
|
||||
changeValue(event) {
|
||||
// setValue() will set the value of the component, which in
|
||||
// turn will validate it and the rest of the form
|
||||
changeValue(event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
// Important: Don't skip this step. This pattern is required
|
||||
// for Formsy to work.
|
||||
this.props.setValue(event.currentTarget.value);
|
||||
}
|
||||
|
||||
render() {
|
||||
// Set a specific className based on the validation
|
||||
// state of this component. showRequired() is true
|
||||
// when the value is empty and the required prop is
|
||||
// passed to the input. showError() is true when the
|
||||
// value typed is invalid
|
||||
const className = this.showRequired() ? 'required' : this.showError() ? 'error' : null;
|
||||
render() {
|
||||
// An error message is returned only if the component is invalid
|
||||
const errorMessage = this.props.getErrorMessage();
|
||||
|
||||
// An error message is returned ONLY if the component is invalid
|
||||
// or the server has returned an error message
|
||||
const errorMessage = this.getErrorMessage();
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
onChange={this.changeValue}
|
||||
type="text"
|
||||
value={this.props.getValue() || ''}
|
||||
/>
|
||||
<span>{errorMessage}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
<span>{errorMessage}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
export default withFormsy(MyInput);
|
||||
```
|
||||
The form element component is what gives the form validation functionality to whatever you want to put inside this wrapper. You do not have to use traditional inputs, it can be anything you want and the value of the form element can also be anything you want. As you can see it is very flexible, you just have a small API to help you identify the state of the component and set its value.
|
||||
|
||||
## Related projects
|
||||
- [formsy-material-ui](https://github.com/mbrookes/formsy-material-ui) - A formsy-react compatibility wrapper for [Material-UI](http://material-ui.com/) form components.
|
||||
- [formsy-react-components](https://github.com/twisty/formsy-react-components) - A set of React JS components for use in a formsy-react form.
|
||||
- ...
|
||||
- Send PR for adding your project to this list!
|
||||
`withFormsy` is a [Higher-Order Component](https://facebook.github.io/react/docs/higher-order-components.html) that exposes additional props to `MyInput`. See the [API](/API.md#withFormsy) documentation to view a complete list of the props.
|
||||
|
||||
### 2. Use your Formsy element
|
||||
|
||||
```jsx
|
||||
import Formsy from 'formsy-react';
|
||||
import React from 'react';
|
||||
import MyInput from './MyInput';
|
||||
|
||||
export default class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.disableButton = this.disableButton.bind(this);
|
||||
this.enableButton = this.enableButton.bind(this);
|
||||
this.state = { canSubmit: false };
|
||||
}
|
||||
|
||||
disableButton() {
|
||||
this.setState({ canSubmit: false });
|
||||
}
|
||||
|
||||
enableButton() {
|
||||
this.setState({ canSubmit: true });
|
||||
}
|
||||
|
||||
submit(model) {
|
||||
fetch('http://example.com/', {
|
||||
method: 'post',
|
||||
body: JSON.stringify(model)
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Formsy onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}>
|
||||
<MyInput
|
||||
name="email"
|
||||
validations="isEmail"
|
||||
validationError="This is not a valid email"
|
||||
required
|
||||
/>
|
||||
<button type="submit" disabled={!this.state.canSubmit}>Submit</button>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This code results in a form with a submit button that will run the `submit` method when the form is submitted with a valid email. The submit button is disabled as long as the input is empty ([required](/API.md#required)) and the value is not an email ([isEmail](/API.md#validators)). On validation error it will show the message: "This is not a valid email".
|
||||
|
||||
## Contribute
|
||||
- Fork repo
|
||||
- `npm install`
|
||||
- `npm run examples` runs the development server on `localhost:8080`
|
||||
- `npm test` runs the tests
|
||||
|
||||
- Fork repo
|
||||
- `yarn`
|
||||
- `yarn examples` runs the development server on `localhost:8080`
|
||||
- `yarn test` runs the tests
|
||||
|
||||
## Changelog
|
||||
|
||||
[Check out releases](https://github.com/christianalfoni/formsy-react/releases)
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
25
bower.json
25
bower.json
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"name": "formsy-react",
|
||||
"version": "0.18.0",
|
||||
"description": "A form input builder and validator for React JS",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/christianalfoni/formsy-react.git"
|
||||
},
|
||||
"main": "src/main.js",
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"build/",
|
||||
"Gulpfile.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"react": "^0.14.7 || ^15.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
"form",
|
||||
"forms",
|
||||
"validation",
|
||||
"react-component"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
"react/jsx-filename-extension": 0,
|
||||
"react/no-multi-comp": 0
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ If it is not helped try update your node.js and npm.
|
|||
|
||||
2. [**Custom Validation**](custom-validation)
|
||||
|
||||
One field with added validation rule (`Formsy.addValidationRule`) and one field with dynamically added validation and error messages.
|
||||
One field with added validation rule (`addValidationRule`) and one field with dynamically added validation and error messages.
|
||||
|
||||
3. [**Reset Values**](reset-values)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
import React from 'react';
|
||||
import { propTypes, withFormsy } from 'formsy-react';
|
||||
|
||||
class MyCheckbox extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.changeValue = this.changeValue.bind(this);
|
||||
this.state = {
|
||||
value: true,
|
||||
};
|
||||
}
|
||||
|
||||
changeValue(event) {
|
||||
// setValue() will set the value of the component, which in
|
||||
// turn will validate it and the rest of the form
|
||||
this.props.setValue(event.target.checked)
|
||||
}
|
||||
|
||||
render() {
|
||||
// Set a specific className based on the validation
|
||||
// state of this component. showRequired() is true
|
||||
// when the value is empty and the required prop is
|
||||
// passed to the input. showError() is true when the
|
||||
// value typed is invalid
|
||||
const className = `form-group ${this.props.className} ${this.props.showRequired() ? 'required' : ''} ${this.props.showError() ? 'error' : ''}`;
|
||||
const value = this.props.getValue();
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
>
|
||||
<label htmlFor={this.props.name}>{this.props.title}</label>
|
||||
<input
|
||||
onChange={this.changeValue}
|
||||
id={this.props.name}
|
||||
type='checkbox'
|
||||
checked={value}
|
||||
data-checked={value}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MyCheckbox.propTypes = {
|
||||
...propTypes,
|
||||
};
|
||||
|
||||
export default withFormsy(MyCheckbox);
|
||||
|
|
@ -1,44 +1,47 @@
|
|||
import React from 'react';
|
||||
import Formsy from 'formsy-react';
|
||||
import { propTypes, withFormsy } from 'formsy-react';
|
||||
|
||||
const MyInput = React.createClass({
|
||||
class MyInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.changeValue = this.changeValue.bind(this);
|
||||
}
|
||||
|
||||
// Add the Formsy Mixin
|
||||
mixins: [Formsy.Mixin],
|
||||
|
||||
// setValue() will set the value of the component, which in
|
||||
// turn will validate it and the rest of the form
|
||||
changeValue(event) {
|
||||
this.setValue(event.currentTarget[this.props.type === 'checkbox' ? 'checked' : 'value']);
|
||||
},
|
||||
render() {
|
||||
// setValue() will set the value of the component, which in
|
||||
// turn will validate it and the rest of the form
|
||||
this.props.setValue(event.currentTarget[this.props.type === 'checkbox' ? 'checked' : 'value']);
|
||||
}
|
||||
|
||||
render() {
|
||||
// Set a specific className based on the validation
|
||||
// state of this component. showRequired() is true
|
||||
// when the value is empty and the required prop is
|
||||
// passed to the input. showError() is true when the
|
||||
// value typed is invalid
|
||||
const className = 'form-group' + (this.props.className || ' ') +
|
||||
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
|
||||
const className = `form-group ${this.props.className} ${this.props.showRequired() ? 'required' : ''} ${this.props.showError() ? 'error' : ''}`;
|
||||
|
||||
// An error message is returned ONLY if the component is invalid
|
||||
// or the server has returned an error message
|
||||
const errorMessage = this.getErrorMessage();
|
||||
const errorMessage = this.props.getErrorMessage();
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<label htmlFor={this.props.name}>{this.props.title}</label>
|
||||
<input
|
||||
type={this.props.type || 'text'}
|
||||
name={this.props.name}
|
||||
onChange={this.changeValue}
|
||||
value={this.getValue()}
|
||||
checked={this.props.type === 'checkbox' && this.getValue() ? 'checked' : null}
|
||||
name={this.props.name}
|
||||
type={this.props.type || 'text'}
|
||||
value={this.props.getValue() || ''}
|
||||
/>
|
||||
<span className='validation-error'>{errorMessage}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default MyInput;
|
||||
MyInput.propTypes = {
|
||||
...propTypes,
|
||||
};
|
||||
|
||||
export default withFormsy(MyInput);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import Formsy from 'formsy-react';
|
||||
import { withFormsy } from 'formsy-react';
|
||||
|
||||
function contains(container, item, cmp) {
|
||||
for (const it of container) {
|
||||
|
|
@ -10,18 +10,16 @@ function contains(container, item, cmp) {
|
|||
return false;
|
||||
}
|
||||
|
||||
const MyRadioGroup = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
getInitialState() {
|
||||
return { value: [], cmp: (a, b) => a === b };
|
||||
},
|
||||
class MyRadioGroup extends React.Component {
|
||||
state = { value: [], cmp: (a, b) => a === b };
|
||||
|
||||
componentDidMount() {
|
||||
const value = this.props.value || [];
|
||||
this.setValue(value);
|
||||
this.props.setValue(value);
|
||||
this.setState({ value: value, cmp: this.props.cmp || this.state.cmp });
|
||||
},
|
||||
}
|
||||
|
||||
changeValue(value, event) {
|
||||
changeValue = (value, event) => {
|
||||
const checked = event.currentTarget.checked;
|
||||
|
||||
let newValue = [];
|
||||
|
|
@ -31,14 +29,14 @@ const MyRadioGroup = React.createClass({
|
|||
newValue = this.state.value.filter(it => !this.state.cmp(it, value));
|
||||
}
|
||||
|
||||
this.setValue(newValue);
|
||||
this.props.setValue(newValue);
|
||||
this.setState({ value: newValue });
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
const className = 'form-group' + (this.props.className || ' ') +
|
||||
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
|
||||
const errorMessage = this.getErrorMessage();
|
||||
(this.props.showRequired() ? 'required' : this.props.showError() ? 'error' : '');
|
||||
const errorMessage = this.props.getErrorMessage();
|
||||
|
||||
const { name, title, items } = this.props;
|
||||
return (
|
||||
|
|
@ -61,6 +59,6 @@ const MyRadioGroup = React.createClass({
|
|||
);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
export default MyRadioGroup;
|
||||
export default withFormsy(MyRadioGroup);
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import React from 'react';
|
||||
import Formsy from 'formsy-react';
|
||||
import { withFormsy } from 'formsy-react';
|
||||
|
||||
const MyRadioGroup = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
class MyRadioGroup extends React.Component {
|
||||
state = {};
|
||||
|
||||
componentDidMount() {
|
||||
const value = this.props.value;
|
||||
this.setValue(value);
|
||||
this.props.setValue(value);
|
||||
this.setState({ value });
|
||||
},
|
||||
}
|
||||
|
||||
changeValue(value) {
|
||||
this.setValue(value);
|
||||
changeValue = (value) => {
|
||||
this.props.setValue(value);
|
||||
this.setState({ value });
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
const className = 'form-group' + (this.props.className || ' ') +
|
||||
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
|
||||
const errorMessage = this.getErrorMessage();
|
||||
(this.props.showRequired() ? 'required' : this.props.showError() ? 'error' : '');
|
||||
const errorMessage = this.props.getErrorMessage();
|
||||
|
||||
const { name, title, items } = this.props;
|
||||
return (
|
||||
|
|
@ -40,7 +40,6 @@ const MyRadioGroup = React.createClass({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default MyRadioGroup;
|
||||
export default withFormsy(MyRadioGroup);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
import React from 'react';
|
||||
import Formsy from 'formsy-react';
|
||||
import { withFormsy } from 'formsy-react';
|
||||
|
||||
const MySelect = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
|
||||
changeValue(event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
class MySelect extends React.Component {
|
||||
changeValue = (event) => {
|
||||
this.props.setValue(event.currentTarget.value);
|
||||
}
|
||||
|
||||
render() {
|
||||
const className = 'form-group' + (this.props.className || ' ') +
|
||||
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
|
||||
const errorMessage = this.getErrorMessage();
|
||||
(this.props.showRequired() ? 'required' : this.props.showError() ? 'error' : '');
|
||||
const errorMessage = this.props.getErrorMessage();
|
||||
|
||||
const options = this.props.options.map((option, i) => (
|
||||
<option key={option.title+option.value} value={option.value}>
|
||||
|
|
@ -22,14 +20,13 @@ const MySelect = React.createClass({
|
|||
return (
|
||||
<div className={className}>
|
||||
<label htmlFor={this.props.name}>{this.props.title}</label>
|
||||
<select name={this.props.name} onChange={this.changeValue} value={this.getValue()}>
|
||||
<select name={this.props.name} onChange={this.changeValue} value={this.props.getValue()}>
|
||||
{options}
|
||||
</select>
|
||||
<span className='validation-error'>{errorMessage}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default MySelect;
|
||||
export default withFormsy(MySelect);
|
||||
|
|
|
|||
|
|
@ -1,105 +1,124 @@
|
|||
import Formsy, { addValidationRule, propTypes, withFormsy } from 'formsy-react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Formsy from 'formsy-react';
|
||||
|
||||
import MyInput from './../components/Input';
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
const validators = {
|
||||
time: {
|
||||
regexp: /^(([0-1]?[0-9])|([2][0-3])):([0-5]?[0-9])(:([0-5]?[0-9]))?$/,
|
||||
message: 'Not valid time'
|
||||
},
|
||||
decimal: {
|
||||
regexp: /(^\d*\.?\d*[0-9]+\d*$)|(^[0-9]+\d*\.\d*$)/,
|
||||
message: 'Please type decimal value'
|
||||
},
|
||||
binary: {
|
||||
regexp: /^([0-1])*$/,
|
||||
message: '10101000'
|
||||
}
|
||||
};
|
||||
|
||||
Formsy.addValidationRule('isYearOfBirth', (values, value) => {
|
||||
value = parseInt(value);
|
||||
if (typeof value !== 'number') {
|
||||
addValidationRule('time', (values, value) => {
|
||||
return /^(([0-1]?[0-9])|([2][0-3])):([0-5]?[0-9])(:([0-5]?[0-9]))?$/.test(value);
|
||||
});
|
||||
addValidationRule('decimal', (values, value) => {
|
||||
return /(^\d*\.?\d*[0-9]+\d*$)|(^[0-9]+\d*\.\d*$)/.test(value);
|
||||
});
|
||||
addValidationRule('binary', (values, value) => {
|
||||
return /^([0-1])*$/.test(value);
|
||||
});
|
||||
addValidationRule('isYearOfBirth', (values, value) => {
|
||||
const parsedValue = parseInt(value, 10);
|
||||
if (typeof parsedValue !== 'number') {
|
||||
return false;
|
||||
}
|
||||
return value < currentYear && value > currentYear - 130;
|
||||
return parsedValue < currentYear && parsedValue > currentYear - 130;
|
||||
});
|
||||
|
||||
const App = React.createClass({
|
||||
class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.submit = this.submit.bind(this);
|
||||
}
|
||||
|
||||
submit(data) {
|
||||
alert(JSON.stringify(data, null, 4));
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={this.submit} className="custom-validation">
|
||||
<Formsy onSubmit={this.submit} className="custom-validation">
|
||||
<MyInput name="year" title="Year of Birth" type="number" validations="isYearOfBirth" validationError="Please type your year of birth" />
|
||||
<DynamicInput name="dynamic" title="..." />
|
||||
<FormsyDynamicInput name="dynamic" title="..." />
|
||||
<button type="submit">Submit</button>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class DynamicInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { validationType: 'time' };
|
||||
this.changeValidation = this.changeValidation.bind(this);
|
||||
this.changeValue = this.changeValue.bind(this);
|
||||
}
|
||||
|
||||
const DynamicInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
getInitialState() {
|
||||
return { validationType: 'time' };
|
||||
},
|
||||
changeValue(event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
changeValidation(validationType) {
|
||||
this.setState({ validationType: validationType });
|
||||
this.setValue(this.getValue());
|
||||
},
|
||||
validate() {
|
||||
const value = this.getValue();
|
||||
console.log(value, this.state.validationType);
|
||||
return value ? validators[this.state.validationType].regexp.test(value) : true;
|
||||
},
|
||||
getCustomErrorMessage() {
|
||||
return this.showError() ? validators[this.state.validationType].message : '';
|
||||
},
|
||||
this.props.setValue(this.props.getValue());
|
||||
}
|
||||
|
||||
changeValue(event) {
|
||||
this.props.setValue(event.currentTarget.value);
|
||||
}
|
||||
|
||||
render() {
|
||||
const className = 'form-group' + (this.props.className || ' ') + (this.showRequired() ? 'required' : this.showError() ? 'error' : null);
|
||||
const errorMessage = this.getCustomErrorMessage();
|
||||
const errorMessage = {
|
||||
time: 'Not a valid time format',
|
||||
decimal: "Not a valid decimal value",
|
||||
binary: "Not a valid binary number"
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div>
|
||||
<label htmlFor={this.props.name}>{this.props.title}</label>
|
||||
<input type='text' name={this.props.name} onChange={this.changeValue} value={this.getValue() || ''}/>
|
||||
<span className='validation-error'>{errorMessage}</span>
|
||||
<Validations validationType={this.state.validationType} changeValidation={this.changeValidation}/>
|
||||
<MyInput validations={this.state.validationType} validationError={errorMessage[this.state.validationType]} type='text' name={this.props.name} onChange={this.changeValue} value={this.props.getValue() || ''} />
|
||||
<Validations validationType={this.state.validationType} changeValidation={this.changeValidation} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
DynamicInput.displayName = "dynamic input";
|
||||
|
||||
DynamicInput.propTypes = {
|
||||
...propTypes,
|
||||
};
|
||||
|
||||
const FormsyDynamicInput = withFormsy(DynamicInput);
|
||||
|
||||
class Validations extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.changeValidation = this.changeValidation.bind(this);
|
||||
}
|
||||
|
||||
const Validations = React.createClass({
|
||||
changeValidation(e) {
|
||||
this.props.changeValidation(e.target.value);
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
const { validationType } = this.props;
|
||||
return (
|
||||
<fieldset onChange={this.changeValidation}>
|
||||
<fieldset>
|
||||
<legend>Validation Type</legend>
|
||||
<div>
|
||||
<input name='validationType' type='radio' value='time' defaultChecked={validationType === 'time'}/>Time
|
||||
<input onChange={this.changeValidation} name='validationType' type='radio' value='time' checked={validationType === 'time'} />Time
|
||||
</div>
|
||||
<div>
|
||||
<input name='validationType' type='radio' value='decimal' defaultChecked={validationType === 'decimal'}/>Decimal
|
||||
<input onChange={this.changeValidation} name='validationType' type='radio' value='decimal' checked={validationType === 'decimal'} />Decimal
|
||||
</div>
|
||||
<div>
|
||||
<input name='validationType' type='radio' value='binary' defaultChecked={validationType === 'binary'}/>Binary
|
||||
<input onChange={this.changeValidation} name='validationType' type='radio' value='binary' checked={validationType === 'binary'} />Binary
|
||||
</div>
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ReactDOM.render(<App/>, document.getElementById('example'));
|
||||
Validations.propTypes = {
|
||||
changeValidation: PropTypes.func.isRequired,
|
||||
validationType: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('example'));
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
<body>
|
||||
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Custom Validation</h1>
|
||||
<div id="example"/>
|
||||
<script src="/__build__/shared.js"></script>
|
||||
<script src="/__build__/custom-validation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -9,7 +9,8 @@
|
|||
}
|
||||
|
||||
.field {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.many-fields .form-group {
|
||||
|
|
@ -17,8 +18,5 @@
|
|||
float: left;
|
||||
}
|
||||
.many-fields .remove-field {
|
||||
margin-top: 30px;
|
||||
margin-left: 8px;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
max-height: 2rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Form } from 'formsy-react';
|
||||
import Formsy from 'formsy-react';
|
||||
|
||||
import MyInput from './../components/Input';
|
||||
import MySelect from './../components/Select';
|
||||
|
|
@ -14,7 +14,7 @@ const Fields = props => {
|
|||
props.onRemove(pos);
|
||||
};
|
||||
}
|
||||
const foo = 'required';
|
||||
|
||||
return (
|
||||
<div className="fields">
|
||||
{props.data.map((field, i) => (
|
||||
|
|
@ -46,7 +46,7 @@ const Fields = props => {
|
|||
/>
|
||||
)
|
||||
}
|
||||
<a href="#" className="remove-field" onClick={onRemove(i)}>X</a>
|
||||
<button className="remove-field" onClick={onRemove(i)}>X</button>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
|
@ -54,36 +54,49 @@ const Fields = props => {
|
|||
);
|
||||
};
|
||||
|
||||
const App = React.createClass({
|
||||
getInitialState() {
|
||||
return { fields: [], canSubmit: false };
|
||||
},
|
||||
class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { fields: [], canSubmit: false };
|
||||
this.addField = this.addField.bind(this);
|
||||
this.removeField = this.removeField.bind(this);
|
||||
this.enableButton = this.enableButton.bind(this);
|
||||
this.disableButton = this.disableButton.bind(this);
|
||||
}
|
||||
|
||||
submit(data) {
|
||||
alert(JSON.stringify(data, null, 4));
|
||||
},
|
||||
}
|
||||
|
||||
addField(fieldData) {
|
||||
fieldData.validations = fieldData.validations.length ?
|
||||
fieldData.validations.reduce((a, b) => Object.assign({}, a, b)) :
|
||||
null;
|
||||
fieldData.id = Date.now();
|
||||
this.setState({ fields: this.state.fields.concat(fieldData) });
|
||||
},
|
||||
}
|
||||
|
||||
removeField(pos) {
|
||||
const fields = this.state.fields;
|
||||
this.setState({ fields: fields.slice(0, pos).concat(fields.slice(pos+1)) })
|
||||
},
|
||||
}
|
||||
|
||||
enableButton() {
|
||||
this.setState({ canSubmit: true });
|
||||
},
|
||||
}
|
||||
|
||||
disableButton() {
|
||||
this.setState({ canSubmit: false });
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
const { fields, canSubmit } = this.state;
|
||||
return (
|
||||
<div>
|
||||
<Form onSubmit={this.addField} className="many-fields-conf">
|
||||
<MyMultiCheckboxSet name="validations" title="Validations"
|
||||
<Formsy onSubmit={this.addField} className="many-fields-conf">
|
||||
<MyMultiCheckboxSet
|
||||
name="validations"
|
||||
title="Validations"
|
||||
cmp={(a, b) => JSON.stringify(a) === JSON.stringify(b)}
|
||||
items={[
|
||||
{isEmail: true},
|
||||
|
|
@ -95,19 +108,27 @@ const App = React.createClass({
|
|||
{maxLength: 7}
|
||||
]}
|
||||
/>
|
||||
<MyRadioGroup name="required" value={false} title="Required"
|
||||
items={[true, false]} />
|
||||
<MyRadioGroup name="type" value="input" title="Type"
|
||||
items={['input', 'select']} />
|
||||
<MyRadioGroup
|
||||
name="required"
|
||||
value={false}
|
||||
title="Required"
|
||||
items={[true, false]}
|
||||
/>
|
||||
<MyRadioGroup
|
||||
name="type"
|
||||
value="input"
|
||||
title="Type"
|
||||
items={['input', 'select']}
|
||||
/>
|
||||
<button type="submit">Add</button>
|
||||
</Form>
|
||||
<Form onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="many-fields">
|
||||
</Formsy>
|
||||
<Formsy onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="many-fields">
|
||||
<Fields data={fields} onRemove={this.removeField} />
|
||||
<button type="submit" disabled={!canSubmit}>Submit</button>
|
||||
</Form>
|
||||
</Formsy>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ReactDOM.render(<App/>, document.getElementById('example'));
|
||||
ReactDOM.render(<App />, document.getElementById('example'));
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
<body>
|
||||
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Dynamic Form Fields</h1>
|
||||
<div id="example"/>
|
||||
<script src="/__build__/shared.js"></script>
|
||||
<script src="/__build__/dynamic-form-fields.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,31 +1,38 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Form } from 'formsy-react';
|
||||
import Formsy from 'formsy-react';
|
||||
|
||||
import MyInput from './../components/Input';
|
||||
|
||||
const App = React.createClass({
|
||||
getInitialState() {
|
||||
return { canSubmit: false };
|
||||
},
|
||||
class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { canSubmit: false };
|
||||
this.disableButton = this.disableButton.bind(this);
|
||||
this.enableButton = this.enableButton.bind(this);
|
||||
}
|
||||
|
||||
submit(data) {
|
||||
alert(JSON.stringify(data, null, 4));
|
||||
},
|
||||
}
|
||||
|
||||
enableButton() {
|
||||
this.setState({ canSubmit: true });
|
||||
},
|
||||
}
|
||||
|
||||
disableButton() {
|
||||
this.setState({ canSubmit: false });
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Form onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="login">
|
||||
<MyInput value="" name="email" title="Email" validations="isEmail" validationError="This is not a valid email" required />
|
||||
<MyInput value="" name="password" title="Password" type="password" required />
|
||||
<Formsy onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="login">
|
||||
<MyInput name="email" title="Email" validations="isEmail" validationError="This is not a valid email" required />
|
||||
<MyInput name="password" title="Password" type="password" required />
|
||||
<button type="submit" disabled={!this.state.canSubmit}>Submit</button>
|
||||
</Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ReactDOM.render(<App/>, document.getElementById('example'));
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
<body>
|
||||
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Login</h1>
|
||||
<div id="example"/>
|
||||
<script src="/__build__/shared.js"></script>
|
||||
<script src="/__build__/login.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Form } from 'formsy-react';
|
||||
import Formsy from 'formsy-react';
|
||||
|
||||
import MyCheckbox from './../components/Checkbox';
|
||||
import MyInput from './../components/Input';
|
||||
import MySelect from './../components/Select';
|
||||
|
||||
|
|
@ -11,19 +12,45 @@ const user = {
|
|||
hair: 'brown'
|
||||
};
|
||||
|
||||
const App = React.createClass({
|
||||
const randomNames = ['Christian', 'Dmitry', 'Aesop'];
|
||||
const randomFree = [true, false];
|
||||
const randomHair = ['brown', 'black', 'blonde', 'red'];
|
||||
|
||||
class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.randomize = this.randomize.bind(this);
|
||||
this.submit = this.submit.bind(this);
|
||||
}
|
||||
|
||||
randomize() {
|
||||
const random = {
|
||||
name: randomNames[Math.floor(Math.random()*randomNames.length)],
|
||||
free: randomFree[Math.floor(Math.random()*randomFree.length)],
|
||||
hair: randomHair[Math.floor(Math.random()*randomHair.length)],
|
||||
};
|
||||
|
||||
this.form.reset(random);
|
||||
}
|
||||
|
||||
submit(data) {
|
||||
alert(JSON.stringify(data, null, 4));
|
||||
},
|
||||
resetForm() {
|
||||
this.refs.form.reset();
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form ref="form" onSubmit={this.submit} className="form">
|
||||
<Formsy
|
||||
ref={(c) => this.form = c}
|
||||
onSubmit={this.submit}
|
||||
onReset={this.reset}
|
||||
className="form"
|
||||
>
|
||||
<MyInput name="name" title="Name" value={user.name} />
|
||||
<MyInput name="free" title="Free to hire" type="checkbox" value={user.free} />
|
||||
<MySelect name="hair" title="Hair" value={user.hair}
|
||||
<MyCheckbox name="free" title="Free to hire" value={user.free} />
|
||||
<MySelect
|
||||
name="hair"
|
||||
title="Hair"
|
||||
value={user.hair}
|
||||
options={[
|
||||
{ value: "black", title: "Black" },
|
||||
{ value: "brown", title: "Brown" },
|
||||
|
|
@ -33,12 +60,13 @@ const App = React.createClass({
|
|||
/>
|
||||
|
||||
<div className="buttons">
|
||||
<button type="reset" onClick={this.resetForm}>Reset</button>
|
||||
<button type="reset">Reset</button>
|
||||
<button type="button" onClick={this.randomize}>Randomize</button>
|
||||
<button type="submit">Submit</button>
|
||||
</div>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ReactDOM.render(<App/>, document.getElementById('example'));
|
||||
ReactDOM.render(<App />, document.getElementById('example'));
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
<body>
|
||||
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Reset Values</h1>
|
||||
<div id="example"/>
|
||||
<script src="/__build__/shared.js"></script>
|
||||
<script src="/__build__/reset-values.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,49 +1,41 @@
|
|||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var webpack = require('webpack');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
function isDirectory(dir) {
|
||||
return fs.lstatSync(dir).isDirectory();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
devtool: 'inline-source-map',
|
||||
|
||||
entry: fs.readdirSync(__dirname).reduce(function (entries, dir) {
|
||||
var isDraft = dir.charAt(0) === '_' || dir.indexOf('components') >= 0;
|
||||
|
||||
entry: fs.readdirSync(__dirname).reduce((entries, dir) => {
|
||||
const isDraft = dir.charAt(0) === '_' || dir.indexOf('components') >= 0;
|
||||
if (!isDraft && isDirectory(path.join(__dirname, dir))) {
|
||||
entries[dir] = path.join(__dirname, dir, 'app.js');
|
||||
}
|
||||
|
||||
return entries;
|
||||
}, {}),
|
||||
|
||||
output: {
|
||||
path: 'examples/__build__',
|
||||
path: path.resolve(__dirname, 'examples/__build__'),
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[id].chunk.js',
|
||||
publicPath: '/__build__/'
|
||||
publicPath: '/__build__/',
|
||||
},
|
||||
|
||||
module: {
|
||||
loaders: [
|
||||
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel' }
|
||||
]
|
||||
loaders: [{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
}],
|
||||
},
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
'formsy-react': '../../src/main'
|
||||
}
|
||||
'formsy-react': '../../src/index',
|
||||
},
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.optimize.CommonsChunkPlugin('shared.js'),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
|
||||
})
|
||||
]
|
||||
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
|
|
|||
80
package.json
80
package.json
|
|
@ -1,49 +1,61 @@
|
|||
{
|
||||
"name": "formsy-react",
|
||||
"version": "0.19.5",
|
||||
"description": "A form input builder and validator for React JS",
|
||||
"version": "1.0.0",
|
||||
"description": "A form input builder and validator for React",
|
||||
"keywords": [
|
||||
"form",
|
||||
"forms",
|
||||
"formsy",
|
||||
"react",
|
||||
"react-component",
|
||||
"validation"
|
||||
],
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/christianalfoni/formsy-react",
|
||||
"bugs": "https://github.com/christianalfoni/formsy-react/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/christianalfoni/formsy-react.git"
|
||||
},
|
||||
"main": "lib/main.js",
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production webpack -p --config webpack.production.config.js",
|
||||
"test": "babel-node testrunner",
|
||||
"examples": "webpack-dev-server --config examples/webpack.config.js --content-base examples",
|
||||
"prepublish": "babel ./src/ -d ./lib/"
|
||||
},
|
||||
"author": "Christian Alfoni",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"react",
|
||||
"form",
|
||||
"forms",
|
||||
"validation",
|
||||
"react-component"
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"main": "release/formsy-react.js",
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production webpack -p --config webpack.config.js && yarn run prepublish",
|
||||
"examples": "webpack-dev-server --config examples/webpack.config.js --content-base examples",
|
||||
"lint": "eslint src/**/*.js",
|
||||
"prepublish": "rm -Rf ./lib && babel ./src/ -d ./lib/",
|
||||
"test": "babel-node testrunner"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data-to-object": "^0.2.0"
|
||||
"form-data-to-object": "^0.2.0",
|
||||
"prop-types": "^15.5.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.6.5",
|
||||
"babel-loader": "^6.2.4",
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-preset-react": "^6.5.0",
|
||||
"babel-preset-stage-2": "^6.5.0",
|
||||
"create-react-class": "^15.6.0",
|
||||
"jsdom": "^6.5.1",
|
||||
"nodeunit": "^0.9.1",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "^15.0.0",
|
||||
"react-addons-pure-render-mixin": "^15.0.0",
|
||||
"react-addons-test-utils": "^15.0.0",
|
||||
"react-dom": "^15.0.0",
|
||||
"sinon": "^1.17.3",
|
||||
"webpack": "^1.12.14",
|
||||
"webpack-dev-server": "^1.14.1"
|
||||
"babel-cli": "^6.24.1",
|
||||
"babel-loader": "^7.1.1",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"babel-preset-stage-2": "^6.24.1",
|
||||
"eslint": "^3.19.0 || ^4.3.0",
|
||||
"eslint-config-airbnb": "^15.1.0",
|
||||
"eslint-plugin-import": "^2.7.0",
|
||||
"eslint-plugin-jsx-a11y": "^5.1.1",
|
||||
"eslint-plugin-react": "^7.1.0",
|
||||
"jsdom": "^11.1.0",
|
||||
"nodeunit": "^0.11.1",
|
||||
"react": "^15.6.1",
|
||||
"react-addons-pure-render-mixin": "^15.6.0",
|
||||
"react-addons-test-utils": "^15.6.0",
|
||||
"react-dom": "^15.6.1",
|
||||
"sinon": "^3.2.0",
|
||||
"webpack": "^3.5.4",
|
||||
"webpack-dev-server": "^2.7.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^0.14.0 || ^15.0.0"
|
||||
"react": "^15.6.1",
|
||||
"react-dom": "^15.6.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,30 +0,0 @@
|
|||
var React = global.React || require('react');
|
||||
var createReactClass = require('create-react-class');
|
||||
var Mixin = require('./Mixin.js');
|
||||
module.exports = function () {
|
||||
return function (Component) {
|
||||
return createReactClass({
|
||||
mixins: [Mixin],
|
||||
render: function () {
|
||||
return React.createElement(Component, {
|
||||
setValidations: this.setValidations,
|
||||
setValue: this.setValue,
|
||||
resetValue: this.resetValue,
|
||||
getValue: this.getValue,
|
||||
hasValue: this.hasValue,
|
||||
getErrorMessage: this.getErrorMessage,
|
||||
getErrorMessages: this.getErrorMessages,
|
||||
isFormDisabled: this.isFormDisabled,
|
||||
isValid: this.isValid,
|
||||
isPristine: this.isPristine,
|
||||
isFormSubmitted: this.isFormSubmitted,
|
||||
isRequired: this.isRequired,
|
||||
showRequired: this.showRequired,
|
||||
showError: this.showError,
|
||||
isValidValue: this.isValidValue,
|
||||
...this.props
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
44
src/HOC.js
44
src/HOC.js
|
|
@ -1,44 +0,0 @@
|
|||
var React = global.React || require('react');
|
||||
var createReactClass = require('create-react-class');
|
||||
var Mixin = require('./Mixin.js');
|
||||
module.exports = function (Component) {
|
||||
return createReactClass({
|
||||
displayName: 'Formsy(' + getDisplayName(Component) + ')',
|
||||
mixins: [Mixin],
|
||||
|
||||
render: function () {
|
||||
const { innerRef } = this.props;
|
||||
const propsForElement = {
|
||||
setValidations: this.setValidations,
|
||||
setValue: this.setValue,
|
||||
resetValue: this.resetValue,
|
||||
getValue: this.getValue,
|
||||
hasValue: this.hasValue,
|
||||
getErrorMessage: this.getErrorMessage,
|
||||
getErrorMessages: this.getErrorMessages,
|
||||
isFormDisabled: this.isFormDisabled,
|
||||
isValid: this.isValid,
|
||||
isPristine: this.isPristine,
|
||||
isFormSubmitted: this.isFormSubmitted,
|
||||
isRequired: this.isRequired,
|
||||
showRequired: this.showRequired,
|
||||
showError: this.showError,
|
||||
isValidValue: this.isValidValue,
|
||||
...this.props
|
||||
};
|
||||
|
||||
if (innerRef) {
|
||||
propsForElement.ref = innerRef;
|
||||
}
|
||||
return React.createElement(Component, propsForElement);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function getDisplayName(Component) {
|
||||
return (
|
||||
Component.displayName ||
|
||||
Component.name ||
|
||||
(typeof Component === 'string' ? Component : 'Component')
|
||||
);
|
||||
}
|
||||
176
src/Mixin.js
176
src/Mixin.js
|
|
@ -1,176 +0,0 @@
|
|||
var PropTypes = require('prop-types');
|
||||
var utils = require('./utils.js');
|
||||
var React = global.React || require('react');
|
||||
|
||||
var convertValidationsToObject = function (validations) {
|
||||
|
||||
if (typeof validations === 'string') {
|
||||
|
||||
return validations.split(/\,(?![^{\[]*[}\]])/g).reduce(function (validations, validation) {
|
||||
var args = validation.split(':');
|
||||
var validateMethod = args.shift();
|
||||
|
||||
args = args.map(function (arg) {
|
||||
try {
|
||||
return JSON.parse(arg);
|
||||
} catch (e) {
|
||||
return arg; // It is a string if it can not parse it
|
||||
}
|
||||
});
|
||||
|
||||
if (args.length > 1) {
|
||||
throw new Error('Formsy does not support multiple args on string validations. Use object format of validations instead.');
|
||||
}
|
||||
|
||||
validations[validateMethod] = args.length ? args[0] : true;
|
||||
return validations;
|
||||
}, {});
|
||||
|
||||
}
|
||||
|
||||
return validations || {};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
_value: this.props.value,
|
||||
_isRequired: false,
|
||||
_isValid: true,
|
||||
_isPristine: true,
|
||||
_pristineValue: this.props.value,
|
||||
_validationError: [],
|
||||
_externalError: null,
|
||||
_formSubmitted: false
|
||||
};
|
||||
},
|
||||
contextTypes: {
|
||||
formsy: PropTypes.object // What about required?
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
validationError: '',
|
||||
validationErrors: {}
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function () {
|
||||
var configure = function () {
|
||||
this.setValidations(this.props.validations, this.props.required);
|
||||
|
||||
// Pass a function instead?
|
||||
this.context.formsy.attachToForm(this);
|
||||
//this.props._attachToForm(this);
|
||||
}.bind(this);
|
||||
|
||||
if (!this.props.name) {
|
||||
throw new Error('Form Input requires a name property when used');
|
||||
}
|
||||
|
||||
/*
|
||||
if (!this.props._attachToForm) {
|
||||
return setTimeout(function () {
|
||||
if (!this.isMounted()) return;
|
||||
if (!this.props._attachToForm) {
|
||||
throw new Error('Form Mixin requires component to be nested in a Form');
|
||||
}
|
||||
configure();
|
||||
}.bind(this), 0);
|
||||
}
|
||||
*/
|
||||
configure();
|
||||
},
|
||||
|
||||
// We have to make the validate method is kept when new props are added
|
||||
componentWillReceiveProps: function (nextProps) {
|
||||
this.setValidations(nextProps.validations, nextProps.required);
|
||||
|
||||
},
|
||||
|
||||
componentDidUpdate: function (prevProps) {
|
||||
|
||||
// If the value passed has changed, set it. If value is not passed it will
|
||||
// internally update, and this will never run
|
||||
if (!utils.isSame(this.props.value, prevProps.value)) {
|
||||
this.setValue(this.props.value);
|
||||
}
|
||||
|
||||
// If validations or required is changed, run a new validation
|
||||
if (!utils.isSame(this.props.validations, prevProps.validations) || !utils.isSame(this.props.required, prevProps.required)) {
|
||||
this.context.formsy.validate(this);
|
||||
}
|
||||
},
|
||||
|
||||
// Detach it when component unmounts
|
||||
componentWillUnmount: function () {
|
||||
this.context.formsy.detachFromForm(this);
|
||||
//this.props._detachFromForm(this);
|
||||
},
|
||||
|
||||
setValidations: function (validations, required) {
|
||||
|
||||
// Add validations to the store itself as the props object can not be modified
|
||||
this._validations = convertValidationsToObject(validations) || {};
|
||||
this._requiredValidations = required === true ? {isDefaultRequiredValue: true} : convertValidationsToObject(required);
|
||||
|
||||
},
|
||||
|
||||
// We validate after the value has been set
|
||||
setValue: function (value) {
|
||||
this.setState({
|
||||
_value: value,
|
||||
_isPristine: false
|
||||
}, function () {
|
||||
this.context.formsy.validate(this);
|
||||
//this.props._validate(this);
|
||||
}.bind(this));
|
||||
},
|
||||
resetValue: function () {
|
||||
this.setState({
|
||||
_value: this.state._pristineValue,
|
||||
_isPristine: true
|
||||
}, function () {
|
||||
this.context.formsy.validate(this);
|
||||
//this.props._validate(this);
|
||||
});
|
||||
},
|
||||
getValue: function () {
|
||||
return this.state._value;
|
||||
},
|
||||
hasValue: function () {
|
||||
return this.state._value !== '';
|
||||
},
|
||||
getErrorMessage: function () {
|
||||
var messages = this.getErrorMessages();
|
||||
return messages.length ? messages[0] : null;
|
||||
},
|
||||
getErrorMessages: function () {
|
||||
return !this.isValid() || this.showRequired() ? (this.state._externalError || this.state._validationError || []) : [];
|
||||
},
|
||||
isFormDisabled: function () {
|
||||
return this.context.formsy.isFormDisabled();
|
||||
//return this.props._isFormDisabled();
|
||||
},
|
||||
isValid: function () {
|
||||
return this.state._isValid;
|
||||
},
|
||||
isPristine: function () {
|
||||
return this.state._isPristine;
|
||||
},
|
||||
isFormSubmitted: function () {
|
||||
return this.state._formSubmitted;
|
||||
},
|
||||
isRequired: function () {
|
||||
return !!this.props.required;
|
||||
},
|
||||
showRequired: function () {
|
||||
return this.state._isRequired;
|
||||
},
|
||||
showError: function () {
|
||||
return !this.showRequired() && !this.isValid();
|
||||
},
|
||||
isValidValue: function (value) {
|
||||
return this.context.formsy.isValidValue.call(null, this, value);
|
||||
//return this.props._isValidValue.call(null, this, value);
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import utils from './utils';
|
||||
|
||||
const convertValidationsToObject = (validations) => {
|
||||
if (typeof validations === 'string') {
|
||||
return validations.split(/,(?![^{[]*[}\]])/g).reduce((validationsAccumulator, validation) => {
|
||||
let args = validation.split(':');
|
||||
const validateMethod = args.shift();
|
||||
|
||||
args = args.map((arg) => {
|
||||
try {
|
||||
return JSON.parse(arg);
|
||||
} catch (e) {
|
||||
return arg; // It is a string if it can not parse it
|
||||
}
|
||||
});
|
||||
|
||||
if (args.length > 1) {
|
||||
throw new Error('Formsy does not support multiple args on string validations. Use object format of validations instead.');
|
||||
}
|
||||
|
||||
// Avoid parameter reassignment
|
||||
const validationsAccumulatorCopy = Object.assign({}, validationsAccumulator);
|
||||
validationsAccumulatorCopy[validateMethod] = args.length ? args[0] : true;
|
||||
return validationsAccumulatorCopy;
|
||||
}, {});
|
||||
}
|
||||
|
||||
return validations || {};
|
||||
};
|
||||
|
||||
const propTypes = {
|
||||
innerRef: PropTypes.func,
|
||||
name: PropTypes.string.isRequired,
|
||||
required: PropTypes.bool,
|
||||
validations: PropTypes.oneOfType([
|
||||
PropTypes.object,
|
||||
PropTypes.string,
|
||||
]),
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.bool,
|
||||
PropTypes.string,
|
||||
]),
|
||||
};
|
||||
|
||||
export {
|
||||
propTypes,
|
||||
};
|
||||
|
||||
export default (Component) => {
|
||||
class WrappedComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: props.value,
|
||||
isRequired: false,
|
||||
isValid: true,
|
||||
isPristine: true,
|
||||
pristineValue: props.value,
|
||||
validationError: [],
|
||||
externalError: null,
|
||||
formSubmitted: false,
|
||||
};
|
||||
this.getErrorMessage = this.getErrorMessage.bind(this);
|
||||
this.getErrorMessages = this.getErrorMessages.bind(this);
|
||||
this.getValue = this.getValue.bind(this);
|
||||
this.isFormDisabled = this.isFormDisabled.bind(this);
|
||||
this.isPristine = this.isPristine.bind(this);
|
||||
this.isRequired = this.isRequired.bind(this);
|
||||
this.isValid = this.isValid.bind(this);
|
||||
this.resetValue = this.resetValue.bind(this);
|
||||
this.setValue = this.setValue.bind(this);
|
||||
this.showRequired = this.showRequired.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const configure = () => {
|
||||
this.setValidations(this.props.validations, this.props.required);
|
||||
|
||||
// Pass a function instead?
|
||||
this.context.formsy.attachToForm(this);
|
||||
};
|
||||
|
||||
if (!this.props.name) {
|
||||
throw new Error('Form Input requires a name property when used');
|
||||
}
|
||||
|
||||
configure();
|
||||
}
|
||||
|
||||
// We have to make sure the validate method is kept when new props are added
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setValidations(nextProps.validations, nextProps.required);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
// If the value passed has changed, set it. If value is not passed it will
|
||||
// internally update, and this will never run
|
||||
if (!utils.isSame(this.props.value, prevProps.value)) {
|
||||
this.setValue(this.props.value);
|
||||
}
|
||||
|
||||
// If validations or required is changed, run a new validation
|
||||
if (!utils.isSame(this.props.validations, prevProps.validations) ||
|
||||
!utils.isSame(this.props.required, prevProps.required)) {
|
||||
this.context.formsy.validate(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Detach it when component unmounts
|
||||
componentWillUnmount() {
|
||||
this.context.formsy.detachFromForm(this);
|
||||
}
|
||||
|
||||
getErrorMessage() {
|
||||
const messages = this.getErrorMessages();
|
||||
return messages.length ? messages[0] : null;
|
||||
}
|
||||
|
||||
getErrorMessages() {
|
||||
return !this.isValid() || this.showRequired() ?
|
||||
(this.state.externalError || this.state.validationError || []) : [];
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.state.value;
|
||||
}
|
||||
|
||||
hasValue() {
|
||||
return this.state.value !== '';
|
||||
}
|
||||
|
||||
isFormDisabled() {
|
||||
return this.context.formsy.isFormDisabled();
|
||||
}
|
||||
|
||||
isFormSubmitted() {
|
||||
return this.state.formSubmitted;
|
||||
}
|
||||
|
||||
isPristine() {
|
||||
return this.state.isPristine;
|
||||
}
|
||||
|
||||
isRequired() {
|
||||
return !!this.props.required;
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return this.state.isValid;
|
||||
}
|
||||
|
||||
isValidValue(value) {
|
||||
return this.context.formsy.isValidValue.call(null, this, value);
|
||||
// return this.props.isValidValue.call(null, this, value);
|
||||
}
|
||||
|
||||
resetValue() {
|
||||
this.setState({
|
||||
value: this.state.pristineValue,
|
||||
isPristine: true,
|
||||
}, () => {
|
||||
this.context.formsy.validate(this);
|
||||
});
|
||||
}
|
||||
|
||||
setValidations(validations, required) {
|
||||
// Add validations to the store itself as the props object can not be modified
|
||||
this.validations = convertValidationsToObject(validations) || {};
|
||||
this.requiredValidations = required === true ? { isDefaultRequiredValue: true } :
|
||||
convertValidationsToObject(required);
|
||||
}
|
||||
|
||||
// By default, we validate after the value has been set.
|
||||
// A user can override this and pass a second parameter of `false` to skip validation.
|
||||
setValue(value, validate = true) {
|
||||
if (!validate) {
|
||||
this.setState({
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
value,
|
||||
isPristine: false,
|
||||
}, () => {
|
||||
this.context.formsy.validate(this);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showError() {
|
||||
return !this.showRequired() && !this.isValid();
|
||||
}
|
||||
|
||||
showRequired() {
|
||||
return this.state.isRequired;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { innerRef } = this.props;
|
||||
const propsForElement = {
|
||||
getErrorMessage: this.getErrorMessage,
|
||||
getErrorMessages: this.getErrorMessages,
|
||||
getValue: this.getValue,
|
||||
hasValue: this.hasValue,
|
||||
isFormDisabled: this.isFormDisabled,
|
||||
isValid: this.isValid,
|
||||
isPristine: this.isPristine,
|
||||
isFormSubmitted: this.isFormSubmitted,
|
||||
isRequired: this.isRequired,
|
||||
isValidValue: this.isValidValue,
|
||||
resetValue: this.resetValue,
|
||||
setValidations: this.setValidations,
|
||||
setValue: this.setValue,
|
||||
showRequired: this.showRequired,
|
||||
showError: this.showError,
|
||||
...this.props,
|
||||
};
|
||||
|
||||
if (innerRef) {
|
||||
propsForElement.ref = innerRef;
|
||||
}
|
||||
|
||||
return React.createElement(Component, propsForElement);
|
||||
}
|
||||
}
|
||||
|
||||
function getDisplayName(component) {
|
||||
return (
|
||||
component.displayName ||
|
||||
component.name ||
|
||||
(typeof component === 'string' ? component : 'Component')
|
||||
);
|
||||
}
|
||||
|
||||
WrappedComponent.displayName = `Formsy(${getDisplayName(Component)})`;
|
||||
|
||||
WrappedComponent.contextTypes = {
|
||||
formsy: PropTypes.object, // What about required?
|
||||
};
|
||||
|
||||
WrappedComponent.defaultProps = {
|
||||
innerRef: () => {},
|
||||
required: false,
|
||||
validationError: '',
|
||||
validationErrors: {},
|
||||
validations: null,
|
||||
value: Component.defaultValue,
|
||||
};
|
||||
|
||||
WrappedComponent.propTypes = propTypes;
|
||||
|
||||
return WrappedComponent;
|
||||
};
|
||||
|
|
@ -0,0 +1,468 @@
|
|||
import formDataToObject from 'form-data-to-object';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import utils from './utils';
|
||||
import validationRules from './validationRules';
|
||||
import Wrapper, { propTypes } from './Wrapper';
|
||||
|
||||
class Formsy extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isValid: true,
|
||||
isSubmitting: false,
|
||||
canChange: false,
|
||||
};
|
||||
this.inputs = [];
|
||||
this.attachToForm = this.attachToForm.bind(this);
|
||||
this.detachFromForm = this.detachFromForm.bind(this);
|
||||
this.getCurrentValues = this.getCurrentValues.bind(this);
|
||||
this.getPristineValues = this.getPristineValues.bind(this);
|
||||
this.isChanged = this.isChanged.bind(this);
|
||||
this.isFormDisabled = this.isFormDisabled.bind(this);
|
||||
this.reset = this.reset.bind(this);
|
||||
this.resetModel = this.resetModel.bind(this);
|
||||
this.resetInternal = this.resetInternal.bind(this);
|
||||
this.runValidation = this.runValidation.bind(this);
|
||||
this.submit = this.submit.bind(this);
|
||||
this.updateInputsWithError = this.updateInputsWithError.bind(this);
|
||||
this.validate = this.validate.bind(this);
|
||||
this.validateForm = this.validateForm.bind(this);
|
||||
}
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
formsy: {
|
||||
attachToForm: this.attachToForm,
|
||||
detachFromForm: this.detachFromForm,
|
||||
validate: this.validate,
|
||||
isFormDisabled: this.isFormDisabled,
|
||||
isValidValue: (component, value) => this.runValidation(component, value).isValid,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.validateForm();
|
||||
}
|
||||
|
||||
componentWillUpdate() {
|
||||
// Keep a reference to input names before form updates,
|
||||
// to check if inputs has changed after render
|
||||
this.prevInputNames = this.inputs.map(component => component.props.name);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.props.validationErrors && typeof this.props.validationErrors === 'object' && Object.keys(this.props.validationErrors).length > 0) {
|
||||
this.setInputValidationErrors(this.props.validationErrors);
|
||||
}
|
||||
|
||||
const newInputNames = this.inputs.map(component => component.props.name);
|
||||
if (utils.arraysDiffer(this.prevInputNames, newInputNames)) {
|
||||
this.validateForm();
|
||||
}
|
||||
}
|
||||
|
||||
// Method put on each input component to register
|
||||
// itself to the form
|
||||
attachToForm(component) {
|
||||
if (this.inputs.indexOf(component) === -1) {
|
||||
this.inputs.push(component);
|
||||
}
|
||||
|
||||
this.validate(component);
|
||||
}
|
||||
|
||||
// Method put on each input component to unregister
|
||||
// itself from the form
|
||||
detachFromForm(component) {
|
||||
const componentPos = this.inputs.indexOf(component);
|
||||
|
||||
if (componentPos !== -1) {
|
||||
this.inputs = this.inputs.slice(0, componentPos).concat(this.inputs.slice(componentPos + 1));
|
||||
}
|
||||
|
||||
this.validateForm();
|
||||
}
|
||||
|
||||
getCurrentValues() {
|
||||
return this.inputs.reduce((data, component) => {
|
||||
const name = component.props.name;
|
||||
const dataCopy = Object.assign({}, data); // avoid param reassignment
|
||||
dataCopy[name] = component.state.value;
|
||||
return dataCopy;
|
||||
}, {});
|
||||
}
|
||||
|
||||
getModel() {
|
||||
const currentValues = this.getCurrentValues();
|
||||
return this.mapModel(currentValues);
|
||||
}
|
||||
|
||||
getPristineValues() {
|
||||
return this.inputs.reduce((data, component) => {
|
||||
const name = component.props.name;
|
||||
const dataCopy = Object.assign({}, data); // avoid param reassignment
|
||||
dataCopy[name] = component.props.value;
|
||||
return dataCopy;
|
||||
}, {});
|
||||
}
|
||||
|
||||
// Checks if the values have changed from their initial value
|
||||
isChanged() {
|
||||
return !utils.isSame(this.getPristineValues(), this.getCurrentValues());
|
||||
}
|
||||
|
||||
isFormDisabled() {
|
||||
return this.props.disabled;
|
||||
}
|
||||
|
||||
mapModel(model) {
|
||||
if (this.props.mapping) {
|
||||
return this.props.mapping(model);
|
||||
}
|
||||
|
||||
return formDataToObject.toObj(Object.keys(model).reduce((mappedModel, key) => {
|
||||
const keyArray = key.split('.');
|
||||
let base = mappedModel;
|
||||
while (keyArray.length) {
|
||||
const currentKey = keyArray.shift();
|
||||
base[currentKey] = (keyArray.length ? base[currentKey] || {} : model[key]);
|
||||
base = base[currentKey];
|
||||
}
|
||||
return mappedModel;
|
||||
}, {}));
|
||||
}
|
||||
|
||||
reset(data) {
|
||||
this.setFormPristine(true);
|
||||
this.resetModel(data);
|
||||
}
|
||||
|
||||
resetInternal(event) {
|
||||
event.preventDefault();
|
||||
this.reset();
|
||||
if (this.props.onReset) {
|
||||
this.props.onReset();
|
||||
}
|
||||
}
|
||||
|
||||
// Reset each key in the model to the original / initial / specified value
|
||||
resetModel(data) {
|
||||
this.inputs.forEach((component) => {
|
||||
const name = component.props.name;
|
||||
if (data && Object.prototype.hasOwnProperty.call(data, name)) {
|
||||
component.setValue(data[name]);
|
||||
} else {
|
||||
component.resetValue();
|
||||
}
|
||||
});
|
||||
this.validateForm();
|
||||
}
|
||||
|
||||
// Checks validation on current value or a passed value
|
||||
runValidation(component, value = component.state.value) {
|
||||
const currentValues = this.getCurrentValues();
|
||||
const validationErrors = component.props.validationErrors;
|
||||
const validationError = component.props.validationError;
|
||||
|
||||
const validationResults = utils.runRules(
|
||||
value, currentValues, component.validations, validationRules,
|
||||
);
|
||||
const requiredResults = utils.runRules(
|
||||
value, currentValues, component.requiredValidations, validationRules,
|
||||
);
|
||||
|
||||
const isRequired = Object.keys(component.requiredValidations).length ?
|
||||
!!requiredResults.success.length : false;
|
||||
const isValid = !validationResults.failed.length &&
|
||||
!(this.props.validationErrors && this.props.validationErrors[component.props.name]);
|
||||
|
||||
return {
|
||||
isRequired,
|
||||
isValid: isRequired ? false : isValid,
|
||||
error: (() => {
|
||||
if (isValid && !isRequired) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (validationResults.errors.length) {
|
||||
return validationResults.errors;
|
||||
}
|
||||
|
||||
if (this.props.validationErrors && this.props.validationErrors[component.props.name]) {
|
||||
return typeof this.props.validationErrors[component.props.name] === 'string' ? [this.props.validationErrors[component.props.name]] : this.props.validationErrors[component.props.name];
|
||||
}
|
||||
|
||||
if (isRequired) {
|
||||
const error = validationErrors[requiredResults.success[0]];
|
||||
return error ? [error] : null;
|
||||
}
|
||||
|
||||
if (validationResults.failed.length) {
|
||||
return validationResults.failed.map(failed =>
|
||||
(validationErrors[failed] ? validationErrors[failed] : validationError))
|
||||
.filter((x, pos, arr) => arr.indexOf(x) === pos); // remove duplicates
|
||||
}
|
||||
|
||||
return undefined;
|
||||
})(),
|
||||
};
|
||||
}
|
||||
|
||||
setInputValidationErrors(errors) {
|
||||
this.inputs.forEach((component) => {
|
||||
const name = component.props.name;
|
||||
const args = [{
|
||||
isValid: !(name in errors),
|
||||
validationError: typeof errors[name] === 'string' ? [errors[name]] : errors[name],
|
||||
}];
|
||||
component.setState(...args);
|
||||
});
|
||||
}
|
||||
|
||||
setFormPristine(isPristine) {
|
||||
this.setState({
|
||||
formSubmitted: !isPristine,
|
||||
});
|
||||
|
||||
// Iterate through each component and set it as pristine
|
||||
// or "dirty".
|
||||
this.inputs.forEach((component) => {
|
||||
component.setState({
|
||||
formSubmitted: !isPristine,
|
||||
isPristine,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Update model, submit to url prop and send the model
|
||||
submit(event) {
|
||||
if (event && event.preventDefault) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// Trigger form as not pristine.
|
||||
// If any inputs have not been touched yet this will make them dirty
|
||||
// so validation becomes visible (if based on isPristine)
|
||||
this.setFormPristine(false);
|
||||
const model = this.getModel();
|
||||
this.props.onSubmit(model, this.resetModel, this.updateInputsWithError);
|
||||
if (this.state.isValid) {
|
||||
this.props.onValidSubmit(model, this.resetModel, this.updateInputsWithError);
|
||||
} else {
|
||||
this.props.onInvalidSubmit(model, this.resetModel, this.updateInputsWithError);
|
||||
}
|
||||
}
|
||||
|
||||
// Go through errors from server and grab the components
|
||||
// stored in the inputs map. Change their state to invalid
|
||||
// and set the serverError message
|
||||
updateInputsWithError(errors) {
|
||||
Object.keys(errors).forEach((name) => {
|
||||
const component = utils.find(this.inputs, input => input.props.name === name);
|
||||
if (!component) {
|
||||
throw new Error(`You are trying to update an input that does not exist. Verify errors object with input names. ${JSON.stringify(errors)}`);
|
||||
}
|
||||
const args = [{
|
||||
isValid: this.props.preventExternalInvalidation,
|
||||
externalError: typeof errors[name] === 'string' ? [errors[name]] : errors[name],
|
||||
}];
|
||||
component.setState(...args);
|
||||
});
|
||||
}
|
||||
|
||||
// Use the binded values and the actual input value to
|
||||
// validate the input and set its state. Then check the
|
||||
// state of the form itself
|
||||
validate(component) {
|
||||
// Trigger onChange
|
||||
if (this.state.canChange) {
|
||||
this.props.onChange(this.getCurrentValues(), this.isChanged());
|
||||
}
|
||||
|
||||
const validation = this.runValidation(component);
|
||||
// Run through the validations, split them up and call
|
||||
// the validator IF there is a value or it is required
|
||||
component.setState({
|
||||
isValid: validation.isValid,
|
||||
isRequired: validation.isRequired,
|
||||
validationError: validation.error,
|
||||
externalError: null,
|
||||
}, this.validateForm);
|
||||
}
|
||||
|
||||
// Validate the form by going through all child input components
|
||||
// and check their state
|
||||
validateForm() {
|
||||
// We need a callback as we are validating all inputs again. This will
|
||||
// run when the last component has set its state
|
||||
const onValidationComplete = () => {
|
||||
const allIsValid = this.inputs.every(component => component.state.isValid);
|
||||
|
||||
this.setState({
|
||||
isValid: allIsValid,
|
||||
});
|
||||
|
||||
if (allIsValid) {
|
||||
this.props.onValid();
|
||||
} else {
|
||||
this.props.onInvalid();
|
||||
}
|
||||
|
||||
// Tell the form that it can start to trigger change events
|
||||
this.setState({
|
||||
canChange: true,
|
||||
});
|
||||
};
|
||||
|
||||
// Run validation again in case affected by other inputs. The
|
||||
// last component validated will run the onValidationComplete callback
|
||||
this.inputs.forEach((component, index) => {
|
||||
const validation = this.runValidation(component);
|
||||
if (validation.isValid && component.state.externalError) {
|
||||
validation.isValid = false;
|
||||
}
|
||||
component.setState({
|
||||
isValid: validation.isValid,
|
||||
isRequired: validation.isRequired,
|
||||
validationError: validation.error,
|
||||
externalError: !validation.isValid && component.state.externalError ?
|
||||
component.state.externalError : null,
|
||||
}, index === this.inputs.length - 1 ? onValidationComplete : null);
|
||||
});
|
||||
|
||||
// If there are no inputs, set state where form is ready to trigger
|
||||
// change event. New inputs might be added later
|
||||
if (!this.inputs.length) {
|
||||
this.setState({
|
||||
canChange: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
getErrorMessage,
|
||||
getErrorMessages,
|
||||
getValue,
|
||||
hasValue,
|
||||
isFormDisabled,
|
||||
isFormSubmitted,
|
||||
isPristine,
|
||||
isRequired,
|
||||
isValid,
|
||||
isValidValue,
|
||||
mapping,
|
||||
onChange,
|
||||
// onError,
|
||||
onInvalidSubmit,
|
||||
onInvalid,
|
||||
onReset,
|
||||
onSubmit,
|
||||
onValid,
|
||||
onValidSubmit,
|
||||
preventExternalInvalidation,
|
||||
// reset,
|
||||
resetValue,
|
||||
setValidations,
|
||||
setValue,
|
||||
showError,
|
||||
showRequired,
|
||||
validationErrors,
|
||||
...nonFormsyProps
|
||||
} = this.props;
|
||||
|
||||
return React.createElement(
|
||||
'form',
|
||||
{
|
||||
onReset: this.resetInternal,
|
||||
onSubmit: this.submit,
|
||||
...nonFormsyProps,
|
||||
},
|
||||
this.props.children,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Formsy.displayName = 'Formsy';
|
||||
|
||||
Formsy.defaultProps = {
|
||||
children: null,
|
||||
disabled: false,
|
||||
getErrorMessage: () => {},
|
||||
getErrorMessages: () => {},
|
||||
getValue: () => {},
|
||||
hasValue: () => {},
|
||||
isFormDisabled: () => {},
|
||||
isFormSubmitted: () => {},
|
||||
isPristine: () => {},
|
||||
isRequired: () => {},
|
||||
isValid: () => {},
|
||||
isValidValue: () => {},
|
||||
mapping: null,
|
||||
onChange: () => {},
|
||||
onError: () => {},
|
||||
onInvalid: () => {},
|
||||
onInvalidSubmit: () => {},
|
||||
onReset: () => {},
|
||||
onSubmit: () => {},
|
||||
onValid: () => {},
|
||||
onValidSubmit: () => {},
|
||||
preventExternalInvalidation: false,
|
||||
resetValue: () => {},
|
||||
setValidations: () => {},
|
||||
setValue: () => {},
|
||||
showError: () => {},
|
||||
showRequired: () => {},
|
||||
validationErrors: null,
|
||||
};
|
||||
|
||||
Formsy.propTypes = {
|
||||
children: PropTypes.node,
|
||||
disabled: PropTypes.bool,
|
||||
getErrorMessage: PropTypes.func,
|
||||
getErrorMessages: PropTypes.func,
|
||||
getValue: PropTypes.func,
|
||||
hasValue: PropTypes.func,
|
||||
isFormDisabled: PropTypes.func,
|
||||
isFormSubmitted: PropTypes.func,
|
||||
isPristine: PropTypes.func,
|
||||
isRequired: PropTypes.func,
|
||||
isValid: PropTypes.func,
|
||||
isValidValue: PropTypes.func,
|
||||
mapping: PropTypes.object, // eslint-disable-line
|
||||
preventExternalInvalidation: PropTypes.bool,
|
||||
onChange: PropTypes.func,
|
||||
onInvalid: PropTypes.func,
|
||||
onInvalidSubmit: PropTypes.func,
|
||||
onReset: PropTypes.func,
|
||||
onSubmit: PropTypes.func,
|
||||
onValid: PropTypes.func,
|
||||
onValidSubmit: PropTypes.func,
|
||||
resetValue: PropTypes.func,
|
||||
setValidations: PropTypes.func,
|
||||
setValue: PropTypes.func,
|
||||
showError: PropTypes.func,
|
||||
showRequired: PropTypes.func,
|
||||
validationErrors: PropTypes.object, // eslint-disable-line
|
||||
};
|
||||
|
||||
Formsy.childContextTypes = {
|
||||
formsy: PropTypes.object,
|
||||
};
|
||||
|
||||
const addValidationRule = (name, func) => {
|
||||
validationRules[name] = func;
|
||||
};
|
||||
|
||||
const withFormsy = Wrapper;
|
||||
|
||||
export {
|
||||
addValidationRule,
|
||||
propTypes,
|
||||
withFormsy,
|
||||
};
|
||||
|
||||
export default Formsy;
|
||||
457
src/main.js
457
src/main.js
|
|
@ -1,457 +0,0 @@
|
|||
var PropTypes = require('prop-types');
|
||||
var React = global.React || require('react');
|
||||
var createReactClass = require('create-react-class');
|
||||
var Formsy = {};
|
||||
var validationRules = require('./validationRules.js');
|
||||
var formDataToObject = require('form-data-to-object');
|
||||
var utils = require('./utils.js');
|
||||
var Mixin = require('./Mixin.js');
|
||||
var HOC = require('./HOC.js');
|
||||
var Decorator = require('./Decorator.js');
|
||||
var options = {};
|
||||
var emptyArray = [];
|
||||
|
||||
Formsy.Mixin = Mixin;
|
||||
Formsy.HOC = HOC;
|
||||
Formsy.Decorator = Decorator;
|
||||
|
||||
Formsy.defaults = function (passedOptions) {
|
||||
options = passedOptions;
|
||||
};
|
||||
|
||||
Formsy.addValidationRule = function (name, func) {
|
||||
validationRules[name] = func;
|
||||
};
|
||||
|
||||
Formsy.Form = createReactClass({
|
||||
displayName: 'Formsy',
|
||||
getInitialState: function () {
|
||||
return {
|
||||
isValid: true,
|
||||
isSubmitting: false,
|
||||
canChange: false
|
||||
};
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
onSuccess: function () {},
|
||||
onError: function () {},
|
||||
onSubmit: function () {},
|
||||
onValidSubmit: function () {},
|
||||
onInvalidSubmit: function () {},
|
||||
onValid: function () {},
|
||||
onInvalid: function () {},
|
||||
onChange: function () {},
|
||||
validationErrors: null,
|
||||
preventExternalInvalidation: false
|
||||
};
|
||||
},
|
||||
|
||||
childContextTypes: {
|
||||
formsy: PropTypes.object
|
||||
},
|
||||
getChildContext: function () {
|
||||
return {
|
||||
formsy: {
|
||||
attachToForm: this.attachToForm,
|
||||
detachFromForm: this.detachFromForm,
|
||||
validate: this.validate,
|
||||
isFormDisabled: this.isFormDisabled,
|
||||
isValidValue: (component, value) => {
|
||||
return this.runValidation(component, value).isValid;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Add a map to store the inputs of the form, a model to store
|
||||
// the values of the form and register child inputs
|
||||
componentWillMount: function () {
|
||||
this.inputs = [];
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
this.validateForm();
|
||||
},
|
||||
|
||||
componentWillUpdate: function () {
|
||||
// Keep a reference to input names before form updates,
|
||||
// to check if inputs has changed after render
|
||||
this.prevInputNames = this.inputs.map(component => component.props.name);
|
||||
},
|
||||
|
||||
componentDidUpdate: function () {
|
||||
|
||||
if (this.props.validationErrors && typeof this.props.validationErrors === 'object' && Object.keys(this.props.validationErrors).length > 0) {
|
||||
this.setInputValidationErrors(this.props.validationErrors);
|
||||
}
|
||||
|
||||
var newInputNames = this.inputs.map(component => component.props.name);
|
||||
if (utils.arraysDiffer(this.prevInputNames, newInputNames)) {
|
||||
this.validateForm();
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// Allow resetting to specified data
|
||||
reset: function (data) {
|
||||
this.setFormPristine(true);
|
||||
this.resetModel(data);
|
||||
},
|
||||
|
||||
// Update model, submit to url prop and send the model
|
||||
submit: function (event) {
|
||||
|
||||
event && event.preventDefault();
|
||||
|
||||
// Trigger form as not pristine.
|
||||
// If any inputs have not been touched yet this will make them dirty
|
||||
// so validation becomes visible (if based on isPristine)
|
||||
this.setFormPristine(false);
|
||||
var model = this.getModel();
|
||||
this.props.onSubmit(model, this.resetModel, this.updateInputsWithError);
|
||||
this.state.isValid ? this.props.onValidSubmit(model, this.resetModel, this.updateInputsWithError) : this.props.onInvalidSubmit(model, this.resetModel, this.updateInputsWithError);
|
||||
|
||||
},
|
||||
|
||||
mapModel: function (model) {
|
||||
|
||||
if (this.props.mapping) {
|
||||
return this.props.mapping(model)
|
||||
} else {
|
||||
return formDataToObject.toObj(Object.keys(model).reduce((mappedModel, key) => {
|
||||
|
||||
var keyArray = key.split('.');
|
||||
var base = mappedModel;
|
||||
while (keyArray.length) {
|
||||
var currentKey = keyArray.shift();
|
||||
base = (base[currentKey] = keyArray.length ? base[currentKey] || {} : model[key]);
|
||||
}
|
||||
|
||||
return mappedModel;
|
||||
|
||||
}, {}));
|
||||
}
|
||||
},
|
||||
|
||||
getModel: function () {
|
||||
var currentValues = this.getCurrentValues();
|
||||
return this.mapModel(currentValues);
|
||||
},
|
||||
|
||||
// Reset each key in the model to the original / initial / specified value
|
||||
resetModel: function (data) {
|
||||
this.inputs.forEach(component => {
|
||||
var name = component.props.name;
|
||||
if (data && data.hasOwnProperty(name)) {
|
||||
component.setValue(data[name]);
|
||||
} else {
|
||||
component.resetValue();
|
||||
}
|
||||
});
|
||||
this.validateForm();
|
||||
},
|
||||
|
||||
setInputValidationErrors: function (errors) {
|
||||
this.inputs.forEach(component => {
|
||||
var name = component.props.name;
|
||||
var args = [{
|
||||
_isValid: !(name in errors),
|
||||
_validationError: typeof errors[name] === 'string' ? [errors[name]] : errors[name]
|
||||
}];
|
||||
component.setState.apply(component, args);
|
||||
});
|
||||
},
|
||||
|
||||
// Checks if the values have changed from their initial value
|
||||
isChanged: function() {
|
||||
return !utils.isSame(this.getPristineValues(), this.getCurrentValues());
|
||||
},
|
||||
|
||||
getPristineValues: function() {
|
||||
return this.inputs.reduce((data, component) => {
|
||||
var name = component.props.name;
|
||||
data[name] = component.props.value;
|
||||
return data;
|
||||
}, {});
|
||||
},
|
||||
|
||||
// Go through errors from server and grab the components
|
||||
// stored in the inputs map. Change their state to invalid
|
||||
// and set the serverError message
|
||||
updateInputsWithError: function (errors) {
|
||||
Object.keys(errors).forEach((name, index) => {
|
||||
var component = utils.find(this.inputs, component => component.props.name === name);
|
||||
if (!component) {
|
||||
throw new Error('You are trying to update an input that does not exist. ' +
|
||||
'Verify errors object with input names. ' + JSON.stringify(errors));
|
||||
}
|
||||
var args = [{
|
||||
_isValid: this.props.preventExternalInvalidation || false,
|
||||
_externalError: typeof errors[name] === 'string' ? [errors[name]] : errors[name]
|
||||
}];
|
||||
component.setState.apply(component, args);
|
||||
});
|
||||
},
|
||||
|
||||
isFormDisabled: function () {
|
||||
return this.props.disabled;
|
||||
},
|
||||
|
||||
getCurrentValues: function () {
|
||||
return this.inputs.reduce((data, component) => {
|
||||
var name = component.props.name;
|
||||
data[name] = component.state._value;
|
||||
return data;
|
||||
}, {});
|
||||
},
|
||||
|
||||
setFormPristine: function (isPristine) {
|
||||
this.setState({
|
||||
_formSubmitted: !isPristine
|
||||
});
|
||||
|
||||
// Iterate through each component and set it as pristine
|
||||
// or "dirty".
|
||||
this.inputs.forEach((component, index) => {
|
||||
component.setState({
|
||||
_formSubmitted: !isPristine,
|
||||
_isPristine: isPristine
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// Use the binded values and the actual input value to
|
||||
// validate the input and set its state. Then check the
|
||||
// state of the form itself
|
||||
validate: function (component) {
|
||||
|
||||
// Trigger onChange
|
||||
if (this.state.canChange) {
|
||||
this.props.onChange(this.getCurrentValues(), this.isChanged());
|
||||
}
|
||||
|
||||
var validation = this.runValidation(component);
|
||||
// Run through the validations, split them up and call
|
||||
// the validator IF there is a value or it is required
|
||||
component.setState({
|
||||
_isValid: validation.isValid,
|
||||
_isRequired: validation.isRequired,
|
||||
_validationError: validation.error,
|
||||
_externalError: null
|
||||
}, this.validateForm);
|
||||
|
||||
},
|
||||
|
||||
// Checks validation on current value or a passed value
|
||||
runValidation: function (component, value) {
|
||||
|
||||
var currentValues = this.getCurrentValues();
|
||||
var validationErrors = component.props.validationErrors;
|
||||
var validationError = component.props.validationError;
|
||||
value = arguments.length === 2 ? value : component.state._value;
|
||||
|
||||
var validationResults = this.runRules(value, currentValues, component._validations);
|
||||
var requiredResults = this.runRules(value, currentValues, component._requiredValidations);
|
||||
|
||||
// the component defines an explicit validate function
|
||||
if (typeof component.validate === "function") {
|
||||
validationResults.failed = component.validate() ? [] : ['failed'];
|
||||
}
|
||||
|
||||
var isRequired = Object.keys(component._requiredValidations).length ? !!requiredResults.success.length : false;
|
||||
var isValid = !validationResults.failed.length && !(this.props.validationErrors && this.props.validationErrors[component.props.name]);
|
||||
|
||||
return {
|
||||
isRequired: isRequired,
|
||||
isValid: isRequired ? false : isValid,
|
||||
error: (function () {
|
||||
|
||||
if (isValid && !isRequired) {
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
if (validationResults.errors.length) {
|
||||
return validationResults.errors;
|
||||
}
|
||||
|
||||
if (this.props.validationErrors && this.props.validationErrors[component.props.name]) {
|
||||
return typeof this.props.validationErrors[component.props.name] === 'string' ? [this.props.validationErrors[component.props.name]] : this.props.validationErrors[component.props.name];
|
||||
}
|
||||
|
||||
if (isRequired) {
|
||||
var error = validationErrors[requiredResults.success[0]];
|
||||
return error ? [error] : null;
|
||||
}
|
||||
|
||||
if (validationResults.failed.length) {
|
||||
return validationResults.failed.map(function(failed) {
|
||||
return validationErrors[failed] ? validationErrors[failed] : validationError;
|
||||
}).filter(function(x, pos, arr) {
|
||||
// Remove duplicates
|
||||
return arr.indexOf(x) === pos;
|
||||
});
|
||||
}
|
||||
|
||||
}.call(this))
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
runRules: function (value, currentValues, validations) {
|
||||
|
||||
var results = {
|
||||
errors: [],
|
||||
failed: [],
|
||||
success: []
|
||||
};
|
||||
if (Object.keys(validations).length) {
|
||||
Object.keys(validations).forEach(function (validationMethod) {
|
||||
|
||||
if (validationRules[validationMethod] && typeof validations[validationMethod] === 'function') {
|
||||
throw new Error('Formsy does not allow you to override default validations: ' + validationMethod);
|
||||
}
|
||||
|
||||
if (!validationRules[validationMethod] && typeof validations[validationMethod] !== 'function') {
|
||||
throw new Error('Formsy does not have the validation rule: ' + validationMethod);
|
||||
}
|
||||
|
||||
if (typeof validations[validationMethod] === 'function') {
|
||||
var validation = validations[validationMethod](currentValues, value);
|
||||
if (typeof validation === 'string') {
|
||||
results.errors.push(validation);
|
||||
results.failed.push(validationMethod);
|
||||
} else if (!validation) {
|
||||
results.failed.push(validationMethod);
|
||||
}
|
||||
return;
|
||||
|
||||
} else if (typeof validations[validationMethod] !== 'function') {
|
||||
var validation = validationRules[validationMethod](currentValues, value, validations[validationMethod]);
|
||||
if (typeof validation === 'string') {
|
||||
results.errors.push(validation);
|
||||
results.failed.push(validationMethod);
|
||||
} else if (!validation) {
|
||||
results.failed.push(validationMethod);
|
||||
} else {
|
||||
results.success.push(validationMethod);
|
||||
}
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
return results.success.push(validationMethod);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
|
||||
},
|
||||
|
||||
// Validate the form by going through all child input components
|
||||
// and check their state
|
||||
validateForm: function () {
|
||||
|
||||
// We need a callback as we are validating all inputs again. This will
|
||||
// run when the last component has set its state
|
||||
var onValidationComplete = function () {
|
||||
var allIsValid = this.inputs.every(component => {
|
||||
return component.state._isValid;
|
||||
});
|
||||
|
||||
this.setState({
|
||||
isValid: allIsValid
|
||||
});
|
||||
|
||||
if (allIsValid) {
|
||||
this.props.onValid();
|
||||
} else {
|
||||
this.props.onInvalid();
|
||||
}
|
||||
|
||||
// Tell the form that it can start to trigger change events
|
||||
this.setState({
|
||||
canChange: true
|
||||
});
|
||||
|
||||
}.bind(this);
|
||||
|
||||
// Run validation again in case affected by other inputs. The
|
||||
// last component validated will run the onValidationComplete callback
|
||||
this.inputs.forEach((component, index) => {
|
||||
var validation = this.runValidation(component);
|
||||
if (validation.isValid && component.state._externalError) {
|
||||
validation.isValid = false;
|
||||
}
|
||||
component.setState({
|
||||
_isValid: validation.isValid,
|
||||
_isRequired: validation.isRequired,
|
||||
_validationError: validation.error,
|
||||
_externalError: !validation.isValid && component.state._externalError ? component.state._externalError : null
|
||||
}, index === this.inputs.length - 1 ? onValidationComplete : null);
|
||||
});
|
||||
|
||||
// If there are no inputs, set state where form is ready to trigger
|
||||
// change event. New inputs might be added later
|
||||
if (!this.inputs.length) {
|
||||
this.setState({
|
||||
canChange: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Method put on each input component to register
|
||||
// itself to the form
|
||||
attachToForm: function (component) {
|
||||
|
||||
if (this.inputs.indexOf(component) === -1) {
|
||||
this.inputs.push(component);
|
||||
}
|
||||
|
||||
this.validate(component);
|
||||
},
|
||||
|
||||
// Method put on each input component to unregister
|
||||
// itself from the form
|
||||
detachFromForm: function (component) {
|
||||
var componentPos = this.inputs.indexOf(component);
|
||||
|
||||
if (componentPos !== -1) {
|
||||
this.inputs = this.inputs.slice(0, componentPos)
|
||||
.concat(this.inputs.slice(componentPos + 1));
|
||||
}
|
||||
|
||||
this.validateForm();
|
||||
},
|
||||
render: function () {
|
||||
var {
|
||||
mapping,
|
||||
validationErrors,
|
||||
onSubmit,
|
||||
onValid,
|
||||
onValidSubmit,
|
||||
onInvalid,
|
||||
onInvalidSubmit,
|
||||
onChange,
|
||||
reset,
|
||||
preventExternalInvalidation,
|
||||
onSuccess,
|
||||
onError,
|
||||
...nonFormsyProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<form {...nonFormsyProps} onSubmit={this.submit}>
|
||||
{this.props.children}
|
||||
</form>
|
||||
);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
if (!global.exports && !global.module && (!global.define || !global.define.amd)) {
|
||||
global.Formsy = Formsy;
|
||||
}
|
||||
|
||||
module.exports = Formsy;
|
||||
72
src/utils.js
72
src/utils.js
|
|
@ -1,10 +1,10 @@
|
|||
module.exports = {
|
||||
arraysDiffer: function (a, b) {
|
||||
var isDifferent = false;
|
||||
export default {
|
||||
arraysDiffer(a, b) {
|
||||
let isDifferent = false;
|
||||
if (a.length !== b.length) {
|
||||
isDifferent = true;
|
||||
} else {
|
||||
a.forEach(function (item, index) {
|
||||
a.forEach((item, index) => {
|
||||
if (!this.isSame(item, b[index])) {
|
||||
isDifferent = true;
|
||||
}
|
||||
|
|
@ -13,12 +13,12 @@ module.exports = {
|
|||
return isDifferent;
|
||||
},
|
||||
|
||||
objectsDiffer: function (a, b) {
|
||||
var isDifferent = false;
|
||||
objectsDiffer(a, b) {
|
||||
let isDifferent = false;
|
||||
if (Object.keys(a).length !== Object.keys(b).length) {
|
||||
isDifferent = true;
|
||||
} else {
|
||||
Object.keys(a).forEach(function (key) {
|
||||
Object.keys(a).forEach((key) => {
|
||||
if (!this.isSame(a[key], b[key])) {
|
||||
isDifferent = true;
|
||||
}
|
||||
|
|
@ -27,7 +27,7 @@ module.exports = {
|
|||
return isDifferent;
|
||||
},
|
||||
|
||||
isSame: function (a, b) {
|
||||
isSame(a, b) {
|
||||
if (typeof a !== typeof b) {
|
||||
return false;
|
||||
} else if (Array.isArray(a) && Array.isArray(b)) {
|
||||
|
|
@ -41,13 +41,61 @@ module.exports = {
|
|||
return a === b;
|
||||
},
|
||||
|
||||
find: function (collection, fn) {
|
||||
for (var i = 0, l = collection.length; i < l; i++) {
|
||||
var item = collection[i];
|
||||
find(collection, fn) {
|
||||
for (let i = 0, l = collection.length; i < l; i += 1) {
|
||||
const item = collection[i];
|
||||
if (fn(item)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
runRules(value, currentValues, validations, validationRules) {
|
||||
const results = {
|
||||
errors: [],
|
||||
failed: [],
|
||||
success: [],
|
||||
};
|
||||
|
||||
if (Object.keys(validations).length) {
|
||||
Object.keys(validations).forEach((validationMethod) => {
|
||||
if (validationRules[validationMethod] && typeof validations[validationMethod] === 'function') {
|
||||
throw new Error(`Formsy does not allow you to override default validations: ${validationMethod}`);
|
||||
}
|
||||
|
||||
if (!validationRules[validationMethod] && typeof validations[validationMethod] !== 'function') {
|
||||
throw new Error(`Formsy does not have the validation rule: ${validationMethod}`);
|
||||
}
|
||||
|
||||
if (typeof validations[validationMethod] === 'function') {
|
||||
const validation = validations[validationMethod](currentValues, value);
|
||||
if (typeof validation === 'string') {
|
||||
results.errors.push(validation);
|
||||
results.failed.push(validationMethod);
|
||||
} else if (!validation) {
|
||||
results.failed.push(validationMethod);
|
||||
}
|
||||
return;
|
||||
} else if (typeof validations[validationMethod] !== 'function') {
|
||||
const validation = validationRules[validationMethod](
|
||||
currentValues, value, validations[validationMethod],
|
||||
);
|
||||
if (typeof validation === 'string') {
|
||||
results.errors.push(validation);
|
||||
results.failed.push(validationMethod);
|
||||
} else if (!validation) {
|
||||
results.failed.push(validationMethod);
|
||||
} else {
|
||||
results.success.push(validationMethod);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
results.success.push(validationMethod);
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,78 +1,73 @@
|
|||
var isExisty = function (value) {
|
||||
return value !== null && value !== undefined;
|
||||
};
|
||||
const isExisty = value => value !== null && value !== undefined;
|
||||
const isEmpty = value => value === '';
|
||||
|
||||
var isEmpty = function (value) {
|
||||
return value === '';
|
||||
};
|
||||
|
||||
var validations = {
|
||||
isDefaultRequiredValue: function (values, value) {
|
||||
const validations = {
|
||||
isDefaultRequiredValue(values, value) {
|
||||
return value === undefined || value === '';
|
||||
},
|
||||
isExisty: function (values, value) {
|
||||
isExisty(values, value) {
|
||||
return isExisty(value);
|
||||
},
|
||||
matchRegexp: function (values, value, regexp) {
|
||||
matchRegexp(values, value, regexp) {
|
||||
return !isExisty(value) || isEmpty(value) || regexp.test(value);
|
||||
},
|
||||
isUndefined: function (values, value) {
|
||||
isUndefined(values, value) {
|
||||
return value === undefined;
|
||||
},
|
||||
isEmptyString: function (values, value) {
|
||||
isEmptyString(values, value) {
|
||||
return isEmpty(value);
|
||||
},
|
||||
isEmail: function (values, value) {
|
||||
return validations.matchRegexp(values, value, /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i);
|
||||
isEmail(values, value) {
|
||||
return validations.matchRegexp(values, value, /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i);
|
||||
},
|
||||
isUrl: function (values, value) {
|
||||
return validations.matchRegexp(values, value, /^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i);
|
||||
isUrl(values, value) {
|
||||
return validations.matchRegexp(values, value, /^(?:\w+:)?\/\/([^\s.]+\.\S{2}|localhost[:?\d]*)\S*$/i);
|
||||
},
|
||||
isTrue: function (values, value) {
|
||||
isTrue(values, value) {
|
||||
return value === true;
|
||||
},
|
||||
isFalse: function (values, value) {
|
||||
isFalse(values, value) {
|
||||
return value === false;
|
||||
},
|
||||
isNumeric: function (values, value) {
|
||||
isNumeric(values, value) {
|
||||
if (typeof value === 'number') {
|
||||
return true;
|
||||
}
|
||||
return validations.matchRegexp(values, value, /^[-+]?(?:\d*[.])?\d+$/);
|
||||
},
|
||||
isAlpha: function (values, value) {
|
||||
isAlpha(values, value) {
|
||||
return validations.matchRegexp(values, value, /^[A-Z]+$/i);
|
||||
},
|
||||
isAlphanumeric: function (values, value) {
|
||||
isAlphanumeric(values, value) {
|
||||
return validations.matchRegexp(values, value, /^[0-9A-Z]+$/i);
|
||||
},
|
||||
isInt: function (values, value) {
|
||||
isInt(values, value) {
|
||||
return validations.matchRegexp(values, value, /^(?:[-+]?(?:0|[1-9]\d*))$/);
|
||||
},
|
||||
isFloat: function (values, value) {
|
||||
return validations.matchRegexp(values, value, /^(?:[-+]?(?:\d+))?(?:\.\d*)?(?:[eE][\+\-]?(?:\d+))?$/);
|
||||
isFloat(values, value) {
|
||||
return validations.matchRegexp(values, value, /^(?:[-+]?(?:\d+))?(?:\.\d*)?(?:[eE][+-]?(?:\d+))?$/);
|
||||
},
|
||||
isWords: function (values, value) {
|
||||
isWords(values, value) {
|
||||
return validations.matchRegexp(values, value, /^[A-Z\s]+$/i);
|
||||
},
|
||||
isSpecialWords: function (values, value) {
|
||||
isSpecialWords(values, value) {
|
||||
return validations.matchRegexp(values, value, /^[A-Z\s\u00C0-\u017F]+$/i);
|
||||
},
|
||||
isLength: function (values, value, length) {
|
||||
isLength(values, value, length) {
|
||||
return !isExisty(value) || isEmpty(value) || value.length === length;
|
||||
},
|
||||
equals: function (values, value, eql) {
|
||||
return !isExisty(value) || isEmpty(value) || value == eql;
|
||||
equals(values, value, eql) {
|
||||
return !isExisty(value) || isEmpty(value) || value === eql;
|
||||
},
|
||||
equalsField: function (values, value, field) {
|
||||
return value == values[field];
|
||||
equalsField(values, value, field) {
|
||||
return value === values[field];
|
||||
},
|
||||
maxLength: function (values, value, length) {
|
||||
maxLength(values, value, length) {
|
||||
return !isExisty(value) || value.length <= length;
|
||||
},
|
||||
minLength: function (values, value, length) {
|
||||
minLength(values, value, length) {
|
||||
return !isExisty(value) || isEmpty(value) || value.length >= length;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = validations;
|
||||
export default validations;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,21 @@
|
|||
import path from 'path';
|
||||
import testrunner from 'nodeunit/lib/reporters/default.js';
|
||||
import {jsdom} from 'jsdom';
|
||||
import testrunner from 'nodeunit/lib/reporters/default';
|
||||
import { jsdom } from 'jsdom/lib/old-api';
|
||||
|
||||
global.document = jsdom();
|
||||
global.window = document.defaultView;
|
||||
global.window = global.document.defaultView;
|
||||
global.navigator = global.window.navigator;
|
||||
|
||||
testrunner.run(['tests'], {
|
||||
"error_prefix": "\u001B[31m",
|
||||
"error_suffix": "\u001B[39m",
|
||||
"ok_prefix": "\u001B[32m",
|
||||
"ok_suffix": "\u001B[39m",
|
||||
"bold_prefix": "\u001B[1m",
|
||||
"bold_suffix": "\u001B[22m",
|
||||
"assertion_prefix": "\u001B[35m",
|
||||
"assertion_suffix": "\u001B[39m"
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
error_prefix: '\u001B[31m',
|
||||
error_suffix: '\u001B[39m',
|
||||
ok_prefix: '\u001B[32m',
|
||||
ok_suffix: '\u001B[39m',
|
||||
bold_prefix: '\u001B[1m',
|
||||
bold_suffix: '\u001B[22m',
|
||||
assertion_prefix: '\u001B[35m',
|
||||
assertion_suffix: '\u001B[39m',
|
||||
}, (err) => {
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import TestUtils from 'react-dom/test-utils';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import Formsy from './..';
|
||||
import Formsy, { withFormsy } from './..';
|
||||
import TestInput, { InputFactory } from './utils/TestInput';
|
||||
import immediate from './utils/immediate';
|
||||
|
||||
|
|
@ -12,9 +12,9 @@ export default {
|
|||
'should return passed and setValue() value when using getValue()': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" value="foo"/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
|
|
@ -26,18 +26,45 @@ export default {
|
|||
|
||||
},
|
||||
|
||||
'should only set the value and not validate when calling setValue(val, false)': function (test) {
|
||||
|
||||
const Input = withFormsy(class TestInput extends React.Component {
|
||||
updateValue = (event) => {
|
||||
this.props.setValue(event.target.value, false);
|
||||
}
|
||||
render() {
|
||||
return <input type="text" value={this.props.getValue()} onChange={this.updateValue}/>;
|
||||
}
|
||||
})
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy>
|
||||
<Input name="foo" value="foo" innerRef="comp" />
|
||||
</Formsy>
|
||||
);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, Input);
|
||||
const setStateSpy = sinon.spy(inputComponent, 'setState');
|
||||
const inputElement = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
|
||||
test.equal(setStateSpy.called, false);
|
||||
TestUtils.Simulate.change(inputElement, {target: {value: 'foobar'}});
|
||||
test.equal(setStateSpy.calledOnce, true);
|
||||
test.equal(setStateSpy.calledWithExactly({ value: 'foobar' }), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should set back to pristine value when running reset': function (test) {
|
||||
|
||||
let reset = null;
|
||||
const Input = InputFactory({
|
||||
componentDidMount() {
|
||||
reset = this.resetValue;
|
||||
componentDidMount: function() {
|
||||
reset = this.props.resetValue;
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<Input name="foo" value="foo"/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
|
|
@ -53,14 +80,14 @@ export default {
|
|||
|
||||
let getErrorMessage = null;
|
||||
const Input = InputFactory({
|
||||
componentDidMount() {
|
||||
getErrorMessage = this.getErrorMessage;
|
||||
componentDidMount: function() {
|
||||
getErrorMessage = this.props.getErrorMessage;
|
||||
}
|
||||
});
|
||||
TestUtils.renderIntoDocument(
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<Input name="foo" value="foo" validations="isEmail" validationError="Has to be email"/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
|
||||
test.equal(getErrorMessage(), 'Has to be email');
|
||||
|
|
@ -73,14 +100,14 @@ export default {
|
|||
|
||||
let isValid = null;
|
||||
const Input = InputFactory({
|
||||
componentDidMount() {
|
||||
isValid = this.isValid;
|
||||
componentDidMount: function() {
|
||||
isValid = this.props.isValid;
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users">
|
||||
<Formsy action="/users">
|
||||
<Input name="foo" value="foo" validations="isEmail"/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
|
||||
test.equal(isValid(), false);
|
||||
|
|
@ -96,16 +123,16 @@ export default {
|
|||
|
||||
const isRequireds = [];
|
||||
const Input = InputFactory({
|
||||
componentDidMount() {
|
||||
isRequireds.push(this.isRequired);
|
||||
componentDidMount: function() {
|
||||
isRequireds.push(this.props.isRequired);
|
||||
}
|
||||
});
|
||||
TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users">
|
||||
<Formsy action="/users">
|
||||
<Input name="foo" value=""/>
|
||||
<Input name="foo" value="" required/>
|
||||
<Input name="foo" value="foo" required="isLength:3"/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
|
||||
test.equal(isRequireds[0](), false);
|
||||
|
|
@ -120,16 +147,16 @@ export default {
|
|||
|
||||
const showRequireds = [];
|
||||
const Input = InputFactory({
|
||||
componentDidMount() {
|
||||
showRequireds.push(this.showRequired);
|
||||
componentDidMount: function() {
|
||||
showRequireds.push(this.props.showRequired);
|
||||
}
|
||||
});
|
||||
TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users">
|
||||
<Formsy action="/users">
|
||||
<Input name="A" value="foo"/>
|
||||
<Input name="B" value="" required/>
|
||||
<Input name="C" value=""/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
|
||||
test.equal(showRequireds[0](), false);
|
||||
|
|
@ -144,14 +171,14 @@ export default {
|
|||
|
||||
let isPristine = null;
|
||||
const Input = InputFactory({
|
||||
componentDidMount() {
|
||||
isPristine = this.isPristine;
|
||||
componentDidMount: function() {
|
||||
isPristine = this.props.isPristine;
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users">
|
||||
<Formsy action="/users">
|
||||
<Input name="A" value="foo"/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
|
||||
test.equal(isPristine(), true);
|
||||
|
|
@ -165,23 +192,21 @@ export default {
|
|||
|
||||
'should allow an undefined value to be updated to a value': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() {
|
||||
return {value: undefined};
|
||||
},
|
||||
changeValue() {
|
||||
class TestForm extends React.Component {
|
||||
state = {value: undefined};
|
||||
changeValue = () => {
|
||||
this.setState({
|
||||
value: 'foo'
|
||||
});
|
||||
},
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form url="/users">
|
||||
<TestInput name="A" value={this.state.value}/>
|
||||
</Formsy.Form>
|
||||
<Formsy action="/users">
|
||||
<TestInput name="A" value={this.state.value} />
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
form.changeValue();
|
||||
|
|
@ -195,15 +220,15 @@ export default {
|
|||
|
||||
'should be able to test a values validity': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="A" validations="isEmail"/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
|
|
@ -215,17 +240,17 @@ export default {
|
|||
|
||||
'should be able to use an object as validations property': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="A" validations={{
|
||||
isEmail: true
|
||||
}}/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
|
|
@ -238,17 +263,17 @@ export default {
|
|||
|
||||
'should be able to pass complex values to a validation rule': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="A" validations={{
|
||||
matchRegexp: /foo/
|
||||
}} value="foo"/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
|
|
@ -263,26 +288,26 @@ export default {
|
|||
|
||||
'should be able to run a function to validate': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
customValidationA(values, value) {
|
||||
return value === 'foo';
|
||||
},
|
||||
}
|
||||
customValidationB(values, value) {
|
||||
return value === 'foo' && values.A === 'foo';
|
||||
},
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="A" validations={{
|
||||
custom: this.customValidationA
|
||||
}} value="foo"/>
|
||||
<TestInput name="B" validations={{
|
||||
custom: this.customValidationB
|
||||
}} value="foo"/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const inputComponent = TestUtils.scryRenderedComponentsWithType(form, TestInput);
|
||||
|
|
@ -299,17 +324,17 @@ export default {
|
|||
|
||||
'should not override error messages with error messages passed by form if passed eror messages is an empty object': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form validationErrors={{}}>
|
||||
<Formsy validationErrors={{}}>
|
||||
<TestInput name="A" validations={{
|
||||
isEmail: true
|
||||
}} validationError="bar2" validationErrors={{isEmail: 'bar3'}} value="foo"/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
|
|
@ -319,20 +344,19 @@ export default {
|
|||
|
||||
},
|
||||
|
||||
|
||||
'should override all error messages with error messages passed by form': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form validationErrors={{A: 'bar'}}>
|
||||
<Formsy validationErrors={{A: 'bar'}}>
|
||||
<TestInput name="A" validations={{
|
||||
isEmail: true
|
||||
}} validationError="bar2" validationErrors={{isEmail: 'bar3'}} value="foo"/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
|
|
@ -344,10 +368,10 @@ export default {
|
|||
|
||||
'should override validation rules with required rules': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="A"
|
||||
validations={{
|
||||
isEmail: true
|
||||
|
|
@ -359,10 +383,10 @@ export default {
|
|||
isLength: 1
|
||||
}}
|
||||
/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
|
|
@ -374,26 +398,26 @@ export default {
|
|||
|
||||
'should fall back to default error message when non exist in validationErrors map': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="A"
|
||||
validations={{
|
||||
isEmail: true
|
||||
}}
|
||||
validationError="bar"
|
||||
validationErrors={{foo: 'bar'}}
|
||||
validationError="bar1"
|
||||
validationErrors={{foo: 'bar2'}}
|
||||
value="foo"
|
||||
/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.getErrorMessage(), 'bar');
|
||||
test.equal(inputComponent.getErrorMessage(), 'bar1');
|
||||
|
||||
test.done();
|
||||
|
||||
|
|
@ -401,17 +425,17 @@ export default {
|
|||
|
||||
'should not be valid if it is required and required rule is true': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="A"
|
||||
required
|
||||
/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
|
|
@ -423,22 +447,20 @@ export default {
|
|||
|
||||
'should handle objects and arrays as values': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
foo: {foo: 'bar'},
|
||||
bar: ['foo']
|
||||
};
|
||||
},
|
||||
class TestForm extends React.Component {
|
||||
state = {
|
||||
foo: {foo: 'bar'},
|
||||
bar: ['foo']
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" value={this.state.foo}/>
|
||||
<TestInput name="bar" value={this.state.bar}/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
form.setState({
|
||||
|
|
@ -456,28 +478,24 @@ export default {
|
|||
|
||||
'should handle isFormDisabled with dynamic inputs': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
bool: true
|
||||
};
|
||||
},
|
||||
flip() {
|
||||
class TestForm extends React.Component {
|
||||
state = { bool: true }
|
||||
flip = () => {
|
||||
this.setState({
|
||||
bool: !this.state.bool
|
||||
});
|
||||
},
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form disabled={this.state.bool}>
|
||||
<Formsy disabled={this.state.bool}>
|
||||
{this.state.bool ?
|
||||
<TestInput name="foo" /> :
|
||||
<TestInput name="bar" />
|
||||
}
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
|
|
@ -491,19 +509,19 @@ export default {
|
|||
|
||||
'should allow for dot notation in name which maps to a deep object': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
onSubmit(model) {
|
||||
test.deepEqual(model, {foo: {bar: 'foo', test: 'test'}});
|
||||
},
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={this.onSubmit}>
|
||||
<Formsy onSubmit={this.onSubmit}>
|
||||
<TestInput name="foo.bar" value="foo"/>
|
||||
<TestInput name="foo.test" value="test"/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
test.expect(1);
|
||||
|
|
@ -517,19 +535,19 @@ export default {
|
|||
|
||||
'should allow for application/x-www-form-urlencoded syntax and convert to object': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
onSubmit(model) {
|
||||
test.deepEqual(model, {foo: ['foo', 'bar']});
|
||||
},
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={this.onSubmit}>
|
||||
<Formsy onSubmit={this.onSubmit}>
|
||||
<TestInput name="foo[0]" value="foo"/>
|
||||
<TestInput name="foo[1]" value="bar"/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
test.expect(1);
|
||||
|
|
@ -546,17 +564,17 @@ export default {
|
|||
var renderSpy = sinon.spy();
|
||||
|
||||
const Input = InputFactory({
|
||||
mixins: [Formsy.Mixin, PureRenderMixin],
|
||||
render() {
|
||||
shouldComponentUpdate: function() { return false },
|
||||
render: function() {
|
||||
renderSpy();
|
||||
return <input type={this.props.type} value={this.getValue()} onChange={this.updateValue}/>;
|
||||
return <input type={this.props.type} value={this.props.getValue()} onChange={this.updateValue}/>;
|
||||
}
|
||||
});
|
||||
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<Input name="foo" value="foo"/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
|
||||
test.equal(renderSpy.calledOnce, true);
|
||||
|
|
|
|||
|
|
@ -1,26 +1,25 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import TestUtils from 'react-dom/test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import Formsy, { addValidationRule } from './..';
|
||||
import TestInput from './utils/TestInput';
|
||||
import TestInputHoc from './utils/TestInputHoc';
|
||||
import immediate from './utils/immediate';
|
||||
import sinon from 'sinon';
|
||||
|
||||
export default {
|
||||
|
||||
'Setting up a form': {
|
||||
'should expose the users DOM node through an innerRef prop': function (test) {
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInputHoc name="name" innerRef={(c) => { this.name = c; }} />
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const input = form.name;
|
||||
|
|
@ -31,7 +30,7 @@ export default {
|
|||
|
||||
'should render a form into the document': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<Formsy.Form></Formsy.Form>);
|
||||
const form = TestUtils.renderIntoDocument(<Formsy></Formsy>);
|
||||
test.equal(ReactDOM.findDOMNode(form).tagName, 'FORM');
|
||||
|
||||
test.done();
|
||||
|
|
@ -40,7 +39,7 @@ export default {
|
|||
|
||||
'should set a class name if passed': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument( <Formsy.Form className="foo"></Formsy.Form>);
|
||||
const form = TestUtils.renderIntoDocument( <Formsy className="foo"></Formsy>);
|
||||
test.equal(ReactDOM.findDOMNode(form).className, 'foo');
|
||||
|
||||
test.done();
|
||||
|
|
@ -50,18 +49,18 @@ export default {
|
|||
'should allow for null/undefined children': function (test) {
|
||||
|
||||
let model = null;
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
|
||||
<Formsy onSubmit={(formModel) => (model = formModel)}>
|
||||
<h1>Test</h1>
|
||||
{ null }
|
||||
{ undefined }
|
||||
<TestInput name="name" value={ 'foo' } />
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
immediate(() => {
|
||||
|
|
@ -77,17 +76,17 @@ export default {
|
|||
const inputs = [];
|
||||
let forceUpdate = null;
|
||||
let model = null;
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
componentWillMount() {
|
||||
forceUpdate = this.forceUpdate.bind(this);
|
||||
},
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
|
||||
<Formsy onSubmit={(formModel) => (model = formModel)}>
|
||||
{inputs}
|
||||
</Formsy.Form>);
|
||||
</Formsy>);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
// Wait before adding the input
|
||||
|
|
@ -113,17 +112,17 @@ export default {
|
|||
const inputs = [];
|
||||
let forceUpdate = null;
|
||||
let model = null;
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
componentWillMount() {
|
||||
forceUpdate = this.forceUpdate.bind(this);
|
||||
},
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
|
||||
<Formsy onSubmit={(formModel) => (model = formModel)}>
|
||||
{inputs}
|
||||
</Formsy.Form>);
|
||||
</Formsy>);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
// Wait before adding the input
|
||||
|
|
@ -151,19 +150,19 @@ export default {
|
|||
let forceUpdate = null;
|
||||
let model = null;
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
componentWillMount() {
|
||||
forceUpdate = this.forceUpdate.bind(this);
|
||||
},
|
||||
}
|
||||
render() {
|
||||
const input = <TestInput name="test" value={this.props.value} />;
|
||||
|
||||
return (
|
||||
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
|
||||
<Formsy onSubmit={(formModel) => (model = formModel)}>
|
||||
{input}
|
||||
</Formsy.Form>);
|
||||
</Formsy>);
|
||||
}
|
||||
});
|
||||
}
|
||||
let form = TestUtils.renderIntoDocument(<TestForm value="foo"/>);
|
||||
|
||||
// Wait before changing the input
|
||||
|
|
@ -193,13 +192,13 @@ export default {
|
|||
const runRule = sinon.spy();
|
||||
const notRunRule = sinon.spy();
|
||||
|
||||
Formsy.addValidationRule('runRule', runRule);
|
||||
Formsy.addValidationRule('notRunRule', notRunRule);
|
||||
addValidationRule('runRule', runRule);
|
||||
addValidationRule('notRunRule', notRunRule);
|
||||
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="one" validations="runRule" value="foo"/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
|
||||
|
|
@ -215,24 +214,24 @@ export default {
|
|||
|
||||
const ruleA = sinon.spy();
|
||||
const ruleB = sinon.spy();
|
||||
Formsy.addValidationRule('ruleA', ruleA);
|
||||
Formsy.addValidationRule('ruleB', ruleB);
|
||||
addValidationRule('ruleA', ruleA);
|
||||
addValidationRule('ruleB', ruleB);
|
||||
|
||||
class TestForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {rule: 'ruleA'};
|
||||
}
|
||||
changeRule() {
|
||||
changeRule = () => {
|
||||
this.setState({
|
||||
rule: 'ruleB'
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="one" validations={this.state.rule} value="foo"/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -256,14 +255,14 @@ export default {
|
|||
super(props);
|
||||
this.state = {showSecondInput: false};
|
||||
}
|
||||
addInput() {
|
||||
addInput = () => {
|
||||
this.setState({
|
||||
showSecondInput: true
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form ref="formsy" onInvalid={isInValidSpy}>
|
||||
<Formsy ref="formsy" onInvalid={isInValidSpy}>
|
||||
<TestInput name="one" validations="isEmail" value="foo@bar.com"/>
|
||||
{
|
||||
this.state.showSecondInput ?
|
||||
|
|
@ -271,7 +270,7 @@ export default {
|
|||
:
|
||||
null
|
||||
}
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -304,7 +303,7 @@ export default {
|
|||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form ref="formsy" onValid={isValidSpy}>
|
||||
<Formsy ref="formsy" onValid={isValidSpy}>
|
||||
<TestInput name="one" validations="isEmail" value="foo@bar.com"/>
|
||||
{
|
||||
this.state.showSecondInput ?
|
||||
|
|
@ -312,7 +311,7 @@ export default {
|
|||
:
|
||||
null
|
||||
}
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -334,13 +333,13 @@ export default {
|
|||
|
||||
const ruleA = sinon.spy();
|
||||
const ruleB = sinon.spy();
|
||||
Formsy.addValidationRule('ruleA', ruleA);
|
||||
Formsy.addValidationRule('ruleB', ruleB);
|
||||
addValidationRule('ruleA', ruleA);
|
||||
addValidationRule('ruleB', ruleB);
|
||||
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="one" validations="ruleA,ruleB" value="foo" />
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
|
||||
|
|
@ -357,11 +356,11 @@ export default {
|
|||
|
||||
|
||||
const hasChanged = sinon.spy();
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return <Formsy.Form onChange={hasChanged}></Formsy.Form>;
|
||||
return <Formsy onChange={hasChanged}></Formsy>;
|
||||
}
|
||||
});
|
||||
}
|
||||
TestUtils.renderIntoDocument(<TestForm/>);
|
||||
test.equal(hasChanged.called, false);
|
||||
test.done();
|
||||
|
|
@ -372,9 +371,9 @@ export default {
|
|||
|
||||
const hasChanged = sinon.spy();
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form onChange={hasChanged}>
|
||||
<Formsy onChange={hasChanged}>
|
||||
<TestInput name="foo"/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
TestUtils.Simulate.change(TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'), {target: {value: 'bar'}});
|
||||
test.equal(hasChanged.calledOnce, true);
|
||||
|
|
@ -385,29 +384,27 @@ export default {
|
|||
'should trigger onChange once when new input is added to form': function (test) {
|
||||
|
||||
const hasChanged = sinon.spy();
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
showInput: false
|
||||
};
|
||||
},
|
||||
class TestForm extends React.Component {
|
||||
state = {
|
||||
showInput: false
|
||||
}
|
||||
addInput() {
|
||||
this.setState({
|
||||
showInput: true
|
||||
})
|
||||
},
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onChange={hasChanged}>
|
||||
<Formsy onChange={hasChanged}>
|
||||
{
|
||||
this.state.showInput ?
|
||||
<TestInput name="test"/>
|
||||
:
|
||||
null
|
||||
}
|
||||
</Formsy.Form>);
|
||||
</Formsy>);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
form.addInput();
|
||||
|
|
@ -422,16 +419,16 @@ export default {
|
|||
|
||||
'should allow elements to check if the form is disabled': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() { return { disabled: true }; },
|
||||
enableForm() { this.setState({ disabled: false }); },
|
||||
class TestForm extends React.Component {
|
||||
state = { disabled: true };
|
||||
enableForm() { this.setState({ disabled: false }); }
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form disabled={this.state.disabled}>
|
||||
<Formsy disabled={this.state.disabled}>
|
||||
<TestInput name="foo"/>
|
||||
</Formsy.Form>);
|
||||
</Formsy>);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
|
|
@ -447,18 +444,18 @@ export default {
|
|||
|
||||
'should be possible to pass error state of elements by changing an errors attribute': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() { return { validationErrors: { foo: 'bar' } }; },
|
||||
onChange(values) {
|
||||
class TestForm extends React.Component {
|
||||
state = { validationErrors: { foo: 'bar' } };
|
||||
onChange = (values) => {
|
||||
this.setState(values.foo ? { validationErrors: {} } : { validationErrors: {foo: 'bar'} });
|
||||
},
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onChange={this.onChange} validationErrors={this.state.validationErrors}>
|
||||
<Formsy onChange={this.onChange} validationErrors={this.state.validationErrors}>
|
||||
<TestInput name="foo"/>
|
||||
</Formsy.Form>);
|
||||
</Formsy>);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
// Wait for update
|
||||
|
|
@ -479,14 +476,14 @@ export default {
|
|||
'should trigger an onValidSubmit when submitting a valid form': function (test) {
|
||||
|
||||
let isCalled = sinon.spy();
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onValidSubmit={isCalled}>
|
||||
<Formsy onValidSubmit={isCalled}>
|
||||
<TestInput name="foo" validations="isEmail" value="foo@bar.com"/>
|
||||
</Formsy.Form>);
|
||||
</Formsy>);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const FoundForm = TestUtils.findRenderedComponentWithType(form, TestForm);
|
||||
TestUtils.Simulate.submit(ReactDOM.findDOMNode(FoundForm));
|
||||
|
|
@ -498,14 +495,14 @@ export default {
|
|||
'should trigger an onInvalidSubmit when submitting an invalid form': function (test) {
|
||||
|
||||
let isCalled = sinon.spy();
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onInvalidSubmit={isCalled}>
|
||||
<Formsy onInvalidSubmit={isCalled}>
|
||||
<TestInput name="foo" validations="isEmail" value="foo@bar"/>
|
||||
</Formsy.Form>);
|
||||
</Formsy>);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const FoundForm = TestUtils.findRenderedComponentWithType(form, TestForm);
|
||||
|
|
@ -523,16 +520,16 @@ export default {
|
|||
'should call onSubmit correctly': function (test) {
|
||||
|
||||
const onSubmit = sinon.spy();
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={onSubmit}>
|
||||
<Formsy onSubmit={onSubmit}>
|
||||
<TestInput name="foo" value={false} type="checkbox" />
|
||||
<button type="submit">Save</button>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
|
||||
|
|
@ -544,26 +541,24 @@ export default {
|
|||
'should allow dynamic changes to false': function (test) {
|
||||
|
||||
const onSubmit = sinon.spy();
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
value: true
|
||||
};
|
||||
},
|
||||
class TestForm extends React.Component {
|
||||
state = {
|
||||
value: true
|
||||
}
|
||||
changeValue() {
|
||||
this.setState({
|
||||
value: false
|
||||
});
|
||||
},
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={onSubmit}>
|
||||
<Formsy onSubmit={onSubmit}>
|
||||
<TestInput name="foo" value={this.state.value} type="checkbox" />
|
||||
<button type="submit">Save</button>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
form.changeValue();
|
||||
|
|
@ -575,16 +570,16 @@ export default {
|
|||
|
||||
'should say the form is submitted': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" value={true} type="checkbox" />
|
||||
<button type="submit">Save</button>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(input.isFormSubmitted(), false);
|
||||
|
|
@ -596,29 +591,27 @@ export default {
|
|||
|
||||
'should be able to reset the form to its pristine state': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
value: true
|
||||
};
|
||||
},
|
||||
class TestForm extends React.Component {
|
||||
state = {
|
||||
value: true
|
||||
}
|
||||
changeValue() {
|
||||
this.setState({
|
||||
value: false
|
||||
});
|
||||
},
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" value={this.state.value} type="checkbox" />
|
||||
<button type="submit">Save</button>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy.Form);
|
||||
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy);
|
||||
test.equal(input.getValue(), true);
|
||||
form.changeValue();
|
||||
test.equal(input.getValue(), false);
|
||||
|
|
@ -631,29 +624,27 @@ export default {
|
|||
|
||||
'should be able to reset the form using custom data': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
value: true
|
||||
};
|
||||
},
|
||||
class TestForm extends React.Component {
|
||||
state = {
|
||||
value: true
|
||||
}
|
||||
changeValue() {
|
||||
this.setState({
|
||||
value: false
|
||||
});
|
||||
},
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" value={this.state.value} type="checkbox" />
|
||||
<button type="submit">Save</button>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy.Form);
|
||||
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy);
|
||||
|
||||
test.equal(input.getValue(), true);
|
||||
form.changeValue();
|
||||
|
|
@ -670,19 +661,19 @@ export default {
|
|||
|
||||
'should be able to reset the form to empty values': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" value="42" type="checkbox" />
|
||||
<button type="submit">Save</button>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy.Form);
|
||||
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy);
|
||||
|
||||
formsyForm.reset({
|
||||
foo: ''
|
||||
|
|
@ -698,9 +689,9 @@ export default {
|
|||
|
||||
const hasOnChanged = sinon.spy();
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form onChange={hasOnChanged}>
|
||||
<Formsy onChange={hasOnChanged}>
|
||||
<TestInput name="one" value="foo" />
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
test.equal(form.isChanged(), false);
|
||||
test.equal(hasOnChanged.called, false);
|
||||
|
|
@ -712,9 +703,9 @@ export default {
|
|||
|
||||
const hasOnChanged = sinon.spy();
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form onChange={hasOnChanged}>
|
||||
<Formsy onChange={hasOnChanged}>
|
||||
<TestInput name="one" value="foo" />
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
|
||||
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}});
|
||||
|
|
@ -728,9 +719,9 @@ export default {
|
|||
|
||||
const hasOnChanged = sinon.spy();
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form onChange={hasOnChanged}>
|
||||
<Formsy onChange={hasOnChanged}>
|
||||
<TestInput name="one" value="foo" />
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
|
||||
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}});
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import TestUtils from 'react-dom/test-utils';
|
||||
import immediate from './utils/immediate';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
render: function() {
|
||||
return <input value={this.props.getValue()} readOnly />;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" validations="equals:foo" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import TestUtils from 'react-dom/test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
render: function() {
|
||||
return <input value={this.props.getValue()} readOnly />;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" validations="isAlpha" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import TestUtils from 'react-dom/test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
return <input value={this.props.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" validations="isAlphanumeric" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import TestUtils from 'react-dom/test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
return <input value={this.props.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" validations="isEmail" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import TestUtils from 'react-dom/test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
return <input value={this.props.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" validations="isEmptyString" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import TestUtils from 'react-dom/test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
return <input value={this.props.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" validations="isExisty" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import TestUtils from 'react-dom/test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
return <input value={this.props.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" validations="isFloat" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import TestUtils from 'react-dom/test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
return <input value={this.props.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" validations="isInt" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import TestUtils from 'react-dom/test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
return <input value={this.props.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" validations={this.props.rule} value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import TestUtils from 'react-dom/test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
return <input value={this.props.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" validations="isNumeric" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import TestUtils from 'react-dom/test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
return <input value={this.props.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" validations="isUrl" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import TestUtils from 'react-dom/test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
return <input value={this.props.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" validations="isWords" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import TestUtils from 'react-dom/test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
return <input value={this.props.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" validations="maxLength:3" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import TestUtils from 'react-dom/test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
return <input value={this.props.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<TestInput name="foo" validations={this.props.rule} value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,37 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import TestUtils from 'react-dom/test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import TestInput, {InputFactory} from './utils/TestInput';
|
||||
import Formsy, { withFormsy } from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
import immediate from './utils/immediate';
|
||||
import sinon from 'sinon';
|
||||
|
||||
class MyTest extends React.Component {
|
||||
static defaultProps = { type: 'text' };
|
||||
|
||||
handleChange = (event) => {
|
||||
this.props.setValue(event.target.value);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <input type={this.props.type} value={this.props.getValue()} onChange={this.handleChange}/>;
|
||||
}
|
||||
}
|
||||
const FormsyTest = withFormsy(MyTest);
|
||||
|
||||
export default {
|
||||
|
||||
'should reset only changed form element when external error is passed': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar', bar: 'foo' })}>
|
||||
<TestInput name="foo"/>
|
||||
<TestInput name="bar"/>
|
||||
</Formsy.Form>
|
||||
<Formsy onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar', bar: 'foo' })}>
|
||||
<FormsyTest name="foo"/>
|
||||
<FormsyTest name="bar"/>
|
||||
</Formsy>
|
||||
);
|
||||
|
||||
const input = TestUtils.scryRenderedDOMComponentsWithTag(form, 'INPUT')[0];
|
||||
const inputComponents = TestUtils.scryRenderedComponentsWithType(form, TestInput);
|
||||
const inputComponents = TestUtils.scryRenderedComponentsWithType(form, FormsyTest);
|
||||
|
||||
form.submit();
|
||||
test.equal(inputComponents[0].isValid(), false);
|
||||
|
|
@ -36,13 +49,13 @@ export default {
|
|||
'should let normal validation take over when component with external error is changed': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
|
||||
<TestInput name="foo" validations="isEmail"/>
|
||||
</Formsy.Form>
|
||||
<Formsy onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
|
||||
<FormsyTest name="foo" validations="isEmail"/>
|
||||
</Formsy>
|
||||
);
|
||||
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, FormsyTest);
|
||||
|
||||
form.submit();
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
|
|
@ -62,9 +75,9 @@ export default {
|
|||
const onInvalid = sinon.spy();
|
||||
|
||||
TestUtils.renderIntoDocument(
|
||||
<Formsy.Form onValid={onValid} onInvalid={onInvalid}>
|
||||
<TestInput name="foo" value="bar" required/>
|
||||
</Formsy.Form>
|
||||
<Formsy onValid={onValid} onInvalid={onInvalid}>
|
||||
<FormsyTest name="foo" value="bar" required/>
|
||||
</Formsy>
|
||||
);
|
||||
|
||||
test.equal(onValid.called, true);
|
||||
|
|
@ -79,9 +92,9 @@ export default {
|
|||
const onInvalid = sinon.spy();
|
||||
|
||||
TestUtils.renderIntoDocument(
|
||||
<Formsy.Form onValid={onValid} onInvalid={onInvalid}>
|
||||
<TestInput name="foo" required />
|
||||
</Formsy.Form>
|
||||
<Formsy onValid={onValid} onInvalid={onInvalid}>
|
||||
<FormsyTest name="foo" required />
|
||||
</Formsy>
|
||||
);
|
||||
|
||||
test.equal(onValid.called, false);
|
||||
|
|
@ -94,14 +107,14 @@ export default {
|
|||
|
||||
let isValid = false;
|
||||
const CustomInput = InputFactory({
|
||||
componentDidMount() {
|
||||
isValid = this.isValid();
|
||||
componentDidMount: function() {
|
||||
isValid = this.props.isValid();
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form>
|
||||
<Formsy>
|
||||
<CustomInput name="foo" value="foo" required/>
|
||||
</Formsy.Form>
|
||||
</Formsy>
|
||||
);
|
||||
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
|
|
@ -110,22 +123,22 @@ export default {
|
|||
|
||||
},
|
||||
|
||||
'should provide invalidate callback on onValiSubmit': function (test) {
|
||||
'should provide invalidate callback on onValidSubmit': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onValidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
|
||||
<TestInput name="foo" value="foo"/>
|
||||
</Formsy.Form>
|
||||
<Formsy onValidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
|
||||
<FormsyTest name="foo" value="foo"/>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
const input = TestUtils.findRenderedComponentWithType(form, FormsyTest);
|
||||
TestUtils.Simulate.submit(formEl);
|
||||
test.equal(input.isValid(), false);
|
||||
test.done();
|
||||
|
|
@ -134,19 +147,19 @@ export default {
|
|||
|
||||
'should provide invalidate callback on onInvalidSubmit': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onInvalidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
|
||||
<TestInput name="foo" value="foo" validations="isEmail"/>
|
||||
</Formsy.Form>
|
||||
<Formsy onInvalidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
|
||||
<FormsyTest name="foo" value="foo" validations="isEmail"/>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
const input = TestUtils.findRenderedComponentWithType(form, FormsyTest);
|
||||
TestUtils.Simulate.submit(formEl);
|
||||
test.equal(input.getErrorMessage(), 'bar');
|
||||
|
||||
|
|
@ -156,21 +169,21 @@ export default {
|
|||
|
||||
'should not invalidate inputs on external errors with preventExternalInvalidation prop': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form
|
||||
<Formsy
|
||||
preventExternalInvalidation
|
||||
onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
|
||||
<TestInput name="foo" value="foo"/>
|
||||
</Formsy.Form>
|
||||
<FormsyTest name="foo" value="foo"/>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
const input = TestUtils.findRenderedComponentWithType(form, FormsyTest);
|
||||
TestUtils.Simulate.submit(formEl);
|
||||
test.equal(input.isValid(), true);
|
||||
test.done();
|
||||
|
|
@ -179,19 +192,19 @@ export default {
|
|||
|
||||
'should invalidate inputs on external errors without preventExternalInvalidation prop': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
class TestForm extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
|
||||
<TestInput name="foo" value="foo"/>
|
||||
</Formsy.Form>
|
||||
<Formsy onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
|
||||
<FormsyTest name="foo" value="foo"/>
|
||||
</Formsy>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
const input = TestUtils.findRenderedComponentWithType(form, FormsyTest);
|
||||
TestUtils.Simulate.submit(formEl);
|
||||
test.equal(input.isValid(), false);
|
||||
test.done();
|
||||
|
|
|
|||
|
|
@ -1,21 +1,25 @@
|
|||
import React from 'react';
|
||||
import Formsy from './../..';
|
||||
import Formsy, { withFormsy } from './../..';
|
||||
|
||||
const defaultProps = {
|
||||
mixins: [Formsy.Mixin],
|
||||
getDefaultProps() {
|
||||
return { type: 'text' };
|
||||
},
|
||||
updateValue(event) {
|
||||
this.setValue(event.target[this.props.type === 'checkbox' ? 'checked' : 'value']);
|
||||
},
|
||||
render() {
|
||||
return <input type={this.props.type} value={this.getValue()} onChange={this.updateValue}/>;
|
||||
}
|
||||
};
|
||||
class TestInput extends React.Component {
|
||||
static defaultProps = { type: 'text' };
|
||||
|
||||
export function InputFactory(props) {
|
||||
return React.createClass(Object.assign(defaultProps, props));
|
||||
updateValue = (event) => {
|
||||
this.props.setValue(event.target[this.props.type === 'checkbox' ? 'checked' : 'value']);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <input type={this.props.type} value={this.props.getValue()} onChange={this.updateValue}/>;
|
||||
}
|
||||
}
|
||||
|
||||
export default React.createClass(defaultProps);
|
||||
export function InputFactory(methods) {
|
||||
for (let method in methods) {
|
||||
if (methods.hasOwnProperty(method)) {
|
||||
TestInput.prototype[method] = methods[method];
|
||||
}
|
||||
}
|
||||
return withFormsy(TestInput);
|
||||
}
|
||||
|
||||
export default withFormsy(TestInput);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import React from 'react';
|
||||
import { HOC as formsyHoc } from './../..';
|
||||
import Formsy, { withFormsy } from './../..';
|
||||
|
||||
const defaultProps = {
|
||||
methodOnWrappedInstance(param) {
|
||||
return param;
|
||||
},
|
||||
render() {
|
||||
return (<input />);
|
||||
},
|
||||
};
|
||||
class TestComponent extends React.Component {
|
||||
methodOnWrappedInstance = (param) => {
|
||||
return param;
|
||||
}
|
||||
|
||||
export default formsyHoc(React.createClass(defaultProps));
|
||||
render() {
|
||||
return (<input />);
|
||||
}
|
||||
}
|
||||
|
||||
export default withFormsy(TestComponent);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
export default function (fn) {
|
||||
setTimeout(fn, 0);
|
||||
setTimeout(fn, 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
devtool: 'source-map',
|
||||
entry: path.resolve(__dirname, 'src', 'index.js'),
|
||||
externals: 'react',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'release'),
|
||||
filename: 'formsy-react.js',
|
||||
libraryTarget: 'umd',
|
||||
library: 'Formsy',
|
||||
},
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
}],
|
||||
},
|
||||
};
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
var path = require('path');
|
||||
|
||||
module.exports = {
|
||||
|
||||
devtool: 'source-map',
|
||||
entry: path.resolve(__dirname, 'src', 'main.js'),
|
||||
externals: 'react',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'release'),
|
||||
filename: 'formsy-react.js',
|
||||
libraryTarget: 'umd',
|
||||
library: 'Formsy'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel' },
|
||||
{ test: /\.json$/, loader: 'json' }
|
||||
]
|
||||
}
|
||||
|
||||
};
|
||||
Loading…
Reference in New Issue