Permalink
Browse files

Refactor validators to accept callback as first argument instead to c…

…all with return value instead of returning directly to allow for asynchronous callbacks.
  • Loading branch information...
morganchristiansson committed Nov 22, 2011
1 parent 167ef7c commit 19f72eeffda7ff242d3301ae265a8c40dabd4a60
Showing with 100 additions and 57 deletions.
  1. +38 −6 test/javascript/public/test/validators/numericality.js
  2. +62 −51 vendor/assets/javascripts/rails.validations.js
@@ -4,37 +4,69 @@ test('when value is a number', function() {
var element = $('<input type="text" />');
var options = { message: "failed validation" };
element.val('123');
- equal(ClientSideValidations.validators.local.numericality(element, options), undefined);
+ var called=false;

This comment has been minimized.

Show comment
Hide comment
@bcardarella

bcardarella Dec 6, 2011

there should be spaces between the assignment operator and the left and right hand

@bcardarella

bcardarella Dec 6, 2011

there should be spaces between the assignment operator and the left and right hand

+ var callback = function(result) {
+ equal(result, undefined);
+ called=true;
+ };
+ ClientSideValidations.validators.local.numericality(callback, element, options);
+ equal(called, true)
});
test('when value is a decimal number', function() {
var element = $('<input type="text" />');
var options = { message: "failed validation" };
element.val('123.456');
- equal(ClientSideValidations.validators.local.numericality(element, options), undefined);
+ var called=false;
+ var callback = function(result) {
+ equal(result, undefined);
+ called=true;
+ };
+ ClientSideValidations.validators.local.numericality(callback, element, options);
+ ok(called);
});
test('when value is not a number', function() {
var element = $('<input type="text" />');
var options = { messages: { numericality: "failed validation" } };
element.val('abc123');
- equal(ClientSideValidations.validators.local.numericality(element, options), "failed validation");
+ var called=false;
+ var callback = function(result) {
+ equal(result, "failed validation");
+ called=true;
+ };
+ ClientSideValidations.validators.local.numericality(callback, element, options);
+ ok(called);
});
test('when only allowing integers and value is integer', function() {
var element = $('<input type="text" />');
var options = { messages: { only_integer: "failed validation" }, only_integer: true };
element.val('123');
- equal(ClientSideValidations.validators.local.numericality(element, options), undefined);
+ var called=false;
+ var callback = function(result) {
+ equal(result, undefined);
+ called=true;
+ }
+ ClientSideValidations.validators.local.numericality(callback, element, options);
+ ok(called);
});
test('when only allowing integers and value has a negative or positive sign', function() {
var element = $('<input type="text" />');
var options = { messages: { only_integer: "failed validation"}, only_integer: true };
element.val('-23');
- equal(ClientSideValidations.validators.local.numericality(element, options), undefined);
+ var called=false;
+ var callback = function(result) {
+ equal(result, undefined);
+ called=true;
+ }
+ ClientSideValidations.validators.local.numericality(callback, element, options);
+ ok(called);
+ called = false;
element.val('+23');
- equal(ClientSideValidations.validators.local.numericality(element, options), undefined);
+ ClientSideValidations.validators.local.numericality(callback, element, options);
+ ok(called);
});
test('when only allowing integers and value is not integer', function() {
@@ -97,23 +97,27 @@
if (element.data('changed') !== false) {
var valid = true;
+ var active_callbacks = 0;
+ var callback = function(message) {
+ active_callbacks--;
+ element.trigger('element:validate:fail', message).data('valid', false);
+ valid = false;
+ };
element.data('changed', false);
// Because 'length' is defined on the list of validators we cannot call jQuery.each on
for (kind in ClientSideValidations.validators.local) {
- if (validators[kind] && (message = ClientSideValidations.validators.all()[kind](element, validators[kind]))) {
- element.trigger('element:validate:fail', message).data('valid', false);
- valid = false;
- break;
+ if (valid && validators[kind]) {
+ active_callbacks++;
+ ClientSideValidations.validators.all()[kind](callback, element, validators[kind]);
}
}
if (valid) {
for (kind in ClientSideValidations.validators.remote) {
- if (validators[kind] && (message = ClientSideValidations.validators.all()[kind](element, validators[kind]))) {
- element.trigger('element:validate:fail', message).data('valid', false);
- valid = false;
- break;
+ if (valid && validators[kind]) {
+ active_callbacks++;
+ ClientSideValidations.validators.all()[kind](callback, element, validators[kind])
}
}
}
@@ -130,71 +134,78 @@
// must be invoked on that form
$(function () { $('form[data-validate]').validate(); });
})(jQuery);
-
+var _presence = function(element, options) {
+ if (/^\s*$/.test(element.val() || "")) {
+ return options.message;
+ }
+}
var ClientSideValidations = {
forms: {},
validators: {
all: function() { return jQuery.extend({}, ClientSideValidations.validators.local, ClientSideValidations.validators.remote); },
local: {
- presence: function (element, options) {
+ presence: function (callback, element, options) {
if (/^\s*$/.test(element.val() || "")) {
- return options.message;
+ return callback(options.message);
}
},
- acceptance: function (element, options) {
+ acceptance: function (callback, element, options) {
switch (element.attr('type')) {
case 'checkbox':
if (!element.attr('checked')) {
- return options.message;
+ return callback(options.message);
}
+ callback();
break;
case 'text':
if (element.val() != (options.accept || '1')) {
- return options.message;
+ return callback(options.message);
}
+ callback();
break;
}
},
- format: function (element, options) {
- if ((message = this.presence(element, options)) && options.allow_blank === true) {
- return;
+ format: function (callback, element, options) {
+ if ((message = _presence(element, options)) && options.allow_blank === true) {
+ return callback();
} else if (message) {
- return message;
+ return callback(message);
} else {
if (options['with'] && !options['with'].test(element.val())) {
- return options.message;
+ return callback(options.message);
} else if (options['without'] && options['without'].test(element.val())) {
- return options.message;
+ return callback(options.message);
}
}
},
- numericality: function (element, options) {
+ numericality: function (callback, element, options) {
if (!/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d*)?$/.test(element.val())) {
- return options.messages.numericality;
+ return callback(options.messages.numericality);
}
if (options.only_integer && !/^[+-]?\d+$/.test(element.val())) {
- return options.messages.only_integer;
+ return callback(options.messages.only_integer);
}
var CHECKS = { greater_than: '>', greater_than_or_equal_to: '>=',
equal_to: '==', less_than: '<', less_than_or_equal_to: '<=' };
for (check in CHECKS) {
if (options[check] !== undefined && !(new Function("return " + element.val() + CHECKS[check] + options[check])())) {
- return options.messages[check];
+ return callback(options.messages[check]);
}
}
if (options.odd && !(parseInt(element.val(), 10) % 2)) {
- return options.messages.odd;
+ return callback(options.messages.odd);
}
if (options.even && (parseInt(element.val(), 10) % 2)) {
- return options.messages.even;
+ return callback(options.messages.even);
}
+ return callback();
},
- length: function (element, options) {
+ length: function (callback, element, options) {
var blankOptions = {},
CHECKS = { is: '==', minimum: '>=', maximum: '<=' },
tokenizer = options.js_tokenizer || "split('')",
@@ -204,78 +215,78 @@ var ClientSideValidations = {
} else if (options.minimum) {
blankOptions.message = options.messages.minimum;
}
- if ((message = this.presence(element, blankOptions)) && options.allow_blank === true) {
- return;
+ if ((message = _presence(element, blankOptions)) && options.allow_blank === true) {
+ return callback();
} else if (message) {
- return message;
+ return callback(message);
} else {
for (check in CHECKS) {
if (options[check] && !(new Function("return " + tokenized_length + CHECKS[check] + options[check])())) {
- return options.messages[check];
+ return callback(options.messages[check]);
}
}
}
},
- exclusion: function (element, options) {
+ exclusion: function (callback, element, options) {
var lower = null, upper = null;
- if ((message = this.presence(element, options)) && options.allow_blank === true) {
- return;
+ if ((message = _presence(element, options)) && options.allow_blank === true) {
+ return callback();
} else if (message) {
- return message;
+ return callback(message);
} else {
if (options['in']) {
for (i = 0; i < options['in'].length; i = i + 1) {
if (options['in'][i] == element.val()) {
- return options.message;
+ return callback(options.message);
}
}
} else if (options.range) {
lower = options.range[0];
upper = options.range[1];
if (element.val() >= lower && element.val() <= upper) {
- return options.message;
+ return callback(options.message);
}
}
}
},
- inclusion: function (element, options) {
+ inclusion: function (callback, element, options) {
var lower = null, upper = null;
- if ((message = this.presence(element, options)) && options.allow_blank === true) {
- return;
+ if ((message = _presence(element, options)) && options.allow_blank === true) {
+ return callback();
} else if (message) {
- return message;
+ return callback(message);
} else {
if (options['in']) {
for (i = 0; i < options['in'].length; i = i + 1) {
if (options['in'][i] == element.val()) {
- return;
+ return callback();
}
}
- return options.message;
+ return callback(options.message);
} else if (options.range) {
lower = options.range[0];
upper = options.range[1];
if (element.val() >= lower && element.val() <= upper) {
- return;
+ return callback();
} else {
- return options.message;
+ return callback(options.message);
}
}
}
},
- confirmation: function (element, options) {
+ confirmation: function (callback, element, options) {
if (element.val() !== jQuery('#' + element.attr('id') + '_confirmation').val()) {
- return options.message;
+ return callback(options.message);
}
}
},
remote: {
- uniqueness: function (element, options) {
+ uniqueness: function (callback, element, options) {
if ((message = ClientSideValidations.validators.local.presence(element, options)) && options.allow_blank === true) {
- return;
+ return callback();
} else if (message) {
- return message;
+ return callback(message);
} else {
var data = {},
name = null;
@@ -319,7 +330,7 @@ var ClientSideValidations = {
data: data,
async: false
}).status === 200) {
- return options.message;
+ return callback(options.message);
}
}
}

2 comments on commit 19f72ee

@bcardarella

This comment has been minimized.

Show comment
Hide comment
@bcardarella

bcardarella Dec 6, 2011

can you explain why the numericality validator test needed modifications? I would have thought the remote validator test would have been necessary to modify for this.

can you explain why the numericality validator test needed modifications? I would have thought the remote validator test would have been necessary to modify for this.

@morganchristiansson

This comment has been minimized.

Show comment
Hide comment
@morganchristiansson

morganchristiansson Dec 6, 2011

Owner

Hi, yes you're right about this. I abandoned the refactor1 branch and started a refactor2 branch where I only changed the remote validator. Sorry about the confusion. Please have a look at https://github.com/morganchristiansson/client_side_validations/commits/refactor2

Hi, yes you're right about this. I abandoned the refactor1 branch and started a refactor2 branch where I only changed the remote validator. Sorry about the confusion. Please have a look at https://github.com/morganchristiansson/client_side_validations/commits/refactor2

Please sign in to comment.