Fixed dynamic validations, disable form, dynamic value update etc.
This commit is contained in:
parent
d9bf45d417
commit
86aec656df
18
CHANGES.md
18
CHANGES.md
|
|
@ -1,3 +1,21 @@
|
|||
**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
|
||||
|
||||
|
|
|
|||
47
README.md
47
README.md
|
|
@ -39,6 +39,7 @@ A form input builder and validator for React JS
|
|||
- [showRequired()](#showrequired)
|
||||
- [showError()](#showerror)
|
||||
- [isPristine()](#ispristine)
|
||||
- [isFormDisabled()](#isformdisabled)
|
||||
- [Formsy.addValidationRule](#formsyaddvalidationrule)
|
||||
- [Validators](#validators)
|
||||
|
||||
|
|
@ -68,6 +69,14 @@ The main concept is that forms, inputs and validation is done very differently a
|
|||
|
||||
## <a name="changes">Changes</a>
|
||||
|
||||
**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)
|
||||
|
|
@ -82,24 +91,6 @@ The main concept is that forms, inputs and validation is done very differently a
|
|||
- 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
|
||||
|
||||
[Older changes](CHANGES.md)
|
||||
|
||||
## <a name="howtouse">How to use</a>
|
||||
|
|
@ -513,6 +504,22 @@ By default all formsy input elements are pristine, which means they are not "tou
|
|||
|
||||
**note!** When the form is reset, using the resetForm callback function on **onSubmit** the inputs are not reset to pristine.
|
||||
|
||||
#### <a name="ispristine">isFormDisabled()</a>
|
||||
```javascript
|
||||
var MyInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
render: function () {
|
||||
return (
|
||||
<div>
|
||||
<input type="text" value={this.getValue()} disabled={this.isFormDisabled()}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
React.render(<Formy.Form disabled={true}/>);
|
||||
```
|
||||
You can now disable the form itself with a prop and use **isFormDisabled()** inside form elements to verify this prop.
|
||||
|
||||
### <a name="formsyaddvalidationrule">Formsy.addValidationRule(name, ruleFunc)</a>
|
||||
An example:
|
||||
|
|
@ -611,8 +618,8 @@ Return true if the value from input component matches value passed (==).
|
|||
|
||||
## Run tests
|
||||
- Run `gulp`
|
||||
- Run a server in `build` folder
|
||||
- Go to `localhost/testrunner.html` (live reload)
|
||||
- Run a server in `build` folder, e.g. on port 3000
|
||||
- Go to `localhost:3000/testrunner.html` (live reload)
|
||||
|
||||
License
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "formsy-react",
|
||||
"version": "0.7.1",
|
||||
"version": "0.8.0",
|
||||
"main": "src/main.js",
|
||||
"dependencies": {
|
||||
"react": "^0.11.2"
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
549
build/specs.js
549
build/specs.js
File diff suppressed because one or more lines are too long
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "formsy-react",
|
||||
"version": "0.7.1",
|
||||
"version": "0.8.0",
|
||||
"description": "A form input builder and validator for React JS",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -2,226 +2,17 @@
|
|||
(function (global){
|
||||
var React = global.React || require('react');
|
||||
var Formsy = {};
|
||||
var validationRules = {
|
||||
'isValue': function (value) {
|
||||
return value !== '';
|
||||
},
|
||||
'isEmail': function (value) {
|
||||
return value.match(/^((([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);
|
||||
},
|
||||
'isTrue': function (value) {
|
||||
return value === true;
|
||||
},
|
||||
'isNumeric': function (value) {
|
||||
if (typeof value === 'number') {
|
||||
return true;
|
||||
} else {
|
||||
return value.match(/^-?[0-9]+$/);
|
||||
}
|
||||
},
|
||||
'isAlpha': function (value) {
|
||||
return value.match(/^[a-zA-Z]+$/);
|
||||
},
|
||||
'isWords': function (value) {
|
||||
return value.match(/^[a-zA-Z\s]+$/);
|
||||
},
|
||||
'isSpecialWords': function (value) {
|
||||
return value.match(/^[a-zA-Z\s\u00C0-\u017F]+$/);
|
||||
},
|
||||
isLength: function (value, min, max) {
|
||||
if (max !== undefined) {
|
||||
return value.length >= min && value.length <= max;
|
||||
}
|
||||
return value.length >= min;
|
||||
},
|
||||
equals: function (value, eql) {
|
||||
return value == eql;
|
||||
},
|
||||
equalsField: function (value, field) {
|
||||
return value === this[field];
|
||||
}
|
||||
};
|
||||
|
||||
var toURLEncoded = function (element, key, list) {
|
||||
var list = list || [];
|
||||
if (typeof (element) == 'object') {
|
||||
for (var idx in element)
|
||||
toURLEncoded(element[idx], key ? key + '[' + idx + ']' : idx, list);
|
||||
} else {
|
||||
list.push(key + '=' + encodeURIComponent(element));
|
||||
}
|
||||
return list.join('&');
|
||||
};
|
||||
|
||||
var request = function (method, url, data, contentType, headers) {
|
||||
|
||||
var contentType = contentType === 'urlencoded' ? 'application/' + contentType.replace('urlencoded', 'x-www-form-urlencoded') : 'application/json';
|
||||
data = contentType === 'application/json' ? JSON.stringify(data) : toURLEncoded(data);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url, true);
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.setRequestHeader('Content-Type', contentType);
|
||||
|
||||
// Add passed headers
|
||||
Object.keys(headers).forEach(function (header) {
|
||||
xhr.setRequestHeader(header, headers[header]);
|
||||
});
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
|
||||
try {
|
||||
var response = xhr.responseText ? JSON.parse(xhr.responseText) : null;
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(response);
|
||||
} else {
|
||||
reject(response);
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
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')
|
||||
};
|
||||
var validationRules = require('./validationRules.js');
|
||||
var utils = require('./utils.js');
|
||||
var Mixin = require('./Mixin.js');
|
||||
var options = {};
|
||||
|
||||
Formsy.Mixin = Mixin;
|
||||
|
||||
Formsy.defaults = function (passedOptions) {
|
||||
options = passedOptions;
|
||||
};
|
||||
|
||||
Formsy.Mixin = {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
_value: this.props.value ? this.props.value : '',
|
||||
_isValid: true,
|
||||
_isPristine: true
|
||||
};
|
||||
},
|
||||
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) {
|
||||
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();
|
||||
|
||||
},
|
||||
|
||||
// We have to make the validate method is kept when new props are added
|
||||
componentWillReceiveProps: function (nextProps) {
|
||||
nextProps._attachToForm = this.props._attachToForm;
|
||||
nextProps._detachFromForm = this.props._detachFromForm;
|
||||
nextProps._validate = this.props._validate;
|
||||
},
|
||||
|
||||
componentDidUpdate: function(prevProps, prevState) {
|
||||
|
||||
// If the input is untouched and something outside changes the value
|
||||
// update the FORM model by re-attaching to the form
|
||||
if (this.state._isPristine) {
|
||||
if (this.props.value !== prevProps.value && this.state._value === prevProps.value) {
|
||||
this.state._value = this.props.value || '';
|
||||
this.props._attachToForm(this);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Detach it when component unmounts
|
||||
componentWillUnmount: function () {
|
||||
this.props._detachFromForm(this);
|
||||
},
|
||||
|
||||
// We validate after the value has been set
|
||||
setValue: function (value) {
|
||||
this.setState({
|
||||
_value: value,
|
||||
_isPristine: false
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
}.bind(this));
|
||||
},
|
||||
resetValue: function () {
|
||||
this.setState({
|
||||
_value: '',
|
||||
_isPristine: true
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
});
|
||||
},
|
||||
getValue: function () {
|
||||
return this.state._value;
|
||||
},
|
||||
hasValue: function () {
|
||||
return this.state._value !== '';
|
||||
},
|
||||
getErrorMessage: function () {
|
||||
return this.isValid() || this.showRequired() ? null : this.state._serverError || this.props.validationError;
|
||||
},
|
||||
isValid: function () {
|
||||
return this.state._isValid;
|
||||
},
|
||||
isPristine: function () {
|
||||
return this.state._isPristine;
|
||||
},
|
||||
isRequired: function () {
|
||||
return !!this.props.required;
|
||||
},
|
||||
showRequired: function () {
|
||||
return this.isRequired() && this.state._value === '';
|
||||
},
|
||||
showError: function () {
|
||||
return !this.showRequired() && !this.state._isValid;
|
||||
}
|
||||
};
|
||||
|
||||
Formsy.addValidationRule = function (name, func) {
|
||||
validationRules[name] = func;
|
||||
};
|
||||
|
|
@ -265,12 +56,20 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
// 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();
|
||||
// The component might have been unmounted on an
|
||||
// update
|
||||
if (this.isMounted()) {
|
||||
|
||||
this.registerInputs(this.props.children);
|
||||
|
||||
var newInputKeys = Object.keys(this.inputs);
|
||||
if (utils.arraysDiffer(inputKeys, newInputKeys)) {
|
||||
this.validateForm();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}.bind(this), 0);
|
||||
},
|
||||
|
||||
|
|
@ -301,8 +100,8 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
|
||||
var headers = (Object.keys(this.props.headers).length && this.props.headers) || options.headers || {};
|
||||
|
||||
var method = this.props.method && ajax[this.props.method.toLowerCase()] ? this.props.method.toLowerCase() : 'post';
|
||||
ajax[method](this.props.url, this.mapModel(), this.props.contentType || options.contentType || 'json', headers)
|
||||
var method = this.props.method && utils.ajax[this.props.method.toLowerCase()] ? this.props.method.toLowerCase() : 'post';
|
||||
utils.ajax[method](this.props.url, this.mapModel(), this.props.contentType || options.contentType || 'json', headers)
|
||||
.then(function (response) {
|
||||
this.props.onSuccess(response);
|
||||
this.props.onSubmitted();
|
||||
|
|
@ -369,6 +168,7 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
child.props._attachToForm = this.attachToForm;
|
||||
child.props._detachFromForm = this.detachFromForm;
|
||||
child.props._validate = this.validate;
|
||||
child.props._isFormDisabled = this.isFormDisabled;
|
||||
}
|
||||
|
||||
if (child && child.props && child.props.children) {
|
||||
|
|
@ -378,6 +178,10 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
}.bind(this));
|
||||
},
|
||||
|
||||
isFormDisabled: function () {
|
||||
return this.props.disabled;
|
||||
},
|
||||
|
||||
getCurrentValues: function () {
|
||||
return Object.keys(this.inputs).reduce(function (data, name) {
|
||||
var component = this.inputs[name];
|
||||
|
|
@ -406,7 +210,9 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
validate: function (component) {
|
||||
|
||||
// Trigger onChange
|
||||
this.state.canChange && this.props.onChange && this.props.onChange(this.getCurrentValues());
|
||||
if (this.state.canChange) {
|
||||
this.props.onChange(this.getCurrentValues());
|
||||
}
|
||||
|
||||
if (!component.props.required && !component._validations) {
|
||||
return;
|
||||
|
|
@ -468,11 +274,16 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
isValid: allIsValid
|
||||
});
|
||||
|
||||
allIsValid && this.props.onValid();
|
||||
!allIsValid && this.props.onInvalid();
|
||||
if (allIsValid) {
|
||||
this.props.onValid();
|
||||
} else {
|
||||
this.props.onInvalid();
|
||||
}
|
||||
|
||||
// Tell the form that it can start to trigger change events
|
||||
this.setState({canChange: true});
|
||||
this.setState({
|
||||
canChange: true
|
||||
});
|
||||
|
||||
}.bind(this);
|
||||
|
||||
|
|
@ -487,11 +298,13 @@ 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});
|
||||
// If there are no inputs, set state where form is ready to trigger
|
||||
// change event. New inputs might be added later
|
||||
if (!inputKeys.length && this.isMounted()) {
|
||||
this.setState({
|
||||
canChange: true
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// Method put on each input component to register
|
||||
|
|
@ -527,4 +340,257 @@ if (!global.exports && !global.module && (!global.define || !global.define.amd))
|
|||
module.exports = Formsy;
|
||||
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{"react":"react"}]},{},[1]);
|
||||
},{"./Mixin.js":2,"./utils.js":3,"./validationRules.js":4,"react":"react"}],2:[function(require,module,exports){
|
||||
module.exports = {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
_value: this.props.value ? this.props.value : '',
|
||||
_isValid: true,
|
||||
_isPristine: true
|
||||
};
|
||||
},
|
||||
componentWillMount: function () {
|
||||
|
||||
var configure = function () {
|
||||
this.setValidations(this.props.validations, this.props.required);
|
||||
this.props._attachToForm(this);
|
||||
}.bind(this);
|
||||
|
||||
if (!this.props.name) {
|
||||
throw new Error('Form Input requires a name property when used');
|
||||
}
|
||||
|
||||
if (!this.props._attachToForm) {
|
||||
return setTimeout(function () {
|
||||
if (!this.isMounted()) return;
|
||||
if (!this.props._attachToForm) {
|
||||
throw new Error('Form Mixin requires component to be nested in a Form');
|
||||
}
|
||||
configure();
|
||||
}.bind(this), 0);
|
||||
}
|
||||
configure();
|
||||
|
||||
},
|
||||
|
||||
// We have to make the validate method is kept when new props are added
|
||||
componentWillReceiveProps: function (nextProps) {
|
||||
nextProps._attachToForm = this.props._attachToForm;
|
||||
nextProps._detachFromForm = this.props._detachFromForm;
|
||||
nextProps._validate = this.props._validate;
|
||||
this.setValidations(nextProps.validations, nextProps.required);
|
||||
},
|
||||
|
||||
componentDidUpdate: function (prevProps, prevState) {
|
||||
|
||||
var isValueChanged = function () {
|
||||
|
||||
return (
|
||||
this.props.value !== prevProps.value && (
|
||||
this.state._value === prevProps.value ||
|
||||
|
||||
// Since undefined is converted to empty string we have to
|
||||
// check that specifically
|
||||
(this.state._value === '' && prevProps.value === undefined)
|
||||
)
|
||||
);
|
||||
|
||||
}.bind(this);
|
||||
|
||||
|
||||
// If validations has changed or something outside changes
|
||||
// the value, set the value again running a validation
|
||||
|
||||
if (prevProps.validations !== this.props.validations || isValueChanged()) {
|
||||
this.setValue(this.props.value || '');
|
||||
}
|
||||
},
|
||||
|
||||
// Detach it when component unmounts
|
||||
componentWillUnmount: function () {
|
||||
this.props._detachFromForm(this);
|
||||
},
|
||||
|
||||
setValidations: function (validations, required) {
|
||||
|
||||
// Add validations to the store itself as the props object can not be modified
|
||||
this._validations = validations || '';
|
||||
|
||||
if (required) {
|
||||
this._validations = validations ? validations + ',' : '';
|
||||
this._validations += 'isValue';
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// We validate after the value has been set
|
||||
setValue: function (value) {
|
||||
this.setState({
|
||||
_value: value,
|
||||
_isPristine: false
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
}.bind(this));
|
||||
},
|
||||
resetValue: function () {
|
||||
this.setState({
|
||||
_value: '',
|
||||
_isPristine: true
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
});
|
||||
},
|
||||
getValue: function () {
|
||||
return this.state._value;
|
||||
},
|
||||
hasValue: function () {
|
||||
return this.state._value !== '';
|
||||
},
|
||||
getErrorMessage: function () {
|
||||
return this.isValid() || this.showRequired() ? null : this.state._serverError || this.props.validationError;
|
||||
},
|
||||
isFormDisabled: function () {
|
||||
return this.props._isFormDisabled();
|
||||
},
|
||||
isValid: function () {
|
||||
return this.state._isValid;
|
||||
},
|
||||
isPristine: function () {
|
||||
return this.state._isPristine;
|
||||
},
|
||||
isRequired: function () {
|
||||
return !!this.props.required;
|
||||
},
|
||||
showRequired: function () {
|
||||
return this.isRequired() && this.state._value === '';
|
||||
},
|
||||
showError: function () {
|
||||
return !this.showRequired() && !this.state._isValid;
|
||||
}
|
||||
};
|
||||
|
||||
},{}],3:[function(require,module,exports){
|
||||
var csrfTokenSelector = document.querySelector('meta[name="csrf-token"]');
|
||||
|
||||
var toURLEncoded = function (element, key, list) {
|
||||
var list = list || [];
|
||||
if (typeof (element) == 'object') {
|
||||
for (var idx in element)
|
||||
toURLEncoded(element[idx], key ? key + '[' + idx + ']' : idx, list);
|
||||
} else {
|
||||
list.push(key + '=' + encodeURIComponent(element));
|
||||
}
|
||||
return list.join('&');
|
||||
};
|
||||
|
||||
var request = function (method, url, data, contentType, headers) {
|
||||
|
||||
var contentType = contentType === 'urlencoded' ? 'application/' + contentType.replace('urlencoded', 'x-www-form-urlencoded') : 'application/json';
|
||||
data = contentType === 'application/json' ? JSON.stringify(data) : toURLEncoded(data);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url, true);
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.setRequestHeader('Content-Type', contentType);
|
||||
|
||||
if (!!csrfTokenSelector && !!csrfTokenSelector.content) {
|
||||
xhr.setRequestHeader('X-CSRF-Token', csrfTokenSelector.content);
|
||||
}
|
||||
|
||||
// Add passed headers
|
||||
Object.keys(headers).forEach(function (header) {
|
||||
xhr.setRequestHeader(header, headers[header]);
|
||||
});
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
|
||||
try {
|
||||
var response = xhr.responseText ? JSON.parse(xhr.responseText) : null;
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(response);
|
||||
} else {
|
||||
reject(response);
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
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;
|
||||
},
|
||||
ajax: {
|
||||
post: request.bind(null, 'POST'),
|
||||
put: request.bind(null, 'PUT')
|
||||
}
|
||||
};
|
||||
|
||||
},{}],4:[function(require,module,exports){
|
||||
module.exports = {
|
||||
'isValue': function (value) {
|
||||
return value !== '';
|
||||
},
|
||||
'isEmail': function (value) {
|
||||
return value.match(/^((([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);
|
||||
},
|
||||
'isTrue': function (value) {
|
||||
return value === true;
|
||||
},
|
||||
'isNumeric': function (value) {
|
||||
if (typeof value === 'number') {
|
||||
return true;
|
||||
} else {
|
||||
matchResults = value.match(/[-+]?(\d*[.])?\d+/);
|
||||
if (!! matchResults) {
|
||||
return matchResults[0] == value;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
'isAlpha': function (value) {
|
||||
return value.match(/^[a-zA-Z]+$/);
|
||||
},
|
||||
'isWords': function (value) {
|
||||
return value.match(/^[a-zA-Z\s]+$/);
|
||||
},
|
||||
'isSpecialWords': function (value) {
|
||||
return value.match(/^[a-zA-Z\s\u00C0-\u017F]+$/);
|
||||
},
|
||||
isLength: function (value, min, max) {
|
||||
if (max !== undefined) {
|
||||
return value.length >= min && value.length <= max;
|
||||
}
|
||||
return value.length >= min;
|
||||
},
|
||||
equals: function (value, eql) {
|
||||
return value == eql;
|
||||
},
|
||||
equalsField: function (value, field) {
|
||||
return value === this[field];
|
||||
}
|
||||
};
|
||||
|
||||
},{}]},{},[1]);
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -126,11 +126,7 @@ describe('Element', function() {
|
|||
isValid = this.isValid;
|
||||
},
|
||||
updateValue: function (event) {
|
||||
console.log('event.target.value', event.target.value);
|
||||
this.setValue(event.target.value);
|
||||
setTimeout(function () {
|
||||
console.log('this.getValue()', this.getValue());
|
||||
}.bind(this), 100);
|
||||
},
|
||||
render: function () {
|
||||
return <input value={this.getValue()} onChange={this.updateValue}/>
|
||||
|
|
@ -274,4 +270,81 @@ describe('Element', function() {
|
|||
|
||||
});
|
||||
|
||||
it('should allow an undefined value to be updated to a value', function (done) {
|
||||
var TestInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
render: function () {
|
||||
return <input value={this.getValue()}/>
|
||||
}
|
||||
});
|
||||
var TestForm = React.createClass({
|
||||
getInitialState: function () {
|
||||
return {value: undefined};
|
||||
},
|
||||
changeValue: function () {
|
||||
this.setState({
|
||||
value: 'foo'
|
||||
});
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form url="/users">
|
||||
<TestInput name="A" value={this.state.value}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<TestForm/>
|
||||
);
|
||||
|
||||
form.changeValue();
|
||||
var input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
setTimeout(function () {
|
||||
expect(input.getDOMNode().value).toBe('foo');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should be able to dynamically change validations', function (done) {
|
||||
|
||||
var isInvalid = false;
|
||||
var TestInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
render: function () {
|
||||
return <input value={this.getValue()}/>
|
||||
}
|
||||
});
|
||||
var TestForm = React.createClass({
|
||||
getInitialState: function () {
|
||||
return {value: 'foo@bar.com', validations: 'isEmail'};
|
||||
},
|
||||
changeValidations: function () {
|
||||
this.setState({
|
||||
validations: 'equals:foo'
|
||||
});
|
||||
},
|
||||
setInvalid: function () {
|
||||
console.log('Running it!');
|
||||
isInvalid = true;
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form url="/users" onInvalid={this.setInvalid}>
|
||||
<TestInput name="A" validations={this.state.validations} value={this.state.value}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<TestForm/>
|
||||
);
|
||||
|
||||
form.changeValidations();
|
||||
setTimeout(function () {
|
||||
expect(isInvalid).toBe(true);
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -361,4 +361,46 @@ describe('Formsy', function () {
|
|||
|
||||
});
|
||||
|
||||
describe('Update a form', function () {
|
||||
|
||||
it('should allow elements to check if the form is disabled', function (done) {
|
||||
|
||||
var TestInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
render: function () {
|
||||
return <input value={this.getValue()}/>
|
||||
}
|
||||
});
|
||||
var TestForm = React.createClass({
|
||||
getInitialState: function () {
|
||||
return {disabled: true};
|
||||
},
|
||||
enableForm: function () {
|
||||
this.setState({
|
||||
disabled: false
|
||||
});
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form onChange={this.onChange} disabled={this.state.disabled}>
|
||||
<TestInput name="foo"/>
|
||||
</Formsy.Form>);
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<TestForm/>
|
||||
);
|
||||
|
||||
var input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
expect(input.isFormDisabled()).toBe(true);
|
||||
form.enableForm();
|
||||
setTimeout(function () {
|
||||
expect(input.isFormDisabled()).toBe(false);
|
||||
done();
|
||||
}, 0);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
module.exports = {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
_value: this.props.value ? this.props.value : '',
|
||||
_isValid: true,
|
||||
_isPristine: true
|
||||
};
|
||||
},
|
||||
componentWillMount: function () {
|
||||
|
||||
var configure = function () {
|
||||
this.setValidations(this.props.validations, this.props.required);
|
||||
this.props._attachToForm(this);
|
||||
}.bind(this);
|
||||
|
||||
if (!this.props.name) {
|
||||
throw new Error('Form Input requires a name property when used');
|
||||
}
|
||||
|
||||
if (!this.props._attachToForm) {
|
||||
return setTimeout(function () {
|
||||
if (!this.isMounted()) return;
|
||||
if (!this.props._attachToForm) {
|
||||
throw new Error('Form Mixin requires component to be nested in a Form');
|
||||
}
|
||||
configure();
|
||||
}.bind(this), 0);
|
||||
}
|
||||
configure();
|
||||
|
||||
},
|
||||
|
||||
// We have to make the validate method is kept when new props are added
|
||||
componentWillReceiveProps: function (nextProps) {
|
||||
nextProps._attachToForm = this.props._attachToForm;
|
||||
nextProps._detachFromForm = this.props._detachFromForm;
|
||||
nextProps._validate = this.props._validate;
|
||||
this.setValidations(nextProps.validations, nextProps.required);
|
||||
},
|
||||
|
||||
componentDidUpdate: function (prevProps, prevState) {
|
||||
|
||||
var isValueChanged = function () {
|
||||
|
||||
return (
|
||||
this.props.value !== prevProps.value && (
|
||||
this.state._value === prevProps.value ||
|
||||
|
||||
// Since undefined is converted to empty string we have to
|
||||
// check that specifically
|
||||
(this.state._value === '' && prevProps.value === undefined)
|
||||
)
|
||||
);
|
||||
|
||||
}.bind(this);
|
||||
|
||||
|
||||
// If validations has changed or something outside changes
|
||||
// the value, set the value again running a validation
|
||||
|
||||
if (prevProps.validations !== this.props.validations || isValueChanged()) {
|
||||
this.setValue(this.props.value || '');
|
||||
}
|
||||
},
|
||||
|
||||
// Detach it when component unmounts
|
||||
componentWillUnmount: function () {
|
||||
this.props._detachFromForm(this);
|
||||
},
|
||||
|
||||
setValidations: function (validations, required) {
|
||||
|
||||
// Add validations to the store itself as the props object can not be modified
|
||||
this._validations = validations || '';
|
||||
|
||||
if (required) {
|
||||
this._validations = validations ? validations + ',' : '';
|
||||
this._validations += 'isValue';
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// We validate after the value has been set
|
||||
setValue: function (value) {
|
||||
this.setState({
|
||||
_value: value,
|
||||
_isPristine: false
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
}.bind(this));
|
||||
},
|
||||
resetValue: function () {
|
||||
this.setState({
|
||||
_value: '',
|
||||
_isPristine: true
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
});
|
||||
},
|
||||
getValue: function () {
|
||||
return this.state._value;
|
||||
},
|
||||
hasValue: function () {
|
||||
return this.state._value !== '';
|
||||
},
|
||||
getErrorMessage: function () {
|
||||
return this.isValid() || this.showRequired() ? null : this.state._serverError || this.props.validationError;
|
||||
},
|
||||
isFormDisabled: function () {
|
||||
return this.props._isFormDisabled();
|
||||
},
|
||||
isValid: function () {
|
||||
return this.state._isValid;
|
||||
},
|
||||
isPristine: function () {
|
||||
return this.state._isPristine;
|
||||
},
|
||||
isRequired: function () {
|
||||
return !!this.props.required;
|
||||
},
|
||||
showRequired: function () {
|
||||
return this.isRequired() && this.state._value === '';
|
||||
},
|
||||
showError: function () {
|
||||
return !this.showRequired() && !this.state._isValid;
|
||||
}
|
||||
};
|
||||
281
src/main.js
281
src/main.js
|
|
@ -1,241 +1,16 @@
|
|||
var React = global.React || require('react');
|
||||
var Formsy = {};
|
||||
var validationRules = {
|
||||
'isValue': function (value) {
|
||||
return value !== '';
|
||||
},
|
||||
'isEmail': function (value) {
|
||||
return value.match(/^((([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);
|
||||
},
|
||||
'isTrue': function (value) {
|
||||
return value === true;
|
||||
},
|
||||
'isNumeric': function (value) {
|
||||
if (typeof value === 'number') {
|
||||
return true;
|
||||
} else {
|
||||
matchResults = value.match(/[-+]?(\d*[.])?\d+/);
|
||||
if (!! matchResults) {
|
||||
return matchResults[0] == value;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
'isAlpha': function (value) {
|
||||
return value.match(/^[a-zA-Z]+$/);
|
||||
},
|
||||
'isWords': function (value) {
|
||||
return value.match(/^[a-zA-Z\s]+$/);
|
||||
},
|
||||
'isSpecialWords': function (value) {
|
||||
return value.match(/^[a-zA-Z\s\u00C0-\u017F]+$/);
|
||||
},
|
||||
isLength: function (value, min, max) {
|
||||
if (max !== undefined) {
|
||||
return value.length >= min && value.length <= max;
|
||||
}
|
||||
return value.length >= min;
|
||||
},
|
||||
equals: function (value, eql) {
|
||||
return value == eql;
|
||||
},
|
||||
equalsField: function (value, field) {
|
||||
return value === this[field];
|
||||
}
|
||||
};
|
||||
|
||||
var toURLEncoded = function (element, key, list) {
|
||||
var list = list || [];
|
||||
if (typeof (element) == 'object') {
|
||||
for (var idx in element)
|
||||
toURLEncoded(element[idx], key ? key + '[' + idx + ']' : idx, list);
|
||||
} else {
|
||||
list.push(key + '=' + encodeURIComponent(element));
|
||||
}
|
||||
return list.join('&');
|
||||
};
|
||||
|
||||
var csrfTokenSelector = document.querySelector('meta[name="csrf-token"]');
|
||||
var request = function (method, url, data, contentType, headers) {
|
||||
|
||||
var contentType = contentType === 'urlencoded' ? 'application/' + contentType.replace('urlencoded', 'x-www-form-urlencoded') : 'application/json';
|
||||
data = contentType === 'application/json' ? JSON.stringify(data) : toURLEncoded(data);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url, true);
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.setRequestHeader('Content-Type', contentType);
|
||||
|
||||
if (!!csrfTokenSelector && !!csrfTokenSelector.content) {
|
||||
xhr.setRequestHeader('X-CSRF-Token', csrfTokenSelector.content);
|
||||
}
|
||||
|
||||
// Add passed headers
|
||||
Object.keys(headers).forEach(function (header) {
|
||||
xhr.setRequestHeader(header, headers[header]);
|
||||
});
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
|
||||
try {
|
||||
var response = xhr.responseText ? JSON.parse(xhr.responseText) : null;
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(response);
|
||||
} else {
|
||||
reject(response);
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
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')
|
||||
};
|
||||
var validationRules = require('./validationRules.js');
|
||||
var utils = require('./utils.js');
|
||||
var Mixin = require('./Mixin.js');
|
||||
var options = {};
|
||||
|
||||
Formsy.Mixin = Mixin;
|
||||
|
||||
Formsy.defaults = function (passedOptions) {
|
||||
options = passedOptions;
|
||||
};
|
||||
|
||||
Formsy.Mixin = {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
_value: this.props.value ? this.props.value : '',
|
||||
_isValid: true,
|
||||
_isPristine: true
|
||||
};
|
||||
},
|
||||
componentWillMount: function () {
|
||||
|
||||
var configure = function () {
|
||||
this.setValidations(this.props.validations, this.props.required);
|
||||
this.props._attachToForm(this);
|
||||
}.bind(this);
|
||||
|
||||
if (!this.props.name) {
|
||||
throw new Error('Form Input requires a name property when used');
|
||||
}
|
||||
|
||||
if (!this.props._attachToForm) {
|
||||
return setTimeout(function () {
|
||||
if (!this.isMounted()) return;
|
||||
if (!this.props._attachToForm) {
|
||||
throw new Error('Form Mixin requires component to be nested in a Form');
|
||||
}
|
||||
configure();
|
||||
}.bind(this), 0);
|
||||
}
|
||||
configure();
|
||||
|
||||
},
|
||||
|
||||
// We have to make the validate method is kept when new props are added
|
||||
componentWillReceiveProps: function (nextProps) {
|
||||
nextProps._attachToForm = this.props._attachToForm;
|
||||
nextProps._detachFromForm = this.props._detachFromForm;
|
||||
nextProps._validate = this.props._validate;
|
||||
this.setValidations(nextProps.validations, nextProps.required);
|
||||
},
|
||||
|
||||
componentDidUpdate: function(prevProps, prevState) {
|
||||
|
||||
// If the input is untouched and something outside changes the value
|
||||
// update the FORM model by re-attaching to the form
|
||||
if (this.state._isPristine) {
|
||||
if (this.props.value !== prevProps.value && this.state._value === prevProps.value) {
|
||||
this.state._value = this.props.value || '';
|
||||
this.props._attachToForm(this);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Detach it when component unmounts
|
||||
componentWillUnmount: function () {
|
||||
this.props._detachFromForm(this);
|
||||
},
|
||||
|
||||
setValidations: function(validations, required) {
|
||||
// Add validations to the store itself as the props object can not be modified
|
||||
this._validations = validations || '';
|
||||
|
||||
if (required) {
|
||||
this._validations = validations ? validations + ',' : '';
|
||||
this._validations += 'isValue';
|
||||
}
|
||||
},
|
||||
|
||||
// We validate after the value has been set
|
||||
setValue: function (value) {
|
||||
this.setState({
|
||||
_value: value,
|
||||
_isPristine: false
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
}.bind(this));
|
||||
},
|
||||
resetValue: function () {
|
||||
this.setState({
|
||||
_value: '',
|
||||
_isPristine: true
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
});
|
||||
},
|
||||
getValue: function () {
|
||||
return this.state._value;
|
||||
},
|
||||
hasValue: function () {
|
||||
return this.state._value !== '';
|
||||
},
|
||||
getErrorMessage: function () {
|
||||
return this.isValid() || this.showRequired() ? null : this.state._serverError || this.props.validationError;
|
||||
},
|
||||
isValid: function () {
|
||||
return this.state._isValid;
|
||||
},
|
||||
isPristine: function () {
|
||||
return this.state._isPristine;
|
||||
},
|
||||
isRequired: function () {
|
||||
return !!this.props.required;
|
||||
},
|
||||
showRequired: function () {
|
||||
return this.isRequired() && this.state._value === '';
|
||||
},
|
||||
showError: function () {
|
||||
return !this.showRequired() && !this.state._isValid;
|
||||
}
|
||||
};
|
||||
|
||||
Formsy.addValidationRule = function (name, func) {
|
||||
validationRules[name] = func;
|
||||
};
|
||||
|
|
@ -279,13 +54,20 @@ Formsy.Form = React.createClass({
|
|||
// The updated children array is not available here for some reason,
|
||||
// we need to wait for next event loop
|
||||
setTimeout(function () {
|
||||
if (!this.isMounted()) return;
|
||||
this.registerInputs(this.props.children);
|
||||
|
||||
var newInputKeys = Object.keys(this.inputs);
|
||||
if (arraysDiffer(inputKeys, newInputKeys)) {
|
||||
this.validateForm();
|
||||
// The component might have been unmounted on an
|
||||
// update
|
||||
if (this.isMounted()) {
|
||||
|
||||
this.registerInputs(this.props.children);
|
||||
|
||||
var newInputKeys = Object.keys(this.inputs);
|
||||
if (utils.arraysDiffer(inputKeys, newInputKeys)) {
|
||||
this.validateForm();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}.bind(this), 0);
|
||||
},
|
||||
|
||||
|
|
@ -316,8 +98,8 @@ Formsy.Form = React.createClass({
|
|||
|
||||
var headers = (Object.keys(this.props.headers).length && this.props.headers) || options.headers || {};
|
||||
|
||||
var method = this.props.method && ajax[this.props.method.toLowerCase()] ? this.props.method.toLowerCase() : 'post';
|
||||
ajax[method](this.props.url, this.mapModel(), this.props.contentType || options.contentType || 'json', headers)
|
||||
var method = this.props.method && utils.ajax[this.props.method.toLowerCase()] ? this.props.method.toLowerCase() : 'post';
|
||||
utils.ajax[method](this.props.url, this.mapModel(), this.props.contentType || options.contentType || 'json', headers)
|
||||
.then(function (response) {
|
||||
this.props.onSuccess(response);
|
||||
this.props.onSubmitted();
|
||||
|
|
@ -384,6 +166,7 @@ Formsy.Form = React.createClass({
|
|||
child.props._attachToForm = this.attachToForm;
|
||||
child.props._detachFromForm = this.detachFromForm;
|
||||
child.props._validate = this.validate;
|
||||
child.props._isFormDisabled = this.isFormDisabled;
|
||||
}
|
||||
|
||||
if (child && child.props && child.props.children) {
|
||||
|
|
@ -393,6 +176,10 @@ Formsy.Form = React.createClass({
|
|||
}.bind(this));
|
||||
},
|
||||
|
||||
isFormDisabled: function () {
|
||||
return this.props.disabled;
|
||||
},
|
||||
|
||||
getCurrentValues: function () {
|
||||
return Object.keys(this.inputs).reduce(function (data, name) {
|
||||
var component = this.inputs[name];
|
||||
|
|
@ -421,7 +208,7 @@ Formsy.Form = React.createClass({
|
|||
validate: function (component) {
|
||||
|
||||
// Trigger onChange
|
||||
if(this.state.canChange) {
|
||||
if (this.state.canChange) {
|
||||
this.props.onChange(this.getCurrentValues());
|
||||
}
|
||||
|
||||
|
|
@ -485,14 +272,16 @@ Formsy.Form = React.createClass({
|
|||
isValid: allIsValid
|
||||
});
|
||||
|
||||
if(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});
|
||||
this.setState({
|
||||
canChange: true
|
||||
});
|
||||
|
||||
}.bind(this);
|
||||
|
||||
|
|
@ -507,12 +296,12 @@ 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) {
|
||||
// but make sure the component is mounted
|
||||
if(this.isMounted()){
|
||||
this.setState({canChange: true});
|
||||
}
|
||||
// If there are no inputs, set state where form is ready to trigger
|
||||
// change event. New inputs might be added later
|
||||
if (!inputKeys.length && this.isMounted()) {
|
||||
this.setState({
|
||||
canChange: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
var csrfTokenSelector = document.querySelector('meta[name="csrf-token"]');
|
||||
|
||||
var toURLEncoded = function (element, key, list) {
|
||||
var list = list || [];
|
||||
if (typeof (element) == 'object') {
|
||||
for (var idx in element)
|
||||
toURLEncoded(element[idx], key ? key + '[' + idx + ']' : idx, list);
|
||||
} else {
|
||||
list.push(key + '=' + encodeURIComponent(element));
|
||||
}
|
||||
return list.join('&');
|
||||
};
|
||||
|
||||
var request = function (method, url, data, contentType, headers) {
|
||||
|
||||
var contentType = contentType === 'urlencoded' ? 'application/' + contentType.replace('urlencoded', 'x-www-form-urlencoded') : 'application/json';
|
||||
data = contentType === 'application/json' ? JSON.stringify(data) : toURLEncoded(data);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url, true);
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.setRequestHeader('Content-Type', contentType);
|
||||
|
||||
if (!!csrfTokenSelector && !!csrfTokenSelector.content) {
|
||||
xhr.setRequestHeader('X-CSRF-Token', csrfTokenSelector.content);
|
||||
}
|
||||
|
||||
// Add passed headers
|
||||
Object.keys(headers).forEach(function (header) {
|
||||
xhr.setRequestHeader(header, headers[header]);
|
||||
});
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
|
||||
try {
|
||||
var response = xhr.responseText ? JSON.parse(xhr.responseText) : null;
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(response);
|
||||
} else {
|
||||
reject(response);
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
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;
|
||||
},
|
||||
ajax: {
|
||||
post: request.bind(null, 'POST'),
|
||||
put: request.bind(null, 'PUT')
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
module.exports = {
|
||||
'isValue': function (value) {
|
||||
return value !== '';
|
||||
},
|
||||
'isEmail': function (value) {
|
||||
return value.match(/^((([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);
|
||||
},
|
||||
'isTrue': function (value) {
|
||||
return value === true;
|
||||
},
|
||||
'isNumeric': function (value) {
|
||||
if (typeof value === 'number') {
|
||||
return true;
|
||||
} else {
|
||||
matchResults = value.match(/[-+]?(\d*[.])?\d+/);
|
||||
if (!! matchResults) {
|
||||
return matchResults[0] == value;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
'isAlpha': function (value) {
|
||||
return value.match(/^[a-zA-Z]+$/);
|
||||
},
|
||||
'isWords': function (value) {
|
||||
return value.match(/^[a-zA-Z\s]+$/);
|
||||
},
|
||||
'isSpecialWords': function (value) {
|
||||
return value.match(/^[a-zA-Z\s\u00C0-\u017F]+$/);
|
||||
},
|
||||
isLength: function (value, min, max) {
|
||||
if (max !== undefined) {
|
||||
return value.length >= min && value.length <= max;
|
||||
}
|
||||
return value.length >= min;
|
||||
},
|
||||
equals: function (value, eql) {
|
||||
return value == eql;
|
||||
},
|
||||
equalsField: function (value, field) {
|
||||
return value === this[field];
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue