Skip to content

Validator async #88

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Angular-Validator
This is just a fork of https://github.com/turinggroup/angular-validator with Asynchronous Validator, please see demo for example.

# Angular-Validator
[![Build Status](https://travis-ci.org/turinggroup/angular-validator.png)](https://travis-ci.org/turinggroup/angular-validator)

Angular-Validator is an easy to use, powerful and lightweight AngularJS validation directive.
Expand All @@ -11,7 +13,7 @@ Angular-Validator is an easy to use, powerful and lightweight AngularJS validati
* Works seamlessly with all native AngularJS validation directives and native HTML5 validation attributes.
* Supports custom validation message templates and placement using Angular's native `ngMessages` directive.
* Choose when to validate elements, on per-element basis. Choose between on form `submission`, `blur` or `dirty`(change).
* All validation states and validation messages are accessible through `$scope.yourFormName.elementName`.
* All validation states and validation messages are accessible through `$scope.yourFormName.elementName`.
* Prevents submission if the form is invalid
* Built in `reset()` form method
* Supports multi-field dependent validation (one field depends on another such as password matching)
Expand All @@ -20,7 +22,7 @@ Angular-Validator is an easy to use, powerful and lightweight AngularJS validati
* Supports form invalid message service where manage invalid messages in one place and save code in HTML

## Why?
Despite Angular's awesomeness, validation in Angular is still annoying. 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.
Despite Angular's awesomeness, validation in Angular is still annoying. 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.

## Installation
1. Using bower: `bower install tg-angular-validator`.
Expand Down Expand Up @@ -80,6 +82,20 @@ Despite Angular's awesomeness, validation in Angular is still annoying. Surprisi
required>
```

**Usage with asynchronous validator**

```
<input type = "email"
name = "email"
ng-model = "person.email"
validator-async = "checkUserEmail(person.email)"
invalid-message = "'Email already existed'"
required-message = "'Email is required'"
required>
```

* validator-async requires the callback function to return promise to make it work. see [angular's ngModel.$asyncValidators](https://docs.angularjs.org/api/ng/type/ngModel.NgModelController) for how to use it.

**Usage with custom error message text**
```
<input type = "text"
Expand Down
12 changes: 10 additions & 2 deletions demo/app.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
angular.module('angular-validator-demo', ['angularValidator']);


angular.module('angular-validator-demo').controller('DemoCtrl', function($scope) {
angular.module('angular-validator-demo').controller('DemoCtrl', function($scope, $http) {

$scope.submitMyForm = function() {
alert("Form submitted");
Expand All @@ -20,6 +20,14 @@ angular.module('angular-validator-demo').controller('DemoCtrl', function($scope)
};


$scope.userAsyncValidator = function(modelValue, viewValue) {
var endpoint = 'service/' + encodeURIComponent(modelValue) + '.json';

// assuming all 200 response is valid, and 404 is invalid
return $http.get(endpoint);
}


$scope.passwordValidator = function(password) {

if (!password) {
Expand Down Expand Up @@ -52,4 +60,4 @@ angular.module('angular-validator-demo').controller('DemoCtrl', function($scope)
}
}
};
});
});
15 changes: 13 additions & 2 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ <h4>Different types of validation:</h4>
validate-on="dirty"
required></div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Custom validation Function (async)</label>
<div class="col-sm-10">
<input type = "text"
name = "username"
class = "form-control"
ng-model = "form.username"
validator-async = "userAsyncValidator"
validate-on="dirty"
required></div>
</div>
<div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">Email</label>
<div class="col-sm-10">
Expand Down Expand Up @@ -188,8 +199,8 @@ <h4> <u>Scope Sneak Peak</u>
<p>Form submitted: {{myForm.submitted}}</p>

</div>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular.min.js"></script> <script src="app.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script> <script src="app.js"></script>
<script src="app.js"></script>
<script src="../dist/angular-validator.js"></script>
</body>
</html>
</html>
5 changes: 5 additions & 0 deletions demo/service/john.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"username": "john",
"first_name": "John",
"last_name": "Doe"
}
116 changes: 75 additions & 41 deletions dist/angular-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,64 @@ 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(<input>, <textarea>, <select>) is missing a name. We got your back and added a name, but if you want a pretty one you should add it yourself.');
need_to_recompile = true;
}
});

// Uses a ransom to prevent duplicate form names.
if (!attrs.name) {
element.attr('name', 'TGAV_FORM_' + getRandomInt());
console.log('WARNING! AngularValidator -> Your form element(<form>) is missing a name. We got your back and added a name, but if you want a pretty one you should add it yourself.');
need_to_recompile = true;
}

// We need to recompile so that the passed scope is updated with the new form names.
if (need_to_recompile) {
$compile(element)(scope);
return;
}

// 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
// This is the the scope form model, it is created automatically by angular
// All validation states are contained here
var form_name = DOMForm.attributes['name'].value;
var scopeForm = $parse(form_name)(scope);
// See: https://docs.angularjs.org/api/ng/directive/form
var scopeForm = $parse(attrs.name)(scope);

// Set the default submitted state to false
scopeForm.submitted = false;

// Watch form length to add watches for new form elements
scope.$watch(function(){return Object.keys(scopeForm).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();});
angular.forEach(watches, function(watch) {
watch();
});
setupWatches(DOMForm);
});


// Intercept and handle submit events of the form
element.on('submit', function(event) {
Expand All @@ -39,29 +74,29 @@ angular.module('angularValidator').directive('angularValidator', ['$injector', '
// 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);
scope.$eval(attrs.angularValidatorSubmit);
});
}
});


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();
// Clear all the form values. Set everything to pristine.
scopeForm.reset = function() {
angular.forEach(DOMForm, function(formElement) {
if (formElement.name) {
scopeForm[formElement.name].$setViewValue("");
scopeForm[formElement.name].$render();
}
}
});
scopeForm.submitted = false;
scopeForm.$setPristine();
};


// Setup watches on all form fields
// 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;
// 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);
Expand All @@ -82,7 +117,7 @@ angular.module('angularValidator').directive('angularValidator', ['$injector', '
}


// Setup $watch on a single formfield
// Setup $watch on a single form element
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") {
Expand All @@ -92,25 +127,28 @@ angular.module('angularValidator').directive('angularValidator', ['$injector', '
});
}

// setup async validator using angular's $asyncValidators (need angular v1.3+)
if ('validator-async' in elementToWatch.attributes && typeof scopeForm[elementToWatch.name].$asyncValidators.callback !== 'function') {
scopeForm[elementToWatch.name].$asyncValidators.callback = scope.$eval(elementToWatch.attributes['validator-async'].value)
}

var watch = scope.$watch(function() {
return elementToWatch.value + elementToWatch.required + scopeForm.submitted + checkElementValidity(elementToWatch) + getDirtyValue(scopeForm[elementToWatch.name]) + getValidValue(scopeForm[elementToWatch.name]);
},
function() {

if (scopeForm.submitted){
if (scopeForm.submitted) {
updateValidationMessage(elementToWatch, formInvalidMessage);
updateValidationClass(elementToWatch);
}
else {
} else {
// Determine if the element in question is to be updated on blur
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);
}
Expand All @@ -124,19 +162,15 @@ angular.module('angularValidator').directive('angularValidator', ['$injector', '

// Returns the $dirty value of the element if it exists
function getDirtyValue(element) {
if (element) {
if ("$dirty" in element) {
return element.$dirty;
}
if (element && "$dirty" in element) {
return element.$dirty;
}
}


// Returns the $valid value of the element if it exists
function getValidValue(element) {
if (element) {
if ("$valid" in element) {
return element.$valid;
}
if (element && "$valid" in element) {
return element.$valid;
}
}

Expand Down Expand Up @@ -165,14 +199,14 @@ angular.module('angularValidator').directive('angularValidator', ['$injector', '
};

// Make sure the element is a form field and not a button for example
// Only form elements should have names.
// Only form elements should have names.
if (!(element.name in scopeForm)) {
return;
}

var scopeElementModel = scopeForm[element.name];

// Remove all validation messages
// Remove all validation messages
var validationMessageElement = isValidationMessagePresent(element);
if (validationMessageElement) {
validationMessageElement.remove();
Expand Down Expand Up @@ -230,7 +264,7 @@ angular.module('angularValidator').directive('angularValidator', ['$injector', '
// 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.
// Only form fields should have names.
if (!(element.name in scopeForm)) {
return;
}
Expand All @@ -243,8 +277,8 @@ angular.module('angularValidator').directive('angularValidator', ['$injector', '


// 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) {
if (formField.$dirty || (scopeForm.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
Expand All @@ -256,5 +290,5 @@ angular.module('angularValidator').directive('angularValidator', ['$injector', '

}
};
}]
);
}
]);
Loading