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

#136 Support for Severity Observable #625

Open
wants to merge 13 commits into
base: v2.1.0
Choose a base branch
from
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