From 09cc06dd02c156ebe82322850d59721c8f3a0b2f Mon Sep 17 00:00:00 2001 From: Pavel Volgarev Date: Fri, 5 Jul 2013 14:35:44 +0200 Subject: [PATCH 1/2] Extended validation details. Added an ability to retrieve extended validation details (e.g. the name of the failed validation rule, parameters for all rules configured on a target observable, a reference to the target observable, etc.) --- Src/knockout.validation.js | 50 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/Src/knockout.validation.js b/Src/knockout.validation.js index 4cb77bc4..a521c7f5 100644 --- a/Src/knockout.validation.js +++ b/Src/knockout.validation.js @@ -314,6 +314,38 @@ }); }; + result.getDetails = function() { + var details = []; + + // ensure we have latest changes + var latest = result(); + + if (!latest.length) { + // don't do anything if no errors occured + return []; + } + + ko.utils.arrayForEach(validatables(), function (observable) { + if (!observable.isValid()) { + var data = {}; // observable data for every rule + + ko.utils.arrayForEach(observable.rules(), function(ctx) { + // allowing the caller to examine rule parameters for every rule + data[ctx.rule] = ctx.params; + }); + + details.push({ + data: data, + observable: observable, + error: observable.error(), + rule: observable.failedRule() + }); + } + }); + + return details; + }; + obj.errors = result; obj.isValid = function () { return obj.errors().length === 0; @@ -933,6 +965,16 @@ observable.isModified = ko.observable(false); + // holds the name of the validation rule that has failed during the last validation procedure + observable.failedRule = ko.observable(null); + + observable.error.subscribe(function (v) { + if(!v) { + // clearing the failed rule name when validation message is empty + observable.failedRule(null); + } + }); + // we use a computed here to ensure that anytime a dependency changes, the // validation logic evaluates var h_obsValidationTrigger = ko.computed(function () { @@ -998,6 +1040,10 @@ //not valid, so format the error message and stick it in the 'error' variable observable.error(exports.formatMessage(ctx.message || rule.message, ctx.params)); + + // remembering the name of the failed rule (passing it when "getDetails" is called) + observable.failedRule(ctx.rule); + observable.__valid__(false); return false; } else { @@ -1031,6 +1077,10 @@ if (!isValid) { //not valid, so format the error message and stick it in the 'error' variable observable.error(exports.formatMessage(msg || ctx.message || rule.message, ctx.params)); + + // remembering the name of the failed rule (passing it when "getDetails" is called) + observable.failedRule(ctx.rule); + observable.__valid__(isValid); } From 65a02cfcb4b5bfaad71b1b1763204ed33801aee1 Mon Sep 17 00:00:00 2001 From: Pavel Volgarev Date: Mon, 8 Jul 2013 13:49:20 +0200 Subject: [PATCH 2/2] Tests for "getDetails". Tests for "getDetails" (extended validation results). --- Tests/validation-tests.js | 81 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/Tests/validation-tests.js b/Tests/validation-tests.js index b2126b31..698a04bb 100644 --- a/Tests/validation-tests.js +++ b/Tests/validation-tests.js @@ -1350,3 +1350,84 @@ asyncTest('Async Rule Is NOT Valid Test', function () { }); //#endregion + +//#region Extended validation details +module('Extended validation details'); + +test('Basic required', function() { + var o = { required: true }; + + var vm = { + testObj: ko.observable().extend(o) + }; + + vm.errors = ko.validation.group(vm); + + ok(!vm.testObj.isValid(), vm.testObj.error()); + + var details = vm.errors.getDetails(); + + ok(details && details.length == 1, 'Results are available'); + + equal(details[0].observable, vm.testObj, 'Result references the correct observable') + equal(details[0].rule, 'required', 'Result references the correct rule'); + equal(details[0].error, vm.testObj.error(), 'Result contains the correct error message'); + + ok(details[0].data != null, 'Result contains data container'); + equal(details[0].data[details[0].rule], o[details[0].rule], 'Result references the correct extension data'); +}); + +test('Required with options', function() { + var o = { required: { fieldId: 'some_id' } }; + + var vm = { + testObj: ko.observable().extend(o) + }; + + vm.errors = ko.validation.group(vm); + + ok(!vm.testObj.isValid(), vm.testObj.error()); + + var details = vm.errors.getDetails(); + + ok(details && details.length == 1, 'Results are available'); + + equal(details[0].observable, vm.testObj, 'Result references the correct observable') + equal(details[0].rule, 'required', 'Result references the correct rule'); + equal(details[0].error, vm.testObj.error(), 'Result contains the correct error message'); + + ok(details[0].data != null, 'Result contains data container'); + equal(details[0].data[details[0].rule], o[details[0].rule], 'Result references the correct extension data'); + equal(details[0].data[details[0].rule].fieldId, 'some_id', 'Result\'s extension data is the same'); +}); + +test('Multiple errors', function() { + var vm = { + testObj: ko.observable().extend({ required: true }), + anotherProp: ko.observable().extend({ required: true }) + }; + + vm.errors = ko.validation.group(vm); + + ok(!vm.testObj.isValid(), vm.testObj.error()); + + var details = vm.errors.getDetails(); + + ok(details && details.length == 2, 'Results are available'); + + var ensureResult = function (i, p) { + var observable = p(); + + equal(details[i].observable, observable, 'Result references the correct observable') + equal(details[i].rule, 'required', 'Result references the correct rule'); + equal(details[i].error, observable.error(), 'Result contains the correct error message'); + + ok(details[i].data != null, 'Result contains data container'); + equal(details[i].data[details[i].rule], true, 'Result contains the same values for extension data'); + } + + ensureResult(0, function () { return vm.testObj; }); + ensureResult(1, function () { return vm.anotherProp; }); +}); + +//#endregion \ No newline at end of file