Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergiy Stotskiy committed Oct 23, 2013
1 parent a405940 commit c9815c7
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 47 deletions.
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;
};
};
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
2 changes: 2 additions & 0 deletions gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,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"]);
};

0 comments on commit c9815c7

Please sign in to comment.