From 29f93ddb7d16a3de77b78d107c587342c4be8586 Mon Sep 17 00:00:00 2001 From: Kaleb Sturgill Date: Fri, 28 Oct 2011 10:13:55 -0700 Subject: [PATCH 1/6] adding some events onValidationError and onValidationStart. Also adding an array of elements on each observable so we can tell what elements if any that this observable is bound on. This is useful so we can hook into a validation error later and use an external lib like qtip to display the error messages instead of just inserting a span into the html. --- Src/knockout.validation.js | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/Src/knockout.validation.js b/Src/knockout.validation.js index e16cb777..b4d7470d 100644 --- a/Src/knockout.validation.js +++ b/Src/knockout.validation.js @@ -23,7 +23,9 @@ messageTemplate: null, insertMessages: true, parseInputAttributes: false, - errorMessageClass: 'validationMessage' + errorMessageClass: 'validationMessage', + onValidationError: function(failedObservables){}, + onValidationStart: function(){} }; ko.validation = { @@ -43,7 +45,9 @@ configure: function (options) { ko.validation.init(options); }, group: function (obj) { // array of observables or viewModel - var group = isArray(obj) ? obj : values(obj); + configuration.onValidationStart(); + var failedObservables = []; + var group = isArray(obj) ? obj : values(obj); var observables = ko.utils.arrayFilter(group, function (item) { if (ko.isObservable(item)) { item.extend({ validatable: true }); @@ -54,18 +58,20 @@ var result = ko.dependentObservable(function () { var errors = []; ko.utils.arrayForEach(observables, function (observable) { - if (!observable.isValid()) { errors.push(observable.error); + failedObservables.push(observable); } }); return errors; }); + result.showAllMessages = function () { ko.utils.arrayForEach(observables, function (observable) { observable.isModified(true); }); }; + if(failedObservables.length != 0) configuration.onValidationError(failedObservables); return result; }, formatMessage: function (message, params) { @@ -185,8 +191,16 @@ }); }, 0); } - if (config.insertMessages && isValidatable(valueAccessor())) { - var validationMessageElement = ko.validation.insertValidationMessage(element); + + if (isValidatable(valueAccessor())) { + //adds element to elements array on observable + valueAccessor().elements.push(element); + //removes the element from the observable's property when the element is destroyed + ko.utils.domNodeDisposal.addDisposeCallback(element, function() { + if(element in valueAccessor().elements) ko.utils.arrayRemoveItem(valueAccessor().elements, element); + }); + + var validationMessageElement = ko.validation.insertValidationMessage(element); if (config.messageTemplate) { ko.renderTemplate(config.messageTemplate, { field: valueAccessor() }, null, validationMessageElement, 'replaceNode'); } else { @@ -269,12 +283,16 @@ ko.bindingHandlers.validationMessage = { // individual error message, if modified or post binding update: function (element, valueAccessor) { - var obsv = valueAccessor(); + configuration.onValidationStart(); + var obsv = valueAccessor(); obsv.extend({ validatable: true }); var errorMsgAccessor = function () { if (!configuration.messagesOnModified || obsv.isModified()) { - return obsv.isValid() ? null : obsv.error; + var valid = obsv.isValid(); + if(!valid) configuration.onValidationError([obsv]); + var valueToInsert = configuration.insertMessages ? obsv.error : null; + return valid ? null : valueToInsert; } else { return null; } @@ -358,7 +376,7 @@ // // Rule Context = { rule: '', params: '', message: '' } observable.rules = ko.observableArray(); //holds the rule Contexts to use as part of validation - + observable.elements = []; // holds an array of elements that are bind to this observable, most likely always 1. observable.isValid = ko.dependentObservable(function () { var i = 0, r, // the rule validator to execute From 7a2b36f500b25c5dc91c24c975046f258b110638 Mon Sep 17 00:00:00 2001 From: Kaleb Sturgill Date: Tue, 28 Feb 2012 07:51:55 -0700 Subject: [PATCH 2/6] Integrated the events and access to elements in the v1.0 branch --- Src/knockout.validation.js | 39 ++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/Src/knockout.validation.js b/Src/knockout.validation.js index bb608c16..03d76fef 100644 --- a/Src/knockout.validation.js +++ b/Src/knockout.validation.js @@ -17,7 +17,9 @@ grouping: { deep: false, //by default grouping is shallow observable: true //and using observables - } + }, + onValidationError: function(failedObservables){}, + onValidationStart: function(){} }; var html5Attributes = ['required', 'pattern', 'min', 'max', 'step']; @@ -141,6 +143,7 @@ configure: function (options) { ko.validation.init(options); }, group: function group(obj, options) { // array of observables or viewModel + var failedObservables = []; var options = ko.utils.extend(configuration.grouping, options), validatables = [], result = null, @@ -180,10 +183,13 @@ result = ko.dependentObservable(function () { var errors = []; ko.utils.arrayForEach(validatables, function (observable) { - if (!observable.isValid()) { + configuration.onValidationStart(); + if (!observable.isValid()) { + failedObservables.push(observable); errors.push(observable.error); } }); + if(failedObservables.length != 0) configuration.onValidationError(failedObservables); return errors; }); @@ -198,10 +204,13 @@ validatables = []; //clear validatables traverse(obj); // and traverse tree again ko.utils.arrayForEach(validatables, function (observable) { - if (!observable.isValid()) { + configuration.onValidationStart(); + if (!observable.isValid()) { + failedObservables.push(observable); errors.push(observable.error); } }); + if(failedObservables.length != 0) configuration.onValidationError(failedObservables); return errors; }; @@ -212,10 +221,11 @@ }; obj.errors = result; - obj.isValid = function () { - return obj.errors().length === 0; - } + obj.isValid = function() { + return obj.errors().length === 0; + }; } + return result; }, @@ -534,7 +544,14 @@ } //if requested insert message element and apply bindings - if (config.insertMessages && utils.isValidatable(valueAccessor())) { + if (utils.isValidatable(valueAccessor())) { + //adds element to elements array on observable + valueAccessor().elements.push(element); + //removes the element from the observable's property when the element is destroyed + ko.utils.domNodeDisposal.addDisposeCallback(element, function() { + if(element in valueAccessor().elements) ko.utils.arrayRemoveItem(valueAccessor().elements, element); + }); + var validationMessageElement = ko.validation.insertValidationMessage(element); if (config.messageTemplate) { ko.renderTemplate(config.messageTemplate, { field: valueAccessor() }, null, validationMessageElement, 'replaceNode'); @@ -559,7 +576,11 @@ var errorMsgAccessor = function () { if (!config.messagesOnModified || obsv.isModified()) { - return obsv.isValid() ? null : obsv.error; + configuration.onValidationStart(); + var valid = obsv.isValid(); + if(!valid) configuration.onValidationError([obsv]); + var valueToInsert = configuration.insertMessages ? obsv.error : null; + return valid ? null : valueToInsert; } else { return null; } @@ -665,6 +686,8 @@ observable.isModified = ko.observable(false); + observable.elements = []; // holds an array of elements that are bind to this observable, most likely always 1. + // we use a computed here to ensure that anytime a dependency changes, the // validation logic evaluates var h_obsValidationTrigger = ko.computed(function () { From b1c7a376059d1391a157e026132d828fa3987f61 Mon Sep 17 00:00:00 2001 From: Kaleb Sturgill Date: Tue, 28 Feb 2012 10:28:39 -0700 Subject: [PATCH 3/6] fixed a problem with never clearing out the failedObservables and not calling the onValidationStart event correctly --- Src/knockout.validation.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Src/knockout.validation.js b/Src/knockout.validation.js index 03d76fef..2cfebe54 100644 --- a/Src/knockout.validation.js +++ b/Src/knockout.validation.js @@ -182,8 +182,9 @@ traverse(obj); result = ko.dependentObservable(function () { var errors = []; + failedObservables = []; + configuration.onValidationStart(); ko.utils.arrayForEach(validatables, function (observable) { - configuration.onValidationStart(); if (!observable.isValid()) { failedObservables.push(observable); errors.push(observable.error); @@ -203,8 +204,9 @@ var errors = []; validatables = []; //clear validatables traverse(obj); // and traverse tree again + failedObservables = []; + configuration.onValidationStart(); ko.utils.arrayForEach(validatables, function (observable) { - configuration.onValidationStart(); if (!observable.isValid()) { failedObservables.push(observable); errors.push(observable.error); From 68a0d61b4f273e60831f35a80ad074a716dde855 Mon Sep 17 00:00:00 2001 From: Kaleb Sturgill Date: Tue, 28 Feb 2012 11:26:12 -0700 Subject: [PATCH 4/6] Some more bugfixes regarding the elements array. --- Src/knockout.validation.js | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/Src/knockout.validation.js b/Src/knockout.validation.js index 2cfebe54..16903881 100644 --- a/Src/knockout.validation.js +++ b/Src/knockout.validation.js @@ -533,9 +533,19 @@ //setup the 'init' bindingHandler override where we inject validation messages (function () { var init = ko.bindingHandlers.value.init; - - ko.bindingHandlers.value.init = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { - + + var addToElementsArray = function(observable, element) { + //creates it if it does not exist + if(!observable().elements) observable().elements = []; + //adds element to elements array on observable + observable().elements.push(element); + //removes the element from the observable's property when the element is destroyed + ko.utils.domNodeDisposal.addDisposeCallback(element, function() { + if (element in observable().elements) ko.utils.arrayRemoveItem(observable().elements, element); + }); + }; + + ko.bindingHandlers.value.init = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { init(element, valueAccessor, allBindingsAccessor); var config = utils.getConfigOptions(element); @@ -544,16 +554,11 @@ if (config.parseInputAttributes) { async(function () { ko.validation.parseInputValidationAttributes(element, valueAccessor) }); } - - //if requested insert message element and apply bindings + + addToElementsArray(valueAccessor, element); + + //apply bindings if (utils.isValidatable(valueAccessor())) { - //adds element to elements array on observable - valueAccessor().elements.push(element); - //removes the element from the observable's property when the element is destroyed - ko.utils.domNodeDisposal.addDisposeCallback(element, function() { - if(element in valueAccessor().elements) ko.utils.arrayRemoveItem(valueAccessor().elements, element); - }); - var validationMessageElement = ko.validation.insertValidationMessage(element); if (config.messageTemplate) { ko.renderTemplate(config.messageTemplate, { field: valueAccessor() }, null, validationMessageElement, 'replaceNode'); @@ -581,6 +586,7 @@ configuration.onValidationStart(); var valid = obsv.isValid(); if(!valid) configuration.onValidationError([obsv]); + //If requested insert messages var valueToInsert = configuration.insertMessages ? obsv.error : null; return valid ? null : valueToInsert; } else { @@ -688,7 +694,7 @@ observable.isModified = ko.observable(false); - observable.elements = []; // holds an array of elements that are bind to this observable, most likely always 1. + if(!observable.elements) observable.elements = []; // holds an array of elements that are bind to this observable, most likely always 1. // we use a computed here to ensure that anytime a dependency changes, the // validation logic evaluates From 020072b41b87676d1e6bd718fdd1ccf5bc70943a Mon Sep 17 00:00:00 2001 From: Kaleb Sturgill Date: Wed, 29 Feb 2012 12:21:27 -0700 Subject: [PATCH 5/6] fixed an issue where if the value was not an observable it would error --- Src/knockout.validation.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Src/knockout.validation.js b/Src/knockout.validation.js index 16903881..40bc4bed 100644 --- a/Src/knockout.validation.js +++ b/Src/knockout.validation.js @@ -555,10 +555,10 @@ async(function () { ko.validation.parseInputValidationAttributes(element, valueAccessor) }); } - addToElementsArray(valueAccessor, element); + if(ko.isObservable(valueAccessor)) addToElementsArray(valueAccessor, element) //apply bindings - if (utils.isValidatable(valueAccessor())) { + if (ko.isObservable(valueAccessor) && utils.isValidatable(valueAccessor())) { var validationMessageElement = ko.validation.insertValidationMessage(element); if (config.messageTemplate) { ko.renderTemplate(config.messageTemplate, { field: valueAccessor() }, null, validationMessageElement, 'replaceNode'); @@ -586,7 +586,7 @@ configuration.onValidationStart(); var valid = obsv.isValid(); if(!valid) configuration.onValidationError([obsv]); - //If requested insert messages + //If requestes insert messages var valueToInsert = configuration.insertMessages ? obsv.error : null; return valid ? null : valueToInsert; } else { From 668af6fdca32681b07a344ae3a5637104d46e42d Mon Sep 17 00:00:00 2001 From: Kaleb Sturgill Date: Tue, 6 Mar 2012 13:23:04 -0700 Subject: [PATCH 6/6] removed all the unnecessary code and now am not keeping elements on the observable but instead using the validationElement binding to fire off an event that I can hook into with qTip and display the messages. --- Src/knockout.validation.js | 58 ++++++++++---------------------------- 1 file changed, 15 insertions(+), 43 deletions(-) diff --git a/Src/knockout.validation.js b/Src/knockout.validation.js index 40bc4bed..9458215e 100644 --- a/Src/knockout.validation.js +++ b/Src/knockout.validation.js @@ -18,8 +18,7 @@ deep: false, //by default grouping is shallow observable: true //and using observables }, - onValidationError: function(failedObservables){}, - onValidationStart: function(){} + onValidationElementUpdated: function () { } }; var html5Attributes = ['required', 'pattern', 'min', 'max', 'step']; @@ -143,7 +142,6 @@ configure: function (options) { ko.validation.init(options); }, group: function group(obj, options) { // array of observables or viewModel - var failedObservables = []; var options = ko.utils.extend(configuration.grouping, options), validatables = [], result = null, @@ -182,15 +180,11 @@ traverse(obj); result = ko.dependentObservable(function () { var errors = []; - failedObservables = []; - configuration.onValidationStart(); ko.utils.arrayForEach(validatables, function (observable) { - if (!observable.isValid()) { - failedObservables.push(observable); + if (!observable.isValid()) { errors.push(observable.error); } }); - if(failedObservables.length != 0) configuration.onValidationError(failedObservables); return errors; }); @@ -204,15 +198,11 @@ var errors = []; validatables = []; //clear validatables traverse(obj); // and traverse tree again - failedObservables = []; - configuration.onValidationStart(); ko.utils.arrayForEach(validatables, function (observable) { - if (!observable.isValid()) { - failedObservables.push(observable); + if (!observable.isValid()) { errors.push(observable.error); } }); - if(failedObservables.length != 0) configuration.onValidationError(failedObservables); return errors; }; @@ -223,11 +213,10 @@ }; obj.errors = result; - obj.isValid = function() { - return obj.errors().length === 0; - }; + obj.isValid = function () { + return obj.errors().length === 0; + } } - return result; }, @@ -533,19 +522,9 @@ //setup the 'init' bindingHandler override where we inject validation messages (function () { var init = ko.bindingHandlers.value.init; - - var addToElementsArray = function(observable, element) { - //creates it if it does not exist - if(!observable().elements) observable().elements = []; - //adds element to elements array on observable - observable().elements.push(element); - //removes the element from the observable's property when the element is destroyed - ko.utils.domNodeDisposal.addDisposeCallback(element, function() { - if (element in observable().elements) ko.utils.arrayRemoveItem(observable().elements, element); - }); - }; - - ko.bindingHandlers.value.init = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { + + ko.bindingHandlers.value.init = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { + init(element, valueAccessor, allBindingsAccessor); var config = utils.getConfigOptions(element); @@ -554,11 +533,9 @@ if (config.parseInputAttributes) { async(function () { ko.validation.parseInputValidationAttributes(element, valueAccessor) }); } - - if(ko.isObservable(valueAccessor)) addToElementsArray(valueAccessor, element) - - //apply bindings - if (ko.isObservable(valueAccessor) && utils.isValidatable(valueAccessor())) { + + //if requested insert message element and apply bindings + if (config.insertMessages && utils.isValidatable(valueAccessor())) { var validationMessageElement = ko.validation.insertValidationMessage(element); if (config.messageTemplate) { ko.renderTemplate(config.messageTemplate, { field: valueAccessor() }, null, validationMessageElement, 'replaceNode'); @@ -583,12 +560,7 @@ var errorMsgAccessor = function () { if (!config.messagesOnModified || obsv.isModified()) { - configuration.onValidationStart(); - var valid = obsv.isValid(); - if(!valid) configuration.onValidationError([obsv]); - //If requestes insert messages - var valueToInsert = configuration.insertMessages ? obsv.error : null; - return valid ? null : valueToInsert; + return obsv.isValid() ? null : obsv.error; } else { return null; } @@ -609,6 +581,8 @@ var obsv = valueAccessor(); obsv.extend({ validatable: true }), config = utils.getConfigOptions(element); + + config.onValidationElementUpdated(obsv, element); var cssSettingsAccessor = function () { var result = {}; @@ -694,8 +668,6 @@ observable.isModified = ko.observable(false); - if(!observable.elements) observable.elements = []; // holds an array of elements that are bind to this observable, most likely always 1. - // we use a computed here to ensure that anytime a dependency changes, the // validation logic evaluates var h_obsValidationTrigger = ko.computed(function () {