Chore/tweaks for v1 (#473)

* Rename HOC/Wrapper export to `withFormsy`

This is more in alignment with community practices.

* Migrate from npm to yarn

* Update package.json

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

* Update README.md

New examples, and small copy/whitespace changes

* Remove CHANGES.md

We will use github release from now on

* Fix test suite

Replace all references to  with

* Add ESLint

* Update dependencies

* Upgrade babel dependencies

* Upgrade jsdom

* Upgrade nodeunit and sinon

* Upgrade webpack and webpack-dev-server

* Fix examples

* Convert to ES6 classes

* Fix ESLint errors and warnings (WIP)

* Fix more ESLint errors and warnings (WIP)

* Move runRules to utils.js

* Fix more ESLint errors and warnings (WIP)

* Fix more ESLint errors and warnings (WIP)

* Fix more ESLint errors and warnings (WIP)

* Use less complex regex's for url and email

* Change grammar in README

* Change export pattern

* Use ES6 export for utils and validationRules

* Fix login example

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

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

* Update examples (removes `validate` feature)

* Rename webpack file and remove json loader

* Fix code samples in README

* Update reset-values example (WIP)

* Cleanup reset-values example

* Fix prop type for Wrapper value

* Handle onReset event

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

* Update dynamic form fields example
This commit is contained in:
Aesop Wolf 2017-08-29 16:35:17 -07:00 committed by GitHub
parent b413f9db77
commit 42e39cbc8f
52 changed files with 6744 additions and 8158 deletions

3
.eslintrc Normal file
View File

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

View File

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

452
API.md
View File

@ -1,55 +1,50 @@
# API
- [Formsy.Form](#formsyform)
- [className](#classname)
- [mapping](#mapping)
- [validationErrors](#validationerrors)
- [onSubmit()](#onsubmit)
- [onValid()](#onvalid)
- [onInvalid()](#oninvalid)
- [onValidSubmit()](#onvalidsubmit)
- [onInvalidSubmit()](#oninvalidsubmit)
- [onChange()](#onchange)
- [reset()](#resetform)
- [getModel()](#getmodel)
- [updateInputsWithError()](#updateinputswitherrorerrors)
- [preventExternalInvalidation](#preventexternalinvalidation)
- [Formsy.Wrapper](#formsywrapper)
- [name](#name)
- [innerRef](#innerRef)
- [value](#value)
- [validations](#validations)
- [validationError](#validationerror)
- [validationErrors](#validationerrors-1)
- [required](#required)
- [getValue()](#getvalue)
- [setValue()](#setvalue)
- [resetValue()](#resetvalue)
- [getErrorMessage()](#geterrormessage)
- [getErrorMessages()](#geterrormessages)
- [isValid()](#isvalid)
- [isValidValue()](#isvalidvalue)
- [isRequired()](#isrequired)
- [showRequired()](#showrequired)
- [showError()](#showerror)
- [isPristine()](#ispristine)
- [isFormDisabled()](#isformdisabled)
- [isFormSubmitted()](#isformsubmitted)
- [validate](#validate)
- [formNoValidate](#formnovalidate)
- [Formsy.propTypes](#formsyproptypes)
- [Formsy.addValidationRule](#formsyaddvalidationrule)
- [Validators](#validators)
- [Formsy](#formsy)
- [mapping](#mapping)
- [validationErrors](#validationErrors)
- [onSubmit()](#onSubmit)
- [onValid()](#onValid)
- [onInvalid()](#onInvalid)
- [onValidSubmit()](#onValidsubmit)
- [onInvalidSubmit()](#onInvalidsubmit)
- [onChange()](#onChange)
- [reset()](#reset)
- [getModel()](#getModel)
- [updateInputsWithError()](#updateInputsWithError)
- [preventExternalInvalidation](#preventExternalInvalidation)
- [withFormsy](#withFormsy)
- [name](#name)
- [innerRef](#innerRef)
- [value](#value)
- [validations](#validations)
- [validationError](#validationError)
- [validationErrors](#validationErrors)
- [required](#required)
- [getValue()](#getvalue)
- [setValue()](#setValue)
- [resetValue()](#resetValue)
- [getErrorMessage()](#getErrorMessage)
- [getErrorMessages()](#getErrorMessages)
- [isValid()](#isValid)
- [isValidValue()](#isValidValue)
- [isRequired()](#isRequired)
- [showRequired()](#showRequired)
- [showError()](#showError)
- [isPristine()](#isPristine)
- [isFormDisabled()](#isFormDisabled)
- [isFormSubmitted()](#isFormSubmitted)
- [formNoValidate](#formNoValidate)
- [propTypes](#propTypes)
- [addValidationRule](#addValidationRule)
- [Validators](#validators)
### <a name="formsyform">Formsy.Form</a>
### <a id="formsy">Formsy</a>
#### <a name="classname">className</a>
```jsx
<Formsy.Form className="my-class"></Formsy.Form>
```
Sets a class name on the form itself.
`import Formsy from 'react-formsy';`
#### <a id="mapping">mapping</a>
#### <a name="mapping">mapping</a>
```jsx
class MyForm extends React.Component {
mapInputs(inputs) {
@ -63,17 +58,19 @@ class MyForm extends React.Component {
}
render() {
return (
<Formsy.Form onSubmit={this.submit} mapping={this.mapInputs}>
<Formsy onSubmit={this.submit} mapping={this.mapInputs}>
<MyInput name="foo" value=""/>
<MyInput name="bar" value=""/>
</Formsy.Form>
</Formsy>
);
}
}
```
Use mapping to change the data structure of your input elements. This structure is passed to the submit hooks.
#### <a name="validationerrors">validationErrors</a>
#### <a id="validationErrors">validationErrors</a>
You can manually pass down errors to your form. In combination with `onChange` you are able to validate using an external validator.
```jsx
@ -94,53 +91,66 @@ class Form extends React.Component {
}
render() {
return (
<Formsy.Form onChange={this.validateForm} validationErrors={this.state.validationErrors}>
<Formsy onChange={this.validateForm} validationErrors={this.state.validationErrors}>
<MyFormElement name="foo"/>
</Formsy.Form>
</Formsy>
);
}
}
```
#### <a name="onsubmit">onSubmit(data, resetForm, invalidateForm)</a>
#### <a id="onSubmit">onSubmit(data, resetForm, invalidateForm)</a>
```jsx
<Formsy.Form onSubmit={this.showFormLoader}></Formsy.Form>
<Formsy onSubmit={this.showFormLoader}></Formsy>
```
Takes a function to run when the submit button has been clicked.
The first argument is the data of the form. The second argument will reset the form. The third argument will invalidate the form by taking an object that maps to inputs. This is useful for server side validation. E.g. `{email: "This email is taken"}`. Resetting or invalidating the form will cause **setState** to run on the form element component.
#### <a name="onvalid">onValid()</a>
#### <a id="onValid">onValid()</a>
```jsx
<Formsy.Form onValid={this.enableSubmitButton}></Formsy.Form>
<Formsy onValid={this.enableSubmitButton}></Formsy>
```
Whenever the form becomes valid the "onValid" handler is called. Use it to change state of buttons or whatever your heart desires.
#### <a name="oninvalid">onInvalid()</a>
#### <a id="onInvalid">onInvalid()</a>
```jsx
<Formsy.Form onInvalid={this.disableSubmitButton}></Formsy.Form>
<Formsy onInvalid={this.disableSubmitButton}></Formsy>
```
Whenever the form becomes invalid the "onInvalid" handler is called. Use it to for example revert "onValid" state.
#### <a name="onvalidsubmit">onValidSubmit(model, resetForm, invalidateForm)</a>
#### <a id="onValidsubmit">onValidSubmit(model, resetForm, invalidateForm)</a>
```jsx
<Formsy.Form onValidSubmit={this.sendToServer}></Formsy.Form>
<Formsy onValidSubmit={this.sendToServer}></Formsy>
```
Triggers when form is submitted with a valid state. The arguments are the same as on `onSubmit`.
#### <a name="oninvalidsubmit">onInvalidSubmit(model, resetForm, invalidateForm)</a>
#### <a id="onInvalidsubmit">onInvalidSubmit(model, resetForm, invalidateForm)</a>
```jsx
<Formsy.Form onInvalidSubmit={this.notifyFormError}></Formsy.Form>
<Formsy onInvalidSubmit={this.notifyFormError}></Formsy>
```
Triggers when form is submitted with an invalid state. The arguments are the same as on `onSubmit`.
#### <a name="onchange">onChange(currentValues, isChanged)</a>
#### <a id="onChange">onChange(currentValues, isChanged)</a>
```jsx
<Formsy.Form onChange={this.saveCurrentValuesToLocalStorage}></Formsy.Form>
<Formsy onChange={this.saveCurrentValuesToLocalStorage}></Formsy>
```
"onChange" triggers when setValue is called on your form elements. It is also triggered when dynamic form elements have been added to the form. The "currentValues" is an object where the key is the name of the input and the value is the current value. The second argument states if the forms initial values actually has changed.
#### <a name="resetform">reset(values)</a>
#### <a id="reset">reset(values)</a>
```jsx
class MyForm extends React.Component {
resetForm = () => {
@ -148,16 +158,18 @@ class MyForm extends React.Component {
}
render() {
return (
<Formsy.Form ref="form">
<Formsy ref="form">
...
</Formsy.Form>
</Formsy>
);
}
}
```
Manually reset the form to its pristine state. You can also pass an object that inserts new values into the inputs. Keys are name of input and value is of course the value.
#### <a name="getmodel">getModel()</a>
#### <a id="getModel">getModel()</a>
```jsx
class MyForm extends React.Component {
getMyData = () => {
@ -165,16 +177,18 @@ class MyForm extends React.Component {
}
render() {
return (
<Formsy.Form ref="form">
<Formsy ref="form">
...
</Formsy.Form>
</Formsy>
);
}
}
```
Manually get values from all registered components. Keys are name of input and value is of course the value.
#### <a name="updateInputsWithError">updateInputsWithError(errors)</a>
#### <a id="updateInputsWithError">updateInputsWithError(errors)</a>
```jsx
class MyForm extends React.Component {
someFunction = () => {
@ -185,16 +199,18 @@ class MyForm extends React.Component {
}
render() {
return (
<Formsy.Form ref="form">
<Formsy ref="form">
...
</Formsy.Form>
</Formsy>
);
}
}
```
Manually invalidate the form by taking an object that maps to inputs. This is useful for server side validation. You can also use a third parameter to the [`onSubmit`](#onsubmitdata-resetform-invalidateform), [`onValidSubmit`](#onvalidsubmitmodel-resetform-invalidateform) or [`onInvalidSubmit`](#oninvalidsubmitmodel-resetform-invalidateform).
#### <a name="preventExternalInvalidation">preventExternalInvalidation</a>
Manually invalidate the form by taking an object that maps to inputs. This is useful for server side validation. You can also use a third parameter to the [`onSubmit`](#onSubmit), [`onValidSubmit`](#onValid) or [`onInvalidSubmit`](#onInvalid).
#### <a id="preventExternalInvalidation">preventExternalInvalidation</a>
```jsx
class MyForm extends React.Component {
onSubmit(model, reset, invalidate) {
@ -204,21 +220,22 @@ class MyForm extends React.Component {
}
render() {
return (
<Formsy.Form onSubmit={this.onSubmit} preventExternalInvalidation>
<Formsy onSubmit={this.onSubmit} preventExternalInvalidation>
...
</Formsy.Form>
</Formsy>
);
}
}
```
With the `preventExternalInvalidation` the input will not be invalidated though it has an error.
### <a name="formsymixin">Formsy.Wrapper</a>
### <a id="withFormsy">`withFormsy`</a>
All Formsy input components must be wrapped in the `Formsy.Wrapper` higher-order component, which provides the following properties and methods through `props`.
All Formsy input components must be wrapped in the `withFormsy` higher-order component, which provides the following properties and methods through `props`.
```jsx
import { Wrapper } from 'formsy-react';
import { withFormsy } from 'formsy-react';
class MyInput extends React.Component {
render() {
@ -229,18 +246,19 @@ class MyInput extends React.Component {
);
}
}
export default Wrapper(MyInput);
export default withFormsy(MyInput);
```
#### <a name="name">name</a>
#### <a id="name">name</a>
```jsx
<MyInputComponent name="email"/>
<MyInputComponent name="address.street"/>
<MyInput name="email"/>
<MyInput name="address.street"/>
```
The name is required to register the form input component in the form. You can also use dot notation. This will result in the "form model" being a nested object. `{email: 'value', address: {street: 'value'}}`.
#### <a name="innerRef">innerRef</a>
#### <a id="innerRef">innerRef</a>
Use an `innerRef` prop to get a reference to your DOM node.
@ -251,29 +269,32 @@ class MyForm extends React.Component {
}
render() {
return (
<Formsy.Form>
<Formsy>
<MyInput name="search" innerRef={(c) => { this.searchInput = c; }} />
</Formsy.Form>
</Formsy>
);
}
}
```
#### <a name="value">value</a>
#### <a id="value">value</a>
```jsx
<MyInputComponent name="email" value="My initial value"/>
<MyInput name="email" value="My initial value"/>
```
You should always use the [**getValue()**](#getvalue) method inside your formsy form element. To pass an initial value, use the value attribute. This value will become the "pristine" value and any reset of the form will bring back this value.
#### <a name="validations">validations</a>
#### <a id="validations">validations</a>
```jsx
<MyInputComponent name="email" validations="isEmail"/>
<MyInputComponent name="number" validations="isNumeric,isLength:5"/>
<MyInputComponent name="number" validations={{
<MyInput name="email" validations="isEmail"/>
<MyInput name="number" validations="isNumeric,isLength:5"/>
<MyInput name="number" validations={{
isNumeric: true,
isLength: 5
}}/>
<MyInputComponent name="number" validations={{
<MyInput name="number" validations={{
myCustomIsFiveValidation: function (values, value) {
values; // Other current values in form {foo: 'bar', 'number': 5}
value; // 5
@ -281,23 +302,28 @@ You should always use the [**getValue()**](#getvalue) method inside your formsy
}
}}/>
```
A comma separated list with validation rules. Take a look at [**Validators**](#validators) to see default rules. Use ":" to separate argument passed to the validator. The argument will go through a **JSON.parse** converting them into correct JavaScript types. Meaning:
```jsx
<MyInputComponent name="fruit" validations="isIn:['apple', 'orange']"/>
<MyInputComponent name="car" validations="mapsTo:{'bmw': true, 'vw': true}"/>
<MyInput name="fruit" validations="isIn:['apple', 'orange']"/>
<MyInput name="car" validations="mapsTo:{'bmw': true, 'vw': true}"/>
```
Works just fine.
#### <a name="validationerror">validationError</a>
#### <a id="validationError">validationError</a>
```jsx
<MyInputComponent name="email" validations="isEmail" validationError="This is not an email"/>
<MyInput name="email" validations="isEmail" validationError="This is not an email"/>
```
The message that will show when the form input component is invalid. It will be used as a default error.
#### <a name="validationerrors">validationErrors</a>
#### <a id="validationErrors">validationErrors</a>
```jsx
<MyInputComponent
<MyInput
name="email"
validations={{
isEmail: true,
@ -309,21 +335,25 @@ The message that will show when the form input component is invalid. It will be
}}
/>
```
The message that will show when the form input component is invalid. You can combine this with `validationError`. Keys not found in `validationErrors` defaults to the general error message.
#### <a name="required">required</a>
#### <a id="required">required</a>
```jsx
<MyInputComponent name="email" validations="isEmail" validationError="This is not an email" required/>
<MyInput name="email" validations="isEmail" validationError="This is not an email" required/>
```
A property that tells the form that the form input component value is required. By default it uses `isDefaultRequiredValue`, but you can define your own definition of what defined a required state.
```jsx
<MyInputComponent name="email" required="isFalse"/>
<MyInput name="email" required="isFalse"/>
```
Would be typical for a checkbox type of form element that must be checked, e.g. agreeing to Terms of Service.
#### <a name="getvalue">getValue()</a>
#### <a id="getvalue">getValue()</a>
```jsx
class MyInput extends React.Component {
render() {
@ -333,9 +363,11 @@ class MyInput extends React.Component {
}
}
```
Gets the current value of the form input component.
#### <a name="setvalue">setValue(value[, validate = true])</a>
#### <a id="setValue">setValue(value\[, validate = true])</a>
```jsx
class MyInput extends React.Component {
changeValue = (event) => {
@ -348,11 +380,13 @@ class MyInput extends React.Component {
}
}
```
Sets the value of your form input component. Notice that it does not have to be a text input. Anything can set a value on the component. Think calendars, checkboxes, autocomplete stuff etc. Running this method will trigger a **setState()** on the component and do a render.
You can also set the value without forcing an immediate validation by passing a second parameter of `false`. This is useful in cases where you want to only validate on blur / change / etc.
#### <a name="resetvalue">resetValue()</a>
#### <a id="resetValue">resetValue()</a>
```jsx
class MyInput extends React.Component {
changeValue = (event) => {
@ -368,9 +402,11 @@ class MyInput extends React.Component {
}
}
```
Resets to empty value. This will run a **setState()** on the component and do a render.
#### <a name="geterrormessage">getErrorMessage()</a>
#### <a id="getErrorMessage">getErrorMessage()</a>
```jsx
class MyInput extends React.Component {
changeValue = (event) => {
@ -386,12 +422,15 @@ class MyInput extends React.Component {
}
}
```
Will return the validation message set if the form input component is invalid. If form input component is valid it returns **null**.
#### <a name="geterrormessages">getErrorMessages()</a>
#### <a id="getErrorMessages">getErrorMessages()</a>
Will return the validation messages set if the form input component is invalid. If form input component is valid it returns empty array.
#### <a name="isvalid">isValid()</a>
#### <a id="isValid">isValid()</a>
```jsx
class MyInput extends React.Component {
changeValue = (event) => {
@ -409,9 +448,11 @@ class MyInput extends React.Component {
}
}
```
Returns the valid state of the form input component.
#### <a name="isvalidvalue">isValidValue()</a>
#### <a id="isValidValue">isValidValue()</a>
You can pre-verify a value against the passed validators to the form element.
```jsx
@ -429,15 +470,16 @@ class MyInput extends React.Component {
class MyForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<MyInput name="foo" validations="isEmail"/>
</Formsy.Form>
</Formsy>
);
}
}
```
#### <a name="isrequired">isRequired()</a>
#### <a id="isRequired">isRequired()</a>
```jsx
class MyInput extends React.Component {
changeValue = (event) => {
@ -454,9 +496,11 @@ class MyInput extends React.Component {
}
}
```
Returns true if the required property has been passed.
#### <a name="showrequired">showRequired()</a>
#### <a id="showRequired">showRequired()</a>
```jsx
class MyInput extends React.Component {
changeValue = (event) => {
@ -473,9 +517,11 @@ class MyInput extends React.Component {
}
}
```
Lets you check if the form input component should indicate if it is a required field. This happens when the form input component value is empty and the required prop has been passed.
#### <a name="showerror">showError()</a>
#### <a id="showError">showError()</a>
```jsx
class MyInput extends React.Component {
changeValue = (event) => {
@ -492,9 +538,11 @@ class MyInput extends React.Component {
}
}
```
Lets you check if the form input component should indicate if there is an error. This happens if there is a form input component value and it is invalid or if a server error is received.
#### <a name="ispristine">isPristine()</a>
#### <a id="isPristine">isPristine()</a>
```jsx
class MyInput extends React.Component {
changeValue = (event) => {
@ -510,11 +558,13 @@ class MyInput extends React.Component {
}
}
```
By default all formsy input elements are pristine, which means they are not "touched". As soon as the [**setValue**](#setvaluevalue) method is run it will no longer be pristine.
**note!** When the form is reset, using the resetForm callback function on for example [**onSubmit**](#onsubmitdata-resetform-invalidateform) the inputs are reset to their pristine state.
By default all Formsy input elements are pristine, which means they are not "touched". As soon as the [**setValue**](#setValue) method is run it will no longer be pristine.
**note!** When the form is reset (using `reset(...)`) the inputs are reset to their pristine state.
#### <a id="isFormDisabled">isFormDisabled()</a>
#### <a name="isformdisabled">isFormDisabled()</a>
```jsx
class MyInput extends React.Component {
render() {
@ -526,11 +576,13 @@ class MyInput extends React.Component {
}
}
React.render(<Formy.Form disabled={true}/>);
React.render(<Formsy disabled={true}/>);
```
You can now disable the form itself with a prop and use **isFormDisabled()** inside form elements to verify this prop.
#### <a name="isformsubmitted">isFormSubmitted()</a>
#### <a id="isFormSubmitted">isFormSubmitted()</a>
```jsx
class MyInput extends React.Component {
render() {
@ -544,32 +596,13 @@ class MyInput extends React.Component {
}
}
```
You can check if the form has been submitted.
#### <a name="validate">validate</a>
```jsx
class MyInput extends React.Component {
changeValue = (event) => {
this.props.setValue(event.target.value);
}
validate = () => {
return !!this.props.getValue();
}
render() {
return (
<div>
<input type="text" value={this.props.getValue()} onChange={this.changeValue}/>
</div>
);
}
}
#### <a id="formNoValidate">formNoValidate</a>
React.render(<Formy.Form disabled={true}/>);
```
You can create custom validation inside a form element. The validate method defined will be run when you set new values to the form element. It will also be run when the form validates itself. This is an alternative to passing in validation rules as props.
#### <a name="formnovalidate">formNoValidate</a>
To avoid native validation behavior on inputs, use the React `formNoValidate` property.
```jsx
class MyInput extends React.Component {
render() {
@ -582,174 +615,237 @@ class MyInput extends React.Component {
}
```
### <a name="formsyproptypes">Formsy.propTypes</a>
If you are using React's PropType typechecking, you can spread Formsy's propTypes into your local propTypes to avoid having to repeatedly add `Formsy.Wrapper`'s methods to your components.
### <a id="propTypes">`propTypes`</a>
If you are using React's PropType type checking, you can spread Formsys propTypes into your local propTypes to avoid having to repeatedly add `withFormsy`s methods to your components.
```jsx
import PropTypes from 'prop-types';
import { propTypes } from 'formsy-react';
class MyInput extends React.Component {
static propTypes = {
firstProp: PropTypes.string,
secondProp: PropTypes.object,
...Formsy.propTypes
...propTypes
}
...
}
MyInput.propTypes = {
firstProp: PropTypes.string,
secondProp: PropTypes.object,
...propTypes,
};
```
### <a name="formsyaddvalidationrule">Formsy.addValidationRule(name, ruleFunc)</a>
### <a id="addValidationRule">`addValidationRule(name, ruleFunc)`</a>
`import { addValidationRule } from 'formsy-react';`
An example:
```jsx
Formsy.addValidationRule('isFruit', function (values, value) {
addValidationRule('isFruit', function (values, value) {
return ['apple', 'orange', 'pear'].indexOf(value) >= 0;
});
```
```jsx
<MyInputComponent name="fruit" validations="isFruit"/>
<MyInput name="fruit" validations="isFruit"/>
```
Another example:
```jsx
Formsy.addValidationRule('isIn', function (values, value, array) {
addValidationRule('isIn', function (values, value, array) {
return array.indexOf(value) >= 0;
});
```
```jsx
<MyInputComponent name="fruit" validations="isIn:['apple', 'orange', 'pear']"/>
<MyInput name="fruit" validations="isIn:['apple', 'orange', 'pear']"/>
```
Cross input validation:
```jsx
Formsy.addValidationRule('isMoreThan', function (values, value, otherField) {
addValidationRule('isMoreThan', function (values, value, otherField) {
// The this context points to an object containing the values
// {childAge: "", parentAge: "5"}
// otherField argument is from the validations rule ("childAge")
return Number(value) > Number(values[otherField]);
});
```
```jsx
<MyInputComponent name="childAge"/>
<MyInputComponent name="parentAge" validations="isMoreThan:childAge"/>
<MyInput name="childAge"/>
<MyInput name="parentAge" validations="isMoreThan:childAge"/>
```
## <a name="validators">Validators</a>
## <a id="validators">Validators</a>
**matchRegexp**
```jsx
<MyInputComponent name="foo" validations={{
<MyInput name="foo" validations={{
matchRegexp: /foo/
}}/>
```
Returns true if the value is thruthful
_For more complicated regular expressions (emoji, international characters) you can use [xregexp](https://github.com/slevithan/xregexp). See [this comment](https://github.com/christianalfoni/formsy-react/issues/407#issuecomment-266306783) for an example._
**isEmail**
```jsx
<MyInputComponent name="foo" validations="isEmail"/>
<MyInput name="foo" validations="isEmail"/>
```
Return true if it is an email
**isUrl**
```jsx
<MyInputComponent name="foo" validations="isUrl"/>
<MyInput name="foo" validations="isUrl"/>
```
Return true if it is an url
**isExisty**
```jsx
<MyInputComponent name="foo" validations="isExisty"/>
<MyInput name="foo" validations="isExisty"/>
```
Returns true if the value is not undefined or null
**isUndefined**
```jsx
<MyInputComponent name="foo" validations="isUndefined"/>
<MyInput name="foo" validations="isUndefined"/>
```
Returns true if the value is the undefined
**isEmptyString**
```jsx
<MyInputComponent name="foo" validations="isEmptyString"/>
<MyInput name="foo" validations="isEmptyString"/>
```
Returns true if the value is an empty string
**isTrue**
```jsx
<MyInputComponent name="foo" validations="isTrue"/>
<MyInput name="foo" validations="isTrue"/>
```
Returns true if the value is the boolean true
**isFalse**
```jsx
<MyInputComponent name="foo" validations="isFalse"/>
<MyInput name="foo" validations="isFalse"/>
```
Returns true if the value is the boolean false
**isAlpha**
```jsx
<MyInputComponent name="foo" validations="isAlpha"/>
<MyInput name="foo" validations="isAlpha"/>
```
Returns true if string is only letters
**isNumeric**
```jsx
<MyInputComponent name="foo" validations="isNumeric"/>
<MyInput name="foo" validations="isNumeric"/>
```
Returns true if string only contains numbers. Examples: 42; -3.14
**isAlphanumeric**
```jsx
<MyInputComponent name="foo" validations="isAlphanumeric"/>
<MyInput name="foo" validations="isAlphanumeric"/>
```
Returns true if string only contains letters or numbers
**isInt**
```jsx
<MyInputComponent name="foo" validations="isInt"/>
<MyInput name="foo" validations="isInt"/>
```
Returns true if string represents integer value. Examples: 42; -12; 0
**isFloat**
```jsx
<MyInputComponent name="foo" validations="isFloat"/>
<MyInput name="foo" validations="isFloat"/>
```
Returns true if string represents float value. Examples: 42; -3.14; 1e3
**isWords**
```jsx
<MyInputComponent name="foo" validations="isWords"/>
<MyInput name="foo" validations="isWords"/>
```
Returns true if string is only letters, including spaces and tabs
**isSpecialWords**
```jsx
<MyInputComponent name="foo" validations="isSpecialWords"/>
<MyInput name="foo" validations="isSpecialWords"/>
```
Returns true if string is only letters, including special letters (a-z,ú,ø,æ,å)
**equals:value**
```jsx
<MyInputComponent name="foo" validations="equals:4"/>
<MyInput name="foo" validations="equals:4"/>
```
Return true if the value from input component matches value passed (==).
**equalsField:fieldName**
```jsx
<MyInputComponent name="password"/>
<MyInputComponent name="repeated_password" validations="equalsField:password"/>
<MyInput name="password"/>
<MyInput name="repeated_password" validations="equalsField:password"/>
```
Return true if the value from input component matches value passed (==).
**isLength:length**
```jsx
<MyInputComponent name="foo" validations="isLength:8"/>
<MyInput name="foo" validations="isLength:8"/>
```
Returns true if the value length is the equal.
**minLength:length**
```jsx
<MyInputComponent name="number" validations="minLength:1"/>
<MyInput name="number" validations="minLength:1"/>
```
Return true if the value is more or equal to argument.
**Also returns true for an empty value.** If you want to get false, then you should use [`required`](#required) additionally.
**maxLength:length**
```jsx
<MyInputComponent name="number" validations="maxLength:5"/>
<MyInput name="number" validations="maxLength:5"/>
```
Return true if the value is less or equal to argument

View File

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

201
README.md
View File

@ -1,124 +1,131 @@
formsy-react [![GitHub release](https://img.shields.io/github/release/christianalfoni/formsy-react.svg)](https://github.com/christianalfoni/formsy-react/releases) [![Build Status](https://travis-ci.org/christianalfoni/formsy-react.svg?branch=master)](https://travis-ci.org/christianalfoni/formsy-react)
============
# formsy-react [![GitHub release](https://img.shields.io/github/release/christianalfoni/formsy-react.svg)](https://github.com/christianalfoni/formsy-react/releases) [![Build Status](https://travis-ci.org/christianalfoni/formsy-react.svg?branch=master)](https://travis-ci.org/christianalfoni/formsy-react)
A form input builder and validator for React JS
A form input builder and validator for React.
| [How to use](#how-to-use) | [API](/API.md) | [Examples](/examples) |
|---|---|---|
| [Quick Start](#quick-start) | [API](/API.md) | [Examples](/examples) |
| --------------------------- | -------------- | --------------------- |
## <a name="background">Background</a>
I wrote an article on forms and validation with React JS, [Nailing that validation with React JS](http://christianalfoni.github.io/javascript/2014/10/22/nailing-that-validation-with-reactjs.html), the result of that was this extension.
## Background
The main concept is that forms, inputs and validation is done very differently across developers and projects. This extension to React JS aims to be that "sweet spot" between flexibility and reusability.
I wrote an article on forms and validation with React, [Nailing that validation with React JS](http://christianalfoni.github.io/javascript/2014/10/22/nailing-that-validation-with-reactjs.html), the result of that was this component.
## What you can do
The main concept is that forms, inputs, and validation are done very differently across developers and projects. This React component aims to be that “sweet spot” between flexibility and reusability.
1. Build any kind of form element components. Not just traditional inputs, but anything you want and get that validation for free
## What You Can Do
2. Add validation rules and use them with simple syntax
3. Use handlers for different states of your form. Ex. "onSubmit", "onError", "onValid" etc.
4. Pass external errors to the form to invalidate elements
5. You can dynamically add form elements to your form and they will register/unregister to the form
## Default elements
You can look at examples in this repo or use the [formsy-react-components](https://github.com/twisty/formsy-react-components) project to use bootstrap with formsy-react, or use [formsy-material-ui](https://github.com/mbrookes/formsy-material-ui) to use [Material-UI](http://material-ui.com/) with formsy-react.
1. Build any kind of form element components. Not just traditional inputs, but anything you want, and get that validation for free
2. Add validation rules and use them with simple syntax
3. Use handlers for different states of your form. (`onError`, `onSubmit`, `onValid`, etc.)
4. Pass external errors to the form to invalidate elements (E.g. a response from a server)
5. Dynamically add form elements to your form and they will register/unregister to the form
## Install
1. Download from this REPO and use globally (Formsy) or with requirejs
2. Install with `npm install formsy-react` and use with browserify etc.
`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';
class MyAppForm extends React.Component {
state = { 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';
class MyOwnInput extends React.Component {
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.props.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.props.showRequired() ? 'required' : this.props.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.props.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.props.getValue()}/>
<span>{errorMessage}</span>
</div>
);
}
});
// Wrap the component in the Formsy.Wrapper higher-order component
export default Formsy.Wrapper(MyOwnInput);
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.
`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

10
examples/.eslintrc Normal file
View File

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

View File

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

View File

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

View File

@ -1,10 +1,15 @@
import React from 'react';
import Formsy from 'formsy-react';
import { propTypes, withFormsy } from 'formsy-react';
class MyInput extends React.Component {
// setValue() will set the value of the component, which in
// turn will validate it and the rest of the form
changeValue = (event) => {
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
this.props.setValue(event.currentTarget[this.props.type === 'checkbox' ? 'checked' : 'value']);
}
@ -14,8 +19,7 @@ class MyInput extends React.Component {
// when the value is empty and the required prop is
// passed to the input. showError() is true when the
// value typed is invalid
const className = 'form-group' + (this.props.className || ' ') +
(this.props.showRequired() ? 'required' : this.props.showError() ? 'error' : '');
const className = `form-group ${this.props.className} ${this.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
@ -25,11 +29,10 @@ class MyInput extends React.Component {
<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.props.getValue()}
checked={this.props.type === 'checkbox' && this.props.getValue() ? 'checked' : null}
name={this.props.name}
type={this.props.type || 'text'}
value={this.props.getValue() || ''}
/>
<span className='validation-error'>{errorMessage}</span>
</div>
@ -37,4 +40,8 @@ class MyInput extends React.Component {
}
}
export default Formsy.Wrapper(MyInput);
MyInput.propTypes = {
...propTypes,
};
export default withFormsy(MyInput);

View File

@ -1,5 +1,5 @@
import React from 'react';
import Formsy from 'formsy-react';
import { withFormsy } from 'formsy-react';
function contains(container, item, cmp) {
for (const it of container) {
@ -61,4 +61,4 @@ class MyRadioGroup extends React.Component {
}
export default Formsy.Wrapper(MyRadioGroup);
export default withFormsy(MyRadioGroup);

View File

@ -1,5 +1,5 @@
import React from 'react';
import Formsy from 'formsy-react';
import { withFormsy } from 'formsy-react';
class MyRadioGroup extends React.Component {
state = {};
@ -42,4 +42,4 @@ class MyRadioGroup extends React.Component {
}
}
export default Formsy.Wrapper(MyRadioGroup);
export default withFormsy(MyRadioGroup);

View File

@ -1,5 +1,5 @@
import React from 'react';
import Formsy from 'formsy-react';
import { withFormsy } from 'formsy-react';
class MySelect extends React.Component {
changeValue = (event) => {
@ -29,4 +29,4 @@ class MySelect extends React.Component {
}
}
export default Formsy.Wrapper(MySelect);
export default withFormsy(MySelect);

View File

@ -1,103 +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;
});
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" />
<FormsyDynamicInput name="dynamic" title="..." />
<button type="submit">Submit</button>
</Formsy.Form>
</Formsy>
);
}
}
class DynamicInput extends React.Component {
state = { validationType: 'time' };
changeValue = (event) => {
this.props.setValue(event.currentTarget.value);
constructor(props) {
super(props);
this.state = { validationType: 'time' };
this.changeValidation = this.changeValidation.bind(this);
this.changeValue = this.changeValue.bind(this);
}
changeValidation = (validationType) => {
changeValidation(validationType) {
this.setState({ validationType: validationType });
this.props.setValue(this.props.getValue());
}
validate = () => {
const value = this.props.getValue();
console.log(value, this.state.validationType);
return value ? validators[this.state.validationType].regexp.test(value) : true;
}
getCustomErrorMessage = () => {
return this.props.showError() ? validators[this.state.validationType].message : '';
changeValue(event) {
this.props.setValue(event.currentTarget.value);
}
render() {
const className = 'form-group' + (this.props.className || ' ') + (this.props.showRequired() ? 'required' : this.props.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.props.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>
);
}
}
const FormsyDynamicInput = Formsy.Wrapper(DynamicInput);
DynamicInput.displayName = "dynamic input";
DynamicInput.propTypes = {
...propTypes,
};
const FormsyDynamicInput = withFormsy(DynamicInput);
class Validations extends React.Component {
changeValidation = (e) => {
constructor(props) {
super(props);
this.changeValidation = this.changeValidation.bind(this);
}
changeValidation(e) {
this.props.changeValidation(e.target.value);
}
render() {
const { validationType } = this.props;
return (
<fieldset onChange={this.changeValidation}>
<fieldset>
<legend>Validation Type</legend>
<div>
<input name='validationType' type='radio' value='time' defaultChecked={validationType === 'time'}/>Time
<input onChange={this.changeValidation} name='validationType' type='radio' value='time' checked={validationType === 'time'} />Time
</div>
<div>
<input name='validationType' type='radio' value='decimal' defaultChecked={validationType === 'decimal'}/>Decimal
<input onChange={this.changeValidation} name='validationType' type='radio' value='decimal' checked={validationType === 'decimal'} />Decimal
</div>
<div>
<input name='validationType' type='radio' value='binary' defaultChecked={validationType === 'binary'}/>Binary
<input onChange={this.changeValidation} name='validationType' type='radio' value='binary' checked={validationType === 'binary'} />Binary
</div>
</fieldset>
);
}
}
ReactDOM.render(<App/>, document.getElementById('example'));
Validations.propTypes = {
changeValidation: PropTypes.func.isRequired,
validationType: PropTypes.string.isRequired,
};
ReactDOM.render(<App />, document.getElementById('example'));

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Form } from 'formsy-react';
import Formsy from 'formsy-react';
import MyInput from './../components/Input';
import MySelect from './../components/Select';
@ -14,7 +14,7 @@ const Fields = props => {
props.onRemove(pos);
};
}
const foo = 'required';
return (
<div className="fields">
{props.data.map((field, i) => (
@ -46,7 +46,7 @@ const Fields = props => {
/>
)
}
<a href="#" className="remove-field" onClick={onRemove(i)}>X</a>
<button className="remove-field" onClick={onRemove(i)}>X</button>
</div>
))
}
@ -55,33 +55,48 @@ const Fields = props => {
};
class App extends React.Component {
state = { fields: [], canSubmit: false };
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) => {
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) => {
removeField(pos) {
const fields = this.state.fields;
this.setState({ fields: fields.slice(0, pos).concat(fields.slice(pos+1)) })
}
enableButton = () => {
enableButton() {
this.setState({ canSubmit: true });
}
disableButton = () => {
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},
@ -93,19 +108,27 @@ class App extends React.Component {
{maxLength: 7}
]}
/>
<MyRadioGroup name="required" value={false} title="Required"
items={[true, false]} />
<MyRadioGroup name="type" value="input" title="Type"
items={['input', 'select']} />
<MyRadioGroup
name="required"
value={false}
title="Required"
items={[true, false]}
/>
<MyRadioGroup
name="type"
value="input"
title="Type"
items={['input', 'select']}
/>
<button type="submit">Add</button>
</Form>
<Form onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="many-fields">
</Formsy>
<Formsy onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="many-fields">
<Fields data={fields} onRemove={this.removeField} />
<button type="submit" disabled={!canSubmit}>Submit</button>
</Form>
</Formsy>
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('example'));
ReactDOM.render(<App />, document.getElementById('example'));

View File

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

View File

@ -1,27 +1,36 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Form } from 'formsy-react';
import Formsy from 'formsy-react';
import MyInput from './../components/Input';
class App extends React.Component {
state = { canSubmit: false };
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 = () => {
enableButton() {
this.setState({ canSubmit: true });
}
disableButton = () => {
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>
);
}
}

View File

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

View File

@ -1,7 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Form } from 'formsy-react';
import Formsy from 'formsy-react';
import MyCheckbox from './../components/Checkbox';
import MyInput from './../components/Input';
import MySelect from './../components/Select';
@ -11,19 +12,45 @@ const user = {
hair: 'brown'
};
const 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 @@ class App extends React.Component {
/>
<div className="buttons">
<button type="reset" onClick={this.resetForm}>Reset</button>
<button type="reset">Reset</button>
<button type="button" onClick={this.randomize}>Randomize</button>
<button type="submit">Submit</button>
</div>
</Formsy.Form>
</Formsy>
);
}
}
ReactDOM.render(<App/>, document.getElementById('example'));
ReactDOM.render(<App />, document.getElementById('example'));

View File

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

View File

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

6732
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +1,61 @@
{
"name": "formsy-react",
"version": "1.0.0",
"description": "A form input builder and validator for React JS",
"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"
},
"author": "Christian Alfoni",
"files": [
"lib"
],
"main": "lib/index.js",
"scripts": {
"build": "NODE_ENV=production webpack -p --config webpack.production.config.js",
"test": "babel-node testrunner",
"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",
"prepublish": "rm -Rf ./lib && babel ./src/ -d ./lib/"
"lint": "eslint src/**/*.js",
"prepublish": "rm -Rf ./lib && babel ./src/ -d ./lib/",
"test": "babel-node testrunner"
},
"author": "Christian Alfoni",
"license": "MIT",
"keywords": [
"react",
"form",
"forms",
"validation",
"react-component"
],
"dependencies": {
"form-data-to-object": "^0.2.0",
"hoist-non-react-statics": "^2.2.1",
"react": "^15.0.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": "^15.6.1",
"react-dom": "^15.6.1"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,219 +1,255 @@
var React = global.React || require('react');
var PropTypes = require('prop-types');
var hoistNonReactStatic = require('hoist-non-react-statics');
var utils = require('./utils.js');
import PropTypes from 'prop-types';
import React from 'react';
import utils from './utils';
var convertValidationsToObject = function (validations) {
if (typeof validations === 'string') {
return validations.split(/\,(?![^{\[]*[}\]])/g).reduce(function (validations, validation) {
var args = validation.split(':');
var validateMethod = args.shift();
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(function (arg) {
try {
return JSON.parse(arg);
} catch (e) {
return arg; // It is a string if it can not parse it
}
});
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.');
}
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;
}, {});
// Avoid parameter reassignment
const validationsAccumulatorCopy = Object.assign({}, validationsAccumulator);
validationsAccumulatorCopy[validateMethod] = args.length ? args[0] : true;
return validationsAccumulatorCopy;
}, {});
}
}
return validations || {};
return validations || {};
};
module.exports = function (Component) {
class WrappedComponent extends React.Component {
static displayName = 'Formsy(' + getDisplayName(Component) + ')';
state = {
_value: typeof this.props.value !== 'undefined' ? this.props.value : Component.defaultProps ? Component.defaultProps.value : undefined,
_isRequired: false,
_isValid: true,
_isPristine: true,
_pristineValue: typeof this.props.value !== 'undefined' ? this.props.value : Component.defaultProps ? Component.defaultProps.value : undefined,
_validationError: [],
_externalError: null,
_formSubmitted: false
}
static contextTypes = {
formsy: PropTypes.object // What about required?
}
static defaultProps = {
validationError: '',
validationErrors: {}
}
componentWillMount() {
var configure = () => {
this.setValidations(this.props.validations, this.props.required);
// Pass a function instead?
this.context.formsy.attachToForm(this);
//this.props._attachToForm(this);
}
if (!this.props.name) {
throw new Error('Form Input requires a name property when used');
}
configure();
}
// We have to make 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);
//this.props._detachFromForm(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: value
});
} else {
this.setState({
_value: value,
_isPristine: false
}, () => {
this.context.formsy.validate(this);
//this.props._validate(this);
});
}
}
resetValue = () => {
this.setState({
_value: this.state._pristineValue,
_isPristine: true
}, function () {
this.context.formsy.validate(this);
//this.props._validate(this);
});
}
getValue = () => {
return this.state._value;
}
hasValue = () => {
return this.state._value !== '';
}
getErrorMessage = () => {
var messages = this.getErrorMessages();
return messages.length ? messages[0] : null;
}
getErrorMessages = () => {
return !this.isValid() || this.showRequired() ? (this.state._externalError || this.state._validationError || []) : [];
}
isFormDisabled = () => {
return this.context.formsy.isFormDisabled();
//return this.props._isFormDisabled();
}
isValid = () => {
return this.state._isValid;
}
isPristine = () => {
return this.state._isPristine;
}
isFormSubmitted = () => {
return this.state._formSubmitted;
}
isRequired = () => {
return !!this.props.required;
}
showRequired = () => {
return this.state._isRequired;
}
showError = () => {
return !this.showRequired() && !this.isValid();
}
isValidValue = (value) => {
return this.context.formsy.isValidValue.call(null, this, value);
//return this.props._isValidValue.call(null, this, value);
}
render() {
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 <Component {...propsForElement} />
}
}
return hoistNonReactStatic(WrappedComponent, Component);
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,
]),
};
function getDisplayName(Component) {
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')
component.displayName ||
component.name ||
(typeof component === 'string' ? component : 'Component')
);
}
}
WrappedComponent.displayName = `Formsy(${getDisplayName(Component)})`;
WrappedComponent.contextTypes = {
formsy: PropTypes.object, // What about required?
};
WrappedComponent.defaultProps = {
innerRef: () => {},
required: false,
validationError: '',
validationErrors: {},
validations: null,
value: Component.defaultValue,
};
WrappedComponent.propTypes = propTypes;
return WrappedComponent;
};

View File

@ -1,459 +1,467 @@
var PropTypes = require('prop-types');
var React = global.React || require('react');
var Formsy = {};
var validationRules = require('./validationRules.js');
var formDataToObject = require('form-data-to-object');
var utils = require('./utils.js');
var Wrapper = require('./Wrapper.js');
var options = {};
var emptyArray = [];
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.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.Wrapper = Wrapper;
Formsy.propTypes = {
setValidations: PropTypes.func,
setValue: PropTypes.func,
resetValue: PropTypes.func,
getValue: PropTypes.func,
hasValue: PropTypes.func,
getErrorMessage: PropTypes.func,
getErrorMessages: PropTypes.func,
isFormDisabled: PropTypes.func,
isValid: PropTypes.func,
isPristine: PropTypes.func,
isFormSubmitted: PropTypes.func,
isRequired: PropTypes.func,
showRequired: PropTypes.func,
showError: PropTypes.func,
isValidValue: PropTypes.func
}
Formsy.defaults = function (passedOptions) {
options = passedOptions;
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.addValidationRule = function (name, func) {
validationRules[name] = func;
Formsy.childContextTypes = {
formsy: PropTypes.object,
};
Formsy.Form = class FormsyForm extends React.Component {
static displayName = 'Formsy.Form'
const addValidationRule = (name, func) => {
validationRules[name] = func;
};
static defaultProps = {
onSuccess: function () {},
onError: function () {},
onSubmit: function () {},
onValidSubmit: function () {},
onInvalidSubmit: function () {},
onValid: function () {},
onInvalid: function () {},
onChange: function () {},
validationErrors: null,
preventExternalInvalidation: false
}
const withFormsy = Wrapper;
static childContextTypes = {
formsy: PropTypes.object
}
export {
addValidationRule,
propTypes,
withFormsy,
};
state = {
isValid: true,
isSubmitting: false,
canChange: false
}
getChildContext() {
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() {
this.inputs = [];
}
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);
}
var newInputNames = this.inputs.map(component => component.props.name);
if (utils.arraysDiffer(this.prevInputNames, newInputNames)) {
this.validateForm();
}
}
// Allow resetting to specified data
reset = (data) => {
this.setFormPristine(true);
this.resetModel(data);
}
// Update model, submit to url prop and send the model
submit = (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 = (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 = () => {
var currentValues = this.getCurrentValues();
return this.mapModel(currentValues);
}
// Reset each key in the model to the original / initial / specified value
resetModel = (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 = (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 = () => {
return !utils.isSame(this.getPristineValues(), this.getCurrentValues());
}
getPristineValues = () => {
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 = (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 = () => {
return this.props.disabled;
}
getCurrentValues = () => {
return this.inputs.reduce((data, component) => {
var name = component.props.name;
data[name] = component.state._value;
return data;
}, {});
}
setFormPristine = (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 = (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 = (component, value) => {
var currentValues = this.getCurrentValues();
var validationErrors = component.props.validationErrors;
var validationError = component.props.validationError;
value = value ? 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 = (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 = () => {
// 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 = (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) => {
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() {
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;
export default Formsy;

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ 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');
@ -28,7 +28,7 @@ export default {
'should only set the value and not validate when calling setValue(val, false)': function (test) {
const Input = Formsy.Wrapper(class TestInput extends React.Component {
const Input = withFormsy(class TestInput extends React.Component {
updateValue = (event) => {
this.props.setValue(event.target.value, false);
}
@ -37,9 +37,9 @@ export default {
}
})
const form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Formsy>
<Input name="foo" value="foo" innerRef="comp" />
</Formsy.Form>
</Formsy>
);
const inputComponent = TestUtils.findRenderedComponentWithType(form, Input);
const setStateSpy = sinon.spy(inputComponent, 'setState');
@ -48,7 +48,7 @@ export default {
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.equal(setStateSpy.calledWithExactly({ value: 'foobar' }), true);
test.done();
},
@ -62,9 +62,9 @@ export default {
}
});
const form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Formsy>
<Input name="foo" value="foo"/>
</Formsy.Form>
</Formsy>
);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
@ -85,9 +85,9 @@ export default {
}
});
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');
@ -105,9 +105,9 @@ export default {
}
});
const form = TestUtils.renderIntoDocument(
<Formsy.Form action="/users">
<Formsy action="/users">
<Input name="foo" value="foo" validations="isEmail"/>
</Formsy.Form>
</Formsy>
);
test.equal(isValid(), false);
@ -128,11 +128,11 @@ export default {
}
});
TestUtils.renderIntoDocument(
<Formsy.Form action="/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);
@ -152,11 +152,11 @@ export default {
}
});
TestUtils.renderIntoDocument(
<Formsy.Form action="/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);
@ -176,9 +176,9 @@ export default {
}
});
const form = TestUtils.renderIntoDocument(
<Formsy.Form action="/users">
<Formsy action="/users">
<Input name="A" value="foo"/>
</Formsy.Form>
</Formsy>
);
test.equal(isPristine(), true);
@ -201,9 +201,9 @@ export default {
}
render() {
return (
<Formsy.Form action="/users">
<Formsy action="/users">
<TestInput name="A" value={this.state.value} />
</Formsy.Form>
</Formsy>
);
}
}
@ -223,9 +223,9 @@ export default {
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="A" validations="isEmail"/>
</Formsy.Form>
</Formsy>
);
}
}
@ -243,11 +243,11 @@ export default {
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="A" validations={{
isEmail: true
}}/>
</Formsy.Form>
</Formsy>
);
}
}
@ -266,11 +266,11 @@ export default {
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="A" validations={{
matchRegexp: /foo/
}} value="foo"/>
</Formsy.Form>
</Formsy>
);
}
}
@ -297,14 +297,14 @@ export default {
}
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>
);
}
}
@ -327,11 +327,11 @@ export default {
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>
);
}
}
@ -349,11 +349,11 @@ export default {
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>
);
}
}
@ -371,7 +371,7 @@ export default {
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="A"
validations={{
isEmail: true
@ -383,7 +383,7 @@ export default {
isLength: 1
}}
/>
</Formsy.Form>
</Formsy>
);
}
}
@ -401,23 +401,23 @@ export default {
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();
@ -428,11 +428,11 @@ export default {
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="A"
required
/>
</Formsy.Form>
</Formsy>
);
}
}
@ -454,10 +454,10 @@ export default {
}
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" value={this.state.foo}/>
<TestInput name="bar" value={this.state.bar}/>
</Formsy.Form>
</Formsy>
);
}
}
@ -487,12 +487,12 @@ export default {
}
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>
);
}
}
@ -515,10 +515,10 @@ export default {
}
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>
);
}
}
@ -541,10 +541,10 @@ export default {
}
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>
);
}
}
@ -572,9 +572,9 @@ export default {
});
const form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Formsy>
<Input name="foo" value="foo"/>
</Formsy.Form>
</Formsy>
);
test.equal(renderSpy.calledOnce, true);

View File

@ -2,7 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
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';
@ -14,9 +14,9 @@ export default {
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInputHoc name="name" innerRef={(c) => { this.name = c; }} />
</Formsy.Form>
</Formsy>
);
}
}
@ -30,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();
@ -39,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();
@ -52,12 +52,12 @@ export default {
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>
);
}
}
@ -82,9 +82,9 @@ export default {
}
render() {
return (
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
<Formsy onSubmit={(formModel) => (model = formModel)}>
{inputs}
</Formsy.Form>);
</Formsy>);
}
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
@ -118,9 +118,9 @@ export default {
}
render() {
return (
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
<Formsy onSubmit={(formModel) => (model = formModel)}>
{inputs}
</Formsy.Form>);
</Formsy>);
}
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
@ -158,9 +158,9 @@ export default {
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"/>);
@ -192,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');
@ -214,8 +214,8 @@ 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) {
@ -229,9 +229,9 @@ export default {
}
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="one" validations={this.state.rule} value="foo"/>
</Formsy.Form>
</Formsy>
);
}
}
@ -262,7 +262,7 @@ export default {
}
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 ?
@ -270,7 +270,7 @@ export default {
:
null
}
</Formsy.Form>
</Formsy>
);
}
}
@ -303,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 ?
@ -311,7 +311,7 @@ export default {
:
null
}
</Formsy.Form>
</Formsy>
);
}
}
@ -333,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');
@ -358,7 +358,7 @@ export default {
const hasChanged = sinon.spy();
class TestForm extends React.Component {
render() {
return <Formsy.Form onChange={hasChanged}></Formsy.Form>;
return <Formsy onChange={hasChanged}></Formsy>;
}
}
TestUtils.renderIntoDocument(<TestForm/>);
@ -371,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);
@ -395,14 +395,14 @@ export default {
}
render() {
return (
<Formsy.Form onChange={hasChanged}>
<Formsy onChange={hasChanged}>
{
this.state.showInput ?
<TestInput name="test"/>
:
null
}
</Formsy.Form>);
</Formsy>);
}
}
@ -424,9 +424,9 @@ export default {
enableForm() { this.setState({ disabled: false }); }
render() {
return (
<Formsy.Form disabled={this.state.disabled}>
<Formsy disabled={this.state.disabled}>
<TestInput name="foo"/>
</Formsy.Form>);
</Formsy>);
}
}
@ -451,9 +451,9 @@ export default {
}
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/>);
@ -479,9 +479,9 @@ export default {
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/>);
@ -498,9 +498,9 @@ export default {
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/>);
@ -523,10 +523,10 @@ export default {
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>
);
}
}
@ -552,10 +552,10 @@ export default {
}
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>
);
}
}
@ -573,10 +573,10 @@ export default {
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>
);
}
}
@ -602,16 +602,16 @@ export default {
}
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);
@ -635,16 +635,16 @@ export default {
}
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();
@ -664,16 +664,16 @@ export default {
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: ''
@ -689,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);
@ -703,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'}});
@ -719,9 +719,9 @@ export default {
const hasOnChanged = sinon.spy();
const form = TestUtils.renderIntoDocument(
<Formsy.Form onChange={hasOnChanged}>
<Formsy onChange={hasOnChanged}>
<TestInput name="one" value="foo" />
</Formsy.Form>
</Formsy>
);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}});

View File

@ -14,9 +14,9 @@ const TestInput = InputFactory({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="equals:foo" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
}

View File

@ -13,9 +13,9 @@ const TestInput = InputFactory({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isAlpha" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
}

View File

@ -13,9 +13,9 @@ const TestInput = InputFactory({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isAlphanumeric" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
}

View File

@ -13,9 +13,9 @@ const TestInput = InputFactory({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isEmail" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
}

View File

@ -13,9 +13,9 @@ const TestInput = InputFactory({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isEmptyString" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
}

View File

@ -13,9 +13,9 @@ const TestInput = InputFactory({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isExisty" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
}

View File

@ -13,9 +13,9 @@ const TestInput = InputFactory({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isFloat" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
}

View File

@ -13,9 +13,9 @@ const TestInput = InputFactory({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isInt" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
}

View File

@ -13,9 +13,9 @@ const TestInput = InputFactory({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations={this.props.rule} value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
}

View File

@ -13,9 +13,9 @@ const TestInput = InputFactory({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isNumeric" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
}

View File

@ -13,9 +13,9 @@ const TestInput = InputFactory({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isUrl" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
}

View File

@ -13,9 +13,9 @@ const TestInput = InputFactory({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="isWords" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
}

View File

@ -13,9 +13,9 @@ const TestInput = InputFactory({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations="maxLength:3" value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
}

View File

@ -13,9 +13,9 @@ const TestInput = InputFactory({
class TestForm extends React.Component {
render() {
return (
<Formsy.Form>
<Formsy>
<TestInput name="foo" validations={this.props.rule} value={this.props.inputValue}/>
</Formsy.Form>
</Formsy>
);
}
}

View File

@ -1,7 +1,7 @@
import React from 'react';
import TestUtils from 'react-dom/test-utils';
import Formsy from './..';
import Formsy, { withFormsy } from './..';
import { InputFactory } from './utils/TestInput';
import immediate from './utils/immediate';
import sinon from 'sinon';
@ -17,17 +17,17 @@ class MyTest extends React.Component {
return <input type={this.props.type} value={this.props.getValue()} onChange={this.handleChange}/>;
}
}
const FormsyTest = Formsy.Wrapper(MyTest);
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' })}>
<Formsy onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar', bar: 'foo' })}>
<FormsyTest name="foo"/>
<FormsyTest name="bar"/>
</Formsy.Form>
</Formsy>
);
const input = TestUtils.scryRenderedDOMComponentsWithTag(form, 'INPUT')[0];
@ -49,9 +49,9 @@ 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' })}>
<Formsy onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<FormsyTest name="foo" validations="isEmail"/>
</Formsy.Form>
</Formsy>
);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
@ -75,9 +75,9 @@ export default {
const onInvalid = sinon.spy();
TestUtils.renderIntoDocument(
<Formsy.Form onValid={onValid} onInvalid={onInvalid}>
<Formsy onValid={onValid} onInvalid={onInvalid}>
<FormsyTest name="foo" value="bar" required/>
</Formsy.Form>
</Formsy>
);
test.equal(onValid.called, true);
@ -92,9 +92,9 @@ export default {
const onInvalid = sinon.spy();
TestUtils.renderIntoDocument(
<Formsy.Form onValid={onValid} onInvalid={onInvalid}>
<Formsy onValid={onValid} onInvalid={onInvalid}>
<FormsyTest name="foo" required />
</Formsy.Form>
</Formsy>
);
test.equal(onValid.called, false);
@ -112,9 +112,9 @@ export default {
}
});
const form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Formsy>
<CustomInput name="foo" value="foo" required/>
</Formsy.Form>
</Formsy>
);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
@ -123,14 +123,14 @@ export default {
},
'should provide invalidate callback on onValiSubmit': function (test) {
'should provide invalidate callback on onValidSubmit': function (test) {
class TestForm extends React.Component {
render() {
return (
<Formsy.Form onValidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<Formsy onValidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<FormsyTest name="foo" value="foo"/>
</Formsy.Form>
</Formsy>
);
}
}
@ -150,9 +150,9 @@ export default {
class TestForm extends React.Component {
render() {
return (
<Formsy.Form onInvalidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<Formsy onInvalidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<FormsyTest name="foo" value="foo" validations="isEmail"/>
</Formsy.Form>
</Formsy>
);
}
}
@ -172,11 +172,11 @@ export default {
class TestForm extends React.Component {
render() {
return (
<Formsy.Form
<Formsy
preventExternalInvalidation
onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<FormsyTest name="foo" value="foo"/>
</Formsy.Form>
</Formsy>
);
}
}
@ -195,9 +195,9 @@ export default {
class TestForm extends React.Component {
render() {
return (
<Formsy.Form onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<Formsy onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<FormsyTest name="foo" value="foo"/>
</Formsy.Form>
</Formsy>
);
}
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import Formsy from './../..';
import Formsy, { withFormsy } from './../..';
class TestInput extends React.Component {
static defaultProps = { type: 'text' };
@ -19,7 +19,7 @@ export function InputFactory(methods) {
TestInput.prototype[method] = methods[method];
}
}
return Formsy.Wrapper(TestInput);
return withFormsy(TestInput);
}
export default Formsy.Wrapper(TestInput);
export default withFormsy(TestInput);

View File

@ -1,5 +1,5 @@
import React from 'react';
import Formsy from './../..';
import Formsy, { withFormsy } from './../..';
class TestComponent extends React.Component {
methodOnWrappedInstance = (param) => {
@ -11,4 +11,4 @@ class TestComponent extends React.Component {
}
}
export default Formsy.Wrapper(TestComponent);
export default withFormsy(TestComponent);

View File

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

5063
yarn.lock Normal file

File diff suppressed because it is too large Load Diff