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

Returning error details instead only the error message in group.errors #226

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
46 changes: 39 additions & 7 deletions Src/knockout.validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@
errorClass: null, // single class for error message and element
errorElementClass: 'validationElement', // class to decorate error element
errorMessageClass: 'validationMessage', // class to decorate error message
enableErrorDetails: false, // add a new property errorDetails which contains the rule, the params, the observable and the message
grouping: {
deep: false, //by default grouping is shallow
observable: true //and using observables
deep: false, //by default grouping is shallow
observable: true, //and using observables
errorDetails: false //insert plain error messages
}
};

Expand Down Expand Up @@ -248,7 +250,7 @@
var errors = [];
ko.utils.arrayForEach(validatables(), function (observable) {
if (!observable.isValid()) {
errors.push(observable.error);
errors.push(options.errorDetails && configuration.enableErrorDetails ? observable.errorDetails : observable.error);
}
});
return errors;
Expand All @@ -261,7 +263,7 @@
traverse(obj); // and traverse tree again
ko.utils.arrayForEach(validatables(), function (observable) {
if (!observable.isValid()) {
errors.push(observable.error);
errors.push(options.errorDetails && configuration.enableErrorDetails ? observable.errorDetails : observable.error);
}
});
return errors;
Expand Down Expand Up @@ -743,12 +745,12 @@
msg = null,
isModified = false,
isValid = false;

obsv.extend({ validatable: true });

isModified = obsv.isModified();
isValid = obsv.isValid();

// create a handler to correctly return an error message
var errorMsgAccessor = function () {
if (!config.messagesOnModified || isModified) {
Expand Down Expand Up @@ -877,7 +879,16 @@
if (enable && !utils.isValidatable(observable)) {

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

if(configuration.enableErrorDetails) {
// holds detailed error information
observable.errorDetails = {
rule: ko.observable(null),
params: ko.observable(null),
observable: observable,
message: ko.observable(null)
};

}
// observable.rules:
// ObservableArray of Rule Contexts, where a Rule Context is simply the name of a rule and the params to supply to it
//
Expand Down Expand Up @@ -922,13 +933,19 @@
observable.__valid__._subscriptions['change'] = [];
h_change.dispose();
h_obsValidationTrigger.dispose();
if(configuration.enableErrorDetails) {
observable.errorDetails.rule.dispose();
observable.errorDetails.params.dispose();
observable.errorDetails.message.dispose();
}

delete observable['rules'];
delete observable['error'];
delete observable['isValid'];
delete observable['isValidating'];
delete observable['__valid__'];
delete observable['isModified'];
if(configuration.enableErrorDetails) delete observable['errorDetails'];
};
} else if (enable === false && utils.isValidatable(observable)) {

Expand All @@ -945,6 +962,11 @@

//not valid, so format the error message and stick it in the 'error' variable
observable.error = exports.formatMessage(ctx.message || rule.message, ctx.params);
if(configuration.enableErrorDetails) {
observable.errorDetails.rule(rule);
observable.errorDetails.params(ctx.params);
observable.errorDetails.message(observable.error);
}
observable.__valid__(false);
return false;
} else {
Expand Down Expand Up @@ -978,6 +1000,11 @@
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);
if(configuration.enableErrorDetails) {
observable.errorDetails.rule(rule);
observable.errorDetails.params(ctx.params);
observable.errorDetails.message(observable.error);
}
observable.__valid__(isValid);
}

Expand Down Expand Up @@ -1021,6 +1048,11 @@
}
//finally if we got this far, make the observable valid again!
observable.error = null;
if(configuration.enableErrorDetails) {
observable.errorDetails.rule(null);
observable.errorDetails.params(null);
observable.errorDetails.message(null);
}
observable.__valid__(true);
return true;
};
Expand Down
121 changes: 121 additions & 0 deletions Tests/validation-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -1258,3 +1258,124 @@ asyncTest('Async Rule Is NOT Valid Test', function () {
});

//#endregion

//#region error details

module('error details');

test('errorDetails property is filled when not valid', function () {
ko.validation.init({enableErrorDetails: true }, true);
var testObj = ko.observable('').extend({ required: true });

equal(testObj.isValid(), false);
equal(testObj.error, ko.validation.rules.required.message);

ok(testObj.hasOwnProperty('errorDetails'), 'errorDetails property does not exist.');
equal(testObj.errorDetails.rule(), ko.validation.rules.required);
equal(testObj.errorDetails.params(), true);
equal(testObj.errorDetails.observable, testObj);
equal(testObj.errorDetails.message(), ko.validation.rules.required.message)
ko.validation.reset();
});

test('errorDetails properties are null when valid', function () {
ko.validation.init({enableErrorDetails: true }, true);
var testObj = ko.observable('').extend({ required: true });
equal(testObj.isValid(), false);

testObj('a value');

equal(testObj.isValid(), true);
equal(testObj.errorDetails.rule(), null);
equal(testObj.errorDetails.params(), null);
equal(testObj.errorDetails.message(), null);
ko.validation.reset();
});

asyncTest('errorDetails property is filled when not valid async', function () {
ko.validation.init({enableErrorDetails: true }, true);
ko.validation.rules['mustEqualAsync'] = {
async: true,
validator: function (val, otherVal, callBack) {
var isValid = (val === otherVal);
setTimeout(function () {
callBack(isValid);
doAssertions();

start();
}, 10);
},
message: 'The field must equal {0}'
};
ko.validation.registerExtenders(); //make sure the new rule is registered


var testObj = ko.observable(4);

var doAssertions = function () {
ok(testObj.hasOwnProperty('errorDetails'), 'errorDetails property does not exist.');
equal(testObj.errorDetails.rule(), ko.validation.rules['mustEqualAsync']);
equal(testObj.errorDetails.params(), 5);
equal(testObj.errorDetails.observable, testObj);
equal(testObj.errorDetails.message(), 'The field must equal 5')
};

testObj.extend({ mustEqualAsync: 5 });
ko.validation.init({enableErrorDetails: true }, true);
});

test('group with errorDetails options works - Not Observable', function () {
ko.validation.init({enableErrorDetails: true }, true);
var vm = {
firstName: ko.observable().extend({ required: true }),
lastName: ko.observable().extend({ required: 2 })
};

var errors = ko.validation.group(vm, { errorDetails: true, observable: false });

equals(errors().length, 2, 'Grouping correctly finds 2 invalid properties');
equals(errors()[0], vm.firstName.errorDetails, 'group with errorDetails returns list of errorDetails');
equals(errors()[1], vm.lastName.errorDetails, 'group with errorDetails returns list of errorDetails');
ko.validation.reset();
});

test('group with errorDetails options works - Observable', function () {
ko.validation.init({enableErrorDetails: true }, true);
var vm = {
firstName: ko.observable().extend({ required: true }),
lastName: ko.observable().extend({ required: 2 })
};

var errors = ko.validation.group(vm, { errorDetails: true, observable: true });

equals(errors().length, 2, 'Grouping correctly finds 2 invalid properties');
equals(errors()[0], vm.firstName.errorDetails, 'group with errorDetails returns list of errorDetails');
equals(errors()[1], vm.lastName.errorDetails, 'group with errorDetails returns list of errorDetails');
ko.validation.reset();
});

test('errorDetails property is not defined if enableErrorDetails equals false', function () {
// enableErrorDetails is disabled by default
//ko.validation.init({enableErrorDetails: false }, true);
var testObj = ko.observable('').extend({ required: true });

equal(testObj.isValid(), false);
equal(testObj.error, ko.validation.rules.required.message);

ok(!testObj.hasOwnProperty('errorDetails'), 'errorDetails property does exist.');
});

test('going from one invalid state to the next creates the correct errorDetails (required -> maxLength)', function () {
ko.validation.init( { enableErrorDetails: true }, true);
var vm = { item : ko.observable().extend( { maxLength: 2, required: true } ) };
var errors = ko.validation.group(vm, { deep: true, observable: true, errorDetails: true });

equals(errors().length, 1, "has initially one error");
equals(errors()[0].rule().message, ko.validation.rules.required.message);

// insert too long text triggering maxLength rule
vm.item('12345');
equals(errors()[0].rule().message, "Please enter no more than {0} characters.");
ko.validation.reset();
});
//#endregion