Skip to content
Open
4 changes: 2 additions & 2 deletions bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "knockout-validation",
"version": "2.0.3",
"version": "2.0.6",
"description": "A KnockoutJS Plugin for model and property validation",
"main": "dist/knockout.validation.js",
"license": "MIT",
Expand All @@ -20,7 +20,7 @@
"homepage": "https://github.com/Knockout-Contrib/Knockout-Validation",
"repository": {
"type": "git",
"url": "git://github.com/Knockout-Contrib/Knockout-Validation.git"
"url": "git://github.com/EikosPartners/Knockout-Validation.git"
},
"dependencies": {
"knockout": ">=2.3.0"
Expand Down
39 changes: 29 additions & 10 deletions dist/knockout.validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ kv.configuration = configuration;
// 1. Just the params to be passed to the validator
// 2. An object containing the Message to be used and the Params to pass to the validator
// 3. A condition when the validation rule to be applied
// 4. An object containing the severity to be used
//
// Example:
// var test = ko.observable(3).extend({
Expand All @@ -524,17 +525,19 @@ kv.configuration = configuration;
// }
// )};
//
if (params && (params.message || params.onlyIf)) { //if it has a message or condition object, then its an object literal to use
if (params && (params.message || params.onlyIf || (params.severity && !ko.isObservable(params.severity)))) { //if it has a message, condition, or severity object, then its an object literal to use
return kv.addRule(observable, {
rule: ruleName,
message: params.message,
params: utils.isEmptyVal(params.params) ? true : params.params,
severity: params.severity || 1,
condition: params.onlyIf
});
} else {
return kv.addRule(observable, {
rule: ruleName,
params: params
params: params,
severity: 1
});
}
};
Expand Down Expand Up @@ -1204,12 +1207,13 @@ ko.extenders['validatable'] = function (observable, options) {
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

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

// observable.rules:
// ObservableArray of Rule Contexts, where a Rule Context is simply the name of a rule and the params to supply to it
//
// Rule Context = { rule: '<rule name>', params: '<passed in params>', message: '<Override of default Message>' }
// Rule Context = { rule: '<rule name>', params: '<passed in params>', message: '<Override of default Message>', severity: '<severity level' }
observable.rules = ko.observableArray(); //holds the rule Contexts to use as part of validation

//in case async validation is occurring
Expand All @@ -1224,11 +1228,12 @@ ko.extenders['validatable'] = function (observable, options) {
observable.isValid = ko.computed(observable.__valid__);

//manually set error state
observable.setError = function (error) {
observable.setError = function (error, severity) {
var previousError = observable.error.peek();
var previousIsValid = observable.__valid__.peek();

observable.error(error);
observable.severity(severity);
observable.__valid__(false);

if (previousError !== error && !previousIsValid) {
Expand Down Expand Up @@ -1296,8 +1301,8 @@ function validateSync(observable, rule, ctx) {
observable.setError(kv.formatMessage(
ctx.message || rule.message,
unwrap(ctx.params),
observable));
return false;
observable), ctx.severity || rule.severity);
return ctx.severity === 1 ? false : "warning";
} else {
return true;
}
Expand Down Expand Up @@ -1350,7 +1355,9 @@ kv.validateObservable = function (observable) {
rule, // the rule validator to execute
ctx, // the current Rule Context for the loop
ruleContexts = observable.rules(), //cache for iterator
len = ruleContexts.length; //cache for iterator
len = ruleContexts.length, //cache for iterator
result, // holds result of validate call
hasWarning; // holds result if there is a warning

for (; i < len; i++) {

Expand All @@ -1371,11 +1378,23 @@ kv.validateObservable = function (observable) {

} else {
//run normal sync validation
if (!validateSync(observable, rule, ctx)) {
result = validateSync(observable, rule, ctx);

if (result === 'warning') {
hasWarning = true;
}

if (!result) {
return false; //break out of the loop
}
}
}
if(hasWarning) {
// durring the loop we encountered a warning
// but wanted to keep looping incase there was an error
// so return false as if we had an error
return false;
}
//finally if we got this far, make the observable valid again!
observable.clearError();
return true;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "knockout.validation",
"name": "ep-knockout.validation",
"version": "2.0.3",
"description": "A KnockoutJS Plugin for model and property validation",
"main": "dist/knockout.validation.js",
Expand Down
7 changes: 5 additions & 2 deletions src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@
// 1. Just the params to be passed to the validator
// 2. An object containing the Message to be used and the Params to pass to the validator
// 3. A condition when the validation rule to be applied
// 4. An object containing the severity to be used
//
// Example:
// var test = ko.observable(3).extend({
Expand All @@ -326,17 +327,19 @@
// }
// )};
//
if (params && (params.message || params.onlyIf)) { //if it has a message or condition object, then its an object literal to use
if (params && (params.message || params.onlyIf || (params.severity && !ko.isObservable(params.severity)))) { //if it has a message, condition, or severity object, then its an object literal to use
return ko.validation.addRule(observable, {
rule: ruleName,
message: params.message,
params: utils.isEmptyVal(params.params) ? true : params.params,
severity: params.severity || 1,
condition: params.onlyIf
});
} else {
return ko.validation.addRule(observable, {
rule: ruleName,
params: params
params: params,
severity: 1
});
}
};
Expand Down
32 changes: 24 additions & 8 deletions src/extenders.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,13 @@ ko.extenders['validatable'] = function (observable, options) {
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

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

// observable.rules:
// ObservableArray of Rule Contexts, where a Rule Context is simply the name of a rule and the params to supply to it
//
// Rule Context = { rule: '<rule name>', params: '<passed in params>', message: '<Override of default Message>' }
// Rule Context = { rule: '<rule name>', params: '<passed in params>', message: '<Override of default Message>', severity: '<severity level' }
observable.rules = ko.observableArray(); //holds the rule Contexts to use as part of validation

//in case async validation is occurring
Expand All @@ -62,11 +63,12 @@ ko.extenders['validatable'] = function (observable, options) {
observable.isValid = ko.computed(observable.__valid__);

//manually set error state
observable.setError = function (error) {
observable.setError = function (error, severity) {
var previousError = observable.error.peek();
var previousIsValid = observable.__valid__.peek();

observable.error(error);
observable.severity(severity);
observable.__valid__(false);

if (previousError !== error && !previousIsValid) {
Expand Down Expand Up @@ -134,8 +136,8 @@ function validateSync(observable, rule, ctx) {
observable.setError(ko.validation.formatMessage(
ctx.message || rule.message,
ko.utils.unwrapObservable(ctx.params),
observable));
return false;
observable), ctx.severity || rule.severity);
return ctx.severity === 1 ? false : "warning";
} else {
return true;
}
Expand Down Expand Up @@ -188,7 +190,9 @@ ko.validation.validateObservable = function (observable) {
rule, // the rule validator to execute
ctx, // the current Rule Context for the loop
ruleContexts = observable.rules(), //cache for iterator
len = ruleContexts.length; //cache for iterator
len = ruleContexts.length, //cache for iterator
result, // holds result of validate call
hasWarning; // holds result if there is a warning

for (; i < len; i++) {

Expand All @@ -209,11 +213,23 @@ ko.validation.validateObservable = function (observable) {

} else {
//run normal sync validation
if (!validateSync(observable, rule, ctx)) {
result = validateSync(observable, rule, ctx);

if (result === 'warning') {
hasWarning = true;
}

if (!result) {
return false; //break out of the loop
}
}
}
if(hasWarning) {
// durring the loop we encountered a warning
// but wanted to keep looping incase there was an error
// so return false as if we had an error
return false;
}
//finally if we got this far, make the observable valid again!
observable.clearError();
return true;
Expand Down
39 changes: 32 additions & 7 deletions test/api-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,10 @@ QUnit.test('setRules applies rules to all properties', function(assert) {
},
grandchild: {
property3: {
number: true
number: true,
required: {
severity: 2
}
}
},
ignoredDefinition: { required: true }
Expand Down Expand Up @@ -799,22 +802,23 @@ QUnit.test('setRules applies rules to all properties', function(assert) {

//check that all rules have been applied
assert.deepEqual(target.property1.rules(), [
{ rule: 'required', params: true },
{ rule: 'min', params: 10 },
{ rule: 'max', params: 99 }
{ rule: 'required', params: true, severity: 1 },
{ rule: 'min', params: 10, severity: 1 },
{ rule: 'max', params: 99, severity: 1 }
]);

assert.deepEqual(target.child.property2.rules(), [
{ rule: 'pattern', message: 'Only AlphaNumeric please', params: '^[a-z0-9].$', condition: undefined }
{ rule: 'pattern', message: 'Only AlphaNumeric please', params: '^[a-z0-9].$', condition: undefined, severity: 1 }
]);

assert.deepEqual(target.child.grandchild.property3.rules(), [
{ rule: 'number', params: true }
{ rule: 'number', params: true, severity: 1 },
{ rule: 'required', condition: undefined, message: undefined, params: true, severity: 2 }
]);

for (var i = 0; i < target.nestedArray().length; i++) {
assert.deepEqual(target.nestedArray()[i].property4.rules(), [
{ rule: 'email', params: true }
{ rule: 'email', params: true, severity: 1 }
]);
}

Expand All @@ -827,6 +831,27 @@ QUnit.test('setRules applies rules to all properties', function(assert) {
assert.ok(!target.nestedArray()[2].ignoredProperty.rules);
});

QUnit.test('setRules work correctly when params is validatedObservable', function(assert) {
var equalityComparison = ko.observable().extend({ min: 2 });

var definition = {
property1: {
equal: equalityComparison
}
};

var target = {
property1: ko.observable()
};

ko.validation.setRules(target, definition);

//check that all rules have been applied
assert.deepEqual(target.property1.rules(), [
{ rule: 'equal', params: equalityComparison, severity: 1 }
]);
});

QUnit.test('Issue #461 - validatedObservable works with nested view models if grouping.deep is true', function(assert) {
ko.validation.init({grouping: {deep: true}}, true);

Expand Down
56 changes: 56 additions & 0 deletions test/validation-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -433,3 +433,59 @@ QUnit.test('message parameter receives params and observable when async', functi
});

//#endregion

//#region Severity tests

QUnit.module('Severity tests');

QUnit.test('isValid returns false for warning severity', function(assert) {
var testObj = ko.observable('something').extend({
required: {
severity: 2
}
});
testObj('');
assert.equal(testObj.isValid(), false);
assert.equal(testObj.severity(), 2, 'Severity should equal severity defined in required-validation');
});

QUnit.test('default severity is 1', function(assert) {
var testObj = ko.observable('something').extend({
required: true
});
testObj('');
assert.equal(testObj.isValid(), false);
assert.equal(testObj.severity(), 1, 'Severity should be 1 when not defined');
});

QUnit.test('Lowest invalid rule severity is returned', function(assert) {
var testObj = ko.observable('something').extend({
minLength: {
params: 200,
severity: 3
},
email: {
severity: 2
},
required: {
severity: 1,
params: true
}
});
testObj('test');
assert.equal(testObj.isValid(), false);
assert.equal(testObj.severity(), 2, 'Lowest broken rule severity should be 2');
});

QUnit.test('Lowest invalid rule severity for default severity is returned', function(assert) {
var testObj = ko.observable('something').extend({
required: {
severity: 2
},
equal: 'cant be this.'
});
testObj('');
assert.equal(testObj.isValid(), false);
assert.equal(testObj.severity(), 1, 'Default severity for broken rule should be 1');
});
//#endregion