onChange, dynamic form elements and bug fix
This commit is contained in:
parent
0834810d67
commit
86ba085cc4
11
CHANGES.md
11
CHANGES.md
|
|
@ -1,3 +1,14 @@
|
|||
**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
|
||||
|
|
|
|||
32
README.md
32
README.md
|
|
@ -22,6 +22,7 @@ A form input builder and validator for React JS
|
|||
- [onError()](#onerror)
|
||||
- [onValid()](#onvalid)
|
||||
- [onInvalid()](#oninvalid)
|
||||
- [onChange()](#onchange)
|
||||
- [Formsy.Mixin](#formsymixin)
|
||||
- [name](#name)
|
||||
- [value](#value)
|
||||
|
|
@ -49,7 +50,7 @@ The main concept is that forms, inputs and validation is done very differently a
|
|||
|
||||
## <a name="whatyoucando">What you can do</a>
|
||||
|
||||
1. Build any kind of form input components. Not just traditional inputs, but anything you want and get that validation for free
|
||||
1. Build any kind of form element components. Not just traditional inputs, but anything you want and get that validation for free
|
||||
|
||||
2. Add validation rules and use them with simple syntax
|
||||
|
||||
|
|
@ -57,6 +58,8 @@ The main concept is that forms, inputs and validation is done very differently a
|
|||
|
||||
4. Server validation errors automatically binds to the correct form input component
|
||||
|
||||
5. You can dynamically add form elements to your form and they will register/unregister to the form
|
||||
|
||||
## <a name="install">Install</a>
|
||||
|
||||
1. Download from this REPO and use globally (Formsy) or with requirejs
|
||||
|
|
@ -65,6 +68,12 @@ The main concept is that forms, inputs and validation is done very differently a
|
|||
|
||||
## <a name="changes">Changes</a>
|
||||
|
||||
**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)
|
||||
|
|
@ -83,17 +92,6 @@ The main concept is that forms, inputs and validation is done very differently a
|
|||
- 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.
|
||||
|
||||
[Older changes](CHANGES.md)
|
||||
|
||||
## <a name="howtouse">How to use</a>
|
||||
|
|
@ -130,7 +128,7 @@ The main concept is that forms, inputs and validation is done very differently a
|
|||
|
||||
This code results in a form with a submit button that will POST to /users when clicked. The submit button is disabled as long as the input is empty (required) or the value is not an email (isEmail). On validation error it will show the message: "This is not a valid email".
|
||||
|
||||
#### Building a form element
|
||||
#### Building a form element (required)
|
||||
```javascript
|
||||
/** @jsx React.DOM */
|
||||
var Formsy = require('formsy-react');
|
||||
|
|
@ -166,7 +164,7 @@ This code results in a form with a submit button that will POST to /users when c
|
|||
}
|
||||
});
|
||||
```
|
||||
So this is basically how you build your form elements. 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.
|
||||
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.
|
||||
|
||||
## <a name="API">API</a>
|
||||
|
||||
|
|
@ -274,6 +272,12 @@ Whenever the form becomes valid the "onValid" handler is called. Use it to chang
|
|||
```
|
||||
Whenever the form becomes invalid the "onInvalid" handler is called. Use it to for example revert "onValid" state.
|
||||
|
||||
#### <a name="onchange">onChange(currentValues)</a>
|
||||
```html
|
||||
<Formsy.Form url="/users" onChange={this.saveCurrentValuesToLocalStorage}></Formsy.Form>
|
||||
```
|
||||
"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.
|
||||
|
||||
### <a name="formsymixin">Formsy.Mixin</a>
|
||||
|
||||
#### <a name="name">name</a>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "formsy-react",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"main": "src/main.js",
|
||||
"dependencies": {
|
||||
"react": "^0.11.2"
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
424
build/specs.js
424
build/specs.js
File diff suppressed because one or more lines are too long
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "formsy-react",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"description": "A form input builder and validator for React JS",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,11 @@ var validationRules = {
|
|||
return value === true;
|
||||
},
|
||||
'isNumeric': function (value) {
|
||||
return value.match(/^-?[0-9]+$/)
|
||||
if (typeof value === 'number') {
|
||||
return true;
|
||||
} else {
|
||||
return value.match(/^-?[0-9]+$/);
|
||||
}
|
||||
},
|
||||
'isAlpha': function (value) {
|
||||
return value.match(/^[a-zA-Z]+$/);
|
||||
|
|
@ -32,6 +36,9 @@ var validationRules = {
|
|||
},
|
||||
equals: function (value, eql) {
|
||||
return value == eql;
|
||||
},
|
||||
equalsField: function (value, field) {
|
||||
return value === this[field];
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -86,6 +93,21 @@ var request = function (method, url, data, contentType, headers) {
|
|||
});
|
||||
|
||||
};
|
||||
|
||||
var arraysDiffer = function (arrayA, arrayB) {
|
||||
var isDifferent = false;
|
||||
if (arrayA.length !== arrayB.length) {
|
||||
isDifferent = true;
|
||||
} else {
|
||||
arrayA.forEach(function (item, index) {
|
||||
if (item !== arrayB[index]) {
|
||||
isDifferent = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
return isDifferent;
|
||||
};
|
||||
|
||||
var ajax = {
|
||||
post: request.bind(null, 'POST'),
|
||||
put: request.bind(null, 'PUT')
|
||||
|
|
@ -106,22 +128,31 @@ Formsy.Mixin = {
|
|||
},
|
||||
componentWillMount: function () {
|
||||
|
||||
var configure = function () {
|
||||
// Add validations to the store itself as the props object can not be modified
|
||||
this._validations = this.props.validations || '';
|
||||
|
||||
if (this.props.required) {
|
||||
this._validations = this.props.validations ? this.props.validations + ',' : '';
|
||||
this._validations += 'isValue';
|
||||
}
|
||||
this.props._attachToForm(this);
|
||||
}.bind(this);
|
||||
|
||||
if (!this.props.name) {
|
||||
throw new Error('Form Input requires a name property when used');
|
||||
}
|
||||
|
||||
if (!this.props._attachToForm) {
|
||||
throw new Error('Form Mixin requires component to be nested in a Form');
|
||||
return setTimeout(function () {
|
||||
if (!this.props._attachToForm) {
|
||||
throw new Error('Form Mixin requires component to be nested in a Form');
|
||||
}
|
||||
configure();
|
||||
}.bind(this), 0);
|
||||
}
|
||||
configure();
|
||||
|
||||
// Add validations to the store itself as the props object can not be modified
|
||||
this._validations = this.props.validations || '';
|
||||
|
||||
if (this.props.required) {
|
||||
this._validations = this.props.validations ? this.props.validations + ',' : '';
|
||||
this._validations += 'isValue';
|
||||
}
|
||||
this.props._attachToForm(this);
|
||||
},
|
||||
|
||||
// We have to make the validate method is kept when new props are added
|
||||
|
|
@ -187,7 +218,8 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
getInitialState: function () {
|
||||
return {
|
||||
isValid: true,
|
||||
isSubmitting: false
|
||||
isSubmitting: false,
|
||||
canChange: false
|
||||
};
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
|
|
@ -198,7 +230,8 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
onSubmit: function () {},
|
||||
onSubmitted: function () {},
|
||||
onValid: function () {},
|
||||
onInvalid: function () {}
|
||||
onInvalid: function () {},
|
||||
onChange: function () {}
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -214,6 +247,21 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
this.validateForm();
|
||||
},
|
||||
|
||||
componentWillUpdate: function () {
|
||||
var inputKeys = Object.keys(this.inputs);
|
||||
|
||||
// The updated children array is not available here for some reason,
|
||||
// we need to wait for next event loop
|
||||
setTimeout(function () {
|
||||
this.registerInputs(this.props.children);
|
||||
|
||||
var newInputKeys = Object.keys(this.inputs);
|
||||
if (arraysDiffer(inputKeys, newInputKeys)) {
|
||||
this.validateForm();
|
||||
}
|
||||
}.bind(this), 0);
|
||||
},
|
||||
|
||||
// Update model, submit to url prop and send the model
|
||||
submit: function (event) {
|
||||
event.preventDefault();
|
||||
|
|
@ -326,7 +374,7 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
}.bind(this), {});
|
||||
},
|
||||
|
||||
setFormPristine: function(isPristine) {
|
||||
setFormPristine: function (isPristine) {
|
||||
var inputs = this.inputs;
|
||||
var inputKeys = Object.keys(inputs);
|
||||
|
||||
|
|
@ -345,6 +393,9 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
// state of the form itself
|
||||
validate: function (component) {
|
||||
|
||||
// Trigger onChange
|
||||
this.state.canChange && this.props.onChange && this.props.onChange(this.getCurrentValues());
|
||||
|
||||
if (!component.props.required && !component._validations) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -408,6 +459,9 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
allIsValid && this.props.onValid();
|
||||
!allIsValid && 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
|
||||
|
|
@ -421,6 +475,11 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
}, index === inputKeys.length - 1 ? onValidationComplete : null);
|
||||
}.bind(this));
|
||||
|
||||
// If there are no inputs, it is ready to trigger change events
|
||||
if (!inputKeys.length) {
|
||||
this.setState({canChange: true});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// Method put on each input component to register
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,23 +1,286 @@
|
|||
var Formsy = require('./../src/main.js');
|
||||
|
||||
describe('Formsy', function() {
|
||||
describe('Formsy', function () {
|
||||
|
||||
describe('Setting up a form', function () {
|
||||
|
||||
it('should render a form into the document', function() {
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form></Formsy.Form>
|
||||
);
|
||||
|
||||
it('should render a form into the document', function () {
|
||||
var form = TestUtils.renderIntoDocument( <Formsy.Form></Formsy.Form>);
|
||||
expect(form.getDOMNode().tagName).toEqual('FORM');
|
||||
});
|
||||
|
||||
it('should set a class name if passed', function () {
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form className="foo"></Formsy.Form>
|
||||
);
|
||||
var form = TestUtils.renderIntoDocument( <Formsy.Form className="foo"></Formsy.Form>);
|
||||
expect(form.getDOMNode().className).toEqual('foo');
|
||||
});
|
||||
|
||||
it('should allow for inputs being added dynamically', function (done) {
|
||||
|
||||
var inputs = [];
|
||||
var forceUpdate = null;
|
||||
var model = null;
|
||||
var TestInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
render: function () {
|
||||
return <div/>
|
||||
}
|
||||
});
|
||||
var TestForm = React.createClass({
|
||||
componentWillMount: function () {
|
||||
forceUpdate = this.forceUpdate.bind(this);
|
||||
},
|
||||
onSubmit: function (formModel) {
|
||||
model = formModel;
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form onSubmit={this.onSubmit}>
|
||||
{inputs}
|
||||
</Formsy.Form>);
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<TestForm/>
|
||||
);
|
||||
|
||||
// Wait before adding the input
|
||||
setTimeout(function () {
|
||||
|
||||
inputs.push(TestInput({
|
||||
name: 'test'
|
||||
}));
|
||||
|
||||
forceUpdate(function () {
|
||||
|
||||
// Wait for next event loop, as that does the form
|
||||
setTimeout(function () {
|
||||
TestUtils.Simulate.submit(form.getDOMNode());
|
||||
expect(model.test).toBeDefined();
|
||||
done();
|
||||
}, 0);
|
||||
|
||||
});
|
||||
|
||||
}, 10);
|
||||
|
||||
});
|
||||
|
||||
it('should allow dynamically added inputs to update the form-model', function (done) {
|
||||
|
||||
var inputs = [];
|
||||
var forceUpdate = null;
|
||||
var model = null;
|
||||
var TestInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.target.value);
|
||||
},
|
||||
render: function () {
|
||||
return <input value={this.getValue()} onChange={this.changeValue}/>
|
||||
}
|
||||
});
|
||||
var TestForm = React.createClass({
|
||||
componentWillMount: function () {
|
||||
forceUpdate = this.forceUpdate.bind(this);
|
||||
},
|
||||
onSubmit: function (formModel) {
|
||||
model = formModel;
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form onSubmit={this.onSubmit}>
|
||||
{inputs}
|
||||
</Formsy.Form>);
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<TestForm/>
|
||||
);
|
||||
|
||||
// Wait before adding the input
|
||||
setTimeout(function () {
|
||||
|
||||
inputs.push(TestInput({
|
||||
name: 'test'
|
||||
}));
|
||||
|
||||
forceUpdate(function () {
|
||||
|
||||
// Wait for next event loop, as that does the form
|
||||
setTimeout(function () {
|
||||
TestUtils.Simulate.change(TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'), {target: {value: 'foo'}});
|
||||
TestUtils.Simulate.submit(form.getDOMNode());
|
||||
expect(model.test).toBe('foo');
|
||||
done();
|
||||
}, 0);
|
||||
|
||||
});
|
||||
|
||||
}, 10);
|
||||
|
||||
});
|
||||
|
||||
it('should invalidate a valid form if dynamically inserted input is invalid', function (done) {
|
||||
|
||||
var forceUpdate = null;
|
||||
var isInvalid = false;
|
||||
var TestInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.target.value);
|
||||
},
|
||||
render: function () {
|
||||
return <input value={this.getValue()} onChange={this.changeValue}/>
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var inputs = [TestInput({
|
||||
name: 'test',
|
||||
validations: 'isEmail',
|
||||
value: 'foo@bar.com'
|
||||
})];
|
||||
|
||||
var TestForm = React.createClass({
|
||||
componentWillMount: function () {
|
||||
forceUpdate = this.forceUpdate.bind(this);
|
||||
},
|
||||
setInvalid: function () {
|
||||
isInvalid = true;
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form onInvalid={this.setInvalid}>
|
||||
{inputs}
|
||||
</Formsy.Form>);
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<TestForm/>
|
||||
);
|
||||
|
||||
expect(isInvalid).toBe(false);
|
||||
|
||||
// Wait before adding the input
|
||||
setTimeout(function () {
|
||||
|
||||
|
||||
inputs.push(TestInput({
|
||||
name: 'test2',
|
||||
validations: 'isEmail',
|
||||
value: 'foo@bar'
|
||||
}));
|
||||
|
||||
|
||||
forceUpdate(function () {
|
||||
|
||||
// Wait for next event loop, as that does the form
|
||||
setTimeout(function () {
|
||||
TestUtils.Simulate.submit(form.getDOMNode());
|
||||
expect(isInvalid).toBe(true);
|
||||
done();
|
||||
}, 0);
|
||||
|
||||
});
|
||||
|
||||
}, 10);
|
||||
|
||||
});
|
||||
|
||||
it('should not trigger onChange when form is mounted', function () {
|
||||
var hasChanged = jasmine.createSpy('onChange');
|
||||
var TestForm = React.createClass({
|
||||
onChange: function () {
|
||||
hasChanged();
|
||||
},
|
||||
render: function () {
|
||||
return <Formsy.Form onChange={this.onChange}></Formsy.Form>;
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
expect(hasChanged).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should trigger onChange when form element is changed', function () {
|
||||
var hasChanged = jasmine.createSpy('onChange');
|
||||
var MyInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
onChange: function (event) {
|
||||
this.setValue(event.target.value);
|
||||
},
|
||||
render: function () {
|
||||
return <input value={this.getValue()} onChange={this.onChange}/>
|
||||
}
|
||||
});
|
||||
var TestForm = React.createClass({
|
||||
onChange: function () {
|
||||
hasChanged();
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form onChange={this.onChange}>
|
||||
<MyInput name="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
TestUtils.Simulate.change(TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'), {target: {value: 'bar'}});
|
||||
expect(hasChanged).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should trigger onChange when new input is added to form', function (done) {
|
||||
var hasChanged = jasmine.createSpy('onChange');
|
||||
var inputs = [];
|
||||
var forceUpdate = null;
|
||||
var TestInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.target.value);
|
||||
},
|
||||
render: function () {
|
||||
return <input value={this.getValue()} onChange={this.changeValue}/>
|
||||
}
|
||||
});
|
||||
var TestForm = React.createClass({
|
||||
componentWillMount: function () {
|
||||
forceUpdate = this.forceUpdate.bind(this);
|
||||
},
|
||||
onChange: function () {
|
||||
hasChanged();
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form onChange={this.onChange}>
|
||||
{inputs}
|
||||
</Formsy.Form>);
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<TestForm/>
|
||||
);
|
||||
|
||||
// Wait before adding the input
|
||||
setTimeout(function () {
|
||||
|
||||
inputs.push(TestInput({
|
||||
name: 'test'
|
||||
}));
|
||||
|
||||
forceUpdate(function () {
|
||||
|
||||
// Wait for next event loop, as that does the form
|
||||
setTimeout(function () {
|
||||
expect(hasChanged).toHaveBeenCalled();
|
||||
done();
|
||||
}, 0);
|
||||
|
||||
});
|
||||
|
||||
}, 10);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -78,4 +78,60 @@ describe('Validation', function() {
|
|||
|
||||
});
|
||||
|
||||
it('RULE: isNumeric', function () {
|
||||
|
||||
var isValid = jasmine.createSpy('valid');
|
||||
var TestInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
updateValue: function (event) {
|
||||
this.setValue(event.target.value);
|
||||
},
|
||||
render: function () {
|
||||
if (this.isValid()) {
|
||||
isValid();
|
||||
}
|
||||
return <input value={this.getValue()} onChange={this.updateValue}/>
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" value="foo" validations="isNumeric"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
var input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
expect(isValid).not.toHaveBeenCalled();
|
||||
TestUtils.Simulate.change(input, {target: {value: '123'}});
|
||||
expect(isValid).toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
it('RULE: isNumeric (actual number)', function () {
|
||||
|
||||
var isValid = jasmine.createSpy('valid');
|
||||
var TestInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
updateValue: function (event) {
|
||||
this.setValue(Number(event.target.value));
|
||||
},
|
||||
render: function () {
|
||||
if (this.isValid()) {
|
||||
isValid();
|
||||
}
|
||||
return <input value={this.getValue()} onChange={this.updateValue}/>
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" value="foo" validations="isNumeric"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
var input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
expect(isValid).not.toHaveBeenCalled();
|
||||
TestUtils.Simulate.change(input, {target: {value: '123'}});
|
||||
expect(isValid).toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
82
src/main.js
82
src/main.js
|
|
@ -11,7 +11,11 @@ var validationRules = {
|
|||
return value === true;
|
||||
},
|
||||
'isNumeric': function (value) {
|
||||
return value.match(/^-?[0-9]+$/)
|
||||
if (typeof value === 'number') {
|
||||
return true;
|
||||
} else {
|
||||
return value.match(/^-?[0-9]+$/);
|
||||
}
|
||||
},
|
||||
'isAlpha': function (value) {
|
||||
return value.match(/^[a-zA-Z]+$/);
|
||||
|
|
@ -87,6 +91,21 @@ var request = function (method, url, data, contentType, headers) {
|
|||
});
|
||||
|
||||
};
|
||||
|
||||
var arraysDiffer = function (arrayA, arrayB) {
|
||||
var isDifferent = false;
|
||||
if (arrayA.length !== arrayB.length) {
|
||||
isDifferent = true;
|
||||
} else {
|
||||
arrayA.forEach(function (item, index) {
|
||||
if (item !== arrayB[index]) {
|
||||
isDifferent = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
return isDifferent;
|
||||
};
|
||||
|
||||
var ajax = {
|
||||
post: request.bind(null, 'POST'),
|
||||
put: request.bind(null, 'PUT')
|
||||
|
|
@ -107,22 +126,31 @@ Formsy.Mixin = {
|
|||
},
|
||||
componentWillMount: function () {
|
||||
|
||||
var configure = function () {
|
||||
// Add validations to the store itself as the props object can not be modified
|
||||
this._validations = this.props.validations || '';
|
||||
|
||||
if (this.props.required) {
|
||||
this._validations = this.props.validations ? this.props.validations + ',' : '';
|
||||
this._validations += 'isValue';
|
||||
}
|
||||
this.props._attachToForm(this);
|
||||
}.bind(this);
|
||||
|
||||
if (!this.props.name) {
|
||||
throw new Error('Form Input requires a name property when used');
|
||||
}
|
||||
|
||||
if (!this.props._attachToForm) {
|
||||
throw new Error('Form Mixin requires component to be nested in a Form');
|
||||
return setTimeout(function () {
|
||||
if (!this.props._attachToForm) {
|
||||
throw new Error('Form Mixin requires component to be nested in a Form');
|
||||
}
|
||||
configure();
|
||||
}.bind(this), 0);
|
||||
}
|
||||
configure();
|
||||
|
||||
// Add validations to the store itself as the props object can not be modified
|
||||
this._validations = this.props.validations || '';
|
||||
|
||||
if (this.props.required) {
|
||||
this._validations = this.props.validations ? this.props.validations + ',' : '';
|
||||
this._validations += 'isValue';
|
||||
}
|
||||
this.props._attachToForm(this);
|
||||
},
|
||||
|
||||
// We have to make the validate method is kept when new props are added
|
||||
|
|
@ -188,7 +216,8 @@ Formsy.Form = React.createClass({
|
|||
getInitialState: function () {
|
||||
return {
|
||||
isValid: true,
|
||||
isSubmitting: false
|
||||
isSubmitting: false,
|
||||
canChange: false
|
||||
};
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
|
|
@ -199,7 +228,8 @@ Formsy.Form = React.createClass({
|
|||
onSubmit: function () {},
|
||||
onSubmitted: function () {},
|
||||
onValid: function () {},
|
||||
onInvalid: function () {}
|
||||
onInvalid: function () {},
|
||||
onChange: function () {}
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -215,6 +245,21 @@ Formsy.Form = React.createClass({
|
|||
this.validateForm();
|
||||
},
|
||||
|
||||
componentWillUpdate: function () {
|
||||
var inputKeys = Object.keys(this.inputs);
|
||||
|
||||
// The updated children array is not available here for some reason,
|
||||
// we need to wait for next event loop
|
||||
setTimeout(function () {
|
||||
this.registerInputs(this.props.children);
|
||||
|
||||
var newInputKeys = Object.keys(this.inputs);
|
||||
if (arraysDiffer(inputKeys, newInputKeys)) {
|
||||
this.validateForm();
|
||||
}
|
||||
}.bind(this), 0);
|
||||
},
|
||||
|
||||
// Update model, submit to url prop and send the model
|
||||
submit: function (event) {
|
||||
event.preventDefault();
|
||||
|
|
@ -327,7 +372,7 @@ Formsy.Form = React.createClass({
|
|||
}.bind(this), {});
|
||||
},
|
||||
|
||||
setFormPristine: function(isPristine) {
|
||||
setFormPristine: function (isPristine) {
|
||||
var inputs = this.inputs;
|
||||
var inputKeys = Object.keys(inputs);
|
||||
|
||||
|
|
@ -346,6 +391,9 @@ Formsy.Form = React.createClass({
|
|||
// state of the form itself
|
||||
validate: function (component) {
|
||||
|
||||
// Trigger onChange
|
||||
this.state.canChange && this.props.onChange && this.props.onChange(this.getCurrentValues());
|
||||
|
||||
if (!component.props.required && !component._validations) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -409,6 +457,9 @@ Formsy.Form = React.createClass({
|
|||
allIsValid && this.props.onValid();
|
||||
!allIsValid && 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
|
||||
|
|
@ -422,6 +473,11 @@ Formsy.Form = React.createClass({
|
|||
}, index === inputKeys.length - 1 ? onValidationComplete : null);
|
||||
}.bind(this));
|
||||
|
||||
// If there are no inputs, it is ready to trigger change events
|
||||
if (!inputKeys.length) {
|
||||
this.setState({canChange: true});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// Method put on each input component to register
|
||||
|
|
|
|||
Loading…
Reference in New Issue