Added validation objects and required validation

This commit is contained in:
christianalfoni 2015-04-13 18:29:18 +02:00
parent 36d4439019
commit b0738a5032
10 changed files with 587 additions and 575 deletions

View File

@ -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

View File

@ -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

View File

@ -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');
});
});

View File

@ -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 () {

View File

@ -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);
});
});

View File

@ -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);

View File

@ -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));

View File

@ -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')
}
};

View File

@ -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;
}
};