Added validation objects and required validation
This commit is contained in:
parent
36d4439019
commit
b0738a5032
38
README.md
38
README.md
|
|
@ -36,6 +36,7 @@ In development you will get a warning about Formsy overriding `props`. This is d
|
|||
- [value](#value)
|
||||
- [validations](#validations)
|
||||
- [validationError](#validationerror)
|
||||
- [validationErrors](#elementvalidationerrors)
|
||||
- [required](#required)
|
||||
- [getValue()](#getvalue)
|
||||
- [setValue()](#setvalue)
|
||||
|
|
@ -331,7 +332,18 @@ You should always use the [**getValue()**](#getvalue) method inside your formsy
|
|||
#### <a name="validations">validations</a>
|
||||
```html
|
||||
<MyInputComponent name="email" validations="isEmail"/>
|
||||
<MyInputComponent name="number" validations="isNumeric,isLength:5:12"/>
|
||||
<MyInputComponent name="number" validations="isNumeric,isLength:5"/>
|
||||
<MyInputComponent name="number" validations={{
|
||||
isNumeric: true,
|
||||
isLength: 5
|
||||
}}/>
|
||||
<MyInputComponent name="number" validations={{
|
||||
myCustomIsFiveValidation: function (values, value) {
|
||||
values; // Other current values in form {foo: 'bar', 'number': 5}
|
||||
value; // 5
|
||||
return 5 === value;
|
||||
}
|
||||
}}/>
|
||||
```
|
||||
An comma seperated list with validation rules. Take a look at [**Validators**](#validators) to see default rules. Use ":" to separate arguments passed to the validator. The arguments will go through a **JSON.parse** converting them into correct JavaScript types. Meaning:
|
||||
|
||||
|
|
@ -347,11 +359,33 @@ Works just fine.
|
|||
```
|
||||
The message that will show when the form input component is invalid.
|
||||
|
||||
#### <a name="validationerrors">validationErrors</a>
|
||||
```html
|
||||
<MyInputComponent
|
||||
name="email"
|
||||
validations={{
|
||||
isEmail: true,
|
||||
maxLength: 50
|
||||
}}
|
||||
validationErrors={{
|
||||
isEmail: 'You have to type valid email',
|
||||
maxLength: 'You can not type in more than 50 characters'
|
||||
}}
|
||||
/>
|
||||
```
|
||||
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>
|
||||
```html
|
||||
<MyInputComponent 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.
|
||||
|
||||
A property that tells the form that the form input component value is required. By default it uses `isEmptyString`, but you can define your own definition of what defined a required state.
|
||||
|
||||
```html
|
||||
<MyInputComponent name="email" required="isFalse"/>
|
||||
```
|
||||
Would be typical for a checkbox type of form element.
|
||||
|
||||
#### <a name="getvalue">getValue()</a>
|
||||
```javascript
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
headers: {},
|
||||
onSuccess: function () {},
|
||||
onError: function () {},
|
||||
onSubmit: function () {},
|
||||
|
|
@ -36,7 +35,8 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
onSubmitted: function () {},
|
||||
onValid: function () {},
|
||||
onInvalid: function () {},
|
||||
onChange: function () {}
|
||||
onChange: function () {},
|
||||
validationErrors: null
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -188,7 +188,9 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
child.props._detachFromForm = this.detachFromForm;
|
||||
child.props._validate = this.validate;
|
||||
child.props._isFormDisabled = this.isFormDisabled;
|
||||
child.props._isValidValue = this.runValidation;
|
||||
child.props._isValidValue = function (component, value) {
|
||||
return this.runValidation(component, value).isValid;
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
if (child && child.props && child.props.children) {
|
||||
|
|
@ -234,18 +236,13 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
this.props.onChange(this.getCurrentValues());
|
||||
}
|
||||
|
||||
var isValid = true;
|
||||
if (component.validate && typeof component.validate === 'function') {
|
||||
isValid = component.validate();
|
||||
} else if (component.props.required || component._validations) {
|
||||
isValid = this.runValidation(component);
|
||||
}
|
||||
|
||||
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: isValid,
|
||||
_serverError: null
|
||||
_isValid: validation.isValid,
|
||||
_isRequired: validation.isRequired,
|
||||
_validationError: validation.error
|
||||
}, this.validateForm);
|
||||
|
||||
},
|
||||
|
|
@ -253,33 +250,78 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
// Checks validation on current value or a passed value
|
||||
runValidation: function (component, value) {
|
||||
|
||||
var isValid = true;
|
||||
|
||||
var currentValues = this.getCurrentValues();
|
||||
var validationErrors = component.props.validationErrors;
|
||||
var validationError = component.props.validationError;
|
||||
value = arguments.length === 2 ? value : component.state._value;
|
||||
if (component._validations.length) {
|
||||
component._validations.split(/\,(?![^{\[]*[}\]])/g).forEach(function (validation) {
|
||||
var args = validation.split(':');
|
||||
var validateMethod = args.shift();
|
||||
args = args.map(function (arg) {
|
||||
try {
|
||||
return JSON.parse(arg);
|
||||
} catch (e) {
|
||||
return arg; // It is a string if it can not parse it
|
||||
}
|
||||
});
|
||||
args = [value].concat(args);
|
||||
if (!validationRules[validateMethod]) {
|
||||
throw new Error('Formsy does not have the validation rule: ' + validateMethod);
|
||||
}
|
||||
if (!validationRules[validateMethod].apply(this.getCurrentValues(), args)) {
|
||||
isValid = false;
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
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") {
|
||||
// the component defines an explicit validate function
|
||||
isValid = component.validate()
|
||||
validationResults.failed = component.validate() ? [] : ['failed'];
|
||||
}
|
||||
return isValid;
|
||||
|
||||
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: isValid,
|
||||
error: (function () {
|
||||
|
||||
if (isValid && !isRequired) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (this.props.validationErrors && this.props.validationErrors[component.props.name]) {
|
||||
return this.props.validationErrors[component.props.name];
|
||||
}
|
||||
|
||||
if (isRequired) {
|
||||
return validationErrors[requiredResults.success[0]] || null;
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
return validationErrors[validationResults.failed[0]] || validationError;
|
||||
}
|
||||
|
||||
}.call(this))
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
runRules: function (value, currentValues, validations) {
|
||||
|
||||
var results = {
|
||||
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' && !validations[validationMethod](currentValues, value)) {
|
||||
return results.failed.push(validationMethod);
|
||||
} else if (typeof validations[validationMethod] !== 'function' && !validationRules[validationMethod](currentValues, value, validations[validationMethod])) {
|
||||
return results.failed.push(validationMethod);
|
||||
}
|
||||
|
||||
return results.success.push(validationMethod);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
|
||||
},
|
||||
|
||||
// Validate the form by going through all child input components
|
||||
|
|
@ -319,10 +361,11 @@ Formsy.Form = React.createClass({displayName: "Form",
|
|||
// last component validated will run the onValidationComplete callback
|
||||
inputKeys.forEach(function (name, index) {
|
||||
var component = inputs[name];
|
||||
var isValid = this.runValidation(component);
|
||||
var validation = this.runValidation(component);
|
||||
component.setState({
|
||||
_isValid: isValid,
|
||||
_serverError: null
|
||||
_isValid: validation.isValid,
|
||||
_isRequired: validation.isRequired,
|
||||
_validationError: validation.error
|
||||
}, index === inputKeys.length - 1 ? onValidationComplete : null);
|
||||
}.bind(this));
|
||||
|
||||
|
|
@ -371,14 +414,48 @@ module.exports = Formsy;
|
|||
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{"./Mixin.js":2,"./utils.js":3,"./validationRules.js":4,"react":"react"}],2:[function(require,module,exports){
|
||||
var convertValidationsToObject = function (validations) {
|
||||
|
||||
if (typeof validations === 'string') {
|
||||
|
||||
return validations.split(/\,(?![^{\[]*[}\]])/g).reduce(function (validations, validation) {
|
||||
var args = validation.split(':');
|
||||
var validateMethod = args.shift();
|
||||
args = args.map(function (arg) {
|
||||
try {
|
||||
return JSON.parse(arg);
|
||||
} catch (e) {
|
||||
return arg; // It is a string if it can not parse it
|
||||
}
|
||||
});
|
||||
|
||||
if (args.length > 1) {
|
||||
throw new Error('Formsy does not support multiple args on string validations. Use object format of validations instead.');
|
||||
}
|
||||
validations[validateMethod] = args[0] || true;
|
||||
return validations;
|
||||
}, {});
|
||||
|
||||
}
|
||||
|
||||
return validations || {};
|
||||
|
||||
};
|
||||
module.exports = {
|
||||
getInitialState: function () {
|
||||
var value = 'value' in this.props ? this.props.value : '';
|
||||
return {
|
||||
_value: value,
|
||||
_value: this.props.value,
|
||||
_isRequired: false,
|
||||
_isValid: true,
|
||||
_isPristine: true,
|
||||
_pristineValue: value
|
||||
_pristineValue: this.props.value,
|
||||
_validationError: ''
|
||||
};
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
validationError: '',
|
||||
validationErrors: {}
|
||||
};
|
||||
},
|
||||
componentWillMount: function () {
|
||||
|
|
@ -419,25 +496,15 @@ module.exports = {
|
|||
|
||||
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)
|
||||
)
|
||||
);
|
||||
return this.props.value !== prevProps.value && this.state._value === prevProps.value;
|
||||
|
||||
}.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()) {
|
||||
var value = 'value' in this.props ? this.props.value : '';
|
||||
this.setValue(value);
|
||||
this.setValue(this.props.value);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -449,12 +516,8 @@ module.exports = {
|
|||
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';
|
||||
}
|
||||
this._validations = convertValidationsToObject(validations) || {};
|
||||
this._requiredValidations = required === true ? {isDefaultRequiredValue: true} : convertValidationsToObject(required);
|
||||
|
||||
},
|
||||
|
||||
|
|
@ -482,7 +545,7 @@ module.exports = {
|
|||
return this.state._value !== '';
|
||||
},
|
||||
getErrorMessage: function () {
|
||||
return this.isValid() || this.showRequired() ? null : this.state._serverError || this.props.validationError;
|
||||
return !this.isValid() || this.showRequired() ? this.state._validationError : null;
|
||||
},
|
||||
isFormDisabled: function () {
|
||||
return this.props._isFormDisabled();
|
||||
|
|
@ -494,13 +557,13 @@ module.exports = {
|
|||
return this.state._isPristine;
|
||||
},
|
||||
isRequired: function () {
|
||||
return !!this.props.required;
|
||||
return this.state._isRequired;
|
||||
},
|
||||
showRequired: function () {
|
||||
return this.isRequired() && this.state._value === '';
|
||||
return this.isRequired();
|
||||
},
|
||||
showError: function () {
|
||||
return !this.showRequired() && !this.state._isValid;
|
||||
return !this.showRequired() && !this.isValid();
|
||||
},
|
||||
isValidValue: function (value) {
|
||||
return this.props._isValidValue.call(null, this, value);
|
||||
|
|
@ -510,63 +573,6 @@ module.exports = {
|
|||
|
||||
|
||||
},{}],3:[function(require,module,exports){
|
||||
var csrfTokenSelector = typeof document != 'undefined' ? document.querySelector('meta[name="csrf-token"]') : null;
|
||||
|
||||
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;
|
||||
|
|
@ -580,10 +586,6 @@ module.exports = {
|
|||
});
|
||||
}
|
||||
return isDifferent;
|
||||
},
|
||||
ajax: {
|
||||
post: request.bind(null, 'POST'),
|
||||
put: request.bind(null, 'PUT')
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -591,47 +593,65 @@ module.exports = {
|
|||
|
||||
},{}],4:[function(require,module,exports){
|
||||
module.exports = {
|
||||
'isValue': function (value) {
|
||||
return value !== '';
|
||||
'isDefaultRequiredValue': function (values, value) {
|
||||
return value === undefined || 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);
|
||||
'hasValue': function (values, value) {
|
||||
return value !== undefined;
|
||||
},
|
||||
'isTrue': function (value) {
|
||||
'matchRegexp': function (values, value, regexp) {
|
||||
return value !== undefined && !!value.match(regexp);
|
||||
},
|
||||
'isUndefined': function (values, value) {
|
||||
return value === undefined;
|
||||
},
|
||||
'isEmptyString': function (values, value) {
|
||||
return value === '';
|
||||
},
|
||||
'isEmail': function (values, value) {
|
||||
return value !== undefined && 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 (values, value) {
|
||||
return value === true;
|
||||
},
|
||||
'isNumeric': function (value) {
|
||||
'isFalse': function (values, value) {
|
||||
return value === false;
|
||||
},
|
||||
'isNumeric': function (values, value) {
|
||||
if (typeof value === 'number') {
|
||||
return true;
|
||||
} else {
|
||||
var matchResults = value.match(/[-+]?(\d*[.])?\d+/);
|
||||
if (!! matchResults) {
|
||||
var matchResults = value !== undefined && value.match(/[-+]?(\d*[.])?\d+/);
|
||||
if (!!matchResults) {
|
||||
return matchResults[0] == value;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
'isAlpha': function (value) {
|
||||
return value.match(/^[a-zA-Z]+$/);
|
||||
'isAlpha': function (values, value) {
|
||||
return value !== undefined && value.match(/^[a-zA-Z]+$/);
|
||||
},
|
||||
'isWords': function (value) {
|
||||
return value.match(/^[a-zA-Z\s]+$/);
|
||||
'isWords': function (values, value) {
|
||||
return value !== undefined && value.match(/^[a-zA-Z\s]+$/);
|
||||
},
|
||||
'isSpecialWords': function (value) {
|
||||
return value.match(/^[a-zA-Z\s\u00C0-\u017F]+$/);
|
||||
'isSpecialWords': function (values, value) {
|
||||
return value !== undefined && 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;
|
||||
isLength: function (values, value, length) {
|
||||
return value !== undefined && value.length === length;
|
||||
},
|
||||
equals: function (value, eql) {
|
||||
equals: function (values, value, eql) {
|
||||
return value == eql;
|
||||
},
|
||||
equalsField: function (value, field) {
|
||||
equalsField: function (values, value, field) {
|
||||
return value == this[field];
|
||||
},
|
||||
maxLength: function (values, value, length) {
|
||||
return value !== undefined && value.length <= length;
|
||||
},
|
||||
minLength: function (values, value, length) {
|
||||
return value !== undefined && value.length >= length;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -79,45 +79,6 @@ describe('Element', function() {
|
|||
|
||||
});
|
||||
|
||||
it('should return server error message when calling getErrorMessage()', function (done) {
|
||||
|
||||
jasmine.Ajax.install();
|
||||
|
||||
var getErrorMessage = null;
|
||||
var TestInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
componentDidMount: function () {
|
||||
getErrorMessage = this.getErrorMessage;
|
||||
},
|
||||
updateValue: function (event) {
|
||||
this.setValue(event.target.value);
|
||||
},
|
||||
render: function () {
|
||||
return <input value={this.getValue()} onChange={this.updateValue}/>
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users">
|
||||
<TestInput name="foo" value="foo" validations="isEmail" validationError="Has to be email"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
var form = TestUtils.Simulate.submit(form.getDOMNode());
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 500,
|
||||
contentType: 'application/json',
|
||||
responseText: '{"foo": "bar"}'
|
||||
})
|
||||
|
||||
setTimeout(function () {
|
||||
expect(getErrorMessage()).toBe('bar');
|
||||
jasmine.Ajax.uninstall();
|
||||
done();
|
||||
}, 0);
|
||||
|
||||
});
|
||||
|
||||
it('should return true or false when calling isValid() depending on valid state', function () {
|
||||
|
||||
var isValid = null;
|
||||
|
|
@ -163,13 +124,15 @@ describe('Element', function() {
|
|||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users">
|
||||
<TestInput name="foo" value="foo"/>
|
||||
<TestInput name="foo" value="foo" required/>
|
||||
<TestInput name="foo" value=""/>
|
||||
<TestInput name="foo" value="" required/>
|
||||
<TestInput name="foo" value="foo" required="isLength:3"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
expect(isRequireds[0]()).toBe(false);
|
||||
expect(isRequireds[1]()).toBe(true);
|
||||
expect(isRequireds[2]()).toBe(true);
|
||||
|
||||
});
|
||||
|
||||
|
|
@ -202,47 +165,6 @@ describe('Element', function() {
|
|||
|
||||
});
|
||||
|
||||
it('should return true or false when calling showError() depending on value is invalid or a server error has arrived, or not', function (done) {
|
||||
|
||||
var showError = null;
|
||||
var TestInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
componentDidMount: function () {
|
||||
showError = this.showError;
|
||||
},
|
||||
updateValue: function (event) {
|
||||
this.setValue(event.target.value);
|
||||
},
|
||||
render: function () {
|
||||
return <input value={this.getValue()} onChange={this.updateValue}/>
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users">
|
||||
<TestInput name="foo" value="foo" validations="isEmail" validationError="This is not an email"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
expect(showError()).toBe(true);
|
||||
|
||||
var input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
TestUtils.Simulate.change(input, {target: {value: 'foo@foo.com'}});
|
||||
expect(showError()).toBe(false);
|
||||
|
||||
jasmine.Ajax.install();
|
||||
TestUtils.Simulate.submit(form.getDOMNode());
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 500,
|
||||
responseType: 'application/json',
|
||||
responseText: '{"foo": "Email already exists"}'
|
||||
});
|
||||
setTimeout(function () {
|
||||
expect(showError()).toBe(true);
|
||||
jasmine.Ajax.uninstall();
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should return true or false when calling isPristine() depending on input has been "touched" or not', function () {
|
||||
|
||||
var isPristine = null;
|
||||
|
|
@ -375,4 +297,200 @@ it('should allow an undefined value to be updated to a value', function (done) {
|
|||
|
||||
});
|
||||
|
||||
it('should be able to use an object as validations property', function () {
|
||||
|
||||
var TestInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
render: function () {
|
||||
return <input value={this.getValue()}/>
|
||||
}
|
||||
});
|
||||
var TestForm = React.createClass({
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="A" validations={{
|
||||
isEmail: true
|
||||
}}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<TestForm/>
|
||||
);
|
||||
|
||||
var input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
expect(input.isValidValue('foo@bar.com')).toBe(true);
|
||||
expect(input.isValidValue('foo@bar')).toBe(false);
|
||||
});
|
||||
|
||||
it('should be able to pass complex values to a validation rule', function () {
|
||||
|
||||
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({
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="A" validations={{
|
||||
matchRegexp: /foo/
|
||||
}} value="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<TestForm/>
|
||||
);
|
||||
|
||||
var inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
expect(inputComponent.isValid()).toBe(true);
|
||||
var input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
TestUtils.Simulate.change(input, {target: {value: 'bar'}});
|
||||
expect(inputComponent.isValid()).toBe(false);
|
||||
});
|
||||
|
||||
it('should be able to run a function to validate', function () {
|
||||
|
||||
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({
|
||||
customValidationA: function (values, value) {
|
||||
return value === 'foo';
|
||||
},
|
||||
customValidationB: function (values, value) {
|
||||
return value === 'foo' && values.A === 'foo';
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="A" validations={{
|
||||
custom: this.customValidationA
|
||||
}} value="foo"/>
|
||||
<TestInput name="B" validations={{
|
||||
custom: this.customValidationB
|
||||
}} value="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<TestForm/>
|
||||
);
|
||||
|
||||
var inputComponent = TestUtils.scryRenderedComponentsWithType(form, TestInput);
|
||||
expect(inputComponent[0].isValid()).toBe(true);
|
||||
expect(inputComponent[1].isValid()).toBe(true);
|
||||
var input = TestUtils.scryRenderedDOMComponentsWithTag(form, 'INPUT');
|
||||
TestUtils.Simulate.change(input[0], {target: {value: 'bar'}});
|
||||
expect(inputComponent[0].isValid()).toBe(false);
|
||||
expect(inputComponent[1].isValid()).toBe(false);
|
||||
});
|
||||
|
||||
it('should override all error messages with error messages passed by form', function () {
|
||||
var TestInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
render: function () {
|
||||
return <input value={this.getValue()}/>
|
||||
}
|
||||
});
|
||||
var TestForm = React.createClass({
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form validationErrors={{A: 'bar'}}>
|
||||
<TestInput name="A" validations={{
|
||||
isEmail: true
|
||||
}} validationError="bar2" validationErrors={{isEmail: 'bar3'}} value="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<TestForm/>
|
||||
);
|
||||
|
||||
var inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
expect(inputComponent.getErrorMessage()).toBe('bar');
|
||||
});
|
||||
|
||||
it('should override validation rules with required rules', function () {
|
||||
var TestInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
render: function () {
|
||||
return <input value={this.getValue()}/>
|
||||
}
|
||||
});
|
||||
var TestForm = React.createClass({
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="A"
|
||||
validations={{
|
||||
isEmail: true
|
||||
}}
|
||||
validationError="bar"
|
||||
validationErrors={{isEmail: 'bar2', isLength: 'bar3'}}
|
||||
value="f"
|
||||
required={{
|
||||
isLength: 1
|
||||
}}
|
||||
/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<TestForm/>
|
||||
);
|
||||
|
||||
var inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
expect(inputComponent.getErrorMessage()).toBe('bar3');
|
||||
});
|
||||
|
||||
it('should fall back to default error message when non exist in validationErrors map', function () {
|
||||
var TestInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
render: function () {
|
||||
return <input value={this.getValue()}/>
|
||||
}
|
||||
});
|
||||
var TestForm = React.createClass({
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="A"
|
||||
validations={{
|
||||
isEmail: true
|
||||
}}
|
||||
validationError="bar"
|
||||
validationErrors={{foo: 'bar'}}
|
||||
value="foo"
|
||||
/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<TestForm/>
|
||||
);
|
||||
|
||||
var inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
expect(inputComponent.getErrorMessage()).toBe('bar');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -82,12 +82,9 @@ describe('Formsy', function () {
|
|||
// Wait before adding the input
|
||||
setTimeout(function () {
|
||||
|
||||
inputs.push(TestInput({
|
||||
name: 'test'
|
||||
}));
|
||||
inputs.push(<TestInput name="test" value=""/>);
|
||||
|
||||
forceUpdate(function () {
|
||||
|
||||
// Wait for next event loop, as that does the form
|
||||
setTimeout(function () {
|
||||
TestUtils.Simulate.submit(form.getDOMNode());
|
||||
|
|
@ -136,9 +133,7 @@ describe('Formsy', function () {
|
|||
// Wait before adding the input
|
||||
setTimeout(function () {
|
||||
|
||||
inputs.push(TestInput({
|
||||
name: 'test'
|
||||
}));
|
||||
inputs.push(<TestInput name="test"/>);
|
||||
|
||||
forceUpdate(function () {
|
||||
|
||||
|
|
@ -257,7 +252,7 @@ describe('Formsy', function () {
|
|||
var form = TestUtils.renderIntoDocument(<TestForm inputs={ [{name: 'one', validations: 'CheckValid', value: 'foo'}] }/>);
|
||||
var input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
|
||||
TestUtils.Simulate.change(input.getDOMNode(), {target: {value: 'bar'}});
|
||||
expect(CheckValid).toHaveBeenCalledWith('bar');
|
||||
expect(CheckValid).toHaveBeenCalledWith({one: 'bar'}, 'bar', true);
|
||||
expect(OtherCheckValid).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
@ -266,7 +261,7 @@ describe('Formsy', function () {
|
|||
form.setProps({inputs: [{name: 'one', validations: 'OtherCheckValid', value: 'foo'}] });
|
||||
var input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
|
||||
TestUtils.Simulate.change(input.getDOMNode(), {target: {value: 'bar'}});
|
||||
expect(OtherCheckValid).toHaveBeenCalledWith('bar');
|
||||
expect(OtherCheckValid).toHaveBeenCalledWith({one: 'bar'}, 'bar', true);
|
||||
});
|
||||
|
||||
it('should invalidate a form if dynamically inserted input is invalid', function(done) {
|
||||
|
|
@ -301,8 +296,8 @@ describe('Formsy', function () {
|
|||
var form = TestUtils.renderIntoDocument(<TestForm inputs={ [{name: 'one', validations: 'CheckValid,OtherCheckValid', value: 'foo'}] }/>);
|
||||
var input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
|
||||
TestUtils.Simulate.change(input.getDOMNode(), {target: {value: 'bar'}});
|
||||
expect(CheckValid).toHaveBeenCalledWith('bar');
|
||||
expect(OtherCheckValid).toHaveBeenCalledWith('bar');
|
||||
expect(CheckValid).toHaveBeenCalledWith({one: 'bar'}, 'bar', true);
|
||||
expect(OtherCheckValid).toHaveBeenCalledWith({one: 'bar'}, 'bar', true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -380,9 +375,7 @@ describe('Formsy', function () {
|
|||
);
|
||||
|
||||
// Wait before adding the input
|
||||
inputs.push(TestInput({
|
||||
name: 'test'
|
||||
}));
|
||||
inputs.push(<TestInput name='test'/>);
|
||||
|
||||
forceUpdate(function () {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,178 +0,0 @@
|
|||
var Formsy = require('./../src/main.js');
|
||||
|
||||
describe('Ajax', function() {
|
||||
|
||||
beforeEach(function () {
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('should post to a given url if passed', function () {
|
||||
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users">
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
TestUtils.Simulate.submit(form.getDOMNode());
|
||||
expect(jasmine.Ajax.requests.mostRecent().url).toBe('/users');
|
||||
expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST');
|
||||
|
||||
});
|
||||
|
||||
it('should put to a given url if passed a method attribute', function () {
|
||||
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users" method="PUT">
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
TestUtils.Simulate.submit(form.getDOMNode());
|
||||
expect(jasmine.Ajax.requests.mostRecent().url).toBe('/users');
|
||||
expect(jasmine.Ajax.requests.mostRecent().method).toBe('PUT');
|
||||
|
||||
});
|
||||
|
||||
it('should pass x-www-form-urlencoded as contentType when urlencoded is set as contentType', function () {
|
||||
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users" contentType="urlencoded">
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
TestUtils.Simulate.submit(form.getDOMNode());
|
||||
expect(jasmine.Ajax.requests.mostRecent().contentType()).toBe('application/x-www-form-urlencoded');
|
||||
|
||||
});
|
||||
|
||||
it('should run an onSuccess handler, if passed and ajax is successfull. First argument is data from server', function (done) {
|
||||
|
||||
var onSuccess = jasmine.createSpy("success");
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users" onSuccess={onSuccess}>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
jasmine.Ajax.stubRequest('/users').andReturn({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: '{}'
|
||||
});
|
||||
|
||||
TestUtils.Simulate.submit(form.getDOMNode());
|
||||
|
||||
// Since ajax is returned as a promise (async), move assertion
|
||||
// to end of event loop
|
||||
setTimeout(function () {
|
||||
expect(onSuccess).toHaveBeenCalledWith({});
|
||||
done();
|
||||
}, 0);
|
||||
|
||||
});
|
||||
|
||||
it('should not do ajax request if onSubmit handler is passed, but pass the model as first argument to onSubmit handler', function () {
|
||||
|
||||
var TestInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
render: function () {
|
||||
return <input value={this.getValue()}/>
|
||||
}
|
||||
});
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form onSubmit={onSubmit}>
|
||||
<TestInput name="foo" value="bar"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
TestUtils.Simulate.submit(form.getDOMNode());
|
||||
|
||||
expect(jasmine.Ajax.requests.count()).toBe(0);
|
||||
|
||||
function onSubmit (data) {
|
||||
expect(data).toEqual({
|
||||
foo: 'bar'
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
it('should trigger an onSubmitted handler, if passed and the submit has responded with SUCCESS', function (done) {
|
||||
|
||||
var onSubmitted = jasmine.createSpy("submitted");
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users" onSubmitted={onSubmitted}>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
jasmine.Ajax.stubRequest('/users').andReturn({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: '{}'
|
||||
});
|
||||
|
||||
TestUtils.Simulate.submit(form.getDOMNode());
|
||||
|
||||
// Since ajax is returned as a promise (async), move assertion
|
||||
// to end of event loop
|
||||
setTimeout(function () {
|
||||
expect(onSubmitted).toHaveBeenCalled();
|
||||
done();
|
||||
}, 0);
|
||||
|
||||
});
|
||||
|
||||
it('should trigger an onSubmitted handler, if passed and the submit has responded with ERROR', function (done) {
|
||||
|
||||
var onSubmitted = jasmine.createSpy("submitted");
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users" onSubmitted={onSubmitted}>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
jasmine.Ajax.stubRequest('/users').andReturn({
|
||||
status: 500,
|
||||
contentType: 'application/json',
|
||||
responseText: '{}'
|
||||
});
|
||||
|
||||
TestUtils.Simulate.submit(form.getDOMNode());
|
||||
|
||||
// Since ajax is returned as a promise (async), move assertion
|
||||
// to end of event loop
|
||||
setTimeout(function () {
|
||||
expect(onSubmitted).toHaveBeenCalled();
|
||||
done();
|
||||
}, 0);
|
||||
|
||||
});
|
||||
|
||||
it('should trigger an onError handler, if passed and the submit has responded with ERROR', function (done) {
|
||||
|
||||
var onError = jasmine.createSpy("error");
|
||||
var form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users" onError={onError}>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
// Do not return any error because there are no inputs
|
||||
jasmine.Ajax.stubRequest('/users').andReturn({
|
||||
status: 500,
|
||||
contentType: 'application/json',
|
||||
responseText: '{}'
|
||||
});
|
||||
|
||||
TestUtils.Simulate.submit(form.getDOMNode());
|
||||
|
||||
// Since ajax is returned as a promise (async), move assertion
|
||||
// to end of event loop
|
||||
setTimeout(function () {
|
||||
expect(onError).toHaveBeenCalledWith({});
|
||||
done();
|
||||
}, 0);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
70
src/Mixin.js
70
src/Mixin.js
|
|
@ -1,11 +1,45 @@
|
|||
var convertValidationsToObject = function (validations) {
|
||||
|
||||
if (typeof validations === 'string') {
|
||||
|
||||
return validations.split(/\,(?![^{\[]*[}\]])/g).reduce(function (validations, validation) {
|
||||
var args = validation.split(':');
|
||||
var validateMethod = args.shift();
|
||||
args = args.map(function (arg) {
|
||||
try {
|
||||
return JSON.parse(arg);
|
||||
} catch (e) {
|
||||
return arg; // It is a string if it can not parse it
|
||||
}
|
||||
});
|
||||
|
||||
if (args.length > 1) {
|
||||
throw new Error('Formsy does not support multiple args on string validations. Use object format of validations instead.');
|
||||
}
|
||||
validations[validateMethod] = args[0] || true;
|
||||
return validations;
|
||||
}, {});
|
||||
|
||||
}
|
||||
|
||||
return validations || {};
|
||||
|
||||
};
|
||||
module.exports = {
|
||||
getInitialState: function () {
|
||||
var value = 'value' in this.props ? this.props.value : '';
|
||||
return {
|
||||
_value: value,
|
||||
_value: this.props.value,
|
||||
_isRequired: false,
|
||||
_isValid: true,
|
||||
_isPristine: true,
|
||||
_pristineValue: value
|
||||
_pristineValue: this.props.value,
|
||||
_validationError: ''
|
||||
};
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
validationError: '',
|
||||
validationErrors: {}
|
||||
};
|
||||
},
|
||||
componentWillMount: function () {
|
||||
|
|
@ -46,25 +80,15 @@ module.exports = {
|
|||
|
||||
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)
|
||||
)
|
||||
);
|
||||
return this.props.value !== prevProps.value && this.state._value === prevProps.value;
|
||||
|
||||
}.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()) {
|
||||
var value = 'value' in this.props ? this.props.value : '';
|
||||
this.setValue(value);
|
||||
this.setValue(this.props.value);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -76,12 +100,8 @@ module.exports = {
|
|||
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';
|
||||
}
|
||||
this._validations = convertValidationsToObject(validations) || {};
|
||||
this._requiredValidations = required === true ? {isDefaultRequiredValue: true} : convertValidationsToObject(required);
|
||||
|
||||
},
|
||||
|
||||
|
|
@ -109,7 +129,7 @@ module.exports = {
|
|||
return this.state._value !== '';
|
||||
},
|
||||
getErrorMessage: function () {
|
||||
return this.isValid() || this.showRequired() ? null : this.state._serverError || this.props.validationError;
|
||||
return !this.isValid() || this.showRequired() ? this.state._validationError : null;
|
||||
},
|
||||
isFormDisabled: function () {
|
||||
return this.props._isFormDisabled();
|
||||
|
|
@ -121,13 +141,13 @@ module.exports = {
|
|||
return this.state._isPristine;
|
||||
},
|
||||
isRequired: function () {
|
||||
return !!this.props.required;
|
||||
return this.state._isRequired;
|
||||
},
|
||||
showRequired: function () {
|
||||
return this.isRequired() && this.state._value === '';
|
||||
return this.isRequired();
|
||||
},
|
||||
showError: function () {
|
||||
return !this.showRequired() && !this.state._isValid;
|
||||
return !this.showRequired() && !this.isValid();
|
||||
},
|
||||
isValidValue: function (value) {
|
||||
return this.props._isValidValue.call(null, this, value);
|
||||
|
|
|
|||
138
src/main.js
138
src/main.js
|
|
@ -25,7 +25,6 @@ Formsy.Form = React.createClass({
|
|||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
headers: {},
|
||||
onSuccess: function () {},
|
||||
onError: function () {},
|
||||
onSubmit: function () {},
|
||||
|
|
@ -34,7 +33,8 @@ Formsy.Form = React.createClass({
|
|||
onSubmitted: function () {},
|
||||
onValid: function () {},
|
||||
onInvalid: function () {},
|
||||
onChange: function () {}
|
||||
onChange: function () {},
|
||||
validationErrors: null
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -86,19 +86,24 @@ Formsy.Form = React.createClass({
|
|||
// so validation becomes visible (if based on isPristine)
|
||||
this.setFormPristine(false);
|
||||
|
||||
this.updateModel();
|
||||
var model = this.mapModel();
|
||||
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);
|
||||
|
||||
// To support use cases where no async or request operation is needed.
|
||||
// The "onSubmit" callback is called with the model e.g. {fieldName: "myValue"},
|
||||
// if wanting to reset the entire form to original state, the second param is a callback for this.
|
||||
if (!this.props.url) {
|
||||
return;
|
||||
this.updateModel();
|
||||
var model = this.mapModel();
|
||||
this.props.onSubmit(model, this.resetModel, this.updateInputsWithError);
|
||||
this.state.isValid ? this.props.onValidSubmit(model, this.resetModel) : this.props.onInvalidSubmit(model, this.resetModel);
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateModel();
|
||||
this.setState({
|
||||
isSubmitting: true
|
||||
});
|
||||
|
||||
this.props.onSubmit(this.mapModel(), this.resetModel, this.updateInputsWithError);
|
||||
|
||||
var headers = (Object.keys(this.props.headers).length && this.props.headers) || options.headers || {};
|
||||
|
||||
var method = this.props.method && utils.ajax[this.props.method.toLowerCase()] ? this.props.method.toLowerCase() : 'post';
|
||||
|
|
@ -181,7 +186,9 @@ Formsy.Form = React.createClass({
|
|||
child.props._detachFromForm = this.detachFromForm;
|
||||
child.props._validate = this.validate;
|
||||
child.props._isFormDisabled = this.isFormDisabled;
|
||||
child.props._isValidValue = this.runValidation;
|
||||
child.props._isValidValue = function (component, value) {
|
||||
return this.runValidation(component, value).isValid;
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
if (child && child.props && child.props.children) {
|
||||
|
|
@ -227,18 +234,13 @@ Formsy.Form = React.createClass({
|
|||
this.props.onChange(this.getCurrentValues());
|
||||
}
|
||||
|
||||
var isValid = true;
|
||||
if (component.validate && typeof component.validate === 'function') {
|
||||
isValid = component.validate();
|
||||
} else if (component.props.required || component._validations) {
|
||||
isValid = this.runValidation(component);
|
||||
}
|
||||
|
||||
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: isValid,
|
||||
_serverError: null
|
||||
_isValid: validation.isValid,
|
||||
_isRequired: validation.isRequired,
|
||||
_validationError: validation.error
|
||||
}, this.validateForm);
|
||||
|
||||
},
|
||||
|
|
@ -246,33 +248,78 @@ Formsy.Form = React.createClass({
|
|||
// Checks validation on current value or a passed value
|
||||
runValidation: function (component, value) {
|
||||
|
||||
var isValid = true;
|
||||
|
||||
var currentValues = this.getCurrentValues();
|
||||
var validationErrors = component.props.validationErrors;
|
||||
var validationError = component.props.validationError;
|
||||
value = arguments.length === 2 ? value : component.state._value;
|
||||
if (component._validations.length) {
|
||||
component._validations.split(/\,(?![^{\[]*[}\]])/g).forEach(function (validation) {
|
||||
var args = validation.split(':');
|
||||
var validateMethod = args.shift();
|
||||
args = args.map(function (arg) {
|
||||
try {
|
||||
return JSON.parse(arg);
|
||||
} catch (e) {
|
||||
return arg; // It is a string if it can not parse it
|
||||
}
|
||||
});
|
||||
args = [value].concat(args);
|
||||
if (!validationRules[validateMethod]) {
|
||||
throw new Error('Formsy does not have the validation rule: ' + validateMethod);
|
||||
}
|
||||
if (!validationRules[validateMethod].apply(this.getCurrentValues(), args)) {
|
||||
isValid = false;
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
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") {
|
||||
// the component defines an explicit validate function
|
||||
isValid = component.validate()
|
||||
validationResults.failed = component.validate() ? [] : ['failed'];
|
||||
}
|
||||
return isValid;
|
||||
|
||||
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: isValid,
|
||||
error: (function () {
|
||||
|
||||
if (isValid && !isRequired) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (this.props.validationErrors && this.props.validationErrors[component.props.name]) {
|
||||
return this.props.validationErrors[component.props.name];
|
||||
}
|
||||
|
||||
if (isRequired) {
|
||||
return validationErrors[requiredResults.success[0]] || null;
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
return validationErrors[validationResults.failed[0]] || validationError;
|
||||
}
|
||||
|
||||
}.call(this))
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
runRules: function (value, currentValues, validations) {
|
||||
|
||||
var results = {
|
||||
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' && !validations[validationMethod](currentValues, value)) {
|
||||
return results.failed.push(validationMethod);
|
||||
} else if (typeof validations[validationMethod] !== 'function' && !validationRules[validationMethod](currentValues, value, validations[validationMethod])) {
|
||||
return results.failed.push(validationMethod);
|
||||
}
|
||||
|
||||
return results.success.push(validationMethod);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
|
||||
},
|
||||
|
||||
// Validate the form by going through all child input components
|
||||
|
|
@ -312,10 +359,11 @@ Formsy.Form = React.createClass({
|
|||
// last component validated will run the onValidationComplete callback
|
||||
inputKeys.forEach(function (name, index) {
|
||||
var component = inputs[name];
|
||||
var isValid = this.runValidation(component);
|
||||
var validation = this.runValidation(component);
|
||||
component.setState({
|
||||
_isValid: isValid,
|
||||
_serverError: null
|
||||
_isValid: validation.isValid,
|
||||
_isRequired: validation.isRequired,
|
||||
_validationError: validation.error
|
||||
}, index === inputKeys.length - 1 ? onValidationComplete : null);
|
||||
}.bind(this));
|
||||
|
||||
|
|
|
|||
61
src/utils.js
61
src/utils.js
|
|
@ -1,60 +1,3 @@
|
|||
var csrfTokenSelector = typeof document != 'undefined' ? document.querySelector('meta[name="csrf-token"]') : null;
|
||||
|
||||
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;
|
||||
|
|
@ -68,9 +11,5 @@ module.exports = {
|
|||
});
|
||||
}
|
||||
return isDifferent;
|
||||
},
|
||||
ajax: {
|
||||
post: request.bind(null, 'POST'),
|
||||
put: request.bind(null, 'PUT')
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,44 +1,62 @@
|
|||
module.exports = {
|
||||
'isValue': function (value) {
|
||||
return value !== '';
|
||||
'isDefaultRequiredValue': function (values, value) {
|
||||
return value === undefined || 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);
|
||||
'hasValue': function (values, value) {
|
||||
return value !== undefined;
|
||||
},
|
||||
'isTrue': function (value) {
|
||||
'matchRegexp': function (values, value, regexp) {
|
||||
return value !== undefined && !!value.match(regexp);
|
||||
},
|
||||
'isUndefined': function (values, value) {
|
||||
return value === undefined;
|
||||
},
|
||||
'isEmptyString': function (values, value) {
|
||||
return value === '';
|
||||
},
|
||||
'isEmail': function (values, value) {
|
||||
return value !== undefined && 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 (values, value) {
|
||||
return value === true;
|
||||
},
|
||||
'isNumeric': function (value) {
|
||||
'isFalse': function (values, value) {
|
||||
return value === false;
|
||||
},
|
||||
'isNumeric': function (values, value) {
|
||||
if (typeof value === 'number') {
|
||||
return true;
|
||||
} else {
|
||||
var matchResults = value.match(/[-+]?(\d*[.])?\d+/);
|
||||
if (!! matchResults) {
|
||||
var matchResults = value !== undefined && value.match(/[-+]?(\d*[.])?\d+/);
|
||||
if (!!matchResults) {
|
||||
return matchResults[0] == value;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
'isAlpha': function (value) {
|
||||
return value.match(/^[a-zA-Z]+$/);
|
||||
'isAlpha': function (values, value) {
|
||||
return value !== undefined && value.match(/^[a-zA-Z]+$/);
|
||||
},
|
||||
'isWords': function (value) {
|
||||
return value.match(/^[a-zA-Z\s]+$/);
|
||||
'isWords': function (values, value) {
|
||||
return value !== undefined && value.match(/^[a-zA-Z\s]+$/);
|
||||
},
|
||||
'isSpecialWords': function (value) {
|
||||
return value.match(/^[a-zA-Z\s\u00C0-\u017F]+$/);
|
||||
'isSpecialWords': function (values, value) {
|
||||
return value !== undefined && 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;
|
||||
isLength: function (values, value, length) {
|
||||
return value !== undefined && value.length === length;
|
||||
},
|
||||
equals: function (value, eql) {
|
||||
equals: function (values, value, eql) {
|
||||
return value == eql;
|
||||
},
|
||||
equalsField: function (value, field) {
|
||||
equalsField: function (values, value, field) {
|
||||
return value == this[field];
|
||||
},
|
||||
maxLength: function (values, value, length) {
|
||||
return value !== undefined && value.length <= length;
|
||||
},
|
||||
minLength: function (values, value, length) {
|
||||
return value !== undefined && value.length >= length;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue