Skip to content
This repository has been archived by the owner on Nov 24, 2018. It is now read-only.

Commit

Permalink
feat(core): add support for <select> elements
Browse files Browse the repository at this point in the history
closes #20
  • Loading branch information
Philipp Denzler committed Apr 17, 2014
1 parent 6b51442 commit adbb18c
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 262 deletions.
2 changes: 1 addition & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ module.exports = function (grunt) {
'src/core/validators/patternValidator.js',
'src/core/valdr-service.js',
'src/core/valdrType-directive.js',
'src/core/valdrInput-directive.js'
'src/core/valdrFormItem-directive.js'
],

message: [
Expand Down
20 changes: 20 additions & 0 deletions demo/core/simple.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ <h4>Person</h4>
<span>$valid {{ demoForm.age.$valid }}</span>
</div>

<div>
<label for="gender">Gender</label>
<select id="gender"
name="gender"
ng-options="gender as gender.label for gender in genders"
ng-model="person.gender">
<option value=""></option>
</select>
<span>$valid {{ demoForm.gender.$valid }}</span>
</div>

<h4>Address</h4>
<div valdr-type="Address">
<div>
Expand Down Expand Up @@ -112,13 +123,22 @@ <h3>constraints</h3>
'Required': {
message: 'Age is required.'
}
},
'gender': {
'Required': {
message: 'Gender is required.'
}
}
}
});
});

demoApp.controller('TestController', function ($scope, valdr) {
$scope.person = {};
$scope.genders = [
{ label: 'Male', value: 1 },
{ label: 'Female', value: 2 }
];
$scope.constraints = valdr.getConstraints();

$scope.addAddressConstraints = function () {
Expand Down
20 changes: 20 additions & 0 deletions demo/message/simple.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ <h4>Person</h4>
ng-model="person.age">

</div>

<div>
<label for="gender">Gender</label>
<select id="gender"
name="gender"
ng-options="gender as gender.label for gender in genders"
ng-model="person.gender">
<option value=""></option>
</select>
</div>

</div>

</form>
Expand Down Expand Up @@ -104,13 +115,22 @@ <h3>constraints</h3>
value: '21',
message: 'Person must be 21 years old.'
}
},
'gender': {
'Required': {
'message': 'Gender is required.'
}
}
}
});
});

demoApp.controller('TestController', function ($scope, valdr) {
$scope.person = {};
$scope.genders = [
{ label: 'Male', value: 1 },
{ label: 'Female', value: 2 }
];
$scope.constraints = valdr.getConstraints();
});
</script>
Expand Down
62 changes: 62 additions & 0 deletions src/core/valdrFormItem-directive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* This directive adds validation to all input and select fields which are bound to an ngModel and are surrounded
* by a valdrType directive.
*/
var valdrFormItemDirectiveDefinition =
['valdrEvents', 'valdr', 'valdrUtil', 'valdrClasses', function (valdrEvents, valdr, valdrUtil, valdrClasses) {
return {
restrict: 'E',
require: ['?^valdrType', '?^ngModel'],
link: function (scope, element, attrs, controllers) {

var valdrTypeController = controllers[0],
ngModelController = controllers[1],
fieldName = attrs.name,
parentElement = element.parent();

// Stop right here if this is a form item that's either not inside of a valdr-type block
// or there is no ng-model bound to it.
if (!valdrTypeController || !ngModelController) {
return;
}

if (valdrUtil.isEmpty(fieldName)) {
throw new Error('form element is not bound to a field name');
}

var updateClassOnParentElement = function(valid) {
parentElement.addClass(valid ? valdrClasses.valid : valdrClasses.invalid);
parentElement.removeClass(valid ? valdrClasses.invalid : valdrClasses.valid);
};

var updateNgModelController = function (validationResult) {
ngModelController.$setValidity('valdr', validationResult.valid);
ngModelController.valdrViolations = validationResult.violations;
};

var validate = function (value) {
var validationResult = valdr.validate(valdrTypeController.getType(), fieldName, value);
updateNgModelController(validationResult);
updateClassOnParentElement(validationResult.valid);
return validationResult.valid ? value : undefined;
};

ngModelController.$parsers.push(validate);
ngModelController.$formatters.push(validate);

scope.$on(valdrEvents.revalidate, function () {
validate(ngModelController.$viewValue);
});

element.bind('blur', function () {
if (ngModelController.$invalid && ngModelController.$dirty) {
parentElement.addClass(valdrClasses.dirtyBlurred);
}
});
}
};
}];

angular.module('valdr')
.directive('input', valdrFormItemDirectiveDefinition)
.directive('select', valdrFormItemDirectiveDefinition);
216 changes: 216 additions & 0 deletions src/core/valdrFormItem-directive.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
describe('valdrFormItem directive', function () {

// VARIABLES

var $scope, $compile, element, valdr, valdrEvents, valdrClasses, ngModelController,
violations = ['violationsArray'];

var inputTemplate =
'<form name="demoForm">' +
'<div valdr-type="TestClass">' +
'<input type="text" name="fieldName" ng-model="myObject.field">' +
'</div>' +
'</form>';

var selectTemplate =
'<form name="demoForm">' +
'<div valdr-type="TestClass">' +
'<select name="fieldName" ng-model="myObject.field">' +
'<option value></option>' +
'<option value="1">First</option>' +
'<option value="2">Second</option>' +
'</select>' +
'</div>' +
'</form>';

// TEST UTILITIES

function compileTemplate (template) {
element = $compile(angular.element(template))($scope);
$scope.$digest();
}

function compileInputTemplate () {
compileTemplate(inputTemplate);
ngModelController = element.find('input').controller('ngModel');
}

// COMMON SETUP

beforeEach(function () {
module('valdr');

/**
* Mock the valdr to always return 'true' when the value equals the string 'valid'.
*/
module(function ($provide) {
$provide.value('valdr', {
validate: function (typeName, fieldName, value) {
return {
valid: value === 'valid',
violations: violations
};
}
});
});
});

beforeEach(inject(function ($rootScope, _$compile_, _valdr_, _valdrEvents_, _valdrClasses_) {
$compile = _$compile_;
$scope = $rootScope.$new();
$scope.myObject = { field: 'fieldValue' };
valdr = _valdr_;
valdrEvents = _valdrEvents_;
valdrClasses = _valdrClasses_;
}));

// COMMON TESTS

function runFormItemCommonTests () {
it('should set the validity to false on ngModelController if validation fails', function () {
// when
$scope.$apply(function () {
$scope.myObject.field = 'invalid';
});

// then
expect(ngModelController.$valid).toBe(false);
expect(ngModelController.valdrViolations).toBe(violations);
});

it('should set the validity to true on ngModelController if validation is ok', function () {
// when
$scope.$apply(function () {
$scope.myObject.field = 'valid';
});

// then
expect(ngModelController.$valid).toBe(true);
expect(ngModelController.valdrViolations).toBe(violations);
});

it('should add class to surrounding element if data is valid', function () {
// given
var surroundingElement = element.find('div');

// when
$scope.$apply(function () {
$scope.myObject.field = 'valid';
});

// then
expect(surroundingElement.hasClass(valdrClasses.valid)).toBe(true);
expect(surroundingElement.hasClass(valdrClasses.invalid)).toBe(false);
});

it('should add class to surrounding element if data is invalid', function () {
// given
var surroundingElement = element.find('div');

// when
$scope.$apply(function () {
$scope.myObject.field = 'invalid';
});

// then
expect(surroundingElement.hasClass(valdrClasses.invalid)).toBe(true);
expect(surroundingElement.hasClass(valdrClasses.valid)).toBe(false);
});


it('should handle constraint changed events', function () {
// given
spyOn(valdr, 'validate').andCallThrough();

// when
$scope.$broadcast(valdrEvents.revalidate);

// then
expect(valdr.validate).toHaveBeenCalled();
});
}

describe('on input fields', function () {

beforeEach(function (){
compileInputTemplate();
});

runFormItemCommonTests();

it('should throw error if no field name is provided on the input', function () {
// given
var invalidInput =
'<form name="demoForm"><div valdr-type="TestClass">' +
'<input type="text" ng-model="myObject.field">' +
'</div></form>';

// when / then
expect(function () {
$compile(angular.element(invalidInput))($scope);
}).toThrow(new Error('form element is not bound to a field name'));
});
});

describe('on select elements', function () {

beforeEach(function (){
compileTemplate(selectTemplate);
ngModelController = element.find('select').controller('ngModel');
});

runFormItemCommonTests();

});

describe('blur behavior', function () {
var input, surroundingElement;

beforeEach(function () {
compileInputTemplate();
input = element.find('input');
surroundingElement = element.find('div');
});

it('should add dirtyBlurred class when the model is dirty, invalid and the input gets blurred', function () {
// given
ngModelController.$dirty = true;
ngModelController.$invalid = true;
ngModelController.$valid = false;

// when
input.triggerHandler('blur');

// then
expect(surroundingElement.hasClass(valdrClasses.dirtyBlurred)).toBe(true);
});

it('should add not dirtyBlurred class when the model is pristine, invalid and the input gets blurred', function () {
// given
ngModelController.$dirty = false;
ngModelController.$invalid = true;
ngModelController.$valid = false;

// when
input.triggerHandler('blur');

// then
expect(surroundingElement.hasClass(valdrClasses.dirtyBlurred)).toBe(false);
});

it('should add not dirtyBlurred class when the model is dirty, valid and the input gets blurred', function () {
// given
ngModelController.$dirty = true;
ngModelController.$invalid = false;
ngModelController.$valid = true;

// when
input.triggerHandler('blur');

// then
expect(surroundingElement.hasClass(valdrClasses.dirtyBlurred)).toBe(false);
});

});

});
Loading

0 comments on commit adbb18c

Please sign in to comment.