From a4059405f3a9c56158e64859e9cfbbb2f8d00c69 Mon Sep 17 00:00:00 2001 From: Sergiy Stotskiy Date: Wed, 23 Oct 2013 12:24:44 +0300 Subject: [PATCH 1/2] Added few more minifications --- Src/ko.validation.start.frag | 2 ++ gruntfile.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Src/ko.validation.start.frag b/Src/ko.validation.start.frag index 868eb834..995832e1 100644 --- a/Src/ko.validation.start.frag +++ b/Src/ko.validation.start.frag @@ -23,3 +23,5 @@ var kv = ko.validation; var koUtils = ko.utils; var unwrap = koUtils.unwrapObservable; + var forEach = koUtils.arrayForEach; + var extend = koUtils.extend; diff --git a/gruntfile.js b/gruntfile.js index 793275ac..91c07725 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -19,6 +19,8 @@ module.exports = function (grunt) { process: function (src, filepath) { return src .replace(/ko\.validation\./g, 'kv.') + .replace(/ko\.utils\.arrayForEach/g, 'forEach') + .replace(/ko\.utils\.extend/g, 'extend') .replace(/ko\.utils\.unwrapObservable/g, 'unwrap') .replace(/ko\.utils\./g, 'koUtils.'); } From c9815c715a18f440030cbe483ea79eb7084b069b Mon Sep 17 00:00:00 2001 From: Sergiy Stotskiy Date: Wed, 23 Oct 2013 14:01:16 +0300 Subject: [PATCH 2/2] It fixes #344 --- Src/bindingHandlers.js | 13 ++++---- Src/configuration.js | 5 ++- Src/extenders.js | 61 +++++++++++++++++++--------------- Src/ko.extensions.js | 7 ++-- Tests/validation-tests.js | 70 +++++++++++++++++++++++++++++++++------ gruntfile.js | 2 ++ 6 files changed, 111 insertions(+), 47 deletions(-) diff --git a/Src/bindingHandlers.js b/Src/bindingHandlers.js index 6a69fe1a..6fef550d 100644 --- a/Src/bindingHandlers.js +++ b/Src/bindingHandlers.js @@ -6,6 +6,7 @@ ko.bindingHandlers['validationCore'] = (function () { return { init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { var config = ko.validation.utils.getConfigOptions(element); + var observable = valueAccessor(); // parse html5 input validation attributes, optional feature if (config.parseInputAttributes) { @@ -13,28 +14,28 @@ ko.bindingHandlers['validationCore'] = (function () { } // if requested insert message element and apply bindings - if (config.insertMessages && ko.validation.utils.isValidatable(valueAccessor())) { + if (config.insertMessages && ko.validation.utils.isValidatable(observable)) { // insert the var validationMessageElement = ko.validation.insertValidationMessage(element); // if we're told to use a template, make sure that gets rendered if (config.messageTemplate) { - ko.renderTemplate(config.messageTemplate, { field: valueAccessor() }, null, validationMessageElement, 'replaceNode'); + ko.renderTemplate(config.messageTemplate, { field: observable }, null, validationMessageElement, 'replaceNode'); } else { - ko.applyBindingsToNode(validationMessageElement, { validationMessage: valueAccessor() }); + ko.applyBindingsToNode(validationMessageElement, { validationMessage: observable }); } } // write the html5 attributes if indicated by the config - if (config.writeInputAttributes && ko.validation.utils.isValidatable(valueAccessor())) { + if (config.writeInputAttributes && ko.validation.utils.isValidatable(observable)) { ko.validation.writeInputValidationAttributes(element, valueAccessor); } // if requested, add binding to decorate element - if (config.decorateElement && ko.validation.utils.isValidatable(valueAccessor())) { - ko.applyBindingsToNode(element, { validationElement: valueAccessor() }); + if (config.decorateElement && ko.validation.utils.isValidatable(observable)) { + ko.applyBindingsToNode(element, { validationElement: observable }); } }, diff --git a/Src/configuration.js b/Src/configuration.js index 6572d7e0..8f3ca5a2 100644 --- a/Src/configuration.js +++ b/Src/configuration.js @@ -17,6 +17,9 @@ var defaults = { grouping: { deep: false, //by default grouping is shallow observable: true //and using observables + }, + validate: { + // throttle: 10 } }; @@ -30,4 +33,4 @@ configuration.reset = function () { ko.utils.extend(configuration, defaults); }; -ko.validation.configuration = configuration; \ No newline at end of file +ko.validation.configuration = configuration; diff --git a/Src/extenders.js b/Src/extenders.js index 15e4589a..461f5606 100644 --- a/Src/extenders.js +++ b/Src/extenders.js @@ -27,8 +27,20 @@ ko.extenders['validation'] = function (observable, rules) { // allow single rule // // 2. test.extend({validatable: false}); // this will remove the validation properties from the Observable object should you need to do that. -ko.extenders['validatable'] = function (observable, enable) { - if (enable && !ko.validation.utils.isValidatable(observable)) { +ko.extenders['validatable'] = function (observable, options) { + if (!ko.validation.utils.isObject(options)) { + options = { enable: options }; + } + + if (!('enable' in options)) { + options.enable = true; + } + + if (options.enable && !ko.validation.utils.isValidatable(observable)) { + var config = ko.validation.configuration.validate || {}; + var validationOptions = { + 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 @@ -46,21 +58,8 @@ ko.extenders['validatable'] = function (observable, enable) { observable.isModified = ko.observable(false); - // we use a computed here to ensure that anytime a dependency changes, the - // validation logic evaluates - var h_obsValidationTrigger = ko.computed(function () { - var obs = observable(), - ruleContexts = observable.rules(); - - ko.validation.validateObservable(observable); - - return true; - }); - // a semi-protected observable - observable.isValid = ko.computed(function () { - return observable.__valid__(); - }); + observable.isValid = ko.computed(observable.__valid__); //manually set error state observable.setError = function (error) { @@ -79,6 +78,21 @@ ko.extenders['validatable'] = function (observable, enable) { observable.isModified(true); }); + // we use a computed here to ensure that anytime a dependency changes, the + // validation logic evaluates + var h_obsValidationTrigger = ko.computed(ko.utils.extend({ + read: function () { + var obs = observable(), + ruleContexts = observable.rules(); + + ko.validation.validateObservable(observable); + + return true; + } + }, validationOptions)); + + ko.utils.extend(h_obsValidationTrigger, validationOptions); + observable._disposeValidation = function () { //first dispose of the subscriptions observable.isValid.dispose(); @@ -96,11 +110,8 @@ ko.extenders['validatable'] = function (observable, enable) { delete observable['__valid__']; delete observable['isModified']; }; - } else if (enable === false && ko.validation.utils.isValidatable(observable)) { - - if (observable._disposeValidation) { - observable._disposeValidation(); - } + } else if (options.enable === false && observable._disposeValidation) { + observable._disposeValidation(); } return observable; }; @@ -110,8 +121,7 @@ function validateSync(observable, rule, ctx) { if (!rule.validator(observable(), ctx.params === undefined ? true : ctx.params)) { // default param is true, eg. required = true //not valid, so format the error message and stick it in the 'error' variable - observable.error(ko.validation.formatMessage(ctx.message || rule.message, ctx.params)); - observable.__valid__(false); + observable.setError(ko.validation.formatMessage(ctx.message || rule.message, ctx.params)); return false; } else { return true; @@ -187,7 +197,6 @@ ko.validation.validateObservable = function (observable) { } } //finally if we got this far, make the observable valid again! - observable.error(null); - observable.__valid__(true); + observable.clearError(); return true; -}; \ No newline at end of file +}; diff --git a/Src/ko.extensions.js b/Src/ko.extensions.js index 5aabb595..0702d8ea 100644 --- a/Src/ko.extensions.js +++ b/Src/ko.extensions.js @@ -35,10 +35,11 @@ ko.validatedObservable = function (initialValue) { if (!ko.validation.utils.isObject(initialValue)) { return ko.observable(initialValue).extend({ validatable: true }); } var obsv = ko.observable(initialValue); + obsv.isValid = ko.observable(); obsv.errors = ko.validation.group(initialValue); - obsv.isValid = ko.computed(function () { - return obsv.errors().length === 0; + obsv.errors.subscribe(function (errors) { + obsv.isValid(errors.length === 0); }); return obsv; -}; \ No newline at end of file +}; diff --git a/Tests/validation-tests.js b/Tests/validation-tests.js index e374f5a7..7dc8f39e 100644 --- a/Tests/validation-tests.js +++ b/Tests/validation-tests.js @@ -1,9 +1,9 @@ -/*global - module:false, - equal:false, +/*global + module:false, + equal:false, notEqual:false, strictEqual:false, - test:false, + test:false, ok:false, asyncTest:false, start: false, @@ -59,7 +59,7 @@ test('Issue #90 - "required: false" doesnt force validation', function () { .extend({ required: false }); equal(testObj.isValid(), true, 'testObj is valid without value'); - + testObj('blah'); equal(testObj.isValid(), true, 'testObj is valid with value'); @@ -372,7 +372,7 @@ test('Pattern validation mismatches numbers', function () { test('Pattern validation doesn\'t break with non-string values', function () { var testObj = ko.observable('') .extend({ pattern: '^$' }); - + testObj(12345); testObj.isValid(); @@ -610,7 +610,7 @@ test('Object is Valid when empty string is present - Preserves Optional Properti }); test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable('').extend({ dateISO: true }); + var testObj = ko.observable('').extend({ dateISO: true }); testObj('2011-11-18'); @@ -878,7 +878,7 @@ test( 'Issue #81 - Dynamic messages', function () { var CustomRule = function () { var self = this; - + this.message = 'before'; this.params = 0; @@ -928,9 +928,9 @@ test('Object is Valid and isValid returns True', function () { }); test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable().extend({ + var testObj = ko.observable().extend({ required: true, - minLength: 2, + minLength: 2, pattern: { message: 'It must contain some', params: 'some' @@ -1212,7 +1212,7 @@ test('Issue #31 - Recursively Show All Messages', function () { ok(!vm.one.isModified(), "Level 1 is not modified"); ok(!vm.two.one.isModified(), "Level 2 is not modified"); ok(!vm.three.two.one.isModified(), "Level 3 is not modified"); - + // now show all the messages errors.showAllMessages(); @@ -1548,3 +1548,51 @@ asyncTest('Async Rule Is NOT Valid Test', function () { testObj.extend({ mustEqualAsync: 5 }); }); //#endregion + +//# validation process tests +module("Validation process", { + setup: function () { + var isStarted = false; + ko.validation._validateObservable = ko.validation.validateObservable; + ko.validation.validateObservable = function () { + ok(true, "Triggered only once"); + if (!isStarted) { + isStarted = true; + start(); + } + + return ko.validation._validateObservable.apply(this, arguments); + }; + }, + + teardown: function () { + ko.validation.validateObservable = ko.validation._validateObservable; + } +}); + +asyncTest("can be throttled using global configuration", function () { + expect(2); // one for initialization and when value changed + + ko.validation.init({ validate: { + throttle: 10 + }}, true); + + var observable = ko.observable().extend({ validatable: true }); + observable("1"); + observable.extend({ minLength: 2 }); + + ko.validation.init({ validate: {} }, true); +}); + +asyncTest("can be throttled using using local configuration", function () { + expect(2); // one for initialization and when value changed + + var observable = ko.observable().extend({ validatable: { + throttle: 10 + } }); + + observable.extend({ minLength: 2 }); + observable("1"); +}); + +//#endregion diff --git a/gruntfile.js b/gruntfile.js index 91c07725..41a26fd0 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -78,4 +78,6 @@ module.exports = function (grunt) { // Default task. grunt.registerTask("default", ["test", "uglify"]); grunt.registerTask("test", ["concat", "qunit", "jshint"]); + + grunt.registerTask("compile", ["concat", "qunit", "uglify"]); };