Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented throttling for validation and made few optimizations #345

Merged
merged 2 commits into from
Oct 23, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions Src/bindingHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,36 @@ ko.bindingHandlers['validationCore'] = (function () {
return {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var config = ko.validation.utils.getConfigOptions(element);
var observable = valueAccessor();

// parse html5 input validation attributes, optional feature
if (config.parseInputAttributes) {
ko.validation.utils.async(function () { ko.validation.parseInputValidationAttributes(element, valueAccessor); });
}

// if requested insert message element and apply bindings
if (config.insertMessages && ko.validation.utils.isValidatable(valueAccessor())) {
if (config.insertMessages && ko.validation.utils.isValidatable(observable)) {

// insert the <span></span>
var validationMessageElement = ko.validation.insertValidationMessage(element);

// if we're told to use a template, make sure that gets rendered
if (config.messageTemplate) {
ko.renderTemplate(config.messageTemplate, { field: valueAccessor() }, null, validationMessageElement, 'replaceNode');
ko.renderTemplate(config.messageTemplate, { field: observable }, null, validationMessageElement, 'replaceNode');
} else {
ko.applyBindingsToNode(validationMessageElement, { validationMessage: valueAccessor() });
ko.applyBindingsToNode(validationMessageElement, { validationMessage: observable });
}
}

// write the html5 attributes if indicated by the config
if (config.writeInputAttributes && ko.validation.utils.isValidatable(valueAccessor())) {
if (config.writeInputAttributes && ko.validation.utils.isValidatable(observable)) {

ko.validation.writeInputValidationAttributes(element, valueAccessor);
}

// if requested, add binding to decorate element
if (config.decorateElement && ko.validation.utils.isValidatable(valueAccessor())) {
ko.applyBindingsToNode(element, { validationElement: valueAccessor() });
if (config.decorateElement && ko.validation.utils.isValidatable(observable)) {
ko.applyBindingsToNode(element, { validationElement: observable });
}
},

Expand Down
5 changes: 4 additions & 1 deletion Src/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ var defaults = {
grouping: {
deep: false, //by default grouping is shallow
observable: true //and using observables
},
validate: {
// throttle: 10
}
};

Expand All @@ -30,4 +33,4 @@ configuration.reset = function () {
ko.utils.extend(configuration, defaults);
};

ko.validation.configuration = configuration;
ko.validation.configuration = configuration;
61 changes: 35 additions & 26 deletions Src/extenders.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,20 @@ ko.extenders['validation'] = function (observable, rules) { // allow single rule
//
// 2. test.extend({validatable: false});
// this will remove the validation properties from the Observable object should you need to do that.
ko.extenders['validatable'] = function (observable, enable) {
if (enable && !ko.validation.utils.isValidatable(observable)) {
ko.extenders['validatable'] = function (observable, options) {
if (!ko.validation.utils.isObject(options)) {
options = { enable: options };
}

if (!('enable' in options)) {
options.enable = true;
}

if (options.enable && !ko.validation.utils.isValidatable(observable)) {
var config = ko.validation.configuration.validate || {};
var validationOptions = {
throttleEvaluation : options.throttle || config.throttle
};

observable.error = ko.observable(null); // holds the error message, we only need one since we stop processing validators when one is invalid

Expand All @@ -46,21 +58,8 @@ ko.extenders['validatable'] = function (observable, enable) {

observable.isModified = ko.observable(false);

// we use a computed here to ensure that anytime a dependency changes, the
// validation logic evaluates
var h_obsValidationTrigger = ko.computed(function () {
var obs = observable(),
ruleContexts = observable.rules();

ko.validation.validateObservable(observable);

return true;
});

// a semi-protected observable
observable.isValid = ko.computed(function () {
return observable.__valid__();
});
observable.isValid = ko.computed(observable.__valid__);

//manually set error state
observable.setError = function (error) {
Expand All @@ -79,6 +78,21 @@ ko.extenders['validatable'] = function (observable, enable) {
observable.isModified(true);
});

// we use a computed here to ensure that anytime a dependency changes, the
// validation logic evaluates
var h_obsValidationTrigger = ko.computed(ko.utils.extend({
read: function () {
var obs = observable(),
ruleContexts = observable.rules();

ko.validation.validateObservable(observable);

return true;
}
}, validationOptions));

ko.utils.extend(h_obsValidationTrigger, validationOptions);

observable._disposeValidation = function () {
//first dispose of the subscriptions
observable.isValid.dispose();
Expand All @@ -96,11 +110,8 @@ ko.extenders['validatable'] = function (observable, enable) {
delete observable['__valid__'];
delete observable['isModified'];
};
} else if (enable === false && ko.validation.utils.isValidatable(observable)) {

if (observable._disposeValidation) {
observable._disposeValidation();
}
} else if (options.enable === false && observable._disposeValidation) {
observable._disposeValidation();
}
return observable;
};
Expand All @@ -110,8 +121,7 @@ function validateSync(observable, rule, ctx) {
if (!rule.validator(observable(), ctx.params === undefined ? true : ctx.params)) { // default param is true, eg. required = true

//not valid, so format the error message and stick it in the 'error' variable
observable.error(ko.validation.formatMessage(ctx.message || rule.message, ctx.params));
observable.__valid__(false);
observable.setError(ko.validation.formatMessage(ctx.message || rule.message, ctx.params));
return false;
} else {
return true;
Expand Down Expand Up @@ -187,7 +197,6 @@ ko.validation.validateObservable = function (observable) {
}
}
//finally if we got this far, make the observable valid again!
observable.error(null);
observable.__valid__(true);
observable.clearError();
return true;
};
};
7 changes: 4 additions & 3 deletions Src/ko.extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ ko.validatedObservable = function (initialValue) {
if (!ko.validation.utils.isObject(initialValue)) { return ko.observable(initialValue).extend({ validatable: true }); }

var obsv = ko.observable(initialValue);
obsv.isValid = ko.observable();
obsv.errors = ko.validation.group(initialValue);
obsv.isValid = ko.computed(function () {
return obsv.errors().length === 0;
obsv.errors.subscribe(function (errors) {
obsv.isValid(errors.length === 0);
});

return obsv;
};
};
2 changes: 2 additions & 0 deletions Src/ko.validation.start.frag
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@
var kv = ko.validation;
var koUtils = ko.utils;
var unwrap = koUtils.unwrapObservable;
var forEach = koUtils.arrayForEach;
var extend = koUtils.extend;
70 changes: 59 additions & 11 deletions Tests/validation-tests.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/*global
module:false,
equal:false,
/*global
module:false,
equal:false,
notEqual:false,
strictEqual:false,
test:false,
test:false,
ok:false,
asyncTest:false,
start: false,
Expand Down Expand Up @@ -59,7 +59,7 @@ test('Issue #90 - "required: false" doesnt force validation', function () {
.extend({ required: false });

equal(testObj.isValid(), true, 'testObj is valid without value');

testObj('blah');
equal(testObj.isValid(), true, 'testObj is valid with value');

Expand Down Expand Up @@ -372,7 +372,7 @@ test('Pattern validation mismatches numbers', function () {
test('Pattern validation doesn\'t break with non-string values', function () {
var testObj = ko.observable('')
.extend({ pattern: '^$' });

testObj(12345);
testObj.isValid();

Expand Down Expand Up @@ -610,7 +610,7 @@ test('Object is Valid when empty string is present - Preserves Optional Properti
});

test('Object is Valid and isValid returns True', function () {
var testObj = ko.observable('').extend({ dateISO: true });
var testObj = ko.observable('').extend({ dateISO: true });

testObj('2011-11-18');

Expand Down Expand Up @@ -878,7 +878,7 @@ test( 'Issue #81 - Dynamic messages', function () {

var CustomRule = function () {
var self = this;

this.message = 'before';
this.params = 0;

Expand Down Expand Up @@ -928,9 +928,9 @@ test('Object is Valid and isValid returns True', function () {
});

test('Object is Valid and isValid returns True', function () {
var testObj = ko.observable().extend({
var testObj = ko.observable().extend({
required: true,
minLength: 2,
minLength: 2,
pattern: {
message: 'It must contain some',
params: 'some'
Expand Down Expand Up @@ -1212,7 +1212,7 @@ test('Issue #31 - Recursively Show All Messages', function () {
ok(!vm.one.isModified(), "Level 1 is not modified");
ok(!vm.two.one.isModified(), "Level 2 is not modified");
ok(!vm.three.two.one.isModified(), "Level 3 is not modified");

// now show all the messages
errors.showAllMessages();

Expand Down Expand Up @@ -1548,3 +1548,51 @@ asyncTest('Async Rule Is NOT Valid Test', function () {
testObj.extend({ mustEqualAsync: 5 });
});
//#endregion

//# validation process tests
module("Validation process", {
setup: function () {
var isStarted = false;
ko.validation._validateObservable = ko.validation.validateObservable;
ko.validation.validateObservable = function () {
ok(true, "Triggered only once");
if (!isStarted) {
isStarted = true;
start();
}

return ko.validation._validateObservable.apply(this, arguments);
};
},

teardown: function () {
ko.validation.validateObservable = ko.validation._validateObservable;
}
});

asyncTest("can be throttled using global configuration", function () {
expect(2); // one for initialization and when value changed

ko.validation.init({ validate: {
throttle: 10
}}, true);

var observable = ko.observable().extend({ validatable: true });
observable("1");
observable.extend({ minLength: 2 });

ko.validation.init({ validate: {} }, true);
});

asyncTest("can be throttled using using local configuration", function () {
expect(2); // one for initialization and when value changed

var observable = ko.observable().extend({ validatable: {
throttle: 10
} });

observable.extend({ minLength: 2 });
observable("1");
});

//#endregion
4 changes: 4 additions & 0 deletions gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ module.exports = function (grunt) {
process: function (src, filepath) {
return src
.replace(/ko\.validation\./g, 'kv.')
.replace(/ko\.utils\.arrayForEach/g, 'forEach')
.replace(/ko\.utils\.extend/g, 'extend')
.replace(/ko\.utils\.unwrapObservable/g, 'unwrap')
.replace(/ko\.utils\./g, 'koUtils.');
}
Expand Down Expand Up @@ -76,4 +78,6 @@ module.exports = function (grunt) {
// Default task.
grunt.registerTask("default", ["test", "uglify"]);
grunt.registerTask("test", ["concat", "qunit", "jshint"]);

grunt.registerTask("compile", ["concat", "qunit", "uglify"]);
};