From e4f589ecfb1ae5480ac5531e6106ef91cbff3436 Mon Sep 17 00:00:00 2001 From: dlear Date: Thu, 18 Jun 2015 10:19:10 +0100 Subject: [PATCH 01/44] Update angular-validator.js --- src/angular-validator.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/angular-validator.js b/src/angular-validator.js index 1efbe3f..a4e8b63 100644 --- a/src/angular-validator.js +++ b/src/angular-validator.js @@ -98,7 +98,7 @@ angular.module('angularValidator').directive('angularValidator', } else { // Determine if the element in question is to be updated on blur - isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; + var isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; if (isDirtyElement){ updateValidationMessage(elementToWatch); @@ -246,4 +246,4 @@ angular.module('angularValidator').directive('angularValidator', } }; } -); \ No newline at end of file +); From 0cbc53e8e245b9b1c62557671fe8cd84c704783c Mon Sep 17 00:00:00 2001 From: David Lear Date: Fri, 19 Jun 2015 08:04:12 +0100 Subject: [PATCH 02/44] Updated dist for Missing "var" declaration #39 --- dist/angular-validator.js | 6 +++--- dist/angular-validator.min.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dist/angular-validator.js b/dist/angular-validator.js index 1713b03..a4e8b63 100644 --- a/dist/angular-validator.js +++ b/dist/angular-validator.js @@ -98,7 +98,7 @@ angular.module('angularValidator').directive('angularValidator', } else { // Determine if the element in question is to be updated on blur - isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; + var isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; if (isDirtyElement){ updateValidationMessage(elementToWatch); @@ -173,7 +173,7 @@ angular.module('angularValidator').directive('angularValidator', // Only add validation messages if the form field is $dirty or the form has been submitted - if (scopeElementModel.$dirty || scope[element.form.name] && scope[element.form.name].submitted) { + if (scopeElementModel.$dirty || scope[element.form.name].submitted) { if (scopeElementModel.$error.required) { // If there is a custom required message display it @@ -232,7 +232,7 @@ angular.module('angularValidator').directive('angularValidator', // Only add/remove validation classes if the field is $dirty or the form has been submitted - if (formField.$dirty || scope[element.form.name] && scope[element.form.name].submitted) { + if (formField.$dirty || scope[element.form.name].submitted) { if (formField.$invalid) { angular.element(element.parentNode).addClass('has-error'); diff --git a/dist/angular-validator.min.js b/dist/angular-validator.min.js index 2e17421..18904f2 100644 --- a/dist/angular-validator.min.js +++ b/dist/angular-validator.min.js @@ -1 +1 @@ -angular.module("angularValidator",[]),angular.module("angularValidator").directive("angularValidator",function(){return{restrict:"A",link:function(a,b){function c(a){for(var b=0;b Required"},d=function(){return" Invalid"};if(b.name in n){var e=n[b.name],f=j(b);f&&f.remove(),(e.$dirty||a[b.form.name].submitted)&&(e.$error.required?angular.element(b).after("required-message"in b.attributes?i(b.attributes["required-message"].value):i(c)):e.$valid||angular.element(b).after("invalid-message"in b.attributes?i(b.attributes["invalid-message"].value):i(d)))}}function i(b){return""}function j(a){for(var b=angular.element(a).parent().children(),c=0;c Required"},d=function(){return" Invalid"};if(b.name in p){var e=p[b.name],f=l(b);f&&f.remove(),(e.$dirty||a[b.form.name].submitted)&&(e.$error.required?"required-message"in b.attributes?angular.element(b).after(k(b.attributes["required-message"].value)):angular.element(b).after(k(c)):e.$valid||("invalid-message"in b.attributes?angular.element(b).after(k(b.attributes["invalid-message"].value)):angular.element(b).after(k(d))))}}function k(b){return""}function l(a){for(var b=angular.element(a).parent().children(),c=0;c Date: Thu, 18 Jun 2015 10:19:10 +0100 Subject: [PATCH 03/44] Update angular-validator.js --- src/angular-validator.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/angular-validator.js b/src/angular-validator.js index 1efbe3f..a4e8b63 100644 --- a/src/angular-validator.js +++ b/src/angular-validator.js @@ -98,7 +98,7 @@ angular.module('angularValidator').directive('angularValidator', } else { // Determine if the element in question is to be updated on blur - isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; + var isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; if (isDirtyElement){ updateValidationMessage(elementToWatch); @@ -246,4 +246,4 @@ angular.module('angularValidator').directive('angularValidator', } }; } -); \ No newline at end of file +); From 34eb15f2a71a6fd79eae8d15e0eec1fbe37ac4ed Mon Sep 17 00:00:00 2001 From: David Lear Date: Fri, 19 Jun 2015 08:04:12 +0100 Subject: [PATCH 04/44] Updated dist for Missing "var" declaration #39 --- dist/angular-validator.js | 6 +++--- dist/angular-validator.min.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dist/angular-validator.js b/dist/angular-validator.js index 1713b03..a4e8b63 100644 --- a/dist/angular-validator.js +++ b/dist/angular-validator.js @@ -98,7 +98,7 @@ angular.module('angularValidator').directive('angularValidator', } else { // Determine if the element in question is to be updated on blur - isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; + var isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; if (isDirtyElement){ updateValidationMessage(elementToWatch); @@ -173,7 +173,7 @@ angular.module('angularValidator').directive('angularValidator', // Only add validation messages if the form field is $dirty or the form has been submitted - if (scopeElementModel.$dirty || scope[element.form.name] && scope[element.form.name].submitted) { + if (scopeElementModel.$dirty || scope[element.form.name].submitted) { if (scopeElementModel.$error.required) { // If there is a custom required message display it @@ -232,7 +232,7 @@ angular.module('angularValidator').directive('angularValidator', // Only add/remove validation classes if the field is $dirty or the form has been submitted - if (formField.$dirty || scope[element.form.name] && scope[element.form.name].submitted) { + if (formField.$dirty || scope[element.form.name].submitted) { if (formField.$invalid) { angular.element(element.parentNode).addClass('has-error'); diff --git a/dist/angular-validator.min.js b/dist/angular-validator.min.js index 2e17421..18904f2 100644 --- a/dist/angular-validator.min.js +++ b/dist/angular-validator.min.js @@ -1 +1 @@ -angular.module("angularValidator",[]),angular.module("angularValidator").directive("angularValidator",function(){return{restrict:"A",link:function(a,b){function c(a){for(var b=0;b Required"},d=function(){return" Invalid"};if(b.name in n){var e=n[b.name],f=j(b);f&&f.remove(),(e.$dirty||a[b.form.name].submitted)&&(e.$error.required?angular.element(b).after("required-message"in b.attributes?i(b.attributes["required-message"].value):i(c)):e.$valid||angular.element(b).after("invalid-message"in b.attributes?i(b.attributes["invalid-message"].value):i(d)))}}function i(b){return""}function j(a){for(var b=angular.element(a).parent().children(),c=0;c Required"},d=function(){return" Invalid"};if(b.name in p){var e=p[b.name],f=l(b);f&&f.remove(),(e.$dirty||a[b.form.name].submitted)&&(e.$error.required?"required-message"in b.attributes?angular.element(b).after(k(b.attributes["required-message"].value)):angular.element(b).after(k(c)):e.$valid||("invalid-message"in b.attributes?angular.element(b).after(k(b.attributes["invalid-message"].value)):angular.element(b).after(k(d))))}}function k(b){return""}function l(a){for(var b=angular.element(a).parent().children(),c=0;c Date: Fri, 19 Jun 2015 16:18:42 -0400 Subject: [PATCH 05/44] add form invalid message feature, inject custom service into directive. --- README.md | 20 ++ demo/app.js | 87 ++++---- demo/index.html | 37 ++++ dist/angular-validator.js | 376 +++++++++++++++++---------------- dist/angular-validator.min.js | 2 +- karma.conf.js | 2 +- src/angular-validator.js | 374 ++++++++++++++++---------------- test/angular-validator-spec.js | 256 ++++++++++++++-------- 8 files changed, 662 insertions(+), 492 deletions(-) diff --git a/README.md b/README.md index 1a552d7..5add76f 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Despite Angular's awesomeness, validation in Angular is still a pain in the ass. * Supports multi-field dependent validation (one field depends on another such as password matching) * Works with or without `novalidate` * Works with Bootstrap out of the box (although Bootstrap is not required) +* Support form invalid message service where manage invalid messages in one place and save code in HTML ## Demo @@ -122,6 +123,25 @@ Need a feature, found a bug? Create an issue. Dont have any issues, love the pro ``` Use `angular-validator-submit` to specify the function to be called when the form is submitted. Note that the function is not called if the form is invalid. +**Use form invalid message service** +``` +
+

Form invalid message:

+
+ +
+
+
+
+``` +Use `invalid-message` on form element to provide the name of the service in which invalid messages are managed. You need to provide a `message` function in your service, which returns the messages you defined. Form invalid message service saves repetitive code in HTML because you do not need to use invalid-message attribute on every field. Please see the demo for examples. + **Resetting the form** ``` myBeautifulForm.reset() diff --git a/demo/app.js b/demo/app.js index bf45201..e86d474 100644 --- a/demo/app.js +++ b/demo/app.js @@ -1,40 +1,55 @@ angular.module('angular-validator-demo', ['angularValidator']); -angular.module('angular-validator-demo').controller('DemoCtrl', function($scope) { - - $scope.submitMyForm = function() { - alert("Form submitted"); - }; - - - $scope.myCustomValidator = function(text) { - return true; - }; - - - $scope.anotherCustomValidator = function(text) { - if (text === "rainbow") { - return true; - } else return "type in 'rainbow'"; - }; - - - $scope.passwordValidator = function(password) { - - if (!password) { - return; - } - else if (password.length < 6) { - return "Password must be at least " + 6 + " characters long"; - } - else if (!password.match(/[A-Z]/)) { - return "Password must have at least one capital letter"; - } - else if (!password.match(/[0-9]/)) { - return "Password must have at least one number"; - } - - return true; - }; +angular.module('angular-validator-demo').controller('DemoCtrl', function ($scope) { + + $scope.submitMyForm = function () { + alert("Form submitted"); + }; + + + $scope.myCustomValidator = function (text) { + return true; + }; + + + $scope.anotherCustomValidator = function (text) { + if (text === "rainbow") { + return true; + } else return "type in 'rainbow'"; + }; + + + $scope.passwordValidator = function (password) { + + if (!password) { + return; + } + else if (password.length < 6) { + return "Password must be at least " + 6 + " characters long"; + } + else if (!password.match(/[A-Z]/)) { + return "Password must have at least one capital letter"; + } + else if (!password.match(/[0-9]/)) { + return "Password must have at least one number"; + } + + return true; + }; +}).factory('customMessage', function () { + // invalid message service with message function + return { + // scopeElementModel is the object in scope version, element is the object in DOM version + message: function (scopeElementModel, element) { + var errors = scopeElementModel.$error; + if (errors.maxlength) { + // be careful with the quote + return "'Should be no longer than " + element.attributes['ng-maxlength'].value + " characters!'"; + } else { + // default message + return "'This field is invalid!'"; + } + } + }; }); \ No newline at end of file diff --git a/demo/index.html b/demo/index.html index e6f00ab..68da567 100644 --- a/demo/index.html +++ b/demo/index.html @@ -145,6 +145,43 @@

Password validation and password matching example

+ +
+

Form invalid message:

+
+ +
+
+
+
+

Field invalid message hides form invalid message

+
+ +
+
+
+
+
+
+ + +
+
+
+

Scope Sneak Peak

diff --git a/dist/angular-validator.js b/dist/angular-validator.js index 1713b03..6b78992 100644 --- a/dist/angular-validator.js +++ b/dist/angular-validator.js @@ -1,249 +1,263 @@ -angular.module('angularValidator', []); +var angularValidator = angular.module('angularValidator', []); -angular.module('angularValidator').directive('angularValidator', - function() { - return { - restrict: 'A', - link: function(scope, element, attrs, fn) { +angularValidator.directive('angularValidator', ['$injector', + function ($injector) { + return { + restrict: 'A', + link: function (scope, element, attrs, ctrl) { - // This is the DOM form element - var DOMForm = angular.element(element)[0]; + // This is the DOM form element + var DOMForm = angular.element(element)[0]; - // This is the the scope form model - // All validation states are contained here - var form_name = DOMForm.attributes['name'].value; - var scopeForm = scope[form_name]; + // This is the the scope form model + // All validation states are contained here + var form_name = DOMForm.attributes['name'].value; + var scopeForm = scope[form_name]; - // Set the default submitted state to false - scopeForm.submitted = false; + // Set the default submitted state to false + scopeForm.submitted = false; - - // Watch form length to add watches for new form elements - scope.$watch(function(){return DOMForm.length;}, function(){ - setupWatches(DOMForm); - }); - - - // Intercept and handle submit events of the form - element.on('submit', function(event) { - event.preventDefault(); - scope.$apply(function() { - scopeForm.submitted = true; + + // Watch form length to add watches for new form elements + scope.$watch(function () { + return DOMForm.length; + }, function () { + setupWatches(DOMForm); }); - // If the form is valid then call the function that is declared in the angular-validator-submit atrribute on the form element - if (scopeForm.$valid) { - scope.$apply(function() { - scope.$eval(DOMForm.attributes["angular-validator-submit"].value); - }); - } - }); + // Intercept and handle submit events of the form + element.on('submit', function (event) { + event.preventDefault(); + scope.$apply(function () { + scopeForm.submitted = true; + }); - scopeForm.reset = function(){ - // Clear all the form values - for (var i = 0; i < DOMForm.length; i++) { - if (DOMForm[i].name){ - scopeForm[DOMForm[i].name].$setViewValue(""); - scopeForm[DOMForm[i].name].$render(); + // If the form is valid then call the function that is declared in the angular-validator-submit atrribute on the form element + if (scopeForm.$valid) { + scope.$apply(function () { + scope.$eval(DOMForm.attributes["angular-validator-submit"].value); + }); } - } + }); - scopeForm.submitted = false; - scopeForm.$setPristine(); - }; + scopeForm.reset = function () { + // Clear all the form values + for (var i = 0; i < DOMForm.length; i++) { + if (DOMForm[i].name) { + scopeForm[DOMForm[i].name].$setViewValue(""); + scopeForm[DOMForm[i].name].$render(); + } + } + scopeForm.submitted = false; + scopeForm.$setPristine(); + }; - // Setup watches on all form fields - setupWatches(DOMForm); + // Setup watches on all form fields + setupWatches(DOMForm); - // Iterate through the form fields and setup watches on each one - function setupWatches(formElement) { - for (var i = 0; i < formElement.length; i++) { - // This ensures we are only watching form fields - if (i in formElement) { - setupWatch(formElement[i]); + //check if there is invalid message service for the entire form; if yes, return the injected service; if no, return false; + function hasFormInvalidMessage(formElement) { + if (formElement && 'invalid-message' in formElement.attributes) { + return $injector.get(formElement.attributes['invalid-message'].value); + } else { + return false; } } - } + // Iterate through the form fields and setup watches on each one + function setupWatches(formElement) { + var formInvalidMessage = hasFormInvalidMessage(formElement); + for (var i = 0; i < formElement.length; i++) { + // This ensures we are only watching form fields + if (i in formElement) { + setupWatch(formElement[i], formInvalidMessage); + } + } + } - // Setup $watch on a single formfield - function setupWatch(elementToWatch) { - if (elementToWatch.isWatchedByValidator){ - return; - } + // Setup $watch on a single formfield + function setupWatch(elementToWatch, formInvalidMessage) { - elementToWatch.isWatchedByValidator = true; + if (elementToWatch.isWatchedByValidator) { + return; + } - // If element is set to validate on blur then update the element on blur - if ("validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "blur") { - angular.element(elementToWatch).on('blur', function() { - updateValidationMessage(elementToWatch); - updateValidationClass(elementToWatch); - }); - } + elementToWatch.isWatchedByValidator = true; - scope.$watch(function() { - return elementToWatch.value + elementToWatch.required + scopeForm.submitted + checkElementValidity(elementToWatch) + getDirtyValue(scopeForm[elementToWatch.name]) + getValidValue(scopeForm[elementToWatch.name]); - }, - function() { - - if (scopeForm.submitted){ - updateValidationMessage(elementToWatch); + // If element is set to validate on blur then update the element on blur + if ("validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "blur") { + angular.element(elementToWatch).on('blur', function () { + updateValidationMessage(elementToWatch, formInvalidMessage); updateValidationClass(elementToWatch); - } - else { - // Determine if the element in question is to be updated on blur - isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; + }); + } - if (isDirtyElement){ - updateValidationMessage(elementToWatch); + scope.$watch(function () { + return elementToWatch.value + elementToWatch.required + scopeForm.submitted + checkElementValidity(elementToWatch) + getDirtyValue(scopeForm[elementToWatch.name]) + getValidValue(scopeForm[elementToWatch.name]); + }, + function () { + + if (scopeForm.submitted) { + updateValidationMessage(elementToWatch, formInvalidMessage); updateValidationClass(elementToWatch); } - // This will get called in the case of resetting the form. This only gets called for elements that update on blur and submit. - else if (scopeForm[elementToWatch.name] && scopeForm[elementToWatch.name].$pristine){ - updateValidationMessage(elementToWatch); - updateValidationClass(elementToWatch); + else { + // Determine if the element in question is to be updated on blur + isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; + + if (isDirtyElement) { + updateValidationMessage(elementToWatch, formInvalidMessage); + updateValidationClass(elementToWatch); + } + // This will get called in the case of resetting the form. This only gets called for elements that update on blur and submit. + else if (scopeForm[elementToWatch.name] && scopeForm[elementToWatch.name].$pristine) { + updateValidationMessage(elementToWatch, formInvalidMessage); + updateValidationClass(elementToWatch); + } } - } - }); - } + }); + } - // Returns the $dirty value of the element if it exists - function getDirtyValue(element) { - if (element) { - if ("$dirty" in element) { - return element.$dirty; + // Returns the $dirty value of the element if it exists + function getDirtyValue(element) { + if (element) { + if ("$dirty" in element) { + return element.$dirty; + } } } - } - function getValidValue(element) { - if (element) { - if ("$valid" in element) { - return element.$valid; + function getValidValue(element) { + if (element) { + if ("$valid" in element) { + return element.$valid; + } } } - } - function checkElementValidity(element) { - // If element has a custom validation function - if ("validator" in element.attributes) { - // Call the custom validator function - var isElementValid = scope.$eval(element.attributes.validator.value); - scopeForm[element.name].$setValidity("angularValidator", isElementValid); - return isElementValid; + function checkElementValidity(element) { + // If element has a custom validation function + if ("validator" in element.attributes) { + // Call the custom validator function + var isElementValid = scope.$eval(element.attributes.validator.value); + scopeForm[element.name].$setValidity("angularValidator", isElementValid); + return isElementValid; + } } - } - // Adds and removes an error message as a sibling element of the form field - // depending on the validity of the form field and the submitted state of the form. - // Will use default message if a custom message is not given - function updateValidationMessage(element) { + // Adds and removes an error message as a sibling element of the form field + // depending on the validity of the form field and the submitted state of the form. + // Will use default message if a custom message is not given + function updateValidationMessage(element, formInvalidMessage) { - var defaultRequiredMessage = function() { - return " Required"; - }; - var defaultInvalidMessage = function() { - return " Invalid"; - }; + var defaultRequiredMessage = function () { + return " Required"; + }; + var defaultInvalidMessage = function () { + return " Invalid"; + }; - // Make sure the element is a form field and not a button for example - // Only form elements should have names. - if (!(element.name in scopeForm)) { - return; - } + // Make sure the element is a form field and not a button for example + // Only form elements should have names. + if (!(element.name in scopeForm)) { + return; + } - var scopeElementModel = scopeForm[element.name]; + var scopeElementModel = scopeForm[element.name]; - // Remove all validation messages - var validationMessageElement = isValidationMessagePresent(element); - if (validationMessageElement) { - validationMessageElement.remove(); - } + // Remove all validation messages + var validationMessageElement = isValidationMessagePresent(element); + if (validationMessageElement) { + validationMessageElement.remove(); + } - // Only add validation messages if the form field is $dirty or the form has been submitted - if (scopeElementModel.$dirty || scope[element.form.name] && scope[element.form.name].submitted) { + // Only add validation messages if the form field is $dirty or the form has been submitted + if (scopeElementModel.$dirty || scope[element.form.name].submitted) { - if (scopeElementModel.$error.required) { - // If there is a custom required message display it - if ("required-message" in element.attributes) { - angular.element(element).after(generateErrorMessage(element.attributes['required-message'].value)); - } - // Display the default required message - else { - angular.element(element).after(generateErrorMessage(defaultRequiredMessage)); - } - } else if (!scopeElementModel.$valid) { - // If there is a custom validation message add it - if ("invalid-message" in element.attributes) { - angular.element(element).after(generateErrorMessage(element.attributes['invalid-message'].value)); - } - // Display the default error message - else { - angular.element(element).after(generateErrorMessage(defaultInvalidMessage)); + if (scopeElementModel.$error.required) { + // If there is a custom required message display it + if ("required-message" in element.attributes) { + angular.element(element).after(generateErrorMessage(element.attributes['required-message'].value)); + } + // Display the default required message + else { + angular.element(element).after(generateErrorMessage(defaultRequiredMessage)); + } + } else if (!scopeElementModel.$valid) { + // If there is a custom validation message add it + if ("invalid-message" in element.attributes) { + angular.element(element).after(generateErrorMessage(element.attributes['invalid-message'].value)); + } + // Display error message provided by custom service + else if (formInvalidMessage) { + angular.element(element).after(generateErrorMessage(formInvalidMessage.message(scopeElementModel, element))); + } + // Display the default error message + else { + angular.element(element).after(generateErrorMessage(defaultInvalidMessage)); + } } } } - } - function generateErrorMessage(messageText) { - return ""; - } + function generateErrorMessage(messageText) { + return ""; + } - // Returns the validation message element or False - function isValidationMessagePresent(element) { - var elementSiblings = angular.element(element).parent().children(); - for (var i = 0; i < elementSiblings.length; i++) { - if (angular.element(elementSiblings[i]).hasClass("validationMessage")) { - return angular.element(elementSiblings[i]); + // Returns the validation message element or False + function isValidationMessagePresent(element) { + var elementSiblings = angular.element(element).parent().children(); + for (var i = 0; i < elementSiblings.length; i++) { + if (angular.element(elementSiblings[i]).hasClass("validationMessage")) { + return angular.element(elementSiblings[i]); + } } + return false; } - return false; - } - // Adds and removes .has-error class to both the form element and the form element's parent - // depending on the validity of the element and the submitted state of the form - function updateValidationClass(element) { - // Make sure the element is a form field and not a button for example - // Only form fields should have names. - if (!(element.name in scopeForm)) { - return; - } - var formField = scopeForm[element.name]; + // Adds and removes .has-error class to both the form element and the form element's parent + // depending on the validity of the element and the submitted state of the form + function updateValidationClass(element) { + // Make sure the element is a form field and not a button for example + // Only form fields should have names. + if (!(element.name in scopeForm)) { + return; + } + var formField = scopeForm[element.name]; - // This is extra for users wishing to implement the .has-error class on the field itself - // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element - angular.element(element).removeClass('has-error'); - angular.element(element.parentNode).removeClass('has-error'); + // This is extra for users wishing to implement the .has-error class on the field itself + // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element + angular.element(element).removeClass('has-error'); + angular.element(element.parentNode).removeClass('has-error'); - // Only add/remove validation classes if the field is $dirty or the form has been submitted - if (formField.$dirty || scope[element.form.name] && scope[element.form.name].submitted) { - if (formField.$invalid) { - angular.element(element.parentNode).addClass('has-error'); + // Only add/remove validation classes if the field is $dirty or the form has been submitted + if (formField.$dirty || scope[element.form.name].submitted) { + if (formField.$invalid) { + angular.element(element.parentNode).addClass('has-error'); - // This is extra for users wishing to implement the .has-error class on the field itself - // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element - angular.element(element).addClass('has-error'); + // This is extra for users wishing to implement the .has-error class on the field itself + // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element + angular.element(element).addClass('has-error'); + } } } - } - } - }; - } -); + } + }; + }] +); \ No newline at end of file diff --git a/dist/angular-validator.min.js b/dist/angular-validator.min.js index 2e17421..693fd2e 100644 --- a/dist/angular-validator.min.js +++ b/dist/angular-validator.min.js @@ -1 +1 @@ -angular.module("angularValidator",[]),angular.module("angularValidator").directive("angularValidator",function(){return{restrict:"A",link:function(a,b){function c(a){for(var b=0;b Required"},d=function(){return" Invalid"};if(b.name in n){var e=n[b.name],f=j(b);f&&f.remove(),(e.$dirty||a[b.form.name].submitted)&&(e.$error.required?angular.element(b).after("required-message"in b.attributes?i(b.attributes["required-message"].value):i(c)):e.$valid||angular.element(b).after("invalid-message"in b.attributes?i(b.attributes["invalid-message"].value):i(d)))}}function i(b){return""}function j(a){for(var b=angular.element(a).parent().children(),c=0;c Required"},e=function(){return" Invalid"};if(a.name in r){var f=r[a.name],g=n(a);g&&g.remove(),(f.$dirty||b[a.form.name].submitted)&&(f.$error.required?"required-message"in a.attributes?angular.element(a).after(m(a.attributes["required-message"].value)):angular.element(a).after(m(d)):f.$valid||("invalid-message"in a.attributes?angular.element(a).after(m(a.attributes["invalid-message"].value)):c?angular.element(a).after(m(c.message(f,a))):angular.element(a).after(m(e))))}}function m(a){return""}function n(a){for(var b=angular.element(a).parent().children(),c=0;c Required"; - }; - var defaultInvalidMessage = function() { - return " Invalid"; - }; + var defaultRequiredMessage = function () { + return " Required"; + }; + var defaultInvalidMessage = function () { + return " Invalid"; + }; - // Make sure the element is a form field and not a button for example - // Only form elements should have names. - if (!(element.name in scopeForm)) { - return; - } + // Make sure the element is a form field and not a button for example + // Only form elements should have names. + if (!(element.name in scopeForm)) { + return; + } - var scopeElementModel = scopeForm[element.name]; + var scopeElementModel = scopeForm[element.name]; - // Remove all validation messages - var validationMessageElement = isValidationMessagePresent(element); - if (validationMessageElement) { - validationMessageElement.remove(); - } + // Remove all validation messages + var validationMessageElement = isValidationMessagePresent(element); + if (validationMessageElement) { + validationMessageElement.remove(); + } - // Only add validation messages if the form field is $dirty or the form has been submitted - if (scopeElementModel.$dirty || scope[element.form.name].submitted) { + // Only add validation messages if the form field is $dirty or the form has been submitted + if (scopeElementModel.$dirty || scope[element.form.name].submitted) { - if (scopeElementModel.$error.required) { - // If there is a custom required message display it - if ("required-message" in element.attributes) { - angular.element(element).after(generateErrorMessage(element.attributes['required-message'].value)); - } - // Display the default required message - else { - angular.element(element).after(generateErrorMessage(defaultRequiredMessage)); - } - } else if (!scopeElementModel.$valid) { - // If there is a custom validation message add it - if ("invalid-message" in element.attributes) { - angular.element(element).after(generateErrorMessage(element.attributes['invalid-message'].value)); - } - // Display the default error message - else { - angular.element(element).after(generateErrorMessage(defaultInvalidMessage)); + if (scopeElementModel.$error.required) { + // If there is a custom required message display it + if ("required-message" in element.attributes) { + angular.element(element).after(generateErrorMessage(element.attributes['required-message'].value)); + } + // Display the default required message + else { + angular.element(element).after(generateErrorMessage(defaultRequiredMessage)); + } + } else if (!scopeElementModel.$valid) { + // If there is a custom validation message add it + if ("invalid-message" in element.attributes) { + angular.element(element).after(generateErrorMessage(element.attributes['invalid-message'].value)); + } + // Display error message provided by custom service + else if (formInvalidMessage) { + angular.element(element).after(generateErrorMessage(formInvalidMessage.message(scopeElementModel, element))); + } + // Display the default error message + else { + angular.element(element).after(generateErrorMessage(defaultInvalidMessage)); + } } } } - } - function generateErrorMessage(messageText) { - return ""; - } + function generateErrorMessage(messageText) { + return ""; + } - // Returns the validation message element or False - function isValidationMessagePresent(element) { - var elementSiblings = angular.element(element).parent().children(); - for (var i = 0; i < elementSiblings.length; i++) { - if (angular.element(elementSiblings[i]).hasClass("validationMessage")) { - return angular.element(elementSiblings[i]); + // Returns the validation message element or False + function isValidationMessagePresent(element) { + var elementSiblings = angular.element(element).parent().children(); + for (var i = 0; i < elementSiblings.length; i++) { + if (angular.element(elementSiblings[i]).hasClass("validationMessage")) { + return angular.element(elementSiblings[i]); + } } + return false; } - return false; - } - // Adds and removes .has-error class to both the form element and the form element's parent - // depending on the validity of the element and the submitted state of the form - function updateValidationClass(element) { - // Make sure the element is a form field and not a button for example - // Only form fields should have names. - if (!(element.name in scopeForm)) { - return; - } - var formField = scopeForm[element.name]; + // Adds and removes .has-error class to both the form element and the form element's parent + // depending on the validity of the element and the submitted state of the form + function updateValidationClass(element) { + // Make sure the element is a form field and not a button for example + // Only form fields should have names. + if (!(element.name in scopeForm)) { + return; + } + var formField = scopeForm[element.name]; - // This is extra for users wishing to implement the .has-error class on the field itself - // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element - angular.element(element).removeClass('has-error'); - angular.element(element.parentNode).removeClass('has-error'); + // This is extra for users wishing to implement the .has-error class on the field itself + // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element + angular.element(element).removeClass('has-error'); + angular.element(element.parentNode).removeClass('has-error'); - // Only add/remove validation classes if the field is $dirty or the form has been submitted - if (formField.$dirty || scope[element.form.name].submitted) { - if (formField.$invalid) { - angular.element(element.parentNode).addClass('has-error'); + // Only add/remove validation classes if the field is $dirty or the form has been submitted + if (formField.$dirty || scope[element.form.name].submitted) { + if (formField.$invalid) { + angular.element(element.parentNode).addClass('has-error'); - // This is extra for users wishing to implement the .has-error class on the field itself - // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element - angular.element(element).addClass('has-error'); + // This is extra for users wishing to implement the .has-error class on the field itself + // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element + angular.element(element).addClass('has-error'); + } } } - } - } - }; - } + } + }; + }] ); \ No newline at end of file diff --git a/test/angular-validator-spec.js b/test/angular-validator-spec.js index 3851b32..7c51caa 100644 --- a/test/angular-validator-spec.js +++ b/test/angular-validator-spec.js @@ -1,136 +1,206 @@ - describe('angularValidator', function() { +describe('angularValidator', function () { - beforeEach(angular.mock.module('angularValidator')); + beforeEach(angular.mock.module('angularValidator')); - var scope, compile; - beforeEach(inject(function($rootScope, $compile) { - scope = $rootScope.$new(); + var scope, compile; - htmlForm = angular.element( - '
' + - '' + - '' + - '' + - '' + - '
' - ); + describe('angularValidator without form invalid message', function () { + beforeEach(inject(function ($rootScope, $compile) { + scope = $rootScope.$new(); - element = $compile(htmlForm)(scope); - scope.$digest(); - })); + htmlForm = angular.element( + '
' + + '' + + '' + + '' + + '' + + '
' + ); + element = $compile(htmlForm)(scope); + scope.$digest(); + })); - describe('$dirty tests', function() { + describe('$dirty tests', function () { - it('should be dirty', function() { - scope.myForm.lastName.$setViewValue('lastName'); - scope.$digest(); + it('should be dirty', function () { + scope.myForm.lastName.$setViewValue('lastName'); + scope.$digest(); - expect(scope.myForm.lastName.$dirty).toBe(true); - expect(element.hasClass('ng-dirty')).toBe(true); + expect(scope.myForm.lastName.$dirty).toBe(true); + expect(element.hasClass('ng-dirty')).toBe(true); - }); + }); - it('should not be $dirty', function() { - expect(scope.myForm.$pristine).toBe(true); - expect(element.hasClass('ng-pristine')).toBe(true); - }); - }); + it('should not be $dirty', function () { + expect(scope.myForm.$pristine).toBe(true); + expect(element.hasClass('ng-pristine')).toBe(true); + }); + }); - describe('$valid tests', function() { + describe('$valid tests', function () { - it('should be $valid', function() { - scope.myForm.lastName.$setViewValue('lastName'); - scope.$digest(); + it('should be $valid', function () { + scope.myForm.lastName.$setViewValue('lastName'); + scope.$digest(); - expect(scope.myForm.lastName.$invalid).toBe(false); - }); + expect(scope.myForm.lastName.$invalid).toBe(false); + }); - it('should not be $valid', function() { - scope.myForm.lastName.$setViewValue('sss'); - scope.$digest(); + it('should not be $valid', function () { + scope.myForm.lastName.$setViewValue('sss'); + scope.$digest(); - expect(scope.myForm.lastName.$invalid).toBe(true); - }); - }); + expect(scope.myForm.lastName.$invalid).toBe(true); + }); + }); + describe('custom validator tests', function () { - describe('custom validator tests', function() { + it('should be $valid', function () { + scope.myForm.city.$setViewValue('chicago'); + scope.$digest(); - it('should be $valid', function() { - scope.myForm.city.$setViewValue('chicago'); - scope.$digest(); + expect(scope.myForm.city.$invalid).toBe(false); + }); - expect(scope.myForm.city.$invalid).toBe(false); - }); + it('should not be $valid', function () { + scope.myForm.city.$setViewValue('sss'); + scope.$digest(); - it('should not be $valid', function() { - scope.myForm.city.$setViewValue('sss'); - scope.$digest(); + expect(scope.myForm.city.$invalid).toBe(true); + }); + }); - expect(scope.myForm.city.$invalid).toBe(true); - }); - }); + describe('.has-error class tests', function () { - describe('.has-error class tests', function() { + it('should not have class .has-error', function () { + expect(element.hasClass('has-error')).toBe(false); + expect(scope.myForm.$valid).toBe(false); + }); - it('should not not have class .has-error', function() { - expect(element.hasClass('has-error')).toBe(false); - expect(scope.myForm.$valid).toBe(false); - }); + it('should not have class .has-error', function () { + scope.myForm.firstName.$setViewValue('blah'); + scope.myForm.lastName.$setViewValue('lastName'); + scope.myForm.city.$setViewValue('chicago'); + scope.myForm.state.$setViewValue('chicago'); - it('should not not have class .has-error', function() { - scope.myForm.firstName.$setViewValue('blah'); - scope.myForm.lastName.$setViewValue('lastName'); - scope.myForm.city.$setViewValue('chicago'); - scope.myForm.state.$setViewValue('chicago'); + scope.$digest(); - scope.$digest(); + expect(element.hasClass('has-error')).toBe(false); + expect(scope.myForm.$valid).toBe(true); + expect(scope.myForm.firstName.$valid).toBe(true); + }); - expect(element.hasClass('has-error')).toBe(false); - expect(scope.myForm.$valid).toBe(true); - expect(scope.myForm.firstName.$valid).toBe(true); - }); + it('should have the class .has-error', function () { + scope.myForm.lastName.$setViewValue('blah'); + scope.$digest(); - it('should have the class .has-error', function() { - scope.myForm.lastName.$setViewValue('blah'); - scope.$digest(); + expect(scope.myForm.lastName.$valid).toBe(false); + expect(element.hasClass('has-error')).toBe(true); + expect(scope.myForm.$valid).toBe(false); + expect(element.hasClass('ng-invalid')).toBe(true); + }); + }); - expect(scope.myForm.lastName.$valid).toBe(false); - expect(element.hasClass('has-error')).toBe(true); - expect(scope.myForm.$valid).toBe(false); - expect(element.hasClass('ng-invalid')).toBe(true); - }); - }); + describe('required and error messages test', function () { - describe('required and error messages test', function() { + it('should not display error or required message', function () { + expect(element.hasClass('has-error')).toBe(false); + expect(scope.myForm.$valid).toBe(false); + }); - it('should not display error or required message', function() { - expect(element.hasClass('has-error')).toBe(false); - expect(scope.myForm.$valid).toBe(false); - }); + it('should show required message', function () { + scope.myForm.firstName.$setViewValue(''); + scope.$digest(); - it('should show required message', function() { - scope.myForm.firstName.$setViewValue(''); - scope.$digest(); + expect(element[0][0].nextSibling.innerHTML.indexOf("Required") > 0).toBe(true); + }); - expect(element[0][0].nextSibling.innerHTML.indexOf("Required") > 0).toBe(true); - }); + it('should have the custom invalid message', function () { + scope.myForm.lastName.$setViewValue('blah'); + scope.$digest(); - it('should have the custom invalid message', function() { - scope.myForm.lastName.$setViewValue('blah'); - scope.$digest(); + expect(element[0][1].nextSibling.innerHTML === "WHOA").toBe(true); + }); + }); + }); - expect(element[0][1].nextSibling.innerHTML === "WHOA").toBe(true); - }); - }); - }); \ No newline at end of file + describe('angularValidator with form invalid message', function () { + + var mockCustomMessage; + + beforeEach(function () { + mockCustomMessage = { + // scopeElementModel is the object in scope version, element is the object in DOM version + message: function (scopeElementModel, element) { + var errors = scopeElementModel.$error; + if (errors.maxlength) { + return "'Should not longer than " + element.attributes['ng-maxlength'].value + " characters!'"; + } else { + // default message + return "'This field is invalid!'"; + } + } + }; + + module(function ($provide) { + $provide.value('customMessage', mockCustomMessage); + }); + }); + + beforeEach(inject(function ($rootScope, $compile) { + scope = $rootScope.$new(); + + htmlForm = angular.element( + '
' + + '' + + '' + + '
' + ); + + element = $compile(htmlForm)(scope); + scope.$digest(); + })); + + + describe('form invalid message function', function () { + + it('should show message from customMessage service', function () { + scope.myForm.firstName.$setViewValue('Joseph'); + scope.$digest(); + + expect(element.hasClass('has-error')).toBe(true); + expect(scope.myForm.firstName.$invalid).toBe(true); + expect(element[0][0].nextSibling.innerHTML === 'Should not longer than 5 characters!').toBe(true); + }); + + it('should not show message from customMessage service', function () { + scope.myForm.firstName.$setViewValue('John'); + scope.$digest(); + + expect(element.hasClass('has-error')).toBe(false); + expect(scope.myForm.firstName.$valid).toBe(true); + }); + + it('field invalid message should have higher priority than form invalid message', function () { + scope.myForm.lastName.$setViewValue('Joseph'); + scope.$digest(); + + expect(element[0][1].nextSibling.innerHTML === 'The last name is too long!').toBe(true); + }); + + }); + }); + + +}); \ No newline at end of file From 40333d4a3365d0a6302d4c1252670a2d14643474 Mon Sep 17 00:00:00 2001 From: codonist Date: Fri, 19 Jun 2015 16:18:42 -0400 Subject: [PATCH 06/44] add form invalid message feature, inject custom service into directive. --- README.md | 20 ++ demo/app.js | 87 +++--- demo/index.html | 37 +++ dist/angular-validator.js | 476 +++++++++++++++++---------------- dist/angular-validator.min.js | 2 +- src/angular-validator.js | 474 ++++++++++++++++---------------- test/angular-validator-spec.js | 256 +++++++++++------- 7 files changed, 761 insertions(+), 591 deletions(-) diff --git a/README.md b/README.md index 1a552d7..5add76f 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Despite Angular's awesomeness, validation in Angular is still a pain in the ass. * Supports multi-field dependent validation (one field depends on another such as password matching) * Works with or without `novalidate` * Works with Bootstrap out of the box (although Bootstrap is not required) +* Support form invalid message service where manage invalid messages in one place and save code in HTML ## Demo @@ -122,6 +123,25 @@ Need a feature, found a bug? Create an issue. Dont have any issues, love the pro ``` Use `angular-validator-submit` to specify the function to be called when the form is submitted. Note that the function is not called if the form is invalid. +**Use form invalid message service** +``` +
+

Form invalid message:

+
+ +
+
+
+
+``` +Use `invalid-message` on form element to provide the name of the service in which invalid messages are managed. You need to provide a `message` function in your service, which returns the messages you defined. Form invalid message service saves repetitive code in HTML because you do not need to use invalid-message attribute on every field. Please see the demo for examples. + **Resetting the form** ``` myBeautifulForm.reset() diff --git a/demo/app.js b/demo/app.js index bf45201..e86d474 100644 --- a/demo/app.js +++ b/demo/app.js @@ -1,40 +1,55 @@ angular.module('angular-validator-demo', ['angularValidator']); -angular.module('angular-validator-demo').controller('DemoCtrl', function($scope) { - - $scope.submitMyForm = function() { - alert("Form submitted"); - }; - - - $scope.myCustomValidator = function(text) { - return true; - }; - - - $scope.anotherCustomValidator = function(text) { - if (text === "rainbow") { - return true; - } else return "type in 'rainbow'"; - }; - - - $scope.passwordValidator = function(password) { - - if (!password) { - return; - } - else if (password.length < 6) { - return "Password must be at least " + 6 + " characters long"; - } - else if (!password.match(/[A-Z]/)) { - return "Password must have at least one capital letter"; - } - else if (!password.match(/[0-9]/)) { - return "Password must have at least one number"; - } - - return true; - }; +angular.module('angular-validator-demo').controller('DemoCtrl', function ($scope) { + + $scope.submitMyForm = function () { + alert("Form submitted"); + }; + + + $scope.myCustomValidator = function (text) { + return true; + }; + + + $scope.anotherCustomValidator = function (text) { + if (text === "rainbow") { + return true; + } else return "type in 'rainbow'"; + }; + + + $scope.passwordValidator = function (password) { + + if (!password) { + return; + } + else if (password.length < 6) { + return "Password must be at least " + 6 + " characters long"; + } + else if (!password.match(/[A-Z]/)) { + return "Password must have at least one capital letter"; + } + else if (!password.match(/[0-9]/)) { + return "Password must have at least one number"; + } + + return true; + }; +}).factory('customMessage', function () { + // invalid message service with message function + return { + // scopeElementModel is the object in scope version, element is the object in DOM version + message: function (scopeElementModel, element) { + var errors = scopeElementModel.$error; + if (errors.maxlength) { + // be careful with the quote + return "'Should be no longer than " + element.attributes['ng-maxlength'].value + " characters!'"; + } else { + // default message + return "'This field is invalid!'"; + } + } + }; }); \ No newline at end of file diff --git a/demo/index.html b/demo/index.html index e6f00ab..68da567 100644 --- a/demo/index.html +++ b/demo/index.html @@ -145,6 +145,43 @@

Password validation and password matching example

+ +
+

Form invalid message:

+
+ +
+
+
+
+

Field invalid message hides form invalid message

+
+ +
+
+
+
+
+
+ + +
+
+
+

Scope Sneak Peak

diff --git a/dist/angular-validator.js b/dist/angular-validator.js index 1713b03..5933d58 100644 --- a/dist/angular-validator.js +++ b/dist/angular-validator.js @@ -1,249 +1,263 @@ angular.module('angularValidator', []); -angular.module('angularValidator').directive('angularValidator', - function() { - return { - restrict: 'A', - link: function(scope, element, attrs, fn) { - - // This is the DOM form element - var DOMForm = angular.element(element)[0]; - - // This is the the scope form model - // All validation states are contained here - var form_name = DOMForm.attributes['name'].value; - var scopeForm = scope[form_name]; - - // Set the default submitted state to false - scopeForm.submitted = false; - - - // Watch form length to add watches for new form elements - scope.$watch(function(){return DOMForm.length;}, function(){ - setupWatches(DOMForm); - }); - - - // Intercept and handle submit events of the form - element.on('submit', function(event) { - event.preventDefault(); - scope.$apply(function() { - scopeForm.submitted = true; - }); - - // If the form is valid then call the function that is declared in the angular-validator-submit atrribute on the form element - if (scopeForm.$valid) { - scope.$apply(function() { - scope.$eval(DOMForm.attributes["angular-validator-submit"].value); - }); - } - }); - - - scopeForm.reset = function(){ - // Clear all the form values - for (var i = 0; i < DOMForm.length; i++) { - if (DOMForm[i].name){ - scopeForm[DOMForm[i].name].$setViewValue(""); - scopeForm[DOMForm[i].name].$render(); - } - } - - scopeForm.submitted = false; - scopeForm.$setPristine(); - }; - - - - // Setup watches on all form fields - setupWatches(DOMForm); - - - // Iterate through the form fields and setup watches on each one - function setupWatches(formElement) { - for (var i = 0; i < formElement.length; i++) { - // This ensures we are only watching form fields - if (i in formElement) { - setupWatch(formElement[i]); - } - } - } - - - // Setup $watch on a single formfield - function setupWatch(elementToWatch) { - - if (elementToWatch.isWatchedByValidator){ - return; - } - - elementToWatch.isWatchedByValidator = true; - - // If element is set to validate on blur then update the element on blur - if ("validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "blur") { - angular.element(elementToWatch).on('blur', function() { - updateValidationMessage(elementToWatch); - updateValidationClass(elementToWatch); - }); - } - - scope.$watch(function() { +angular.module('angularValidator').directive('angularValidator', ['$injector', + function ($injector) { + return { + restrict: 'A', + link: function (scope, element, attrs, ctrl) { + + // This is the DOM form element + var DOMForm = angular.element(element)[0]; + + // This is the the scope form model + // All validation states are contained here + var form_name = DOMForm.attributes['name'].value; + var scopeForm = scope[form_name]; + + // Set the default submitted state to false + scopeForm.submitted = false; + + + // Watch form length to add watches for new form elements + scope.$watch(function () { + return DOMForm.length; + }, function () { + setupWatches(DOMForm); + }); + + + // Intercept and handle submit events of the form + element.on('submit', function (event) { + event.preventDefault(); + scope.$apply(function () { + scopeForm.submitted = true; + }); + + // If the form is valid then call the function that is declared in the angular-validator-submit atrribute on the form element + if (scopeForm.$valid) { + scope.$apply(function () { + scope.$eval(DOMForm.attributes["angular-validator-submit"].value); + }); + } + }); + + + scopeForm.reset = function () { + // Clear all the form values + for (var i = 0; i < DOMForm.length; i++) { + if (DOMForm[i].name) { + scopeForm[DOMForm[i].name].$setViewValue(""); + scopeForm[DOMForm[i].name].$render(); + } + } + + scopeForm.submitted = false; + scopeForm.$setPristine(); + }; + + + // Setup watches on all form fields + setupWatches(DOMForm); + + //check if there is invalid message service for the entire form; if yes, return the injected service; if no, return false; + function hasFormInvalidMessage(formElement) { + if (formElement && 'invalid-message' in formElement.attributes) { + return $injector.get(formElement.attributes['invalid-message'].value); + } else { + return false; + } + } + + // Iterate through the form fields and setup watches on each one + function setupWatches(formElement) { + var formInvalidMessage = hasFormInvalidMessage(formElement); + for (var i = 0; i < formElement.length; i++) { + // This ensures we are only watching form fields + if (i in formElement) { + setupWatch(formElement[i], formInvalidMessage); + } + } + } + + + // Setup $watch on a single formfield + function setupWatch(elementToWatch, formInvalidMessage) { + + if (elementToWatch.isWatchedByValidator) { + return; + } + + elementToWatch.isWatchedByValidator = true; + + // If element is set to validate on blur then update the element on blur + if ("validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "blur") { + angular.element(elementToWatch).on('blur', function () { + updateValidationMessage(elementToWatch, formInvalidMessage); + updateValidationClass(elementToWatch); + }); + } + + scope.$watch(function () { return elementToWatch.value + elementToWatch.required + scopeForm.submitted + checkElementValidity(elementToWatch) + getDirtyValue(scopeForm[elementToWatch.name]) + getValidValue(scopeForm[elementToWatch.name]); }, - function() { - - if (scopeForm.submitted){ - updateValidationMessage(elementToWatch); + function () { + + if (scopeForm.submitted) { + updateValidationMessage(elementToWatch, formInvalidMessage); updateValidationClass(elementToWatch); } else { // Determine if the element in question is to be updated on blur isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; - if (isDirtyElement){ - updateValidationMessage(elementToWatch); + if (isDirtyElement) { + updateValidationMessage(elementToWatch, formInvalidMessage); updateValidationClass(elementToWatch); } // This will get called in the case of resetting the form. This only gets called for elements that update on blur and submit. - else if (scopeForm[elementToWatch.name] && scopeForm[elementToWatch.name].$pristine){ - updateValidationMessage(elementToWatch); + else if (scopeForm[elementToWatch.name] && scopeForm[elementToWatch.name].$pristine) { + updateValidationMessage(elementToWatch, formInvalidMessage); updateValidationClass(elementToWatch); } } }); - } - - - // Returns the $dirty value of the element if it exists - function getDirtyValue(element) { - if (element) { - if ("$dirty" in element) { - return element.$dirty; - } - } - } - - - function getValidValue(element) { - if (element) { - if ("$valid" in element) { - return element.$valid; - } - } - } - - - function checkElementValidity(element) { - // If element has a custom validation function - if ("validator" in element.attributes) { - // Call the custom validator function - var isElementValid = scope.$eval(element.attributes.validator.value); - scopeForm[element.name].$setValidity("angularValidator", isElementValid); - return isElementValid; - } - } - - - // Adds and removes an error message as a sibling element of the form field - // depending on the validity of the form field and the submitted state of the form. - // Will use default message if a custom message is not given - function updateValidationMessage(element) { - - var defaultRequiredMessage = function() { - return " Required"; - }; - var defaultInvalidMessage = function() { - return " Invalid"; - }; - - // Make sure the element is a form field and not a button for example - // Only form elements should have names. - if (!(element.name in scopeForm)) { - return; - } - - var scopeElementModel = scopeForm[element.name]; - - // Remove all validation messages - var validationMessageElement = isValidationMessagePresent(element); - if (validationMessageElement) { - validationMessageElement.remove(); - } - - - // Only add validation messages if the form field is $dirty or the form has been submitted - if (scopeElementModel.$dirty || scope[element.form.name] && scope[element.form.name].submitted) { - - if (scopeElementModel.$error.required) { - // If there is a custom required message display it - if ("required-message" in element.attributes) { - angular.element(element).after(generateErrorMessage(element.attributes['required-message'].value)); - } - // Display the default required message - else { - angular.element(element).after(generateErrorMessage(defaultRequiredMessage)); - } - } else if (!scopeElementModel.$valid) { - // If there is a custom validation message add it - if ("invalid-message" in element.attributes) { - angular.element(element).after(generateErrorMessage(element.attributes['invalid-message'].value)); - } - // Display the default error message - else { - angular.element(element).after(generateErrorMessage(defaultInvalidMessage)); - } - } - } - } - - - function generateErrorMessage(messageText) { - return ""; - } - - - // Returns the validation message element or False - function isValidationMessagePresent(element) { - var elementSiblings = angular.element(element).parent().children(); - for (var i = 0; i < elementSiblings.length; i++) { - if (angular.element(elementSiblings[i]).hasClass("validationMessage")) { - return angular.element(elementSiblings[i]); - } - } - return false; - } - - - // Adds and removes .has-error class to both the form element and the form element's parent - // depending on the validity of the element and the submitted state of the form - function updateValidationClass(element) { - // Make sure the element is a form field and not a button for example - // Only form fields should have names. - if (!(element.name in scopeForm)) { - return; - } - var formField = scopeForm[element.name]; - - // This is extra for users wishing to implement the .has-error class on the field itself - // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element - angular.element(element).removeClass('has-error'); - angular.element(element.parentNode).removeClass('has-error'); - - - // Only add/remove validation classes if the field is $dirty or the form has been submitted - if (formField.$dirty || scope[element.form.name] && scope[element.form.name].submitted) { - if (formField.$invalid) { - angular.element(element.parentNode).addClass('has-error'); - - // This is extra for users wishing to implement the .has-error class on the field itself - // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element - angular.element(element).addClass('has-error'); - } - } - } - - } - }; - } -); + } + + + // Returns the $dirty value of the element if it exists + function getDirtyValue(element) { + if (element) { + if ("$dirty" in element) { + return element.$dirty; + } + } + } + + + function getValidValue(element) { + if (element) { + if ("$valid" in element) { + return element.$valid; + } + } + } + + + function checkElementValidity(element) { + // If element has a custom validation function + if ("validator" in element.attributes) { + // Call the custom validator function + var isElementValid = scope.$eval(element.attributes.validator.value); + scopeForm[element.name].$setValidity("angularValidator", isElementValid); + return isElementValid; + } + } + + + // Adds and removes an error message as a sibling element of the form field + // depending on the validity of the form field and the submitted state of the form. + // Will use default message if a custom message is not given + function updateValidationMessage(element, formInvalidMessage) { + + var defaultRequiredMessage = function () { + return " Required"; + }; + var defaultInvalidMessage = function () { + return " Invalid"; + }; + + // Make sure the element is a form field and not a button for example + // Only form elements should have names. + if (!(element.name in scopeForm)) { + return; + } + + var scopeElementModel = scopeForm[element.name]; + + // Remove all validation messages + var validationMessageElement = isValidationMessagePresent(element); + if (validationMessageElement) { + validationMessageElement.remove(); + } + + + // Only add validation messages if the form field is $dirty or the form has been submitted + if (scopeElementModel.$dirty || scope[element.form.name].submitted) { + + if (scopeElementModel.$error.required) { + // If there is a custom required message display it + if ("required-message" in element.attributes) { + angular.element(element).after(generateErrorMessage(element.attributes['required-message'].value)); + } + // Display the default required message + else { + angular.element(element).after(generateErrorMessage(defaultRequiredMessage)); + } + } else if (!scopeElementModel.$valid) { + // If there is a custom validation message add it + if ("invalid-message" in element.attributes) { + angular.element(element).after(generateErrorMessage(element.attributes['invalid-message'].value)); + } + // Display error message provided by custom service + else if (formInvalidMessage) { + angular.element(element).after(generateErrorMessage(formInvalidMessage.message(scopeElementModel, element))); + } + // Display the default error message + else { + angular.element(element).after(generateErrorMessage(defaultInvalidMessage)); + } + } + } + } + + + function generateErrorMessage(messageText) { + return ""; + } + + + // Returns the validation message element or False + function isValidationMessagePresent(element) { + var elementSiblings = angular.element(element).parent().children(); + for (var i = 0; i < elementSiblings.length; i++) { + if (angular.element(elementSiblings[i]).hasClass("validationMessage")) { + return angular.element(elementSiblings[i]); + } + } + return false; + } + + + // Adds and removes .has-error class to both the form element and the form element's parent + // depending on the validity of the element and the submitted state of the form + function updateValidationClass(element) { + // Make sure the element is a form field and not a button for example + // Only form fields should have names. + if (!(element.name in scopeForm)) { + return; + } + var formField = scopeForm[element.name]; + + // This is extra for users wishing to implement the .has-error class on the field itself + // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element + angular.element(element).removeClass('has-error'); + angular.element(element.parentNode).removeClass('has-error'); + + + // Only add/remove validation classes if the field is $dirty or the form has been submitted + if (formField.$dirty || scope[element.form.name].submitted) { + if (formField.$invalid) { + angular.element(element.parentNode).addClass('has-error'); + + // This is extra for users wishing to implement the .has-error class on the field itself + // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element + angular.element(element).addClass('has-error'); + } + } + } + + } + }; + }] +); \ No newline at end of file diff --git a/dist/angular-validator.min.js b/dist/angular-validator.min.js index 2e17421..521ddb8 100644 --- a/dist/angular-validator.min.js +++ b/dist/angular-validator.min.js @@ -1 +1 @@ -angular.module("angularValidator",[]),angular.module("angularValidator").directive("angularValidator",function(){return{restrict:"A",link:function(a,b){function c(a){for(var b=0;b Required"},d=function(){return" Invalid"};if(b.name in n){var e=n[b.name],f=j(b);f&&f.remove(),(e.$dirty||a[b.form.name].submitted)&&(e.$error.required?angular.element(b).after("required-message"in b.attributes?i(b.attributes["required-message"].value):i(c)):e.$valid||angular.element(b).after("invalid-message"in b.attributes?i(b.attributes["invalid-message"].value):i(d)))}}function i(b){return""}function j(a){for(var b=angular.element(a).parent().children(),c=0;c Required"},e=function(){return" Invalid"};if(a.name in r){var f=r[a.name],g=n(a);g&&g.remove(),(f.$dirty||b[a.form.name].submitted)&&(f.$error.required?"required-message"in a.attributes?angular.element(a).after(m(a.attributes["required-message"].value)):angular.element(a).after(m(d)):f.$valid||("invalid-message"in a.attributes?angular.element(a).after(m(a.attributes["invalid-message"].value)):c?angular.element(a).after(m(c.message(f,a))):angular.element(a).after(m(e))))}}function m(a){return""}function n(a){for(var b=angular.element(a).parent().children(),c=0;c Required"; - }; - var defaultInvalidMessage = function() { - return " Invalid"; - }; - - // Make sure the element is a form field and not a button for example - // Only form elements should have names. - if (!(element.name in scopeForm)) { - return; - } - - var scopeElementModel = scopeForm[element.name]; - - // Remove all validation messages - var validationMessageElement = isValidationMessagePresent(element); - if (validationMessageElement) { - validationMessageElement.remove(); - } - - - // Only add validation messages if the form field is $dirty or the form has been submitted - if (scopeElementModel.$dirty || scope[element.form.name].submitted) { - - if (scopeElementModel.$error.required) { - // If there is a custom required message display it - if ("required-message" in element.attributes) { - angular.element(element).after(generateErrorMessage(element.attributes['required-message'].value)); - } - // Display the default required message - else { - angular.element(element).after(generateErrorMessage(defaultRequiredMessage)); - } - } else if (!scopeElementModel.$valid) { - // If there is a custom validation message add it - if ("invalid-message" in element.attributes) { - angular.element(element).after(generateErrorMessage(element.attributes['invalid-message'].value)); - } - // Display the default error message - else { - angular.element(element).after(generateErrorMessage(defaultInvalidMessage)); - } - } - } - } - - - function generateErrorMessage(messageText) { - return ""; - } - - - // Returns the validation message element or False - function isValidationMessagePresent(element) { - var elementSiblings = angular.element(element).parent().children(); - for (var i = 0; i < elementSiblings.length; i++) { - if (angular.element(elementSiblings[i]).hasClass("validationMessage")) { - return angular.element(elementSiblings[i]); - } - } - return false; - } - - - // Adds and removes .has-error class to both the form element and the form element's parent - // depending on the validity of the element and the submitted state of the form - function updateValidationClass(element) { - // Make sure the element is a form field and not a button for example - // Only form fields should have names. - if (!(element.name in scopeForm)) { - return; - } - var formField = scopeForm[element.name]; - - // This is extra for users wishing to implement the .has-error class on the field itself - // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element - angular.element(element).removeClass('has-error'); - angular.element(element.parentNode).removeClass('has-error'); - - - // Only add/remove validation classes if the field is $dirty or the form has been submitted - if (formField.$dirty || scope[element.form.name].submitted) { - if (formField.$invalid) { - angular.element(element.parentNode).addClass('has-error'); - - // This is extra for users wishing to implement the .has-error class on the field itself - // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element - angular.element(element).addClass('has-error'); - } - } - } - - } - }; - } + } + + + // Returns the $dirty value of the element if it exists + function getDirtyValue(element) { + if (element) { + if ("$dirty" in element) { + return element.$dirty; + } + } + } + + + function getValidValue(element) { + if (element) { + if ("$valid" in element) { + return element.$valid; + } + } + } + + + function checkElementValidity(element) { + // If element has a custom validation function + if ("validator" in element.attributes) { + // Call the custom validator function + var isElementValid = scope.$eval(element.attributes.validator.value); + scopeForm[element.name].$setValidity("angularValidator", isElementValid); + return isElementValid; + } + } + + + // Adds and removes an error message as a sibling element of the form field + // depending on the validity of the form field and the submitted state of the form. + // Will use default message if a custom message is not given + function updateValidationMessage(element, formInvalidMessage) { + + var defaultRequiredMessage = function () { + return " Required"; + }; + var defaultInvalidMessage = function () { + return " Invalid"; + }; + + // Make sure the element is a form field and not a button for example + // Only form elements should have names. + if (!(element.name in scopeForm)) { + return; + } + + var scopeElementModel = scopeForm[element.name]; + + // Remove all validation messages + var validationMessageElement = isValidationMessagePresent(element); + if (validationMessageElement) { + validationMessageElement.remove(); + } + + + // Only add validation messages if the form field is $dirty or the form has been submitted + if (scopeElementModel.$dirty || scope[element.form.name].submitted) { + + if (scopeElementModel.$error.required) { + // If there is a custom required message display it + if ("required-message" in element.attributes) { + angular.element(element).after(generateErrorMessage(element.attributes['required-message'].value)); + } + // Display the default required message + else { + angular.element(element).after(generateErrorMessage(defaultRequiredMessage)); + } + } else if (!scopeElementModel.$valid) { + // If there is a custom validation message add it + if ("invalid-message" in element.attributes) { + angular.element(element).after(generateErrorMessage(element.attributes['invalid-message'].value)); + } + // Display error message provided by custom service + else if (formInvalidMessage) { + angular.element(element).after(generateErrorMessage(formInvalidMessage.message(scopeElementModel, element))); + } + // Display the default error message + else { + angular.element(element).after(generateErrorMessage(defaultInvalidMessage)); + } + } + } + } + + + function generateErrorMessage(messageText) { + return ""; + } + + + // Returns the validation message element or False + function isValidationMessagePresent(element) { + var elementSiblings = angular.element(element).parent().children(); + for (var i = 0; i < elementSiblings.length; i++) { + if (angular.element(elementSiblings[i]).hasClass("validationMessage")) { + return angular.element(elementSiblings[i]); + } + } + return false; + } + + + // Adds and removes .has-error class to both the form element and the form element's parent + // depending on the validity of the element and the submitted state of the form + function updateValidationClass(element) { + // Make sure the element is a form field and not a button for example + // Only form fields should have names. + if (!(element.name in scopeForm)) { + return; + } + var formField = scopeForm[element.name]; + + // This is extra for users wishing to implement the .has-error class on the field itself + // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element + angular.element(element).removeClass('has-error'); + angular.element(element.parentNode).removeClass('has-error'); + + + // Only add/remove validation classes if the field is $dirty or the form has been submitted + if (formField.$dirty || scope[element.form.name].submitted) { + if (formField.$invalid) { + angular.element(element.parentNode).addClass('has-error'); + + // This is extra for users wishing to implement the .has-error class on the field itself + // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element + angular.element(element).addClass('has-error'); + } + } + } + + } + }; + }] ); \ No newline at end of file diff --git a/test/angular-validator-spec.js b/test/angular-validator-spec.js index 3851b32..1ba27e7 100644 --- a/test/angular-validator-spec.js +++ b/test/angular-validator-spec.js @@ -1,136 +1,206 @@ - describe('angularValidator', function() { +describe('angularValidator', function () { - beforeEach(angular.mock.module('angularValidator')); + beforeEach(angular.mock.module('angularValidator')); - var scope, compile; - beforeEach(inject(function($rootScope, $compile) { - scope = $rootScope.$new(); + var scope, compile; - htmlForm = angular.element( - '
' + - '' + - '' + - '' + - '' + - '
' - ); + describe('angularValidator without form invalid message', function () { + beforeEach(inject(function ($rootScope, $compile) { + scope = $rootScope.$new(); - element = $compile(htmlForm)(scope); - scope.$digest(); - })); + htmlForm = angular.element( + '
' + + '' + + '' + + '' + + '' + + '
' + ); + element = $compile(htmlForm)(scope); + scope.$digest(); + })); - describe('$dirty tests', function() { + describe('$dirty tests', function () { - it('should be dirty', function() { - scope.myForm.lastName.$setViewValue('lastName'); - scope.$digest(); + it('should be dirty', function () { + scope.myForm.lastName.$setViewValue('lastName'); + scope.$digest(); - expect(scope.myForm.lastName.$dirty).toBe(true); - expect(element.hasClass('ng-dirty')).toBe(true); + expect(scope.myForm.lastName.$dirty).toBe(true); + expect(element.hasClass('ng-dirty')).toBe(true); - }); + }); - it('should not be $dirty', function() { - expect(scope.myForm.$pristine).toBe(true); - expect(element.hasClass('ng-pristine')).toBe(true); - }); - }); + it('should not be $dirty', function () { + expect(scope.myForm.$pristine).toBe(true); + expect(element.hasClass('ng-pristine')).toBe(true); + }); + }); - describe('$valid tests', function() { + describe('$valid tests', function () { - it('should be $valid', function() { - scope.myForm.lastName.$setViewValue('lastName'); - scope.$digest(); + it('should be $valid', function () { + scope.myForm.lastName.$setViewValue('lastName'); + scope.$digest(); - expect(scope.myForm.lastName.$invalid).toBe(false); - }); + expect(scope.myForm.lastName.$invalid).toBe(false); + }); - it('should not be $valid', function() { - scope.myForm.lastName.$setViewValue('sss'); - scope.$digest(); + it('should not be $valid', function () { + scope.myForm.lastName.$setViewValue('sss'); + scope.$digest(); - expect(scope.myForm.lastName.$invalid).toBe(true); - }); - }); + expect(scope.myForm.lastName.$invalid).toBe(true); + }); + }); + describe('custom validator tests', function () { - describe('custom validator tests', function() { + it('should be $valid', function () { + scope.myForm.city.$setViewValue('chicago'); + scope.$digest(); - it('should be $valid', function() { - scope.myForm.city.$setViewValue('chicago'); - scope.$digest(); + expect(scope.myForm.city.$invalid).toBe(false); + }); - expect(scope.myForm.city.$invalid).toBe(false); - }); + it('should not be $valid', function () { + scope.myForm.city.$setViewValue('sss'); + scope.$digest(); - it('should not be $valid', function() { - scope.myForm.city.$setViewValue('sss'); - scope.$digest(); + expect(scope.myForm.city.$invalid).toBe(true); + }); + }); - expect(scope.myForm.city.$invalid).toBe(true); - }); - }); + describe('.has-error class tests', function () { - describe('.has-error class tests', function() { + it('should not have class .has-error', function () { + expect(element.hasClass('has-error')).toBe(false); + expect(scope.myForm.$valid).toBe(false); + }); - it('should not not have class .has-error', function() { - expect(element.hasClass('has-error')).toBe(false); - expect(scope.myForm.$valid).toBe(false); - }); + it('should not have class .has-error', function () { + scope.myForm.firstName.$setViewValue('blah'); + scope.myForm.lastName.$setViewValue('lastName'); + scope.myForm.city.$setViewValue('chicago'); + scope.myForm.state.$setViewValue('chicago'); - it('should not not have class .has-error', function() { - scope.myForm.firstName.$setViewValue('blah'); - scope.myForm.lastName.$setViewValue('lastName'); - scope.myForm.city.$setViewValue('chicago'); - scope.myForm.state.$setViewValue('chicago'); + scope.$digest(); - scope.$digest(); + expect(element.hasClass('has-error')).toBe(false); + expect(scope.myForm.$valid).toBe(true); + expect(scope.myForm.firstName.$valid).toBe(true); + }); - expect(element.hasClass('has-error')).toBe(false); - expect(scope.myForm.$valid).toBe(true); - expect(scope.myForm.firstName.$valid).toBe(true); - }); + it('should have the class .has-error', function () { + scope.myForm.lastName.$setViewValue('blah'); + scope.$digest(); - it('should have the class .has-error', function() { - scope.myForm.lastName.$setViewValue('blah'); - scope.$digest(); + expect(scope.myForm.lastName.$valid).toBe(false); + expect(element.hasClass('has-error')).toBe(true); + expect(scope.myForm.$valid).toBe(false); + expect(element.hasClass('ng-invalid')).toBe(true); + }); + }); - expect(scope.myForm.lastName.$valid).toBe(false); - expect(element.hasClass('has-error')).toBe(true); - expect(scope.myForm.$valid).toBe(false); - expect(element.hasClass('ng-invalid')).toBe(true); - }); - }); + describe('required and error messages test', function () { - describe('required and error messages test', function() { + it('should not display error or required message', function () { + expect(element.hasClass('has-error')).toBe(false); + expect(scope.myForm.$valid).toBe(false); + }); - it('should not display error or required message', function() { - expect(element.hasClass('has-error')).toBe(false); - expect(scope.myForm.$valid).toBe(false); - }); + it('should show required message', function () { + scope.myForm.firstName.$setViewValue(''); + scope.$digest(); - it('should show required message', function() { - scope.myForm.firstName.$setViewValue(''); - scope.$digest(); + expect(element[0][0].nextSibling.innerHTML.indexOf("Required") > 0).toBe(true); + }); - expect(element[0][0].nextSibling.innerHTML.indexOf("Required") > 0).toBe(true); - }); + it('should have the custom invalid message', function () { + scope.myForm.lastName.$setViewValue('blah'); + scope.$digest(); - it('should have the custom invalid message', function() { - scope.myForm.lastName.$setViewValue('blah'); - scope.$digest(); + expect(element[0][1].nextSibling.innerHTML === "WHOA").toBe(true); + }); + }); + }); - expect(element[0][1].nextSibling.innerHTML === "WHOA").toBe(true); - }); - }); - }); \ No newline at end of file + describe('angularValidator with form invalid message', function () { + + var mockCustomMessage; + + beforeEach(function () { + mockCustomMessage = { + // scopeElementModel is the object in scope version, element is the object in DOM version + message: function (scopeElementModel, element) { + var errors = scopeElementModel.$error; + if (errors.maxlength) { + return "'Should not longer than " + element.attributes['ng-maxlength'].value + " characters!'"; + } else { + // default message + return "'This field is invalid!'"; + } + } + }; + + module(function ($provide) { + $provide.value('customMessage', mockCustomMessage); + }); + }); + + beforeEach(inject(function ($rootScope, $compile) { + scope = $rootScope.$new(); + + htmlForm = angular.element( + '
' + + '' + + '' + + '
' + ); + + element = $compile(htmlForm)(scope); + scope.$digest(); + })); + + + describe('form invalid message function', function () { + + it('should show message from customMessage service', function () { + scope.myForm.firstName.$setViewValue('Joseph'); + scope.$digest(); + + expect(element.hasClass('has-error')).toBe(true); + expect(scope.myForm.firstName.$invalid).toBe(true); + expect(element[0][0].nextSibling.innerHTML === 'Should not longer than 5 characters!').toBe(true); + }); + + it('should not show message from customMessage service', function () { + scope.myForm.firstName.$setViewValue('John'); + scope.$digest(); + + expect(element.hasClass('has-error')).toBe(false); + expect(scope.myForm.firstName.$valid).toBe(true); + }); + + it('field invalid message should have higher priority than form invalid message', function () { + scope.myForm.lastName.$setViewValue('Joseph'); + scope.$digest(); + + expect(element[0][1].nextSibling.innerHTML === 'The last name is too long!').toBe(true); + }); + + }); + }); + + +}); \ No newline at end of file From 5caa673379b1d4fea264d7cd566bc0f0206670d1 Mon Sep 17 00:00:00 2001 From: codonist Date: Fri, 19 Jun 2015 22:33:02 -0400 Subject: [PATCH 07/44] update angular-validator-spec.js --- test/angular-validator-spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/angular-validator-spec.js b/test/angular-validator-spec.js index 1ba27e7..fd1e89a 100644 --- a/test/angular-validator-spec.js +++ b/test/angular-validator-spec.js @@ -145,7 +145,7 @@ describe('angularValidator', function () { message: function (scopeElementModel, element) { var errors = scopeElementModel.$error; if (errors.maxlength) { - return "'Should not longer than " + element.attributes['ng-maxlength'].value + " characters!'"; + return "'Should be no longer than " + element.attributes['ng-maxlength'].value + " characters!'"; } else { // default message return "'This field is invalid!'"; @@ -181,7 +181,7 @@ describe('angularValidator', function () { expect(element.hasClass('has-error')).toBe(true); expect(scope.myForm.firstName.$invalid).toBe(true); - expect(element[0][0].nextSibling.innerHTML === 'Should not longer than 5 characters!').toBe(true); + expect(element[0][0].nextSibling.innerHTML === 'Should be no longer than 5 characters!').toBe(true); }); it('should not show message from customMessage service', function () { From 42427f89ddc92eba3a0b5d5b10f2a213cdd3bf39 Mon Sep 17 00:00:00 2001 From: Zohar Date: Sun, 28 Jun 2015 12:45:00 -0500 Subject: [PATCH 08/44] Updated dependancies, added ngAnnotate, cleaned up code --- CONTRIBUTING.md | 6 +++--- Gruntfile.js | 17 +++++++++++++++-- bower.json | 5 +++-- dist/angular-validator.js | 4 ++-- dist/angular-validator.min.js | 2 +- karma.conf.js | 4 ++-- package.json | 4 +++- src/angular-validator.js | 4 ++-- 8 files changed, 31 insertions(+), 15 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 266a13f..0ebddef 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,13 +6,13 @@ Thanks for wanting to contribute. Please follow the instructions below. 1. Clone the repository 2. Run `bower install` in the repository directory 3. Run `npm install` in the repository directory -4. Run `grunt serve` to start servering the demo on `http://localhost:9001/demo/` +4. Run `grunt serve` to start serving the demo on `http://localhost:9001/demo/` ## Before creating a pull request 1. Make sure you add tests to `/test/angular-validator-spec.js` that test your changes. To run tests `grunt test` -2. If approriate update the readme to reflect your changes. +2. If appropriate update the README to reflect your changes. 3. Run `grunt build` to minify and create a `dist` version of your changes -4. If approriate update the demo in /demo/ +4. If appropriate update the demo in /demo/ 5. The project maintainer will update the Plunker demo that is linked in README.md if needed. \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index 9f4cbc4..ddabbd4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,3 +1,8 @@ +/*jslint node: true */ +'use strict'; + +var pkg = require('./package.json'); + module.exports = function(grunt) { // Grunt Config @@ -21,6 +26,12 @@ module.exports = function(grunt) { "dest": "dist/angular-validator.js" } }, + ngAnnotate: { + main: { + src: 'dist/angular-validator.js', + dest: 'dist/angular-validator.js' + }, + }, uglify: { dist: { src: "dist/angular-validator.js", @@ -66,6 +77,8 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-connect'); + grunt.loadNpmTasks('grunt-ng-annotate'); + // Load the plugin that provides the "jshint" task. grunt.loadNpmTasks('grunt-contrib-jshint'); @@ -81,8 +94,8 @@ module.exports = function(grunt) { // Register Task grunt.registerTask('serve', ['connect', 'watch']); - grunt.registerTask('build', ['concat', 'uglify', 'karma:build',]); - grunt.registerTask('test', ['karma:build',]); + grunt.registerTask('build', ['concat', 'ngAnnotate', 'uglify', 'karma:build']); + grunt.registerTask('test', ['karma:build', ]); grunt.registerTask('test-debug', ['karma:debug']); grunt.registerTask('travis', ['karma:travis']); }; \ No newline at end of file diff --git a/bower.json b/bower.json index 35cf191..3590461 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "tg-angular-validator", - "version": "1.2.5", + "version": "1.2.6", "authors": [ "Zohar Jackson " ], @@ -32,7 +32,8 @@ "dependencies": { "angular": ">=1.3.0", "bootstrap": ">=3.2.0", - "font-awesome": ">=4.0.3" + "font-awesome": ">=4.0.3", + "angular-mocks": "~1.4.1" }, "homepage": "https://github.com/turinggroup/angular-validator" } diff --git a/dist/angular-validator.js b/dist/angular-validator.js index a4e8b63..a5ab597 100644 --- a/dist/angular-validator.js +++ b/dist/angular-validator.js @@ -173,7 +173,7 @@ angular.module('angularValidator').directive('angularValidator', // Only add validation messages if the form field is $dirty or the form has been submitted - if (scopeElementModel.$dirty || scope[element.form.name].submitted) { + if (scopeElementModel.$dirty || (scope[element.form.name] && scope[element.form.name].submitted)) { if (scopeElementModel.$error.required) { // If there is a custom required message display it @@ -232,7 +232,7 @@ angular.module('angularValidator').directive('angularValidator', // Only add/remove validation classes if the field is $dirty or the form has been submitted - if (formField.$dirty || scope[element.form.name].submitted) { + if (formField.$dirty || (scope[element.form.name] && scope[element.form.name].submitted)) { if (formField.$invalid) { angular.element(element.parentNode).addClass('has-error'); diff --git a/dist/angular-validator.min.js b/dist/angular-validator.min.js index 18904f2..ef95326 100644 --- a/dist/angular-validator.min.js +++ b/dist/angular-validator.min.js @@ -1 +1 @@ -angular.module("angularValidator",[]),angular.module("angularValidator").directive("angularValidator",function(){return{restrict:"A",link:function(a,b,c,d){function e(a){for(var b=0;b Required"},d=function(){return" Invalid"};if(b.name in p){var e=p[b.name],f=l(b);f&&f.remove(),(e.$dirty||a[b.form.name].submitted)&&(e.$error.required?"required-message"in b.attributes?angular.element(b).after(k(b.attributes["required-message"].value)):angular.element(b).after(k(c)):e.$valid||("invalid-message"in b.attributes?angular.element(b).after(k(b.attributes["invalid-message"].value)):angular.element(b).after(k(d))))}}function k(b){return""}function l(a){for(var b=angular.element(a).parent().children(),c=0;c Required"},d=function(){return" Invalid"};if(b.name in n){var e=n[b.name],f=j(b);f&&f.remove(),(e.$dirty||a[b.form.name]&&a[b.form.name].submitted)&&(e.$error.required?angular.element(b).after("required-message"in b.attributes?i(b.attributes["required-message"].value):i(c)):e.$valid||angular.element(b).after("invalid-message"in b.attributes?i(b.attributes["invalid-message"].value):i(d)))}}function i(b){return""}function j(a){for(var b=angular.element(a).parent().children(),c=0;c Date: Sun, 28 Jun 2015 13:20:29 -0500 Subject: [PATCH 09/44] fixed spelling mistake --- README.md | 16 ++++++---------- dist/angular-validator.js | 2 +- src/angular-validator.js | 2 +- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1a552d7..31070d5 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,14 @@ # Angular-Validator [![Build Status](https://travis-ci.org/turinggroup/angular-validator.png)](https://travis-ci.org/turinggroup/angular-validator) - -## What? Angular-Validator is an easy to use, powerful and lightweight AngularJS validation directive. - -## Why? -Despite Angular's awesomeness, validation in Angular is still a pain in the ass. Surprisingly there are no seamless, user-friendly, well written Angular validation tools. Unlike other Angular validation tools, Angular-Validator works with out-of-the-box Angular and HTML5 validation, directives and attributes, allowing your forms to work well with the browser and other Javascript code. - +## Demo +[Check out the demo!](http://plnkr.co/edit/XbDYKrM2QUf8g1ubTHma?p=preview) ## Features * Validate using REGEX, required, or custom validation functions -* Validates elements on submit with option to validate individual elements on `blur` or `dirty` as well. +* Configure when to validate elements Choose between on form submit, `blur` or `dirty`(change). * Prevents submission if the form is invalid * Built in reset form method * Adds validation error/success messages as sibling elements @@ -23,8 +19,8 @@ Despite Angular's awesomeness, validation in Angular is still a pain in the ass. * Works with Bootstrap out of the box (although Bootstrap is not required) -## Demo -[Check out the demo!](http://plnkr.co/edit/XbDYKrM2QUf8g1ubTHma?p=preview) +## Why? +Despite Angular's awesomeness, validation in Angular is still a pain in the ass. Surprisingly there are no seamless, user-friendly, well written Angular validation tools. Unlike other Angular validation tools, Angular-Validator works with out-of-the-box Angular and HTML5 validation, directives and attributes, allowing your forms to work well with the browser and other Javascript code. ## Feedback @@ -33,7 +29,7 @@ Need a feature, found a bug? Create an issue. Dont have any issues, love the pro ## Installation 1. Using bower: `bower install tg-angular-validator` 2. Include `angular-validator.min.js` into your application's HTML -3. Add `angularValidator` as a dependancy of your Angular module +3. Add `angularValidator` as a dependency of your Angular module ## Usage diff --git a/dist/angular-validator.js b/dist/angular-validator.js index a5ab597..67fd05d 100644 --- a/dist/angular-validator.js +++ b/dist/angular-validator.js @@ -31,7 +31,7 @@ angular.module('angularValidator').directive('angularValidator', scopeForm.submitted = true; }); - // If the form is valid then call the function that is declared in the angular-validator-submit atrribute on the form element + // If the form is valid then call the function that is declared in the angular-validator-submit attribute on the form element if (scopeForm.$valid) { scope.$apply(function() { scope.$eval(DOMForm.attributes["angular-validator-submit"].value); diff --git a/src/angular-validator.js b/src/angular-validator.js index a5ab597..67fd05d 100644 --- a/src/angular-validator.js +++ b/src/angular-validator.js @@ -31,7 +31,7 @@ angular.module('angularValidator').directive('angularValidator', scopeForm.submitted = true; }); - // If the form is valid then call the function that is declared in the angular-validator-submit atrribute on the form element + // If the form is valid then call the function that is declared in the angular-validator-submit attribute on the form element if (scopeForm.$valid) { scope.$apply(function() { scope.$eval(DOMForm.attributes["angular-validator-submit"].value); From f72a0fbfa1240d1bfec7c8b56b1cf8b54eee3b6c Mon Sep 17 00:00:00 2001 From: Zohar Date: Sun, 28 Jun 2015 14:05:59 -0500 Subject: [PATCH 10/44] Updated dependencies, added ngAnnotate, cleaned up code --- CONTRIBUTING.md | 1 - Gruntfile.js | 1 - bower.json | 2 +- karma.conf.js | 1 - package.json | 7 ++----- 5 files changed, 3 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0ebddef..47319b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,6 @@ Thanks for wanting to contribute. Please follow the instructions below. 4. Run `grunt serve` to start serving the demo on `http://localhost:9001/demo/` - ## Before creating a pull request 1. Make sure you add tests to `/test/angular-validator-spec.js` that test your changes. To run tests `grunt test` 2. If appropriate update the README to reflect your changes. diff --git a/Gruntfile.js b/Gruntfile.js index ddabbd4..0b7eacb 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -79,7 +79,6 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-connect'); grunt.loadNpmTasks('grunt-ng-annotate'); - // Load the plugin that provides the "jshint" task. grunt.loadNpmTasks('grunt-contrib-jshint'); diff --git a/bower.json b/bower.json index 3590461..ed6be05 100644 --- a/bower.json +++ b/bower.json @@ -36,4 +36,4 @@ "angular-mocks": "~1.4.1" }, "homepage": "https://github.com/turinggroup/angular-validator" -} +} \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js index 7db3aad..ea8d1e6 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,5 +1,4 @@ // Karma configuration - module.exports = function(config) { config.set({ diff --git a/package.json b/package.json index 42847df..9e9d7fc 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,7 @@ "grunt-karma": "latest", "karma": "~0.12.1", "karma-jasmine": "~0.1.5", - "karma-chrome-launcher": "~0.1.2", - "karma-phantomjs-launcher": "~0.1.2", - "karma-firefox-launcher": "~0.1.3", - "ng-annotate": "0.15.4", - "grunt-ng-annotate": "~0.10.0" + "ng-annotate": ">=0.15.4", + "grunt-ng-annotate": ">=0.10.0" } } \ No newline at end of file From 9b67aa5eb697fb418fc0865667c250a65714fcb9 Mon Sep 17 00:00:00 2001 From: Zohar Date: Sun, 28 Jun 2015 14:25:22 -0500 Subject: [PATCH 11/44] Fixed removed dependency. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 9e9d7fc..b763bbe 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "grunt-browser-sync": "~0.9.1", "grunt-karma": "latest", "karma": "~0.12.1", + "karma-phantomjs-launcher": "~0.1.2", "karma-jasmine": "~0.1.5", "ng-annotate": ">=0.15.4", "grunt-ng-annotate": ">=0.10.0" From 0d0abad257b431d8458e449abafc3e280f9c651a Mon Sep 17 00:00:00 2001 From: codonist Date: Sun, 28 Jun 2015 21:27:13 -0400 Subject: [PATCH 12/44] adjust indents --- Gruntfile.js | 16 +- bower.json | 7 +- demo/app.js | 54 ++-- dist/angular-validator.js | 467 ++++++++++++++++----------------- dist/angular-validator.min.js | 2 +- karma.conf.js | 5 +- package.json | 5 +- src/angular-validator.js | 467 ++++++++++++++++----------------- test/angular-validator-spec.js | 290 ++++++++++---------- 9 files changed, 662 insertions(+), 651 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 9f4cbc4..0b7eacb 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,3 +1,8 @@ +/*jslint node: true */ +'use strict'; + +var pkg = require('./package.json'); + module.exports = function(grunt) { // Grunt Config @@ -21,6 +26,12 @@ module.exports = function(grunt) { "dest": "dist/angular-validator.js" } }, + ngAnnotate: { + main: { + src: 'dist/angular-validator.js', + dest: 'dist/angular-validator.js' + }, + }, uglify: { dist: { src: "dist/angular-validator.js", @@ -66,6 +77,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-connect'); + grunt.loadNpmTasks('grunt-ng-annotate'); // Load the plugin that provides the "jshint" task. grunt.loadNpmTasks('grunt-contrib-jshint'); @@ -81,8 +93,8 @@ module.exports = function(grunt) { // Register Task grunt.registerTask('serve', ['connect', 'watch']); - grunt.registerTask('build', ['concat', 'uglify', 'karma:build',]); - grunt.registerTask('test', ['karma:build',]); + grunt.registerTask('build', ['concat', 'ngAnnotate', 'uglify', 'karma:build']); + grunt.registerTask('test', ['karma:build', ]); grunt.registerTask('test-debug', ['karma:debug']); grunt.registerTask('travis', ['karma:travis']); }; \ No newline at end of file diff --git a/bower.json b/bower.json index 35cf191..ed6be05 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "tg-angular-validator", - "version": "1.2.5", + "version": "1.2.6", "authors": [ "Zohar Jackson " ], @@ -32,7 +32,8 @@ "dependencies": { "angular": ">=1.3.0", "bootstrap": ">=3.2.0", - "font-awesome": ">=4.0.3" + "font-awesome": ">=4.0.3", + "angular-mocks": "~1.4.1" }, "homepage": "https://github.com/turinggroup/angular-validator" -} +} \ No newline at end of file diff --git a/demo/app.js b/demo/app.js index e86d474..5f78e45 100644 --- a/demo/app.js +++ b/demo/app.js @@ -1,42 +1,42 @@ angular.module('angular-validator-demo', ['angularValidator']); -angular.module('angular-validator-demo').controller('DemoCtrl', function ($scope) { +angular.module('angular-validator-demo').controller('DemoCtrl', function($scope) { - $scope.submitMyForm = function () { - alert("Form submitted"); - }; + $scope.submitMyForm = function() { + alert("Form submitted"); + }; - $scope.myCustomValidator = function (text) { - return true; - }; + $scope.myCustomValidator = function(text) { + return true; + }; - $scope.anotherCustomValidator = function (text) { - if (text === "rainbow") { - return true; - } else return "type in 'rainbow'"; - }; + $scope.anotherCustomValidator = function(text) { + if (text === "rainbow") { + return true; + } else return "type in 'rainbow'"; + }; - $scope.passwordValidator = function (password) { + $scope.passwordValidator = function(password) { - if (!password) { - return; - } - else if (password.length < 6) { - return "Password must be at least " + 6 + " characters long"; - } - else if (!password.match(/[A-Z]/)) { - return "Password must have at least one capital letter"; - } - else if (!password.match(/[0-9]/)) { - return "Password must have at least one number"; - } + if (!password) { + return; + } + else if (password.length < 6) { + return "Password must be at least " + 6 + " characters long"; + } + else if (!password.match(/[A-Z]/)) { + return "Password must have at least one capital letter"; + } + else if (!password.match(/[0-9]/)) { + return "Password must have at least one number"; + } - return true; - }; + return true; + }; }).factory('customMessage', function () { // invalid message service with message function return { diff --git a/dist/angular-validator.js b/dist/angular-validator.js index 5933d58..7890f83 100644 --- a/dist/angular-validator.js +++ b/dist/angular-validator.js @@ -1,263 +1,262 @@ angular.module('angularValidator', []); angular.module('angularValidator').directive('angularValidator', ['$injector', - function ($injector) { - return { - restrict: 'A', - link: function (scope, element, attrs, ctrl) { - - // This is the DOM form element - var DOMForm = angular.element(element)[0]; - - // This is the the scope form model - // All validation states are contained here - var form_name = DOMForm.attributes['name'].value; - var scopeForm = scope[form_name]; - - // Set the default submitted state to false - scopeForm.submitted = false; - - - // Watch form length to add watches for new form elements - scope.$watch(function () { - return DOMForm.length; - }, function () { - setupWatches(DOMForm); - }); - - - // Intercept and handle submit events of the form - element.on('submit', function (event) { - event.preventDefault(); - scope.$apply(function () { - scopeForm.submitted = true; - }); + function($injector) { + return { + restrict: 'A', + link: function(scope, element, attrs, fn) { + + // This is the DOM form element + var DOMForm = angular.element(element)[0]; + + // This is the the scope form model + // All validation states are contained here + var form_name = DOMForm.attributes['name'].value; + var scopeForm = scope[form_name]; + + // Set the default submitted state to false + scopeForm.submitted = false; + + + // Watch form length to add watches for new form elements + scope.$watch(function(){return DOMForm.length;}, function(){ + setupWatches(DOMForm); + }); + + + // Intercept and handle submit events of the form + element.on('submit', function(event) { + event.preventDefault(); + scope.$apply(function() { + scopeForm.submitted = true; + }); + + // If the form is valid then call the function that is declared in the angular-validator-submit attribute on the form element + if (scopeForm.$valid) { + scope.$apply(function() { + scope.$eval(DOMForm.attributes["angular-validator-submit"].value); + }); + } + }); - // If the form is valid then call the function that is declared in the angular-validator-submit atrribute on the form element - if (scopeForm.$valid) { - scope.$apply(function () { - scope.$eval(DOMForm.attributes["angular-validator-submit"].value); - }); - } - }); + scopeForm.reset = function(){ + // Clear all the form values + for (var i = 0; i < DOMForm.length; i++) { + if (DOMForm[i].name){ + scopeForm[DOMForm[i].name].$setViewValue(""); + scopeForm[DOMForm[i].name].$render(); + } + } - scopeForm.reset = function () { - // Clear all the form values - for (var i = 0; i < DOMForm.length; i++) { - if (DOMForm[i].name) { - scopeForm[DOMForm[i].name].$setViewValue(""); - scopeForm[DOMForm[i].name].$render(); - } - } + scopeForm.submitted = false; + scopeForm.$setPristine(); + }; - scopeForm.submitted = false; - scopeForm.$setPristine(); - }; - // Setup watches on all form fields - setupWatches(DOMForm); + // Setup watches on all form fields + setupWatches(DOMForm); //check if there is invalid message service for the entire form; if yes, return the injected service; if no, return false; - function hasFormInvalidMessage(formElement) { - if (formElement && 'invalid-message' in formElement.attributes) { - return $injector.get(formElement.attributes['invalid-message'].value); - } else { - return false; - } - } - - // Iterate through the form fields and setup watches on each one - function setupWatches(formElement) { - var formInvalidMessage = hasFormInvalidMessage(formElement); - for (var i = 0; i < formElement.length; i++) { - // This ensures we are only watching form fields - if (i in formElement) { - setupWatch(formElement[i], formInvalidMessage); - } - } - } - - - // Setup $watch on a single formfield - function setupWatch(elementToWatch, formInvalidMessage) { - - if (elementToWatch.isWatchedByValidator) { - return; - } - - elementToWatch.isWatchedByValidator = true; - - // If element is set to validate on blur then update the element on blur - if ("validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "blur") { - angular.element(elementToWatch).on('blur', function () { - updateValidationMessage(elementToWatch, formInvalidMessage); - updateValidationClass(elementToWatch); - }); - } - - scope.$watch(function () { + function hasFormInvalidMessage(formElement) { + if (formElement && 'invalid-message' in formElement.attributes) { + return $injector.get(formElement.attributes['invalid-message'].value); + } else { + return false; + } + } + + // Iterate through the form fields and setup watches on each one + function setupWatches(formElement) { + var formInvalidMessage = hasFormInvalidMessage(formElement); + for (var i = 0; i < formElement.length; i++) { + // This ensures we are only watching form fields + if (i in formElement) { + setupWatch(formElement[i], formInvalidMessage); + } + } + } + + + // Setup $watch on a single formfield + function setupWatch(elementToWatch, formInvalidMessage) { + + if (elementToWatch.isWatchedByValidator){ + return; + } + + elementToWatch.isWatchedByValidator = true; + + // If element is set to validate on blur then update the element on blur + if ("validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "blur") { + angular.element(elementToWatch).on('blur', function() { + updateValidationMessage(elementToWatch, formInvalidMessage); + updateValidationClass(elementToWatch); + }); + } + + scope.$watch(function() { return elementToWatch.value + elementToWatch.required + scopeForm.submitted + checkElementValidity(elementToWatch) + getDirtyValue(scopeForm[elementToWatch.name]) + getValidValue(scopeForm[elementToWatch.name]); }, - function () { - - if (scopeForm.submitted) { + function() { + + if (scopeForm.submitted){ updateValidationMessage(elementToWatch, formInvalidMessage); updateValidationClass(elementToWatch); } else { // Determine if the element in question is to be updated on blur - isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; + var isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; - if (isDirtyElement) { + if (isDirtyElement){ updateValidationMessage(elementToWatch, formInvalidMessage); updateValidationClass(elementToWatch); } // This will get called in the case of resetting the form. This only gets called for elements that update on blur and submit. - else if (scopeForm[elementToWatch.name] && scopeForm[elementToWatch.name].$pristine) { + else if (scopeForm[elementToWatch.name] && scopeForm[elementToWatch.name].$pristine){ updateValidationMessage(elementToWatch, formInvalidMessage); updateValidationClass(elementToWatch); } } }); - } - - - // Returns the $dirty value of the element if it exists - function getDirtyValue(element) { - if (element) { - if ("$dirty" in element) { - return element.$dirty; - } - } - } - - - function getValidValue(element) { - if (element) { - if ("$valid" in element) { - return element.$valid; - } - } - } - - - function checkElementValidity(element) { - // If element has a custom validation function - if ("validator" in element.attributes) { - // Call the custom validator function - var isElementValid = scope.$eval(element.attributes.validator.value); - scopeForm[element.name].$setValidity("angularValidator", isElementValid); - return isElementValid; - } - } - - - // Adds and removes an error message as a sibling element of the form field - // depending on the validity of the form field and the submitted state of the form. - // Will use default message if a custom message is not given - function updateValidationMessage(element, formInvalidMessage) { - - var defaultRequiredMessage = function () { - return " Required"; - }; - var defaultInvalidMessage = function () { - return " Invalid"; - }; - - // Make sure the element is a form field and not a button for example - // Only form elements should have names. - if (!(element.name in scopeForm)) { - return; - } - - var scopeElementModel = scopeForm[element.name]; - - // Remove all validation messages - var validationMessageElement = isValidationMessagePresent(element); - if (validationMessageElement) { - validationMessageElement.remove(); - } - - - // Only add validation messages if the form field is $dirty or the form has been submitted - if (scopeElementModel.$dirty || scope[element.form.name].submitted) { - - if (scopeElementModel.$error.required) { - // If there is a custom required message display it - if ("required-message" in element.attributes) { - angular.element(element).after(generateErrorMessage(element.attributes['required-message'].value)); - } - // Display the default required message - else { - angular.element(element).after(generateErrorMessage(defaultRequiredMessage)); - } - } else if (!scopeElementModel.$valid) { - // If there is a custom validation message add it - if ("invalid-message" in element.attributes) { - angular.element(element).after(generateErrorMessage(element.attributes['invalid-message'].value)); - } - // Display error message provided by custom service - else if (formInvalidMessage) { - angular.element(element).after(generateErrorMessage(formInvalidMessage.message(scopeElementModel, element))); - } - // Display the default error message - else { - angular.element(element).after(generateErrorMessage(defaultInvalidMessage)); - } - } - } - } - - - function generateErrorMessage(messageText) { - return ""; - } - - - // Returns the validation message element or False - function isValidationMessagePresent(element) { - var elementSiblings = angular.element(element).parent().children(); - for (var i = 0; i < elementSiblings.length; i++) { - if (angular.element(elementSiblings[i]).hasClass("validationMessage")) { - return angular.element(elementSiblings[i]); - } - } - return false; - } - - - // Adds and removes .has-error class to both the form element and the form element's parent - // depending on the validity of the element and the submitted state of the form - function updateValidationClass(element) { - // Make sure the element is a form field and not a button for example - // Only form fields should have names. - if (!(element.name in scopeForm)) { - return; - } - var formField = scopeForm[element.name]; - - // This is extra for users wishing to implement the .has-error class on the field itself - // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element - angular.element(element).removeClass('has-error'); - angular.element(element.parentNode).removeClass('has-error'); - - - // Only add/remove validation classes if the field is $dirty or the form has been submitted - if (formField.$dirty || scope[element.form.name].submitted) { - if (formField.$invalid) { - angular.element(element.parentNode).addClass('has-error'); - - // This is extra for users wishing to implement the .has-error class on the field itself - // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element - angular.element(element).addClass('has-error'); - } - } - } - - } - }; - }] -); \ No newline at end of file + } + + + // Returns the $dirty value of the element if it exists + function getDirtyValue(element) { + if (element) { + if ("$dirty" in element) { + return element.$dirty; + } + } + } + + + function getValidValue(element) { + if (element) { + if ("$valid" in element) { + return element.$valid; + } + } + } + + + function checkElementValidity(element) { + // If element has a custom validation function + if ("validator" in element.attributes) { + // Call the custom validator function + var isElementValid = scope.$eval(element.attributes.validator.value); + scopeForm[element.name].$setValidity("angularValidator", isElementValid); + return isElementValid; + } + } + + + // Adds and removes an error message as a sibling element of the form field + // depending on the validity of the form field and the submitted state of the form. + // Will use default message if a custom message is not given + function updateValidationMessage(element, formInvalidMessage) { + + var defaultRequiredMessage = function() { + return " Required"; + }; + var defaultInvalidMessage = function() { + return " Invalid"; + }; + + // Make sure the element is a form field and not a button for example + // Only form elements should have names. + if (!(element.name in scopeForm)) { + return; + } + + var scopeElementModel = scopeForm[element.name]; + + // Remove all validation messages + var validationMessageElement = isValidationMessagePresent(element); + if (validationMessageElement) { + validationMessageElement.remove(); + } + + + // Only add validation messages if the form field is $dirty or the form has been submitted + if (scopeElementModel.$dirty || (scope[element.form.name] && scope[element.form.name].submitted)) { + + if (scopeElementModel.$error.required) { + // If there is a custom required message display it + if ("required-message" in element.attributes) { + angular.element(element).after(generateErrorMessage(element.attributes['required-message'].value)); + } + // Display the default required message + else { + angular.element(element).after(generateErrorMessage(defaultRequiredMessage)); + } + } else if (!scopeElementModel.$valid) { + // If there is a custom validation message add it + if ("invalid-message" in element.attributes) { + angular.element(element).after(generateErrorMessage(element.attributes['invalid-message'].value)); + } + // Display error message provided by custom service + else if (formInvalidMessage) { + angular.element(element).after(generateErrorMessage(formInvalidMessage.message(scopeElementModel, element))); + } + // Display the default error message + else { + angular.element(element).after(generateErrorMessage(defaultInvalidMessage)); + } + } + } + } + + + function generateErrorMessage(messageText) { + return ""; + } + + + // Returns the validation message element or False + function isValidationMessagePresent(element) { + var elementSiblings = angular.element(element).parent().children(); + for (var i = 0; i < elementSiblings.length; i++) { + if (angular.element(elementSiblings[i]).hasClass("validationMessage")) { + return angular.element(elementSiblings[i]); + } + } + return false; + } + + + // Adds and removes .has-error class to both the form element and the form element's parent + // depending on the validity of the element and the submitted state of the form + function updateValidationClass(element) { + // Make sure the element is a form field and not a button for example + // Only form fields should have names. + if (!(element.name in scopeForm)) { + return; + } + var formField = scopeForm[element.name]; + + // This is extra for users wishing to implement the .has-error class on the field itself + // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element + angular.element(element).removeClass('has-error'); + angular.element(element.parentNode).removeClass('has-error'); + + + // Only add/remove validation classes if the field is $dirty or the form has been submitted + if (formField.$dirty || (scope[element.form.name] && scope[element.form.name].submitted)) { + if (formField.$invalid) { + angular.element(element.parentNode).addClass('has-error'); + + // This is extra for users wishing to implement the .has-error class on the field itself + // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element + angular.element(element).addClass('has-error'); + } + } + } + + } + }; + }] +); diff --git a/dist/angular-validator.min.js b/dist/angular-validator.min.js index 521ddb8..fb04f32 100644 --- a/dist/angular-validator.min.js +++ b/dist/angular-validator.min.js @@ -1 +1 @@ -angular.module("angularValidator",[]),angular.module("angularValidator").directive("angularValidator",["$injector",function(a){return{restrict:"A",link:function(b,c,d,e){function f(b){return b&&"invalid-message"in b.attributes?a.get(b.attributes["invalid-message"].value):!1}function g(a){for(var b=f(a),c=0;c Required"},e=function(){return" Invalid"};if(a.name in r){var f=r[a.name],g=n(a);g&&g.remove(),(f.$dirty||b[a.form.name].submitted)&&(f.$error.required?"required-message"in a.attributes?angular.element(a).after(m(a.attributes["required-message"].value)):angular.element(a).after(m(d)):f.$valid||("invalid-message"in a.attributes?angular.element(a).after(m(a.attributes["invalid-message"].value)):c?angular.element(a).after(m(c.message(f,a))):angular.element(a).after(m(e))))}}function m(a){return""}function n(a){for(var b=angular.element(a).parent().children(),c=0;c Required"},e=function(){return" Invalid"};if(a.name in r){var f=r[a.name],g=n(a);g&&g.remove(),(f.$dirty||b[a.form.name]&&b[a.form.name].submitted)&&(f.$error.required?"required-message"in a.attributes?angular.element(a).after(m(a.attributes["required-message"].value)):angular.element(a).after(m(d)):f.$valid||("invalid-message"in a.attributes?angular.element(a).after(m(a.attributes["invalid-message"].value)):c?angular.element(a).after(m(c.message(f,a))):angular.element(a).after(m(e))))}}function m(a){return""}function n(a){for(var b=angular.element(a).parent().children(),c=0;c=0.15.4", + "grunt-ng-annotate": ">=0.10.0" } } \ No newline at end of file diff --git a/src/angular-validator.js b/src/angular-validator.js index 5933d58..7890f83 100644 --- a/src/angular-validator.js +++ b/src/angular-validator.js @@ -1,263 +1,262 @@ angular.module('angularValidator', []); angular.module('angularValidator').directive('angularValidator', ['$injector', - function ($injector) { - return { - restrict: 'A', - link: function (scope, element, attrs, ctrl) { - - // This is the DOM form element - var DOMForm = angular.element(element)[0]; - - // This is the the scope form model - // All validation states are contained here - var form_name = DOMForm.attributes['name'].value; - var scopeForm = scope[form_name]; - - // Set the default submitted state to false - scopeForm.submitted = false; - - - // Watch form length to add watches for new form elements - scope.$watch(function () { - return DOMForm.length; - }, function () { - setupWatches(DOMForm); - }); - - - // Intercept and handle submit events of the form - element.on('submit', function (event) { - event.preventDefault(); - scope.$apply(function () { - scopeForm.submitted = true; - }); + function($injector) { + return { + restrict: 'A', + link: function(scope, element, attrs, fn) { + + // This is the DOM form element + var DOMForm = angular.element(element)[0]; + + // This is the the scope form model + // All validation states are contained here + var form_name = DOMForm.attributes['name'].value; + var scopeForm = scope[form_name]; + + // Set the default submitted state to false + scopeForm.submitted = false; + + + // Watch form length to add watches for new form elements + scope.$watch(function(){return DOMForm.length;}, function(){ + setupWatches(DOMForm); + }); + + + // Intercept and handle submit events of the form + element.on('submit', function(event) { + event.preventDefault(); + scope.$apply(function() { + scopeForm.submitted = true; + }); + + // If the form is valid then call the function that is declared in the angular-validator-submit attribute on the form element + if (scopeForm.$valid) { + scope.$apply(function() { + scope.$eval(DOMForm.attributes["angular-validator-submit"].value); + }); + } + }); - // If the form is valid then call the function that is declared in the angular-validator-submit atrribute on the form element - if (scopeForm.$valid) { - scope.$apply(function () { - scope.$eval(DOMForm.attributes["angular-validator-submit"].value); - }); - } - }); + scopeForm.reset = function(){ + // Clear all the form values + for (var i = 0; i < DOMForm.length; i++) { + if (DOMForm[i].name){ + scopeForm[DOMForm[i].name].$setViewValue(""); + scopeForm[DOMForm[i].name].$render(); + } + } - scopeForm.reset = function () { - // Clear all the form values - for (var i = 0; i < DOMForm.length; i++) { - if (DOMForm[i].name) { - scopeForm[DOMForm[i].name].$setViewValue(""); - scopeForm[DOMForm[i].name].$render(); - } - } + scopeForm.submitted = false; + scopeForm.$setPristine(); + }; - scopeForm.submitted = false; - scopeForm.$setPristine(); - }; - // Setup watches on all form fields - setupWatches(DOMForm); + // Setup watches on all form fields + setupWatches(DOMForm); //check if there is invalid message service for the entire form; if yes, return the injected service; if no, return false; - function hasFormInvalidMessage(formElement) { - if (formElement && 'invalid-message' in formElement.attributes) { - return $injector.get(formElement.attributes['invalid-message'].value); - } else { - return false; - } - } - - // Iterate through the form fields and setup watches on each one - function setupWatches(formElement) { - var formInvalidMessage = hasFormInvalidMessage(formElement); - for (var i = 0; i < formElement.length; i++) { - // This ensures we are only watching form fields - if (i in formElement) { - setupWatch(formElement[i], formInvalidMessage); - } - } - } - - - // Setup $watch on a single formfield - function setupWatch(elementToWatch, formInvalidMessage) { - - if (elementToWatch.isWatchedByValidator) { - return; - } - - elementToWatch.isWatchedByValidator = true; - - // If element is set to validate on blur then update the element on blur - if ("validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "blur") { - angular.element(elementToWatch).on('blur', function () { - updateValidationMessage(elementToWatch, formInvalidMessage); - updateValidationClass(elementToWatch); - }); - } - - scope.$watch(function () { + function hasFormInvalidMessage(formElement) { + if (formElement && 'invalid-message' in formElement.attributes) { + return $injector.get(formElement.attributes['invalid-message'].value); + } else { + return false; + } + } + + // Iterate through the form fields and setup watches on each one + function setupWatches(formElement) { + var formInvalidMessage = hasFormInvalidMessage(formElement); + for (var i = 0; i < formElement.length; i++) { + // This ensures we are only watching form fields + if (i in formElement) { + setupWatch(formElement[i], formInvalidMessage); + } + } + } + + + // Setup $watch on a single formfield + function setupWatch(elementToWatch, formInvalidMessage) { + + if (elementToWatch.isWatchedByValidator){ + return; + } + + elementToWatch.isWatchedByValidator = true; + + // If element is set to validate on blur then update the element on blur + if ("validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "blur") { + angular.element(elementToWatch).on('blur', function() { + updateValidationMessage(elementToWatch, formInvalidMessage); + updateValidationClass(elementToWatch); + }); + } + + scope.$watch(function() { return elementToWatch.value + elementToWatch.required + scopeForm.submitted + checkElementValidity(elementToWatch) + getDirtyValue(scopeForm[elementToWatch.name]) + getValidValue(scopeForm[elementToWatch.name]); }, - function () { - - if (scopeForm.submitted) { + function() { + + if (scopeForm.submitted){ updateValidationMessage(elementToWatch, formInvalidMessage); updateValidationClass(elementToWatch); } else { // Determine if the element in question is to be updated on blur - isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; + var isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; - if (isDirtyElement) { + if (isDirtyElement){ updateValidationMessage(elementToWatch, formInvalidMessage); updateValidationClass(elementToWatch); } // This will get called in the case of resetting the form. This only gets called for elements that update on blur and submit. - else if (scopeForm[elementToWatch.name] && scopeForm[elementToWatch.name].$pristine) { + else if (scopeForm[elementToWatch.name] && scopeForm[elementToWatch.name].$pristine){ updateValidationMessage(elementToWatch, formInvalidMessage); updateValidationClass(elementToWatch); } } }); - } - - - // Returns the $dirty value of the element if it exists - function getDirtyValue(element) { - if (element) { - if ("$dirty" in element) { - return element.$dirty; - } - } - } - - - function getValidValue(element) { - if (element) { - if ("$valid" in element) { - return element.$valid; - } - } - } - - - function checkElementValidity(element) { - // If element has a custom validation function - if ("validator" in element.attributes) { - // Call the custom validator function - var isElementValid = scope.$eval(element.attributes.validator.value); - scopeForm[element.name].$setValidity("angularValidator", isElementValid); - return isElementValid; - } - } - - - // Adds and removes an error message as a sibling element of the form field - // depending on the validity of the form field and the submitted state of the form. - // Will use default message if a custom message is not given - function updateValidationMessage(element, formInvalidMessage) { - - var defaultRequiredMessage = function () { - return " Required"; - }; - var defaultInvalidMessage = function () { - return " Invalid"; - }; - - // Make sure the element is a form field and not a button for example - // Only form elements should have names. - if (!(element.name in scopeForm)) { - return; - } - - var scopeElementModel = scopeForm[element.name]; - - // Remove all validation messages - var validationMessageElement = isValidationMessagePresent(element); - if (validationMessageElement) { - validationMessageElement.remove(); - } - - - // Only add validation messages if the form field is $dirty or the form has been submitted - if (scopeElementModel.$dirty || scope[element.form.name].submitted) { - - if (scopeElementModel.$error.required) { - // If there is a custom required message display it - if ("required-message" in element.attributes) { - angular.element(element).after(generateErrorMessage(element.attributes['required-message'].value)); - } - // Display the default required message - else { - angular.element(element).after(generateErrorMessage(defaultRequiredMessage)); - } - } else if (!scopeElementModel.$valid) { - // If there is a custom validation message add it - if ("invalid-message" in element.attributes) { - angular.element(element).after(generateErrorMessage(element.attributes['invalid-message'].value)); - } - // Display error message provided by custom service - else if (formInvalidMessage) { - angular.element(element).after(generateErrorMessage(formInvalidMessage.message(scopeElementModel, element))); - } - // Display the default error message - else { - angular.element(element).after(generateErrorMessage(defaultInvalidMessage)); - } - } - } - } - - - function generateErrorMessage(messageText) { - return ""; - } - - - // Returns the validation message element or False - function isValidationMessagePresent(element) { - var elementSiblings = angular.element(element).parent().children(); - for (var i = 0; i < elementSiblings.length; i++) { - if (angular.element(elementSiblings[i]).hasClass("validationMessage")) { - return angular.element(elementSiblings[i]); - } - } - return false; - } - - - // Adds and removes .has-error class to both the form element and the form element's parent - // depending on the validity of the element and the submitted state of the form - function updateValidationClass(element) { - // Make sure the element is a form field and not a button for example - // Only form fields should have names. - if (!(element.name in scopeForm)) { - return; - } - var formField = scopeForm[element.name]; - - // This is extra for users wishing to implement the .has-error class on the field itself - // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element - angular.element(element).removeClass('has-error'); - angular.element(element.parentNode).removeClass('has-error'); - - - // Only add/remove validation classes if the field is $dirty or the form has been submitted - if (formField.$dirty || scope[element.form.name].submitted) { - if (formField.$invalid) { - angular.element(element.parentNode).addClass('has-error'); - - // This is extra for users wishing to implement the .has-error class on the field itself - // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element - angular.element(element).addClass('has-error'); - } - } - } - - } - }; - }] -); \ No newline at end of file + } + + + // Returns the $dirty value of the element if it exists + function getDirtyValue(element) { + if (element) { + if ("$dirty" in element) { + return element.$dirty; + } + } + } + + + function getValidValue(element) { + if (element) { + if ("$valid" in element) { + return element.$valid; + } + } + } + + + function checkElementValidity(element) { + // If element has a custom validation function + if ("validator" in element.attributes) { + // Call the custom validator function + var isElementValid = scope.$eval(element.attributes.validator.value); + scopeForm[element.name].$setValidity("angularValidator", isElementValid); + return isElementValid; + } + } + + + // Adds and removes an error message as a sibling element of the form field + // depending on the validity of the form field and the submitted state of the form. + // Will use default message if a custom message is not given + function updateValidationMessage(element, formInvalidMessage) { + + var defaultRequiredMessage = function() { + return " Required"; + }; + var defaultInvalidMessage = function() { + return " Invalid"; + }; + + // Make sure the element is a form field and not a button for example + // Only form elements should have names. + if (!(element.name in scopeForm)) { + return; + } + + var scopeElementModel = scopeForm[element.name]; + + // Remove all validation messages + var validationMessageElement = isValidationMessagePresent(element); + if (validationMessageElement) { + validationMessageElement.remove(); + } + + + // Only add validation messages if the form field is $dirty or the form has been submitted + if (scopeElementModel.$dirty || (scope[element.form.name] && scope[element.form.name].submitted)) { + + if (scopeElementModel.$error.required) { + // If there is a custom required message display it + if ("required-message" in element.attributes) { + angular.element(element).after(generateErrorMessage(element.attributes['required-message'].value)); + } + // Display the default required message + else { + angular.element(element).after(generateErrorMessage(defaultRequiredMessage)); + } + } else if (!scopeElementModel.$valid) { + // If there is a custom validation message add it + if ("invalid-message" in element.attributes) { + angular.element(element).after(generateErrorMessage(element.attributes['invalid-message'].value)); + } + // Display error message provided by custom service + else if (formInvalidMessage) { + angular.element(element).after(generateErrorMessage(formInvalidMessage.message(scopeElementModel, element))); + } + // Display the default error message + else { + angular.element(element).after(generateErrorMessage(defaultInvalidMessage)); + } + } + } + } + + + function generateErrorMessage(messageText) { + return ""; + } + + + // Returns the validation message element or False + function isValidationMessagePresent(element) { + var elementSiblings = angular.element(element).parent().children(); + for (var i = 0; i < elementSiblings.length; i++) { + if (angular.element(elementSiblings[i]).hasClass("validationMessage")) { + return angular.element(elementSiblings[i]); + } + } + return false; + } + + + // Adds and removes .has-error class to both the form element and the form element's parent + // depending on the validity of the element and the submitted state of the form + function updateValidationClass(element) { + // Make sure the element is a form field and not a button for example + // Only form fields should have names. + if (!(element.name in scopeForm)) { + return; + } + var formField = scopeForm[element.name]; + + // This is extra for users wishing to implement the .has-error class on the field itself + // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element + angular.element(element).removeClass('has-error'); + angular.element(element.parentNode).removeClass('has-error'); + + + // Only add/remove validation classes if the field is $dirty or the form has been submitted + if (formField.$dirty || (scope[element.form.name] && scope[element.form.name].submitted)) { + if (formField.$invalid) { + angular.element(element.parentNode).addClass('has-error'); + + // This is extra for users wishing to implement the .has-error class on the field itself + // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element + angular.element(element).addClass('has-error'); + } + } + } + + } + }; + }] +); diff --git a/test/angular-validator-spec.js b/test/angular-validator-spec.js index fd1e89a..312f403 100644 --- a/test/angular-validator-spec.js +++ b/test/angular-validator-spec.js @@ -1,206 +1,206 @@ -describe('angularValidator', function () { + describe('angularValidator', function() { - beforeEach(angular.mock.module('angularValidator')); + beforeEach(angular.mock.module('angularValidator')); - - var scope, compile; + var scope, compile; describe('angularValidator without form invalid message', function () { - beforeEach(inject(function ($rootScope, $compile) { - scope = $rootScope.$new(); + beforeEach(inject(function($rootScope, $compile) { + scope = $rootScope.$new(); + + htmlForm = angular.element( + '
' + + '' + + '' + + '' + + '' + + '
' + ); - htmlForm = angular.element( - '
' + - '' + - '' + - '' + - '' + - '
' - ); + element = $compile(htmlForm)(scope); + scope.$digest(); + })); - element = $compile(htmlForm)(scope); - scope.$digest(); - })); - describe('$dirty tests', function () { + describe('$dirty tests', function() { - it('should be dirty', function () { - scope.myForm.lastName.$setViewValue('lastName'); - scope.$digest(); + it('should be dirty', function() { + scope.myForm.lastName.$setViewValue('lastName'); + scope.$digest(); - expect(scope.myForm.lastName.$dirty).toBe(true); - expect(element.hasClass('ng-dirty')).toBe(true); + expect(scope.myForm.lastName.$dirty).toBe(true); + expect(element.hasClass('ng-dirty')).toBe(true); - }); + }); - it('should not be $dirty', function () { - expect(scope.myForm.$pristine).toBe(true); - expect(element.hasClass('ng-pristine')).toBe(true); - }); - }); + it('should not be $dirty', function() { + expect(scope.myForm.$pristine).toBe(true); + expect(element.hasClass('ng-pristine')).toBe(true); + }); + }); - describe('$valid tests', function () { + describe('$valid tests', function() { - it('should be $valid', function () { - scope.myForm.lastName.$setViewValue('lastName'); - scope.$digest(); + it('should be $valid', function() { + scope.myForm.lastName.$setViewValue('lastName'); + scope.$digest(); - expect(scope.myForm.lastName.$invalid).toBe(false); - }); + expect(scope.myForm.lastName.$invalid).toBe(false); + }); - it('should not be $valid', function () { - scope.myForm.lastName.$setViewValue('sss'); - scope.$digest(); + it('should not be $valid', function() { + scope.myForm.lastName.$setViewValue('sss'); + scope.$digest(); - expect(scope.myForm.lastName.$invalid).toBe(true); - }); - }); + expect(scope.myForm.lastName.$invalid).toBe(true); + }); + }); - describe('custom validator tests', function () { - it('should be $valid', function () { - scope.myForm.city.$setViewValue('chicago'); - scope.$digest(); + describe('custom validator tests', function() { - expect(scope.myForm.city.$invalid).toBe(false); - }); + it('should be $valid', function() { + scope.myForm.city.$setViewValue('chicago'); + scope.$digest(); - it('should not be $valid', function () { - scope.myForm.city.$setViewValue('sss'); - scope.$digest(); + expect(scope.myForm.city.$invalid).toBe(false); + }); - expect(scope.myForm.city.$invalid).toBe(true); - }); - }); + it('should not be $valid', function() { + scope.myForm.city.$setViewValue('sss'); + scope.$digest(); + expect(scope.myForm.city.$invalid).toBe(true); + }); + }); - describe('.has-error class tests', function () { - it('should not have class .has-error', function () { - expect(element.hasClass('has-error')).toBe(false); - expect(scope.myForm.$valid).toBe(false); - }); + describe('.has-error class tests', function() { - it('should not have class .has-error', function () { - scope.myForm.firstName.$setViewValue('blah'); - scope.myForm.lastName.$setViewValue('lastName'); - scope.myForm.city.$setViewValue('chicago'); - scope.myForm.state.$setViewValue('chicago'); + it('should not not have class .has-error', function() { + expect(element.hasClass('has-error')).toBe(false); + expect(scope.myForm.$valid).toBe(false); + }); - scope.$digest(); + it('should not not have class .has-error', function() { + scope.myForm.firstName.$setViewValue('blah'); + scope.myForm.lastName.$setViewValue('lastName'); + scope.myForm.city.$setViewValue('chicago'); + scope.myForm.state.$setViewValue('chicago'); - expect(element.hasClass('has-error')).toBe(false); - expect(scope.myForm.$valid).toBe(true); - expect(scope.myForm.firstName.$valid).toBe(true); - }); + scope.$digest(); + expect(element.hasClass('has-error')).toBe(false); + expect(scope.myForm.$valid).toBe(true); + expect(scope.myForm.firstName.$valid).toBe(true); + }); - it('should have the class .has-error', function () { - scope.myForm.lastName.$setViewValue('blah'); - scope.$digest(); - expect(scope.myForm.lastName.$valid).toBe(false); - expect(element.hasClass('has-error')).toBe(true); - expect(scope.myForm.$valid).toBe(false); - expect(element.hasClass('ng-invalid')).toBe(true); - }); - }); + it('should have the class .has-error', function() { + scope.myForm.lastName.$setViewValue('blah'); + scope.$digest(); + expect(scope.myForm.lastName.$valid).toBe(false); + expect(element.hasClass('has-error')).toBe(true); + expect(scope.myForm.$valid).toBe(false); + expect(element.hasClass('ng-invalid')).toBe(true); + }); + }); - describe('required and error messages test', function () { - it('should not display error or required message', function () { - expect(element.hasClass('has-error')).toBe(false); - expect(scope.myForm.$valid).toBe(false); - }); + describe('required and error messages test', function() { - it('should show required message', function () { - scope.myForm.firstName.$setViewValue(''); - scope.$digest(); + it('should not display error or required message', function() { + expect(element.hasClass('has-error')).toBe(false); + expect(scope.myForm.$valid).toBe(false); + }); - expect(element[0][0].nextSibling.innerHTML.indexOf("Required") > 0).toBe(true); - }); + it('should show required message', function() { + scope.myForm.firstName.$setViewValue(''); + scope.$digest(); + expect(element[0][0].nextSibling.innerHTML.indexOf("Required") > 0).toBe(true); + }); - it('should have the custom invalid message', function () { - scope.myForm.lastName.$setViewValue('blah'); - scope.$digest(); - expect(element[0][1].nextSibling.innerHTML === "WHOA").toBe(true); - }); - }); - }); + it('should have the custom invalid message', function() { + scope.myForm.lastName.$setViewValue('blah'); + scope.$digest(); + expect(element[0][1].nextSibling.innerHTML === "WHOA").toBe(true); + }); + }); + }); - describe('angularValidator with form invalid message', function () { - var mockCustomMessage; + describe('angularValidator with form invalid message', function () { - beforeEach(function () { - mockCustomMessage = { - // scopeElementModel is the object in scope version, element is the object in DOM version - message: function (scopeElementModel, element) { - var errors = scopeElementModel.$error; - if (errors.maxlength) { - return "'Should be no longer than " + element.attributes['ng-maxlength'].value + " characters!'"; - } else { - // default message - return "'This field is invalid!'"; - } - } - }; + var mockCustomMessage; - module(function ($provide) { - $provide.value('customMessage', mockCustomMessage); - }); - }); + beforeEach(function () { + mockCustomMessage = { + // scopeElementModel is the object in scope version, element is the object in DOM version + message: function (scopeElementModel, element) { + var errors = scopeElementModel.$error; + if (errors.maxlength) { + return "'Should be no longer than " + element.attributes['ng-maxlength'].value + " characters!'"; + } else { + // default message + return "'This field is invalid!'"; + } + } + }; - beforeEach(inject(function ($rootScope, $compile) { - scope = $rootScope.$new(); + module(function ($provide) { + $provide.value('customMessage', mockCustomMessage); + }); + }); - htmlForm = angular.element( - '
' + - '' + - '' + - '
' - ); + beforeEach(inject(function ($rootScope, $compile) { + scope = $rootScope.$new(); - element = $compile(htmlForm)(scope); - scope.$digest(); - })); + htmlForm = angular.element( + '
' + + '' + + '' + + '
' + ); + element = $compile(htmlForm)(scope); + scope.$digest(); + })); - describe('form invalid message function', function () { - it('should show message from customMessage service', function () { - scope.myForm.firstName.$setViewValue('Joseph'); - scope.$digest(); + describe('form invalid message function', function () { - expect(element.hasClass('has-error')).toBe(true); - expect(scope.myForm.firstName.$invalid).toBe(true); - expect(element[0][0].nextSibling.innerHTML === 'Should be no longer than 5 characters!').toBe(true); - }); + it('should show message from customMessage service', function () { + scope.myForm.firstName.$setViewValue('Joseph'); + scope.$digest(); - it('should not show message from customMessage service', function () { - scope.myForm.firstName.$setViewValue('John'); - scope.$digest(); + expect(element.hasClass('has-error')).toBe(true); + expect(scope.myForm.firstName.$invalid).toBe(true); + expect(element[0][0].nextSibling.innerHTML === 'Should be no longer than 5 characters!').toBe(true); + }); - expect(element.hasClass('has-error')).toBe(false); - expect(scope.myForm.firstName.$valid).toBe(true); - }); + it('should not show message from customMessage service', function () { + scope.myForm.firstName.$setViewValue('John'); + scope.$digest(); - it('field invalid message should have higher priority than form invalid message', function () { - scope.myForm.lastName.$setViewValue('Joseph'); - scope.$digest(); + expect(element.hasClass('has-error')).toBe(false); + expect(scope.myForm.firstName.$valid).toBe(true); + }); - expect(element[0][1].nextSibling.innerHTML === 'The last name is too long!').toBe(true); - }); + it('field invalid message should have higher priority than form invalid message', function () { + scope.myForm.lastName.$setViewValue('Joseph'); + scope.$digest(); - }); - }); + expect(element[0][1].nextSibling.innerHTML === 'The last name is too long!').toBe(true); + }); + }); + }); }); \ No newline at end of file From e497496ac5b1cf3c1af7f0ffa0c2e8a21970cedc Mon Sep 17 00:00:00 2001 From: codonist Date: Sun, 28 Jun 2015 21:27:13 -0400 Subject: [PATCH 13/44] adjust indents --- Gruntfile.js | 16 +- bower.json | 7 +- demo/app.js | 54 ++-- dist/angular-validator.js | 467 ++++++++++++++++----------------- dist/angular-validator.min.js | 2 +- karma.conf.js | 5 +- package.json | 5 +- src/angular-validator.js | 467 ++++++++++++++++----------------- test/angular-validator-spec.js | 290 ++++++++++---------- 9 files changed, 662 insertions(+), 651 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 9f4cbc4..0b7eacb 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,3 +1,8 @@ +/*jslint node: true */ +'use strict'; + +var pkg = require('./package.json'); + module.exports = function(grunt) { // Grunt Config @@ -21,6 +26,12 @@ module.exports = function(grunt) { "dest": "dist/angular-validator.js" } }, + ngAnnotate: { + main: { + src: 'dist/angular-validator.js', + dest: 'dist/angular-validator.js' + }, + }, uglify: { dist: { src: "dist/angular-validator.js", @@ -66,6 +77,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-connect'); + grunt.loadNpmTasks('grunt-ng-annotate'); // Load the plugin that provides the "jshint" task. grunt.loadNpmTasks('grunt-contrib-jshint'); @@ -81,8 +93,8 @@ module.exports = function(grunt) { // Register Task grunt.registerTask('serve', ['connect', 'watch']); - grunt.registerTask('build', ['concat', 'uglify', 'karma:build',]); - grunt.registerTask('test', ['karma:build',]); + grunt.registerTask('build', ['concat', 'ngAnnotate', 'uglify', 'karma:build']); + grunt.registerTask('test', ['karma:build', ]); grunt.registerTask('test-debug', ['karma:debug']); grunt.registerTask('travis', ['karma:travis']); }; \ No newline at end of file diff --git a/bower.json b/bower.json index 35cf191..ed6be05 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "tg-angular-validator", - "version": "1.2.5", + "version": "1.2.6", "authors": [ "Zohar Jackson " ], @@ -32,7 +32,8 @@ "dependencies": { "angular": ">=1.3.0", "bootstrap": ">=3.2.0", - "font-awesome": ">=4.0.3" + "font-awesome": ">=4.0.3", + "angular-mocks": "~1.4.1" }, "homepage": "https://github.com/turinggroup/angular-validator" -} +} \ No newline at end of file diff --git a/demo/app.js b/demo/app.js index e86d474..5f78e45 100644 --- a/demo/app.js +++ b/demo/app.js @@ -1,42 +1,42 @@ angular.module('angular-validator-demo', ['angularValidator']); -angular.module('angular-validator-demo').controller('DemoCtrl', function ($scope) { +angular.module('angular-validator-demo').controller('DemoCtrl', function($scope) { - $scope.submitMyForm = function () { - alert("Form submitted"); - }; + $scope.submitMyForm = function() { + alert("Form submitted"); + }; - $scope.myCustomValidator = function (text) { - return true; - }; + $scope.myCustomValidator = function(text) { + return true; + }; - $scope.anotherCustomValidator = function (text) { - if (text === "rainbow") { - return true; - } else return "type in 'rainbow'"; - }; + $scope.anotherCustomValidator = function(text) { + if (text === "rainbow") { + return true; + } else return "type in 'rainbow'"; + }; - $scope.passwordValidator = function (password) { + $scope.passwordValidator = function(password) { - if (!password) { - return; - } - else if (password.length < 6) { - return "Password must be at least " + 6 + " characters long"; - } - else if (!password.match(/[A-Z]/)) { - return "Password must have at least one capital letter"; - } - else if (!password.match(/[0-9]/)) { - return "Password must have at least one number"; - } + if (!password) { + return; + } + else if (password.length < 6) { + return "Password must be at least " + 6 + " characters long"; + } + else if (!password.match(/[A-Z]/)) { + return "Password must have at least one capital letter"; + } + else if (!password.match(/[0-9]/)) { + return "Password must have at least one number"; + } - return true; - }; + return true; + }; }).factory('customMessage', function () { // invalid message service with message function return { diff --git a/dist/angular-validator.js b/dist/angular-validator.js index 5933d58..7890f83 100644 --- a/dist/angular-validator.js +++ b/dist/angular-validator.js @@ -1,263 +1,262 @@ angular.module('angularValidator', []); angular.module('angularValidator').directive('angularValidator', ['$injector', - function ($injector) { - return { - restrict: 'A', - link: function (scope, element, attrs, ctrl) { - - // This is the DOM form element - var DOMForm = angular.element(element)[0]; - - // This is the the scope form model - // All validation states are contained here - var form_name = DOMForm.attributes['name'].value; - var scopeForm = scope[form_name]; - - // Set the default submitted state to false - scopeForm.submitted = false; - - - // Watch form length to add watches for new form elements - scope.$watch(function () { - return DOMForm.length; - }, function () { - setupWatches(DOMForm); - }); - - - // Intercept and handle submit events of the form - element.on('submit', function (event) { - event.preventDefault(); - scope.$apply(function () { - scopeForm.submitted = true; - }); + function($injector) { + return { + restrict: 'A', + link: function(scope, element, attrs, fn) { + + // This is the DOM form element + var DOMForm = angular.element(element)[0]; + + // This is the the scope form model + // All validation states are contained here + var form_name = DOMForm.attributes['name'].value; + var scopeForm = scope[form_name]; + + // Set the default submitted state to false + scopeForm.submitted = false; + + + // Watch form length to add watches for new form elements + scope.$watch(function(){return DOMForm.length;}, function(){ + setupWatches(DOMForm); + }); + + + // Intercept and handle submit events of the form + element.on('submit', function(event) { + event.preventDefault(); + scope.$apply(function() { + scopeForm.submitted = true; + }); + + // If the form is valid then call the function that is declared in the angular-validator-submit attribute on the form element + if (scopeForm.$valid) { + scope.$apply(function() { + scope.$eval(DOMForm.attributes["angular-validator-submit"].value); + }); + } + }); - // If the form is valid then call the function that is declared in the angular-validator-submit atrribute on the form element - if (scopeForm.$valid) { - scope.$apply(function () { - scope.$eval(DOMForm.attributes["angular-validator-submit"].value); - }); - } - }); + scopeForm.reset = function(){ + // Clear all the form values + for (var i = 0; i < DOMForm.length; i++) { + if (DOMForm[i].name){ + scopeForm[DOMForm[i].name].$setViewValue(""); + scopeForm[DOMForm[i].name].$render(); + } + } - scopeForm.reset = function () { - // Clear all the form values - for (var i = 0; i < DOMForm.length; i++) { - if (DOMForm[i].name) { - scopeForm[DOMForm[i].name].$setViewValue(""); - scopeForm[DOMForm[i].name].$render(); - } - } + scopeForm.submitted = false; + scopeForm.$setPristine(); + }; - scopeForm.submitted = false; - scopeForm.$setPristine(); - }; - // Setup watches on all form fields - setupWatches(DOMForm); + // Setup watches on all form fields + setupWatches(DOMForm); //check if there is invalid message service for the entire form; if yes, return the injected service; if no, return false; - function hasFormInvalidMessage(formElement) { - if (formElement && 'invalid-message' in formElement.attributes) { - return $injector.get(formElement.attributes['invalid-message'].value); - } else { - return false; - } - } - - // Iterate through the form fields and setup watches on each one - function setupWatches(formElement) { - var formInvalidMessage = hasFormInvalidMessage(formElement); - for (var i = 0; i < formElement.length; i++) { - // This ensures we are only watching form fields - if (i in formElement) { - setupWatch(formElement[i], formInvalidMessage); - } - } - } - - - // Setup $watch on a single formfield - function setupWatch(elementToWatch, formInvalidMessage) { - - if (elementToWatch.isWatchedByValidator) { - return; - } - - elementToWatch.isWatchedByValidator = true; - - // If element is set to validate on blur then update the element on blur - if ("validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "blur") { - angular.element(elementToWatch).on('blur', function () { - updateValidationMessage(elementToWatch, formInvalidMessage); - updateValidationClass(elementToWatch); - }); - } - - scope.$watch(function () { + function hasFormInvalidMessage(formElement) { + if (formElement && 'invalid-message' in formElement.attributes) { + return $injector.get(formElement.attributes['invalid-message'].value); + } else { + return false; + } + } + + // Iterate through the form fields and setup watches on each one + function setupWatches(formElement) { + var formInvalidMessage = hasFormInvalidMessage(formElement); + for (var i = 0; i < formElement.length; i++) { + // This ensures we are only watching form fields + if (i in formElement) { + setupWatch(formElement[i], formInvalidMessage); + } + } + } + + + // Setup $watch on a single formfield + function setupWatch(elementToWatch, formInvalidMessage) { + + if (elementToWatch.isWatchedByValidator){ + return; + } + + elementToWatch.isWatchedByValidator = true; + + // If element is set to validate on blur then update the element on blur + if ("validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "blur") { + angular.element(elementToWatch).on('blur', function() { + updateValidationMessage(elementToWatch, formInvalidMessage); + updateValidationClass(elementToWatch); + }); + } + + scope.$watch(function() { return elementToWatch.value + elementToWatch.required + scopeForm.submitted + checkElementValidity(elementToWatch) + getDirtyValue(scopeForm[elementToWatch.name]) + getValidValue(scopeForm[elementToWatch.name]); }, - function () { - - if (scopeForm.submitted) { + function() { + + if (scopeForm.submitted){ updateValidationMessage(elementToWatch, formInvalidMessage); updateValidationClass(elementToWatch); } else { // Determine if the element in question is to be updated on blur - isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; + var isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; - if (isDirtyElement) { + if (isDirtyElement){ updateValidationMessage(elementToWatch, formInvalidMessage); updateValidationClass(elementToWatch); } // This will get called in the case of resetting the form. This only gets called for elements that update on blur and submit. - else if (scopeForm[elementToWatch.name] && scopeForm[elementToWatch.name].$pristine) { + else if (scopeForm[elementToWatch.name] && scopeForm[elementToWatch.name].$pristine){ updateValidationMessage(elementToWatch, formInvalidMessage); updateValidationClass(elementToWatch); } } }); - } - - - // Returns the $dirty value of the element if it exists - function getDirtyValue(element) { - if (element) { - if ("$dirty" in element) { - return element.$dirty; - } - } - } - - - function getValidValue(element) { - if (element) { - if ("$valid" in element) { - return element.$valid; - } - } - } - - - function checkElementValidity(element) { - // If element has a custom validation function - if ("validator" in element.attributes) { - // Call the custom validator function - var isElementValid = scope.$eval(element.attributes.validator.value); - scopeForm[element.name].$setValidity("angularValidator", isElementValid); - return isElementValid; - } - } - - - // Adds and removes an error message as a sibling element of the form field - // depending on the validity of the form field and the submitted state of the form. - // Will use default message if a custom message is not given - function updateValidationMessage(element, formInvalidMessage) { - - var defaultRequiredMessage = function () { - return " Required"; - }; - var defaultInvalidMessage = function () { - return " Invalid"; - }; - - // Make sure the element is a form field and not a button for example - // Only form elements should have names. - if (!(element.name in scopeForm)) { - return; - } - - var scopeElementModel = scopeForm[element.name]; - - // Remove all validation messages - var validationMessageElement = isValidationMessagePresent(element); - if (validationMessageElement) { - validationMessageElement.remove(); - } - - - // Only add validation messages if the form field is $dirty or the form has been submitted - if (scopeElementModel.$dirty || scope[element.form.name].submitted) { - - if (scopeElementModel.$error.required) { - // If there is a custom required message display it - if ("required-message" in element.attributes) { - angular.element(element).after(generateErrorMessage(element.attributes['required-message'].value)); - } - // Display the default required message - else { - angular.element(element).after(generateErrorMessage(defaultRequiredMessage)); - } - } else if (!scopeElementModel.$valid) { - // If there is a custom validation message add it - if ("invalid-message" in element.attributes) { - angular.element(element).after(generateErrorMessage(element.attributes['invalid-message'].value)); - } - // Display error message provided by custom service - else if (formInvalidMessage) { - angular.element(element).after(generateErrorMessage(formInvalidMessage.message(scopeElementModel, element))); - } - // Display the default error message - else { - angular.element(element).after(generateErrorMessage(defaultInvalidMessage)); - } - } - } - } - - - function generateErrorMessage(messageText) { - return ""; - } - - - // Returns the validation message element or False - function isValidationMessagePresent(element) { - var elementSiblings = angular.element(element).parent().children(); - for (var i = 0; i < elementSiblings.length; i++) { - if (angular.element(elementSiblings[i]).hasClass("validationMessage")) { - return angular.element(elementSiblings[i]); - } - } - return false; - } - - - // Adds and removes .has-error class to both the form element and the form element's parent - // depending on the validity of the element and the submitted state of the form - function updateValidationClass(element) { - // Make sure the element is a form field and not a button for example - // Only form fields should have names. - if (!(element.name in scopeForm)) { - return; - } - var formField = scopeForm[element.name]; - - // This is extra for users wishing to implement the .has-error class on the field itself - // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element - angular.element(element).removeClass('has-error'); - angular.element(element.parentNode).removeClass('has-error'); - - - // Only add/remove validation classes if the field is $dirty or the form has been submitted - if (formField.$dirty || scope[element.form.name].submitted) { - if (formField.$invalid) { - angular.element(element.parentNode).addClass('has-error'); - - // This is extra for users wishing to implement the .has-error class on the field itself - // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element - angular.element(element).addClass('has-error'); - } - } - } - - } - }; - }] -); \ No newline at end of file + } + + + // Returns the $dirty value of the element if it exists + function getDirtyValue(element) { + if (element) { + if ("$dirty" in element) { + return element.$dirty; + } + } + } + + + function getValidValue(element) { + if (element) { + if ("$valid" in element) { + return element.$valid; + } + } + } + + + function checkElementValidity(element) { + // If element has a custom validation function + if ("validator" in element.attributes) { + // Call the custom validator function + var isElementValid = scope.$eval(element.attributes.validator.value); + scopeForm[element.name].$setValidity("angularValidator", isElementValid); + return isElementValid; + } + } + + + // Adds and removes an error message as a sibling element of the form field + // depending on the validity of the form field and the submitted state of the form. + // Will use default message if a custom message is not given + function updateValidationMessage(element, formInvalidMessage) { + + var defaultRequiredMessage = function() { + return " Required"; + }; + var defaultInvalidMessage = function() { + return " Invalid"; + }; + + // Make sure the element is a form field and not a button for example + // Only form elements should have names. + if (!(element.name in scopeForm)) { + return; + } + + var scopeElementModel = scopeForm[element.name]; + + // Remove all validation messages + var validationMessageElement = isValidationMessagePresent(element); + if (validationMessageElement) { + validationMessageElement.remove(); + } + + + // Only add validation messages if the form field is $dirty or the form has been submitted + if (scopeElementModel.$dirty || (scope[element.form.name] && scope[element.form.name].submitted)) { + + if (scopeElementModel.$error.required) { + // If there is a custom required message display it + if ("required-message" in element.attributes) { + angular.element(element).after(generateErrorMessage(element.attributes['required-message'].value)); + } + // Display the default required message + else { + angular.element(element).after(generateErrorMessage(defaultRequiredMessage)); + } + } else if (!scopeElementModel.$valid) { + // If there is a custom validation message add it + if ("invalid-message" in element.attributes) { + angular.element(element).after(generateErrorMessage(element.attributes['invalid-message'].value)); + } + // Display error message provided by custom service + else if (formInvalidMessage) { + angular.element(element).after(generateErrorMessage(formInvalidMessage.message(scopeElementModel, element))); + } + // Display the default error message + else { + angular.element(element).after(generateErrorMessage(defaultInvalidMessage)); + } + } + } + } + + + function generateErrorMessage(messageText) { + return ""; + } + + + // Returns the validation message element or False + function isValidationMessagePresent(element) { + var elementSiblings = angular.element(element).parent().children(); + for (var i = 0; i < elementSiblings.length; i++) { + if (angular.element(elementSiblings[i]).hasClass("validationMessage")) { + return angular.element(elementSiblings[i]); + } + } + return false; + } + + + // Adds and removes .has-error class to both the form element and the form element's parent + // depending on the validity of the element and the submitted state of the form + function updateValidationClass(element) { + // Make sure the element is a form field and not a button for example + // Only form fields should have names. + if (!(element.name in scopeForm)) { + return; + } + var formField = scopeForm[element.name]; + + // This is extra for users wishing to implement the .has-error class on the field itself + // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element + angular.element(element).removeClass('has-error'); + angular.element(element.parentNode).removeClass('has-error'); + + + // Only add/remove validation classes if the field is $dirty or the form has been submitted + if (formField.$dirty || (scope[element.form.name] && scope[element.form.name].submitted)) { + if (formField.$invalid) { + angular.element(element.parentNode).addClass('has-error'); + + // This is extra for users wishing to implement the .has-error class on the field itself + // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element + angular.element(element).addClass('has-error'); + } + } + } + + } + }; + }] +); diff --git a/dist/angular-validator.min.js b/dist/angular-validator.min.js index 521ddb8..fb04f32 100644 --- a/dist/angular-validator.min.js +++ b/dist/angular-validator.min.js @@ -1 +1 @@ -angular.module("angularValidator",[]),angular.module("angularValidator").directive("angularValidator",["$injector",function(a){return{restrict:"A",link:function(b,c,d,e){function f(b){return b&&"invalid-message"in b.attributes?a.get(b.attributes["invalid-message"].value):!1}function g(a){for(var b=f(a),c=0;c Required"},e=function(){return" Invalid"};if(a.name in r){var f=r[a.name],g=n(a);g&&g.remove(),(f.$dirty||b[a.form.name].submitted)&&(f.$error.required?"required-message"in a.attributes?angular.element(a).after(m(a.attributes["required-message"].value)):angular.element(a).after(m(d)):f.$valid||("invalid-message"in a.attributes?angular.element(a).after(m(a.attributes["invalid-message"].value)):c?angular.element(a).after(m(c.message(f,a))):angular.element(a).after(m(e))))}}function m(a){return""}function n(a){for(var b=angular.element(a).parent().children(),c=0;c Required"},e=function(){return" Invalid"};if(a.name in r){var f=r[a.name],g=n(a);g&&g.remove(),(f.$dirty||b[a.form.name]&&b[a.form.name].submitted)&&(f.$error.required?"required-message"in a.attributes?angular.element(a).after(m(a.attributes["required-message"].value)):angular.element(a).after(m(d)):f.$valid||("invalid-message"in a.attributes?angular.element(a).after(m(a.attributes["invalid-message"].value)):c?angular.element(a).after(m(c.message(f,a))):angular.element(a).after(m(e))))}}function m(a){return""}function n(a){for(var b=angular.element(a).parent().children(),c=0;c=0.15.4", + "grunt-ng-annotate": ">=0.10.0" } } \ No newline at end of file diff --git a/src/angular-validator.js b/src/angular-validator.js index 5933d58..7890f83 100644 --- a/src/angular-validator.js +++ b/src/angular-validator.js @@ -1,263 +1,262 @@ angular.module('angularValidator', []); angular.module('angularValidator').directive('angularValidator', ['$injector', - function ($injector) { - return { - restrict: 'A', - link: function (scope, element, attrs, ctrl) { - - // This is the DOM form element - var DOMForm = angular.element(element)[0]; - - // This is the the scope form model - // All validation states are contained here - var form_name = DOMForm.attributes['name'].value; - var scopeForm = scope[form_name]; - - // Set the default submitted state to false - scopeForm.submitted = false; - - - // Watch form length to add watches for new form elements - scope.$watch(function () { - return DOMForm.length; - }, function () { - setupWatches(DOMForm); - }); - - - // Intercept and handle submit events of the form - element.on('submit', function (event) { - event.preventDefault(); - scope.$apply(function () { - scopeForm.submitted = true; - }); + function($injector) { + return { + restrict: 'A', + link: function(scope, element, attrs, fn) { + + // This is the DOM form element + var DOMForm = angular.element(element)[0]; + + // This is the the scope form model + // All validation states are contained here + var form_name = DOMForm.attributes['name'].value; + var scopeForm = scope[form_name]; + + // Set the default submitted state to false + scopeForm.submitted = false; + + + // Watch form length to add watches for new form elements + scope.$watch(function(){return DOMForm.length;}, function(){ + setupWatches(DOMForm); + }); + + + // Intercept and handle submit events of the form + element.on('submit', function(event) { + event.preventDefault(); + scope.$apply(function() { + scopeForm.submitted = true; + }); + + // If the form is valid then call the function that is declared in the angular-validator-submit attribute on the form element + if (scopeForm.$valid) { + scope.$apply(function() { + scope.$eval(DOMForm.attributes["angular-validator-submit"].value); + }); + } + }); - // If the form is valid then call the function that is declared in the angular-validator-submit atrribute on the form element - if (scopeForm.$valid) { - scope.$apply(function () { - scope.$eval(DOMForm.attributes["angular-validator-submit"].value); - }); - } - }); + scopeForm.reset = function(){ + // Clear all the form values + for (var i = 0; i < DOMForm.length; i++) { + if (DOMForm[i].name){ + scopeForm[DOMForm[i].name].$setViewValue(""); + scopeForm[DOMForm[i].name].$render(); + } + } - scopeForm.reset = function () { - // Clear all the form values - for (var i = 0; i < DOMForm.length; i++) { - if (DOMForm[i].name) { - scopeForm[DOMForm[i].name].$setViewValue(""); - scopeForm[DOMForm[i].name].$render(); - } - } + scopeForm.submitted = false; + scopeForm.$setPristine(); + }; - scopeForm.submitted = false; - scopeForm.$setPristine(); - }; - // Setup watches on all form fields - setupWatches(DOMForm); + // Setup watches on all form fields + setupWatches(DOMForm); //check if there is invalid message service for the entire form; if yes, return the injected service; if no, return false; - function hasFormInvalidMessage(formElement) { - if (formElement && 'invalid-message' in formElement.attributes) { - return $injector.get(formElement.attributes['invalid-message'].value); - } else { - return false; - } - } - - // Iterate through the form fields and setup watches on each one - function setupWatches(formElement) { - var formInvalidMessage = hasFormInvalidMessage(formElement); - for (var i = 0; i < formElement.length; i++) { - // This ensures we are only watching form fields - if (i in formElement) { - setupWatch(formElement[i], formInvalidMessage); - } - } - } - - - // Setup $watch on a single formfield - function setupWatch(elementToWatch, formInvalidMessage) { - - if (elementToWatch.isWatchedByValidator) { - return; - } - - elementToWatch.isWatchedByValidator = true; - - // If element is set to validate on blur then update the element on blur - if ("validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "blur") { - angular.element(elementToWatch).on('blur', function () { - updateValidationMessage(elementToWatch, formInvalidMessage); - updateValidationClass(elementToWatch); - }); - } - - scope.$watch(function () { + function hasFormInvalidMessage(formElement) { + if (formElement && 'invalid-message' in formElement.attributes) { + return $injector.get(formElement.attributes['invalid-message'].value); + } else { + return false; + } + } + + // Iterate through the form fields and setup watches on each one + function setupWatches(formElement) { + var formInvalidMessage = hasFormInvalidMessage(formElement); + for (var i = 0; i < formElement.length; i++) { + // This ensures we are only watching form fields + if (i in formElement) { + setupWatch(formElement[i], formInvalidMessage); + } + } + } + + + // Setup $watch on a single formfield + function setupWatch(elementToWatch, formInvalidMessage) { + + if (elementToWatch.isWatchedByValidator){ + return; + } + + elementToWatch.isWatchedByValidator = true; + + // If element is set to validate on blur then update the element on blur + if ("validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "blur") { + angular.element(elementToWatch).on('blur', function() { + updateValidationMessage(elementToWatch, formInvalidMessage); + updateValidationClass(elementToWatch); + }); + } + + scope.$watch(function() { return elementToWatch.value + elementToWatch.required + scopeForm.submitted + checkElementValidity(elementToWatch) + getDirtyValue(scopeForm[elementToWatch.name]) + getValidValue(scopeForm[elementToWatch.name]); }, - function () { - - if (scopeForm.submitted) { + function() { + + if (scopeForm.submitted){ updateValidationMessage(elementToWatch, formInvalidMessage); updateValidationClass(elementToWatch); } else { // Determine if the element in question is to be updated on blur - isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; + var isDirtyElement = "validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "dirty"; - if (isDirtyElement) { + if (isDirtyElement){ updateValidationMessage(elementToWatch, formInvalidMessage); updateValidationClass(elementToWatch); } // This will get called in the case of resetting the form. This only gets called for elements that update on blur and submit. - else if (scopeForm[elementToWatch.name] && scopeForm[elementToWatch.name].$pristine) { + else if (scopeForm[elementToWatch.name] && scopeForm[elementToWatch.name].$pristine){ updateValidationMessage(elementToWatch, formInvalidMessage); updateValidationClass(elementToWatch); } } }); - } - - - // Returns the $dirty value of the element if it exists - function getDirtyValue(element) { - if (element) { - if ("$dirty" in element) { - return element.$dirty; - } - } - } - - - function getValidValue(element) { - if (element) { - if ("$valid" in element) { - return element.$valid; - } - } - } - - - function checkElementValidity(element) { - // If element has a custom validation function - if ("validator" in element.attributes) { - // Call the custom validator function - var isElementValid = scope.$eval(element.attributes.validator.value); - scopeForm[element.name].$setValidity("angularValidator", isElementValid); - return isElementValid; - } - } - - - // Adds and removes an error message as a sibling element of the form field - // depending on the validity of the form field and the submitted state of the form. - // Will use default message if a custom message is not given - function updateValidationMessage(element, formInvalidMessage) { - - var defaultRequiredMessage = function () { - return " Required"; - }; - var defaultInvalidMessage = function () { - return " Invalid"; - }; - - // Make sure the element is a form field and not a button for example - // Only form elements should have names. - if (!(element.name in scopeForm)) { - return; - } - - var scopeElementModel = scopeForm[element.name]; - - // Remove all validation messages - var validationMessageElement = isValidationMessagePresent(element); - if (validationMessageElement) { - validationMessageElement.remove(); - } - - - // Only add validation messages if the form field is $dirty or the form has been submitted - if (scopeElementModel.$dirty || scope[element.form.name].submitted) { - - if (scopeElementModel.$error.required) { - // If there is a custom required message display it - if ("required-message" in element.attributes) { - angular.element(element).after(generateErrorMessage(element.attributes['required-message'].value)); - } - // Display the default required message - else { - angular.element(element).after(generateErrorMessage(defaultRequiredMessage)); - } - } else if (!scopeElementModel.$valid) { - // If there is a custom validation message add it - if ("invalid-message" in element.attributes) { - angular.element(element).after(generateErrorMessage(element.attributes['invalid-message'].value)); - } - // Display error message provided by custom service - else if (formInvalidMessage) { - angular.element(element).after(generateErrorMessage(formInvalidMessage.message(scopeElementModel, element))); - } - // Display the default error message - else { - angular.element(element).after(generateErrorMessage(defaultInvalidMessage)); - } - } - } - } - - - function generateErrorMessage(messageText) { - return ""; - } - - - // Returns the validation message element or False - function isValidationMessagePresent(element) { - var elementSiblings = angular.element(element).parent().children(); - for (var i = 0; i < elementSiblings.length; i++) { - if (angular.element(elementSiblings[i]).hasClass("validationMessage")) { - return angular.element(elementSiblings[i]); - } - } - return false; - } - - - // Adds and removes .has-error class to both the form element and the form element's parent - // depending on the validity of the element and the submitted state of the form - function updateValidationClass(element) { - // Make sure the element is a form field and not a button for example - // Only form fields should have names. - if (!(element.name in scopeForm)) { - return; - } - var formField = scopeForm[element.name]; - - // This is extra for users wishing to implement the .has-error class on the field itself - // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element - angular.element(element).removeClass('has-error'); - angular.element(element.parentNode).removeClass('has-error'); - - - // Only add/remove validation classes if the field is $dirty or the form has been submitted - if (formField.$dirty || scope[element.form.name].submitted) { - if (formField.$invalid) { - angular.element(element.parentNode).addClass('has-error'); - - // This is extra for users wishing to implement the .has-error class on the field itself - // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element - angular.element(element).addClass('has-error'); - } - } - } - - } - }; - }] -); \ No newline at end of file + } + + + // Returns the $dirty value of the element if it exists + function getDirtyValue(element) { + if (element) { + if ("$dirty" in element) { + return element.$dirty; + } + } + } + + + function getValidValue(element) { + if (element) { + if ("$valid" in element) { + return element.$valid; + } + } + } + + + function checkElementValidity(element) { + // If element has a custom validation function + if ("validator" in element.attributes) { + // Call the custom validator function + var isElementValid = scope.$eval(element.attributes.validator.value); + scopeForm[element.name].$setValidity("angularValidator", isElementValid); + return isElementValid; + } + } + + + // Adds and removes an error message as a sibling element of the form field + // depending on the validity of the form field and the submitted state of the form. + // Will use default message if a custom message is not given + function updateValidationMessage(element, formInvalidMessage) { + + var defaultRequiredMessage = function() { + return " Required"; + }; + var defaultInvalidMessage = function() { + return " Invalid"; + }; + + // Make sure the element is a form field and not a button for example + // Only form elements should have names. + if (!(element.name in scopeForm)) { + return; + } + + var scopeElementModel = scopeForm[element.name]; + + // Remove all validation messages + var validationMessageElement = isValidationMessagePresent(element); + if (validationMessageElement) { + validationMessageElement.remove(); + } + + + // Only add validation messages if the form field is $dirty or the form has been submitted + if (scopeElementModel.$dirty || (scope[element.form.name] && scope[element.form.name].submitted)) { + + if (scopeElementModel.$error.required) { + // If there is a custom required message display it + if ("required-message" in element.attributes) { + angular.element(element).after(generateErrorMessage(element.attributes['required-message'].value)); + } + // Display the default required message + else { + angular.element(element).after(generateErrorMessage(defaultRequiredMessage)); + } + } else if (!scopeElementModel.$valid) { + // If there is a custom validation message add it + if ("invalid-message" in element.attributes) { + angular.element(element).after(generateErrorMessage(element.attributes['invalid-message'].value)); + } + // Display error message provided by custom service + else if (formInvalidMessage) { + angular.element(element).after(generateErrorMessage(formInvalidMessage.message(scopeElementModel, element))); + } + // Display the default error message + else { + angular.element(element).after(generateErrorMessage(defaultInvalidMessage)); + } + } + } + } + + + function generateErrorMessage(messageText) { + return ""; + } + + + // Returns the validation message element or False + function isValidationMessagePresent(element) { + var elementSiblings = angular.element(element).parent().children(); + for (var i = 0; i < elementSiblings.length; i++) { + if (angular.element(elementSiblings[i]).hasClass("validationMessage")) { + return angular.element(elementSiblings[i]); + } + } + return false; + } + + + // Adds and removes .has-error class to both the form element and the form element's parent + // depending on the validity of the element and the submitted state of the form + function updateValidationClass(element) { + // Make sure the element is a form field and not a button for example + // Only form fields should have names. + if (!(element.name in scopeForm)) { + return; + } + var formField = scopeForm[element.name]; + + // This is extra for users wishing to implement the .has-error class on the field itself + // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element + angular.element(element).removeClass('has-error'); + angular.element(element.parentNode).removeClass('has-error'); + + + // Only add/remove validation classes if the field is $dirty or the form has been submitted + if (formField.$dirty || (scope[element.form.name] && scope[element.form.name].submitted)) { + if (formField.$invalid) { + angular.element(element.parentNode).addClass('has-error'); + + // This is extra for users wishing to implement the .has-error class on the field itself + // instead of on the parent element. Note that Bootstrap requires the .has-error class to be on the parent element + angular.element(element).addClass('has-error'); + } + } + } + + } + }; + }] +); diff --git a/test/angular-validator-spec.js b/test/angular-validator-spec.js index fd1e89a..312f403 100644 --- a/test/angular-validator-spec.js +++ b/test/angular-validator-spec.js @@ -1,206 +1,206 @@ -describe('angularValidator', function () { + describe('angularValidator', function() { - beforeEach(angular.mock.module('angularValidator')); + beforeEach(angular.mock.module('angularValidator')); - - var scope, compile; + var scope, compile; describe('angularValidator without form invalid message', function () { - beforeEach(inject(function ($rootScope, $compile) { - scope = $rootScope.$new(); + beforeEach(inject(function($rootScope, $compile) { + scope = $rootScope.$new(); + + htmlForm = angular.element( + '
' + + '' + + '' + + '' + + '' + + '
' + ); - htmlForm = angular.element( - '
' + - '' + - '' + - '' + - '' + - '
' - ); + element = $compile(htmlForm)(scope); + scope.$digest(); + })); - element = $compile(htmlForm)(scope); - scope.$digest(); - })); - describe('$dirty tests', function () { + describe('$dirty tests', function() { - it('should be dirty', function () { - scope.myForm.lastName.$setViewValue('lastName'); - scope.$digest(); + it('should be dirty', function() { + scope.myForm.lastName.$setViewValue('lastName'); + scope.$digest(); - expect(scope.myForm.lastName.$dirty).toBe(true); - expect(element.hasClass('ng-dirty')).toBe(true); + expect(scope.myForm.lastName.$dirty).toBe(true); + expect(element.hasClass('ng-dirty')).toBe(true); - }); + }); - it('should not be $dirty', function () { - expect(scope.myForm.$pristine).toBe(true); - expect(element.hasClass('ng-pristine')).toBe(true); - }); - }); + it('should not be $dirty', function() { + expect(scope.myForm.$pristine).toBe(true); + expect(element.hasClass('ng-pristine')).toBe(true); + }); + }); - describe('$valid tests', function () { + describe('$valid tests', function() { - it('should be $valid', function () { - scope.myForm.lastName.$setViewValue('lastName'); - scope.$digest(); + it('should be $valid', function() { + scope.myForm.lastName.$setViewValue('lastName'); + scope.$digest(); - expect(scope.myForm.lastName.$invalid).toBe(false); - }); + expect(scope.myForm.lastName.$invalid).toBe(false); + }); - it('should not be $valid', function () { - scope.myForm.lastName.$setViewValue('sss'); - scope.$digest(); + it('should not be $valid', function() { + scope.myForm.lastName.$setViewValue('sss'); + scope.$digest(); - expect(scope.myForm.lastName.$invalid).toBe(true); - }); - }); + expect(scope.myForm.lastName.$invalid).toBe(true); + }); + }); - describe('custom validator tests', function () { - it('should be $valid', function () { - scope.myForm.city.$setViewValue('chicago'); - scope.$digest(); + describe('custom validator tests', function() { - expect(scope.myForm.city.$invalid).toBe(false); - }); + it('should be $valid', function() { + scope.myForm.city.$setViewValue('chicago'); + scope.$digest(); - it('should not be $valid', function () { - scope.myForm.city.$setViewValue('sss'); - scope.$digest(); + expect(scope.myForm.city.$invalid).toBe(false); + }); - expect(scope.myForm.city.$invalid).toBe(true); - }); - }); + it('should not be $valid', function() { + scope.myForm.city.$setViewValue('sss'); + scope.$digest(); + expect(scope.myForm.city.$invalid).toBe(true); + }); + }); - describe('.has-error class tests', function () { - it('should not have class .has-error', function () { - expect(element.hasClass('has-error')).toBe(false); - expect(scope.myForm.$valid).toBe(false); - }); + describe('.has-error class tests', function() { - it('should not have class .has-error', function () { - scope.myForm.firstName.$setViewValue('blah'); - scope.myForm.lastName.$setViewValue('lastName'); - scope.myForm.city.$setViewValue('chicago'); - scope.myForm.state.$setViewValue('chicago'); + it('should not not have class .has-error', function() { + expect(element.hasClass('has-error')).toBe(false); + expect(scope.myForm.$valid).toBe(false); + }); - scope.$digest(); + it('should not not have class .has-error', function() { + scope.myForm.firstName.$setViewValue('blah'); + scope.myForm.lastName.$setViewValue('lastName'); + scope.myForm.city.$setViewValue('chicago'); + scope.myForm.state.$setViewValue('chicago'); - expect(element.hasClass('has-error')).toBe(false); - expect(scope.myForm.$valid).toBe(true); - expect(scope.myForm.firstName.$valid).toBe(true); - }); + scope.$digest(); + expect(element.hasClass('has-error')).toBe(false); + expect(scope.myForm.$valid).toBe(true); + expect(scope.myForm.firstName.$valid).toBe(true); + }); - it('should have the class .has-error', function () { - scope.myForm.lastName.$setViewValue('blah'); - scope.$digest(); - expect(scope.myForm.lastName.$valid).toBe(false); - expect(element.hasClass('has-error')).toBe(true); - expect(scope.myForm.$valid).toBe(false); - expect(element.hasClass('ng-invalid')).toBe(true); - }); - }); + it('should have the class .has-error', function() { + scope.myForm.lastName.$setViewValue('blah'); + scope.$digest(); + expect(scope.myForm.lastName.$valid).toBe(false); + expect(element.hasClass('has-error')).toBe(true); + expect(scope.myForm.$valid).toBe(false); + expect(element.hasClass('ng-invalid')).toBe(true); + }); + }); - describe('required and error messages test', function () { - it('should not display error or required message', function () { - expect(element.hasClass('has-error')).toBe(false); - expect(scope.myForm.$valid).toBe(false); - }); + describe('required and error messages test', function() { - it('should show required message', function () { - scope.myForm.firstName.$setViewValue(''); - scope.$digest(); + it('should not display error or required message', function() { + expect(element.hasClass('has-error')).toBe(false); + expect(scope.myForm.$valid).toBe(false); + }); - expect(element[0][0].nextSibling.innerHTML.indexOf("Required") > 0).toBe(true); - }); + it('should show required message', function() { + scope.myForm.firstName.$setViewValue(''); + scope.$digest(); + expect(element[0][0].nextSibling.innerHTML.indexOf("Required") > 0).toBe(true); + }); - it('should have the custom invalid message', function () { - scope.myForm.lastName.$setViewValue('blah'); - scope.$digest(); - expect(element[0][1].nextSibling.innerHTML === "WHOA").toBe(true); - }); - }); - }); + it('should have the custom invalid message', function() { + scope.myForm.lastName.$setViewValue('blah'); + scope.$digest(); + expect(element[0][1].nextSibling.innerHTML === "WHOA").toBe(true); + }); + }); + }); - describe('angularValidator with form invalid message', function () { - var mockCustomMessage; + describe('angularValidator with form invalid message', function () { - beforeEach(function () { - mockCustomMessage = { - // scopeElementModel is the object in scope version, element is the object in DOM version - message: function (scopeElementModel, element) { - var errors = scopeElementModel.$error; - if (errors.maxlength) { - return "'Should be no longer than " + element.attributes['ng-maxlength'].value + " characters!'"; - } else { - // default message - return "'This field is invalid!'"; - } - } - }; + var mockCustomMessage; - module(function ($provide) { - $provide.value('customMessage', mockCustomMessage); - }); - }); + beforeEach(function () { + mockCustomMessage = { + // scopeElementModel is the object in scope version, element is the object in DOM version + message: function (scopeElementModel, element) { + var errors = scopeElementModel.$error; + if (errors.maxlength) { + return "'Should be no longer than " + element.attributes['ng-maxlength'].value + " characters!'"; + } else { + // default message + return "'This field is invalid!'"; + } + } + }; - beforeEach(inject(function ($rootScope, $compile) { - scope = $rootScope.$new(); + module(function ($provide) { + $provide.value('customMessage', mockCustomMessage); + }); + }); - htmlForm = angular.element( - '
' + - '' + - '' + - '
' - ); + beforeEach(inject(function ($rootScope, $compile) { + scope = $rootScope.$new(); - element = $compile(htmlForm)(scope); - scope.$digest(); - })); + htmlForm = angular.element( + '
' + + '' + + '' + + '
' + ); + element = $compile(htmlForm)(scope); + scope.$digest(); + })); - describe('form invalid message function', function () { - it('should show message from customMessage service', function () { - scope.myForm.firstName.$setViewValue('Joseph'); - scope.$digest(); + describe('form invalid message function', function () { - expect(element.hasClass('has-error')).toBe(true); - expect(scope.myForm.firstName.$invalid).toBe(true); - expect(element[0][0].nextSibling.innerHTML === 'Should be no longer than 5 characters!').toBe(true); - }); + it('should show message from customMessage service', function () { + scope.myForm.firstName.$setViewValue('Joseph'); + scope.$digest(); - it('should not show message from customMessage service', function () { - scope.myForm.firstName.$setViewValue('John'); - scope.$digest(); + expect(element.hasClass('has-error')).toBe(true); + expect(scope.myForm.firstName.$invalid).toBe(true); + expect(element[0][0].nextSibling.innerHTML === 'Should be no longer than 5 characters!').toBe(true); + }); - expect(element.hasClass('has-error')).toBe(false); - expect(scope.myForm.firstName.$valid).toBe(true); - }); + it('should not show message from customMessage service', function () { + scope.myForm.firstName.$setViewValue('John'); + scope.$digest(); - it('field invalid message should have higher priority than form invalid message', function () { - scope.myForm.lastName.$setViewValue('Joseph'); - scope.$digest(); + expect(element.hasClass('has-error')).toBe(false); + expect(scope.myForm.firstName.$valid).toBe(true); + }); - expect(element[0][1].nextSibling.innerHTML === 'The last name is too long!').toBe(true); - }); + it('field invalid message should have higher priority than form invalid message', function () { + scope.myForm.lastName.$setViewValue('Joseph'); + scope.$digest(); - }); - }); + expect(element[0][1].nextSibling.innerHTML === 'The last name is too long!').toBe(true); + }); + }); + }); }); \ No newline at end of file From dc611c6b97bdbd30e088504e06c3890129220d70 Mon Sep 17 00:00:00 2001 From: codonist Date: Sun, 28 Jun 2015 21:53:10 -0400 Subject: [PATCH 14/44] adjust indents --- Gruntfile.js | 16 ++-------------- bower.json | 7 +++---- karma.conf.js | 5 +++-- package.json | 5 ++--- 4 files changed, 10 insertions(+), 23 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 0b7eacb..9f4cbc4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,8 +1,3 @@ -/*jslint node: true */ -'use strict'; - -var pkg = require('./package.json'); - module.exports = function(grunt) { // Grunt Config @@ -26,12 +21,6 @@ module.exports = function(grunt) { "dest": "dist/angular-validator.js" } }, - ngAnnotate: { - main: { - src: 'dist/angular-validator.js', - dest: 'dist/angular-validator.js' - }, - }, uglify: { dist: { src: "dist/angular-validator.js", @@ -77,7 +66,6 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-connect'); - grunt.loadNpmTasks('grunt-ng-annotate'); // Load the plugin that provides the "jshint" task. grunt.loadNpmTasks('grunt-contrib-jshint'); @@ -93,8 +81,8 @@ module.exports = function(grunt) { // Register Task grunt.registerTask('serve', ['connect', 'watch']); - grunt.registerTask('build', ['concat', 'ngAnnotate', 'uglify', 'karma:build']); - grunt.registerTask('test', ['karma:build', ]); + grunt.registerTask('build', ['concat', 'uglify', 'karma:build',]); + grunt.registerTask('test', ['karma:build',]); grunt.registerTask('test-debug', ['karma:debug']); grunt.registerTask('travis', ['karma:travis']); }; \ No newline at end of file diff --git a/bower.json b/bower.json index ed6be05..35cf191 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "tg-angular-validator", - "version": "1.2.6", + "version": "1.2.5", "authors": [ "Zohar Jackson " ], @@ -32,8 +32,7 @@ "dependencies": { "angular": ">=1.3.0", "bootstrap": ">=3.2.0", - "font-awesome": ">=4.0.3", - "angular-mocks": "~1.4.1" + "font-awesome": ">=4.0.3" }, "homepage": "https://github.com/turinggroup/angular-validator" -} \ No newline at end of file +} diff --git a/karma.conf.js b/karma.conf.js index ea8d1e6..7ae743a 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,4 +1,5 @@ // Karma configuration + module.exports = function(config) { config.set({ @@ -9,8 +10,8 @@ module.exports = function(config) { // list of files / patterns to load in the browser files: [ - 'bower_components/angular/angular.js', - 'bower_components/angular-mocks/angular-mocks.js', + 'https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.6/angular.js', + 'https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.6/angular-mocks.js', 'dist/angular-validator.min.js', 'test/*.js' ], diff --git a/package.json b/package.json index 42131f1..18d9a10 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,9 @@ "grunt-browser-sync": "~0.9.1", "grunt-karma": "latest", "karma": "~0.12.1", + "karma-jasmine": "~0.1.5", "karma-chrome-launcher": "~0.1.2", "karma-phantomjs-launcher": "~0.1.2", - "karma-jasmine": "~0.1.5", - "ng-annotate": ">=0.15.4", - "grunt-ng-annotate": ">=0.10.0" + "karma-firefox-launcher": "~0.1.3" } } \ No newline at end of file From 226fd527e33364ca19e8ee885def146963f186e2 Mon Sep 17 00:00:00 2001 From: Zohar Jackson Date: Sat, 11 Jul 2015 19:16:56 -0500 Subject: [PATCH 15/44] Fixed issue with validtor and ng-if --- bower.json | 2 +- demo/index.html | 11 +++-------- dist/angular-validator.js | 22 ++++++++++------------ dist/angular-validator.min.js | 2 +- src/angular-validator.js | 22 ++++++++++------------ 5 files changed, 25 insertions(+), 34 deletions(-) diff --git a/bower.json b/bower.json index ed6be05..d85bf91 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "tg-angular-validator", - "version": "1.2.6", + "version": "1.2.7", "authors": [ "Zohar Jackson " ], diff --git a/demo/index.html b/demo/index.html index e6f00ab..8f27478 100644 --- a/demo/index.html +++ b/demo/index.html @@ -2,21 +2,16 @@ Angular-Validator Demo - - - - +

Angular-Validator Demo

https://github.com/turinggroup/angular-validator
-

Choose when to validate:

diff --git a/dist/angular-validator.js b/dist/angular-validator.js index 67fd05d..caef123 100644 --- a/dist/angular-validator.js +++ b/dist/angular-validator.js @@ -9,6 +9,9 @@ angular.module('angularValidator').directive('angularValidator', // This is the DOM form element var DOMForm = angular.element(element)[0]; + // an array to store all the watches for form elements + var watches = []; + // This is the the scope form model // All validation states are contained here var form_name = DOMForm.attributes['name'].value; @@ -16,10 +19,12 @@ angular.module('angularValidator').directive('angularValidator', // Set the default submitted state to false scopeForm.submitted = false; - // Watch form length to add watches for new form elements - scope.$watch(function(){return DOMForm.length;}, function(){ + scope.$watch(function(){return Object.keys(scopeForm).length;}, function(){ + // Destroy all the watches + // This is cleaner than figuring out which items are already being watched and only un-watching those. + angular.forEach(watches, function(watch){watch();}); setupWatches(DOMForm); }); @@ -48,13 +53,11 @@ angular.module('angularValidator').directive('angularValidator', scopeForm[DOMForm[i].name].$render(); } } - scopeForm.submitted = false; scopeForm.$setPristine(); }; - // Setup watches on all form fields setupWatches(DOMForm); @@ -72,13 +75,6 @@ angular.module('angularValidator').directive('angularValidator', // Setup $watch on a single formfield function setupWatch(elementToWatch) { - - if (elementToWatch.isWatchedByValidator){ - return; - } - - elementToWatch.isWatchedByValidator = true; - // If element is set to validate on blur then update the element on blur if ("validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "blur") { angular.element(elementToWatch).on('blur', function() { @@ -87,7 +83,7 @@ angular.module('angularValidator').directive('angularValidator', }); } - scope.$watch(function() { + var watch = scope.$watch(function() { return elementToWatch.value + elementToWatch.required + scopeForm.submitted + checkElementValidity(elementToWatch) + getDirtyValue(scopeForm[elementToWatch.name]) + getValidValue(scopeForm[elementToWatch.name]); }, function() { @@ -112,6 +108,8 @@ angular.module('angularValidator').directive('angularValidator', } }); + + watches.push(watch); } diff --git a/dist/angular-validator.min.js b/dist/angular-validator.min.js index ef95326..edb1205 100644 --- a/dist/angular-validator.min.js +++ b/dist/angular-validator.min.js @@ -1 +1 @@ -angular.module("angularValidator",[]),angular.module("angularValidator").directive("angularValidator",function(){return{restrict:"A",link:function(a,b){function c(a){for(var b=0;b Required"},d=function(){return" Invalid"};if(b.name in n){var e=n[b.name],f=j(b);f&&f.remove(),(e.$dirty||a[b.form.name]&&a[b.form.name].submitted)&&(e.$error.required?angular.element(b).after("required-message"in b.attributes?i(b.attributes["required-message"].value):i(c)):e.$valid||angular.element(b).after("invalid-message"in b.attributes?i(b.attributes["invalid-message"].value):i(d)))}}function i(b){return""}function j(a){for(var b=angular.element(a).parent().children(),c=0;c Required"},d=function(){return" Invalid"};if(b.name in o){var e=o[b.name],f=j(b);f&&f.remove(),(e.$dirty||a[b.form.name]&&a[b.form.name].submitted)&&(e.$error.required?angular.element(b).after("required-message"in b.attributes?i(b.attributes["required-message"].value):i(c)):e.$valid||angular.element(b).after("invalid-message"in b.attributes?i(b.attributes["invalid-message"].value):i(d)))}}function i(b){return""}function j(a){for(var b=angular.element(a).parent().children(),c=0;c Date: Sun, 12 Jul 2015 15:03:13 -0500 Subject: [PATCH 16/44] updated dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b763bbe..5d2eb01 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "scripts": { "test": "grunt test --verbose" }, - "devDependencies": { + "dependencies": { "grunt-cli": ">= 0.1.7", "grunt-contrib-connect" : "*", "grunt-contrib-concat": "*", From 4299b3e3f91b816e12d77819e37d4b453a736e28 Mon Sep 17 00:00:00 2001 From: codonist Date: Fri, 31 Jul 2015 22:23:15 -0400 Subject: [PATCH 17/44] change karma version from ~0.12.1 to ^0.13.0, according to travis --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5d2eb01..e223f53 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "grunt-contrib-watch": "~0.6.1", "grunt-browser-sync": "~0.9.1", "grunt-karma": "latest", - "karma": "~0.12.1", + "karma": "^0.13.0", "karma-phantomjs-launcher": "~0.1.2", "karma-jasmine": "~0.1.5", "ng-annotate": ">=0.15.4", From 2af929eecc87889ac409355ee88b11400d85eaf0 Mon Sep 17 00:00:00 2001 From: codonist Date: Fri, 31 Jul 2015 22:48:21 -0400 Subject: [PATCH 18/44] fix lost function argument formInvalidMessage --- dist/angular-validator.js | 2 +- dist/angular-validator.min.js | 2 +- src/angular-validator.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/angular-validator.js b/dist/angular-validator.js index d9f47ca..b9f666d 100644 --- a/dist/angular-validator.js +++ b/dist/angular-validator.js @@ -83,7 +83,7 @@ angular.module('angularValidator').directive('angularValidator', ['$injector', // Setup $watch on a single formfield - function setupWatch(elementToWatch) { + function setupWatch(elementToWatch, formInvalidMessage) { // If element is set to validate on blur then update the element on blur if ("validate-on" in elementToWatch.attributes && elementToWatch.attributes["validate-on"].value === "blur") { angular.element(elementToWatch).on('blur', function() { diff --git a/dist/angular-validator.min.js b/dist/angular-validator.min.js index 0167cfa..793bdb9 100644 --- a/dist/angular-validator.min.js +++ b/dist/angular-validator.min.js @@ -1 +1 @@ -angular.module("angularValidator",[]),angular.module("angularValidator").directive("angularValidator",["$injector",function(a){return{restrict:"A",link:function(b,c,d,e){function f(b){return b&&"invalid-message"in b.attributes?a.get(b.attributes["invalid-message"].value):!1}function g(a){for(var b=f(a),c=0;c Required"},e=function(){return" Invalid"};if(a.name in s){var f=s[a.name],g=n(a);g&&g.remove(),(f.$dirty||b[a.form.name]&&b[a.form.name].submitted)&&(f.$error.required?"required-message"in a.attributes?angular.element(a).after(m(a.attributes["required-message"].value)):angular.element(a).after(m(d)):f.$valid||("invalid-message"in a.attributes?angular.element(a).after(m(a.attributes["invalid-message"].value)):c?angular.element(a).after(m(c.message(f,a))):angular.element(a).after(m(e))))}}function m(a){return""}function n(a){for(var b=angular.element(a).parent().children(),c=0;c Required"},e=function(){return" Invalid"};if(a.name in s){var f=s[a.name],g=n(a);g&&g.remove(),(f.$dirty||b[a.form.name]&&b[a.form.name].submitted)&&(f.$error.required?"required-message"in a.attributes?angular.element(a).after(m(a.attributes["required-message"].value)):angular.element(a).after(m(d)):f.$valid||("invalid-message"in a.attributes?angular.element(a).after(m(a.attributes["invalid-message"].value)):c?angular.element(a).after(m(c.message(f,a))):angular.element(a).after(m(e))))}}function m(a){return""}function n(a){for(var b=angular.element(a).parent().children(),c=0;c Date: Wed, 5 Aug 2015 18:44:00 +0200 Subject: [PATCH 19/44] removed Bootstrap and Font Awesome as dependencies in bower.json. also moved angular-mocks to dev-dependencies --- bower.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bower.json b/bower.json index d85bf91..1b33dfb 100644 --- a/bower.json +++ b/bower.json @@ -30,10 +30,10 @@ "karma.conf.js" ], "dependencies": { - "angular": ">=1.3.0", - "bootstrap": ">=3.2.0", - "font-awesome": ">=4.0.3", + "angular": ">=1.3.0" + }, + "devDependencies": { "angular-mocks": "~1.4.1" }, "homepage": "https://github.com/turinggroup/angular-validator" -} \ No newline at end of file +} From 29b6e714ac29706d64ecf238b32a6912d0aa469f Mon Sep 17 00:00:00 2001 From: Christopher Eberz Date: Thu, 20 Aug 2015 12:18:52 -0700 Subject: [PATCH 20/44] Two specs added to demo bug --- test/angular-validator-spec.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/angular-validator-spec.js b/test/angular-validator-spec.js index 312f403..682eb77 100644 --- a/test/angular-validator-spec.js +++ b/test/angular-validator-spec.js @@ -4,6 +4,35 @@ var scope, compile; + describe('angularValidator with dotted name', function () { + var htmlForm, element, linkIt; + + beforeEach(inject(function ($rootScope, $compile) { + scope = $rootScope.$new(); + scope.object = {}; + + htmlForm = angular.element( + '' + + '' + + '' + ); + + linkIt = function () { + element = $compile(htmlForm)(scope); + scope.$digest(); + }; + })); + + it('should not throw an error during linking', function () { + expect(linkIt).not.toThrow(); + }); + + it('should correctly parse the dotted form name, evidenced by what\'s on scope', function () { + linkIt(); + expect(scope.object.form).toBeDefined(); + }); + }); + describe('angularValidator without form invalid message', function () { beforeEach(inject(function($rootScope, $compile) { scope = $rootScope.$new(); From 945fa8599f5a059ef582916985082548d939a9e0 Mon Sep 17 00:00:00 2001 From: Christopher Eberz Date: Thu, 20 Aug 2015 12:21:13 -0700 Subject: [PATCH 21/44] Addressing bug using AngularJS $parse service --- dist/angular-validator.js | 6 +++--- dist/angular-validator.min.js | 2 +- src/angular-validator.js | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dist/angular-validator.js b/dist/angular-validator.js index b9f666d..5838d66 100644 --- a/dist/angular-validator.js +++ b/dist/angular-validator.js @@ -1,7 +1,7 @@ angular.module('angularValidator', []); -angular.module('angularValidator').directive('angularValidator', ['$injector', - function($injector) { +angular.module('angularValidator').directive('angularValidator', ['$injector', '$parse', + function($injector, $parse) { return { restrict: 'A', link: function(scope, element, attrs, fn) { @@ -15,7 +15,7 @@ angular.module('angularValidator').directive('angularValidator', ['$injector', // This is the the scope form model // All validation states are contained here var form_name = DOMForm.attributes['name'].value; - var scopeForm = scope[form_name]; + var scopeForm = $parse(form_name)(scope); // Set the default submitted state to false scopeForm.submitted = false; diff --git a/dist/angular-validator.min.js b/dist/angular-validator.min.js index 793bdb9..60a4dda 100644 --- a/dist/angular-validator.min.js +++ b/dist/angular-validator.min.js @@ -1 +1 @@ -angular.module("angularValidator",[]),angular.module("angularValidator").directive("angularValidator",["$injector",function(a){return{restrict:"A",link:function(b,c,d,e){function f(b){return b&&"invalid-message"in b.attributes?a.get(b.attributes["invalid-message"].value):!1}function g(a){for(var b=f(a),c=0;c Required"},e=function(){return" Invalid"};if(a.name in s){var f=s[a.name],g=n(a);g&&g.remove(),(f.$dirty||b[a.form.name]&&b[a.form.name].submitted)&&(f.$error.required?"required-message"in a.attributes?angular.element(a).after(m(a.attributes["required-message"].value)):angular.element(a).after(m(d)):f.$valid||("invalid-message"in a.attributes?angular.element(a).after(m(a.attributes["invalid-message"].value)):c?angular.element(a).after(m(c.message(f,a))):angular.element(a).after(m(e))))}}function m(a){return""}function n(a){for(var b=angular.element(a).parent().children(),c=0;c Required"},e=function(){return" Invalid"};if(a.name in t){var f=t[a.name],g=o(a);g&&g.remove(),(f.$dirty||c[a.form.name]&&c[a.form.name].submitted)&&(f.$error.required?"required-message"in a.attributes?angular.element(a).after(n(a.attributes["required-message"].value)):angular.element(a).after(n(d)):f.$valid||("invalid-message"in a.attributes?angular.element(a).after(n(a.attributes["invalid-message"].value)):b?angular.element(a).after(n(b.message(f,a))):angular.element(a).after(n(e))))}}function n(a){return""}function o(a){for(var b=angular.element(a).parent().children(),c=0;c Date: Mon, 19 Oct 2015 18:01:23 +0300 Subject: [PATCH 22/44] bumped the version to 1.2.8 --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 1b33dfb..e37e3b0 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "tg-angular-validator", - "version": "1.2.7", + "version": "1.2.8", "authors": [ "Zohar Jackson " ], From bcc38645aa29768bee4891826df627c8518aa9a5 Mon Sep 17 00:00:00 2001 From: Zohar Jackson Date: Wed, 16 Dec 2015 17:59:42 +0200 Subject: [PATCH 23/44] Validator now adds names to forms and form elements --- src/angular-validator.js | 66 ++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/src/angular-validator.js b/src/angular-validator.js index 5838d66..c5a206d 100644 --- a/src/angular-validator.js +++ b/src/angular-validator.js @@ -5,6 +5,37 @@ angular.module('angularValidator').directive('angularValidator', ['$injector', ' return { restrict: 'A', link: function(scope, element, attrs, fn) { + var getRandomInt = function() { + return Math.floor((Math.random() * 100000)); + }; + + // For this directive to work the form needs a name attribute as well as every input element. + // This function will add names where missing + var need_to_recompile = false; + + // Iterate through all the children of the form element and add a `name` attribute to the ones + // that are missing it. + angular.forEach(element.find('input,select,textarea'), function(child_element) { + child_element = $(child_element); + if (!child_element.attr('name')) { + child_element.attr('name', getRandomInt()); + console.log('WARNING! AngularValidator -> One of your form elements(,