Skip to content

Commit

Permalink
Run observable validations when observable change.
Browse files Browse the repository at this point in the history
* Also adds a configuration to allow the validation to run only on input
changes, if desired.
  • Loading branch information
luizfar committed Feb 11, 2014
1 parent 398ca74 commit e3ddc01
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 10 deletions.
13 changes: 11 additions & 2 deletions spec/computed-observable-spec.js
Expand Up @@ -5,8 +5,8 @@ describe('Computed observable validation', function () {
var html; var html;


viewModel = {}; viewModel = {};
viewModel.firstName = ko.observable(''); viewModel.firstName = ko.observable('First');
viewModel.lastName = ko.observable(''); viewModel.lastName = ko.observable('Last');
viewModel.fullName = ko.computed(function () { viewModel.fullName = ko.computed(function () {
if (viewModel.firstName() || viewModel.lastName()) { if (viewModel.firstName() || viewModel.lastName()) {
return viewModel.firstName() + viewModel.lastName(); return viewModel.firstName() + viewModel.lastName();
Expand All @@ -30,12 +30,21 @@ describe('Computed observable validation', function () {
it('fails if the computed value is not valid after one of its dependencies change', function () { it('fails if the computed value is not valid after one of its dependencies change', function () {
$('#firstName').val('').trigger('change'); $('#firstName').val('').trigger('change');


expect(viewModel.fullName.isValid()).toBe(true);

$('#lastName').val('').trigger('change');

expect(viewModel.fullName()).toBe(''); expect(viewModel.fullName()).toBe('');
expect(viewModel.fullName.isValid()).toBe(false); expect(viewModel.fullName.isValid()).toBe(false);
expect(viewModel.fullName.validationMessage()).toBe('Full Name is required.'); expect(viewModel.fullName.validationMessage()).toBe('Full Name is required.');
}); });


it('succeeds if the computed value is valid after one of its dependencies change', function () { it('succeeds if the computed value is valid after one of its dependencies change', function () {
$('#firstName').val('').trigger('change');
$('#lastName').val('').trigger('change');

expect(viewModel.fullName.isValid()).toBe(false);

$('#firstName').val('John').trigger('change'); $('#firstName').val('John').trigger('change');


expect(viewModel.fullName()).toBe('John'); expect(viewModel.fullName()).toBe('John');
Expand Down
4 changes: 2 additions & 2 deletions spec/integration-spec.js
Expand Up @@ -29,7 +29,7 @@ describe('ko validation integration', function () {
}; };


viewModel = { viewModel = {
requiredField: ko.observable('').extend({ requiredField: ko.observable('First Name').extend({
'required': ['First Name is required.'] 'required': ['First Name is required.']
}), }),
equalField: ko.observable(''), equalField: ko.observable(''),
Expand Down Expand Up @@ -66,7 +66,7 @@ describe('ko validation integration', function () {
isItRequired: ko.observable(true) isItRequired: ko.observable(true)
}; };


viewModel.sometimesRequired = ko.observable('').extend({ viewModel.sometimesRequired = ko.observable('Sometimes').extend({
'onlyIf': [viewModel.isItRequired, { 'required': [ 'Sometimes' ] }] 'onlyIf': [viewModel.isItRequired, { 'required': [ 'Sometimes' ] }]
}); });


Expand Down
44 changes: 44 additions & 0 deletions spec/observable-spec.js
Expand Up @@ -25,6 +25,50 @@ describe('observables validation', function () {
expect(observable.validationState()).toBe(ko.validation.validationStates.INVALID); expect(observable.validationState()).toBe(ko.validation.validationStates.INVALID);
}); });


it('runs the validation when the observable value changes', function () {
var observable;
observable = ko.observable().extend({ 'required': ['Field is required.'] });

observable('value');
expect(observable.isValid()).toBe(true);

observable('');
expect(observable.isValid()).toBe(false);
});

describe('when the observable is to be validated only on input changes', function () {
var observable, viewModel;

beforeEach(function () {
observable = ko.observable('Value').extend({
required: ['Value is required'],
validatesOn: 'inputChange'
});

viewModel = { obs: observable };

setFixtures('<div id="parent"><input id="input" data-bind="value: obs"/></div>');
ko.applyBindings(viewModel, $('#parent')[0]);
});

it('does not validate when the observable changes', function () {
expect(observable.isValid()).toBe(true);

observable('');

expect(observable.isValid()).toBe(true);
});

it('validates when the input changes', function () {
expect(observable.isValid()).toBe(true);

$('#input').val('').trigger('change');

expect(observable.isValid()).toBe(false);
expect(observable.validationMessage()).toBe('Value is required');
});
});

describe('validating an input', function () { describe('validating an input', function () {
var observable, viewModel; var observable, viewModel;


Expand Down
2 changes: 1 addition & 1 deletion spec/validation-element-spec.js
Expand Up @@ -3,7 +3,7 @@ describe('Validation message element', function () {


beforeEach(function () { beforeEach(function () {
viewModel = { viewModel = {
firstName: ko.observable('').extend({ firstName: ko.observable('name').extend({
'required': ['First Name is required.'], 'required': ['First Name is required.'],
'maxLength': [10, 'Up to 10 chars, please.'] 'maxLength': [10, 'Up to 10 chars, please.']
}) })
Expand Down
28 changes: 23 additions & 5 deletions src/ko-validation.js
Expand Up @@ -105,6 +105,12 @@ ko.validation.registerValidator = function (name, validatorFactory) {
observable.isValid = ko.computed(function () { observable.isValid = ko.computed(function () {
return observable.validationState() !== ko.validation.validationStates.INVALID; return observable.validationState() !== ko.validation.validationStates.INVALID;
}); });
if (observable.__validatesOn__ !== 'inputChange') {
observable.__validatesOn__ = 'change';
observable.__validationSubscription__ = observable.subscribe(function () {
ko.validation.utils.runValidations(observable);
});
}
} }


validator = ko.validation.utils.createValidator(name, param); validator = ko.validation.utils.createValidator(name, param);
Expand Down Expand Up @@ -153,22 +159,34 @@ ko.validation.registerValidator = function (name, validatorFactory) {
} }


function initValidationFor(inputElement, observable) { function initValidationFor(inputElement, observable) {
bindEventListenerToRunValidation(inputElement, observable); if (observable.__validatesOn__ === 'inputChange') {

bindEventListenerToRunValidation(inputElement, observable);
var subscription = observable.validationState.subscribe(function () { }
var messageSubscription = observable.validationState.subscribe(function () {
if (observable.__hasCustomValidationElement__) { if (observable.__hasCustomValidationElement__) {
subscription.dispose(); messageSubscription.dispose();
return; return;
} }
var validationElement = insertOrGetMessageElementAt(inputElement.parentNode); var validationElement = insertOrGetMessageElementAt(inputElement.parentNode);
updateValidationMessage(validationElement, observable); updateValidationMessage(validationElement, observable);
}); });


ko.utils.domNodeDisposal.addDisposeCallback(inputElement, function () { ko.utils.domNodeDisposal.addDisposeCallback(inputElement, function () {
subscription.dispose(); messageSubscription.dispose();
}); });
} }


ko.extenders.validatesOn = function (observable, eventName) {
if (eventName !== 'change' && eventName !== 'inputChange') {
throw new Error('Observable can be validated only on events "change" or "inputChange".');
}
observable.__validatesOn__ = eventName;
if (eventName === 'inputChange' && observable.__validationSubscription__) {
observable.__validationSubscription__.dispose();
}
return observable;
};

ko.extenders.validatesAfter = function (observable, dependentObservables) { ko.extenders.validatesAfter = function (observable, dependentObservables) {
ko.utils.arrayForEach(dependentObservables, function (dependentObservable) { ko.utils.arrayForEach(dependentObservables, function (dependentObservable) {
dependentObservable.__validates__ = dependentObservable.__validates__ || []; dependentObservable.__validates__ = dependentObservable.__validates__ || [];
Expand Down

0 comments on commit e3ddc01

Please sign in to comment.