-
-
Checkbox 1
+
diff --git a/src/validator.directive.js b/src/validator.directive.js
index 045e0e3..9a70e36 100644
--- a/src/validator.directive.js
+++ b/src/validator.directive.js
@@ -21,9 +21,11 @@
var validFunc = function(element, validMessage, validation, scope, ctrl, attrs) {
var messageToShow = validMessage || $validationProvider.getDefaultMsg(validation).success;
var validCallback = $parse('success');
+ var messageId = attrs.messageId;
+ var validationGroup = attrs.validationGroup;
var messageElem;
- if (attrs.messageId) messageElem = angular.element(document.querySelector('#' + attrs.messageId));
+ if (messageId || validationGroup) messageElem = angular.element(document.querySelector('#' + (messageId || validationGroup)));
else messageElem = element.next();
if (element.attr('no-validation-message')) {
@@ -57,9 +59,11 @@
var invalidFunc = function(element, validMessage, validation, scope, ctrl, attrs) {
var messageToShow = validMessage || $validationProvider.getDefaultMsg(validation).error;
var invalidCallback = $parse('error');
+ var messageId = attrs.messageId;
+ var validationGroup = attrs.validationGroup;
var messageElem;
- if (attrs.messageId) messageElem = angular.element(document.querySelector('#' + attrs.messageId));
+ if (messageId || validationGroup) messageElem = angular.element(document.querySelector('#' + (messageId || validationGroup)));
else messageElem = element.next();
if (element.attr('no-validation-message')) {
@@ -80,6 +84,33 @@
return false;
};
+ /**
+ * Verify whether there is one of the elements inside the group valid.
+ * If so, it returns true, otherwise, it returns false
+ *
+ * @param scope
+ * @param element
+ * @param attrs
+ * @param ctrl
+ * @return {boolean}
+ */
+ var checkValidationGroup = function(scope, element, attrs, ctrl) {
+ var validationGroup = attrs.validationGroup;
+ var validationGroupElems = document.querySelectorAll('*[validation-group=' + validationGroup + ']');
+ var validationGroupElem;
+
+ // Set the element to be invalid
+ ctrl.$setValidity(ctrl.$name, false);
+
+ // Loop through all elements inside the group
+ for (var i = 0, len = validationGroupElems.length; i < len; i++) {
+ validationGroupElem = angular.element(validationGroupElems[i]);
+
+ // If the element is valid and it's not the same element with the current checking element, returns true
+ if (validationGroupElem.hasClass('ng-valid') && validationGroupElem[0] !== element[0]) return true;
+ }
+ return false;
+ };
/**
* collect elements for focus
@@ -109,6 +140,7 @@
var successMessage = validator + 'SuccessMessage';
var errorMessage = validator + 'ErrorMessage';
var expression = $validationProvider.getExpression(validator);
+ var validationGroup = attrs.validationGroup;
var valid = {
success: function() {
validFunc(element, attrs[successMessage], validator, scope, ctrl, attrs);
@@ -133,7 +165,11 @@
return $q.all([$validationProvider.getExpression(validator)(value, scope, element, attrs, validatorParam)])
.then(function(data) {
if (data && data.length > 0 && data[0]) return valid.success();
- else return valid.error();
+ else if (validationGroup) {
+ // Whenever the element is invalid, we'll check whether one of the elements inside the its group valid or not.
+ // If there is a valid element, its invalid message won't be shown, Otherwise, shows its invalid message.
+ if (!checkValidationGroup(scope, element, attrs, ctrl)) valid.error();
+ } else return valid.error();
}, function() {
return valid.error();
});
@@ -143,11 +179,14 @@
else if (expression.constructor === RegExp) {
// Only apply the test if the value is neither undefined or null
if (value !== undefined && value !== null) return $validationProvider.getExpression(validator).test(value) ? valid.success() : valid.error();
- else return valid.error();
+ else if (validationGroup) {
+ // Whenever the element is invalid, we'll check whether one of the elements inside the its group valid or not.
+ // If there is a valid element, its invalid message won't be shown, Otherwise, shows its invalid message.
+ if (!checkValidationGroup(scope, element, attrs, ctrl)) valid.error();
+ } else return valid.error();
} else return valid.error();
};
-
/**
* generate unique guid
*/
@@ -163,6 +202,15 @@
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
+ /**
+ * All attributes
+ */
+ var validator = attrs.validator;
+ var messageId = attrs.messageId;
+ var validationGroup = attrs.validationGroup;
+ var validMethod = attrs.validMethod;
+ var ngModel = attrs.ngModel;
+
/**
* watch
* @type {watch}
@@ -179,7 +227,7 @@
*
* Convert user input String to Array
*/
- var validation = attrs.validator.split(',');
+ var validation = validator.split(',');
/**
* guid use
@@ -198,7 +246,7 @@
/**
* Default Valid/Invalid Message
*/
- if (!attrs.messageId) element.after('
');
+ if (!(messageId || validationGroup)) element.after('
');
/**
* Set custom initial validity
@@ -223,7 +271,7 @@
ctrl.$setPristine();
ctrl.$setValidity(ctrl.$name, undefined);
ctrl.$render();
- if (attrs.messageId) angular.element(document.querySelector('#' + attrs.messageId)).html('');
+ if (messageId || validationGroup) angular.element(document.querySelector('#' + (messageId || validationGroup))).html('');
else element.next().html('');
if ($validationProvider.resetCallback) $validationProvider.resetCallback(element);
@@ -233,9 +281,7 @@
/**
* Check validator
*/
-
-
- var validMethod = (angular.isUndefined(attrs.validMethod)) ? $validationProvider.getValidMethod() : attrs.validMethod;
+ validMethod = (angular.isUndefined(validMethod)) ? $validationProvider.getValidMethod() : validMethod;
/**
* Click submit form, check the validity when submit
@@ -250,7 +296,7 @@
// clear previous scope.$watch
watch();
watch = scope.$watch(function() {
- return scope.$eval(attrs.ngModel);
+ return scope.$eval(ngModel);
}, function(value, oldValue) {
// don't watch when init
if (value === oldValue) {
@@ -289,7 +335,7 @@
*/
if (validMethod === 'blur') {
element.bind('blur', function() {
- var value = scope.$eval(attrs.ngModel);
+ var value = scope.$eval(ngModel);
scope.$apply(function() {
checkValidation(scope, element, attrs, ctrl, validation, value);
});
@@ -310,7 +356,7 @@
* This is the default method
*/
scope.$watch(function() {
- return scope.$eval(attrs.ngModel);
+ return scope.$eval(ngModel);
}, function(value) {
/**
* dirty, pristine, viewValue control here
@@ -320,7 +366,7 @@
ctrl.$setViewValue(ctrl.$viewValue);
} else if (ctrl.$pristine) {
// Don't validate form when the input is clean(pristine)
- if (attrs.messageId) angular.element(document.querySelector('#' + attrs.messageId)).html('');
+ if (messageId || validationGroup) angular.element(document.querySelector('#' + (messageId || validationGroup))).html('');
else element.next().html('');
return;
}
@@ -333,7 +379,7 @@
*/
attrs.$observe('noValidationMessage', function(value) {
var el;
- if (attrs.messageId) el = angular.element(document.querySelector('#' + attrs.messageId));
+ if (messageId || validationGroup) el = angular.element(document.querySelector('#' + (messageId || validationGroup)));
else el = element.next();
if (value === 'true' || value === true) el.css('display', 'none');
else if (value === 'false' || value === false) el.css('display', 'block');
diff --git a/test/unit/validationGroupSpec.js b/test/unit/validationGroupSpec.js
new file mode 100644
index 0000000..c25fd1a
--- /dev/null
+++ b/test/unit/validationGroupSpec.js
@@ -0,0 +1,269 @@
+'use strict';
+
+describe('validation-group directive', function() {
+ var $scope;
+ var $rootScope;
+ var $compile;
+ var $timeout;
+ var validationProvider;
+ var element;
+
+ beforeEach(module('validation.directive'));
+ beforeEach(module('validation.rule'));
+
+ describe('validation-group attribute for checkbox elements', function() {
+ var messageElem;
+
+ beforeEach(inject(function($injector) {
+ $rootScope = $injector.get('$rootScope');
+ $compile = $injector.get('$compile');
+ $scope = $rootScope.$new();
+
+ element = $compile('
')($scope);
+ angular.element(document.body).append(element);
+ $scope.$digest();
+ }));
+
+ afterEach(function() {
+ element.remove();
+ element = null;
+ });
+
+ it('should be pristine', function() {
+ expect($scope.Form.$pristine).toBe(true);
+ expect(element.hasClass('ng-pristine')).toBe(true);
+ expect($scope.Form.$valid).toBeUndefined(true);
+ expect($scope.Form.$invalid).toBeUndefined(true);
+ });
+
+ it('should be dirty and valid', function() {
+ $scope.Form.checkbox1.$setViewValue(true);
+ $scope.Form.checkbox2.$setViewValue(true);
+
+ expect($scope.Form.$dirty).toBe(true);
+ expect(element.hasClass('ng-dirty')).toBe(true);
+ expect($scope.Form.$valid).toBe(true);
+ expect(element.hasClass('ng-valid')).toBe(true);
+ });
+
+ it('should be dirty and invalid', function() {
+ $scope.Form.checkbox1.$setViewValue(true);
+ $scope.Form.checkbox2.$setViewValue(true);
+ $scope.Form.checkbox1.$setViewValue(false);
+ $scope.Form.checkbox2.$setViewValue(false);
+
+ expect($scope.Form.$dirty).toBe(true);
+ expect(element.hasClass('ng-dirty')).toBe(true);
+ expect($scope.Form.$invalid).toBe(true);
+ expect(element.hasClass('ng-invalid')).toBe(true);
+ });
+
+ it('should have a success message inside the #checkbox element when an element is valid', function() {
+ $scope.Form.checkbox1.$setViewValue(true);
+
+ messageElem = angular.element(element[0].querySelector('#checkbox > p'));
+ expect(messageElem.hasClass('validation-valid')).toBe(true);
+ });
+
+ it('should have an error message inside the #checkbox element when no element is valid', function() {
+ $scope.Form.checkbox1.$setViewValue(true);
+ $scope.Form.checkbox1.$setViewValue(false);
+
+ messageElem = angular.element(element[0].querySelector('#checkbox > p'));
+ expect(messageElem.hasClass('validation-invalid')).toBe(true);
+ });
+
+ it('should have a success message inside the #checkbox element when both elements are valid', function() {
+ $scope.Form.checkbox1.$setViewValue(true);
+ $scope.Form.checkbox2.$setViewValue(true);
+
+ messageElem = angular.element(element[0].querySelector('#checkbox > p'));
+ expect(messageElem.hasClass('validation-valid')).toBe(true);
+ });
+
+ it('should have a success message inside the #checkbox element when one of element is valid', function() {
+ $scope.Form.checkbox1.$setViewValue(true);
+ $scope.Form.checkbox2.$setViewValue(true);
+ $scope.Form.checkbox1.$setViewValue(false);
+
+ messageElem = angular.element(element[0].querySelector('#checkbox > p'));
+ expect(messageElem.hasClass('validation-valid')).toBe(true);
+ });
+
+ it('should have an error message inside the #checkbox element when both of elements are invalid', function() {
+ $scope.Form.checkbox1.$setViewValue(true);
+ $scope.Form.checkbox2.$setViewValue(true);
+ $scope.Form.checkbox1.$setViewValue(false);
+ $scope.Form.checkbox2.$setViewValue(false);
+
+ messageElem = angular.element(element[0].querySelector('#checkbox > p'));
+ expect(messageElem.hasClass('validation-invalid')).toBe(true);
+ });
+ });
+
+ describe('validation-group attribute for any elements', function() {
+ var messageElem;
+
+ beforeEach(inject(function($injector) {
+ $rootScope = $injector.get('$rootScope');
+ $compile = $injector.get('$compile');
+ $scope = $rootScope.$new();
+
+ element = $compile('
')($scope);
+ angular.element(document.body).append(element);
+ $scope.$digest();
+ }));
+
+ afterEach(function() {
+ element.remove();
+ element = null;
+ });
+
+ it('should be pristine', function() {
+ expect($scope.Form.$pristine).toBe(true);
+ expect(element.hasClass('ng-pristine')).toBe(true);
+ expect($scope.Form.$valid).toBeUndefined(true);
+ expect($scope.Form.$invalid).toBeUndefined(true);
+ });
+
+ it('should be dirty and valid', function() {
+ $scope.Form.email.$setViewValue('foo@bar.com');
+ $scope.Form.telephone.$setViewValue('065839481');
+
+ expect($scope.Form.$dirty).toBe(true);
+ expect(element.hasClass('ng-dirty')).toBe(true);
+ expect($scope.Form.$valid).toBe(true);
+ expect(element.hasClass('ng-valid')).toBe(true);
+ });
+
+ it('should be dirty and invalid', function() {
+ $scope.Form.email.$setViewValue('foo@bar.com');
+ $scope.Form.telephone.$setViewValue('065839481');
+ $scope.Form.email.$setViewValue();
+ $scope.Form.telephone.$setViewValue();
+
+ expect($scope.Form.$dirty).toBe(true);
+ expect(element.hasClass('ng-dirty')).toBe(true);
+ expect($scope.Form.$invalid).toBe(true);
+ expect(element.hasClass('ng-invalid')).toBe(true);
+ });
+
+ it('should have a success message inside the #contact element when an element is valid', function() {
+ $scope.Form.email.$setViewValue('foo@bar.com');
+
+ messageElem = angular.element(element[0].querySelector('#contact > p'));
+ expect(messageElem.hasClass('validation-valid')).toBe(true);
+ });
+
+ it('should have an error message inside the #contact element when no element is valid', function() {
+ $scope.Form.email.$setViewValue('foo@bar.com');
+ $scope.Form.email.$setViewValue();
+
+ messageElem = angular.element(element[0].querySelector('#contact > p'));
+ expect(messageElem.hasClass('validation-invalid')).toBe(true);
+ });
+
+ it('should have a success message inside the #contact element when both elements are valid', function() {
+ $scope.Form.email.$setViewValue('foo@bar.com');
+ $scope.Form.telephone.$setViewValue('065839481');
+
+ messageElem = angular.element(element[0].querySelector('#contact > p'));
+ expect(messageElem.hasClass('validation-valid')).toBe(true);
+ });
+
+ it('should have a success message inside the #contact element when one of element is valid', function() {
+ $scope.Form.email.$setViewValue('foo@bar.com');
+ $scope.Form.telephone.$setViewValue('065839481');
+ $scope.Form.email.$setViewValue();
+
+ messageElem = angular.element(element[0].querySelector('#contact > p'));
+ expect(messageElem.hasClass('validation-valid')).toBe(true);
+ });
+
+ it('should have an error message inside the #contact element when both of elements are invalid', function() {
+ $scope.Form.email.$setViewValue('foo@bar.com');
+ $scope.Form.telephone.$setViewValue('065839481');
+ $scope.Form.email.$setViewValue();
+ $scope.Form.telephone.$setViewValue();
+
+ messageElem = angular.element(element[0].querySelector('#contact > p'));
+ expect(messageElem.hasClass('validation-invalid')).toBe(true);
+ });
+ });
+
+ describe('validation-group attribute validated by using the provider', function() {
+ var successSpy;
+ var errorSpy;
+ beforeEach(inject(function($injector) {
+ $rootScope = $injector.get('$rootScope');
+ $compile = $injector.get('$compile');
+ validationProvider = $injector.get('$validation');
+ $timeout = $injector.get('$timeout');
+ $scope = $rootScope.$new();
+
+ element = $compile('
')($scope);
+ angular.element(document.body).append(element);
+ $scope.$digest();
+ }));
+
+ afterEach(function() {
+ element.remove();
+ element = null;
+ });
+
+ it('should validate a form and call a success callback', function() {
+ successSpy = jasmine.createSpy('successSpy');
+ errorSpy = jasmine.createSpy('errorSpy');
+
+ $scope.Form.checkbox1.$setViewValue(true);
+ $scope.Form.checkbox2.$setViewValue(true);
+
+ validationProvider.validate($scope.Form)
+ .success(function() {
+ successSpy();
+ })
+ .error(function() {
+ errorSpy();
+ });
+ $timeout.flush();
+ expect(successSpy).toHaveBeenCalled();
+ expect(errorSpy).not.toHaveBeenCalled();
+ });
+
+ it('should validate a form element and call a success callback', function() {
+ successSpy = jasmine.createSpy('successSpy');
+ errorSpy = jasmine.createSpy('errorSpy');
+
+ $scope.Form.checkbox1.$setViewValue(true);
+
+ validationProvider.validate($scope.Form.checkbox1)
+ .success(function() {
+ successSpy();
+ })
+ .error(function() {
+ errorSpy();
+ });
+ $timeout.flush();
+ expect(successSpy).toHaveBeenCalled();
+ expect(errorSpy).not.toHaveBeenCalled();
+ });
+
+ it('should validate a form element and call an error callback', function() {
+ successSpy = jasmine.createSpy('successSpy');
+ errorSpy = jasmine.createSpy('errorSpy');
+
+ $scope.Form.checkbox1.$setViewValue(true);
+
+ validationProvider.validate($scope.Form)
+ .success(function() {
+ successSpy();
+ })
+ .error(function() {
+ errorSpy();
+ });
+ $timeout.flush();
+ expect(successSpy).not.toHaveBeenCalled();
+ expect(errorSpy).toHaveBeenCalled();
+ });
+ });
+});