+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
index 8f40dac..a723271 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "angular-validation-ghiscoding",
- "version": "1.4.13",
+ "version": "1.4.14",
"author": "Ghislain B.",
"description": "Angular-Validation Directive and Service (ghiscoding)",
"main": "app.js",
diff --git a/protractor/callback_spec.js b/protractor/callback_spec.js
new file mode 100644
index 0000000..bc65537
--- /dev/null
+++ b/protractor/callback_spec.js
@@ -0,0 +1,104 @@
+describe('Angular-Validation Remote Validation Tests:', function () {
+ // global variables
+ var formElementNames = ['firstName1', 'lastName1', 'firstName2', 'lastName2'];
+ var firstNameElements = ['firstName1', 'firstName2'];
+ var lastNameElements = ['lastName1', 'lastName2'];
+ var firstNames = ['John', 'Doe'];
+ var lastNames = ['Jane', 'Smith'];
+ var errorMessages = [
+ 'First name is required',
+ 'Last name is required',
+ 'First name is required',
+ 'Last name is required'
+ ]
+ var formIsValidBindings = ['vmd.formValid1', 'vms.formValid2'];
+ var fullNameBindings = ['vmd.fullName1', 'vms.fullName2'];
+ var errorFromPromise = 'Must be at least 2 characters. Returned error from promise.';
+ var defaultErrorMessage = 'May only contain letters. Must be at least 2 characters. Field is required.';
+ var errorTooShort = [
+ 'Must be at least 2 characters. Alternate error message.',
+ 'Must be at least 2 characters. Returned error from custom function.',
+ 'Must be at least 2 characters. Alternate error message.',
+ 'Must be at least 2 characters. Returned error from custom function.'
+ ];
+ var oneChar = ['a', 'd', 'a', 'd'];
+ var validInputTexts = ['abc', 'def', 'abc', 'def'];
+
+ describe('When choosing `more-examples` Validation Callback', function () {
+ it('Should navigate to home page', function () {
+ browser.get('http://localhost/Github/angular-validation/more-examples/validationCallback/');
+
+ // Find the title element
+ var titleElement = element(by.css('h2'));
+ expect(titleElement.getText()).toEqual('Example of Validation Callback');
+ });
+
+ it('Should check that both submit buttons are disabled', function() {
+ var elmSubmit1 = $('[name=btn_ngDisabled1]');
+ expect(elmSubmit1.isEnabled()).toBe(false);
+
+ var elmSubmit2 = $('[name=btn_ngDisabled2]');
+ expect(elmSubmit2.isEnabled()).toBe(false);
+ });
+
+ it('Should click, blur on each form elements and error message should display on each of them', function () {
+ for (var i = 0, ln = formElementNames.length; i < ln; i++) {
+ var elmInput = $('[name=' + formElementNames[i] + ']');
+ elmInput.click();
+ elmInput.sendKeys(protractor.Key.TAB);
+
+ var elmError = $('.validation-' + formElementNames[i]);
+ expect(elmError.getText()).toEqual(errorMessages[i]);
+ }
+ });
+
+ it('Should enter First Name on both forms and have undefined Last Name', function() {
+ for (var i = 0, ln = firstNames.length; i < ln; i++) {
+ var elmInput = $('[name=' + firstNameElements[i] + ']');
+ elmInput.click();
+ elmInput.sendKeys(firstNames[i]);
+
+ // form should show invalid
+ var formValid = element(by.binding(formIsValidBindings[i]));
+ expect(formValid.getText()).toEqual('Form is valid: false');
+
+ // Full name should show the first name we just entered + undefined as last name
+ var fullName = element(by.binding(fullNameBindings[i]));
+ expect(fullName.getText()).toEqual('Full Name: ' + firstNames[i] + ' undefined');
+ }
+ });
+
+ it('Should check that both submit buttons are disabled', function() {
+ var elmSubmit1 = $('[name=btn_ngDisabled1]');
+ expect(elmSubmit1.isEnabled()).toBe(false);
+
+ var elmSubmit2 = $('[name=btn_ngDisabled2]');
+ expect(elmSubmit2.isEnabled()).toBe(false);
+ });
+
+ it('Should enter Last Name on both forms and have Full Name', function() {
+ for (var i = 0, ln = firstNames.length; i < ln; i++) {
+ var elmInput = $('[name=' + lastNameElements[i] + ']');
+ elmInput.click();
+ elmInput.sendKeys(lastNames[i]);
+
+ // form should show invalid
+ var formValid = element(by.binding(formIsValidBindings[i]));
+ expect(formValid.getText()).toEqual('Form is valid: true');
+
+ // Full name should show the first name we just entered + undefined as last name
+ var fullName = element(by.binding(fullNameBindings[i]));
+ expect(fullName.getText()).toEqual('Full Name: ' + firstNames[i] + ' ' + lastNames[i]);
+ }
+ });
+
+ it('Should check that both submit buttons are now enabled', function() {
+ var elmSubmit1 = $('[name=btn_ngDisabled1]');
+ expect(elmSubmit1.isEnabled()).toBe(true);
+
+ var elmSubmit2 = $('[name=btn_ngDisabled2]');
+ expect(elmSubmit2.isEnabled()).toBe(true);
+ });
+
+ });
+});
\ No newline at end of file
diff --git a/protractor/conf.js b/protractor/conf.js
index 5dba351..2068ede 100644
--- a/protractor/conf.js
+++ b/protractor/conf.js
@@ -22,7 +22,9 @@
// Spec patterns are relative to the current working directory when protractor is called
specs: [
'badInput_spec.js',
+ 'callback_spec.js',
'custom_spec.js',
+ 'remote_spec.js',
'mixed_validation_spec.js',
'angularUI_spec.js',
'dynamic_spec.js',
diff --git a/protractor/custom_spec.js b/protractor/custom_spec.js
index efb3b23..2b3c818 100644
--- a/protractor/custom_spec.js
+++ b/protractor/custom_spec.js
@@ -1,12 +1,7 @@
describe('Angular-Validation Custom Javascript Validation Tests:', function () {
// global variables
var formElementNames = ['input1', 'input2', 'input3', 'input4'];
- var errorMessages = [
- 'May only contain letters. Must be at least 2 characters. Field is required.',
- 'May only contain letters. Must be at least 2 characters. Field is required.',
- 'May only contain letters. Must be at least 2 characters. Field is required.',
- 'May only contain letters. Must be at least 2 characters. Field is required.'
- ];
+ var defaultErrorMessage = 'May only contain letters. Must be at least 2 characters. Field is required.';
var errorTooShort = [
'Must be at least 2 characters. Alternate error message.',
'Must be at least 2 characters. Returned error from custom function.',
@@ -18,7 +13,7 @@
describe('When choosing `more-examples` custom javascript', function () {
it('Should navigate to home page', function () {
- browser.get('http://localhost/Github/angular-validation/more-examples/customJavascript/');
+ browser.get('http://localhost/Github/angular-validation/more-examples/customValidation/');
// Find the title element
var titleElement = element(by.css('h2'));
@@ -30,7 +25,7 @@
var inputName;
for (var i = 0, j = 0, ln = itemRows.length; i < ln; i++) {
- expect(itemRows.get(i).getText()).toEqual(defaultErrorMessages[i]);
+ expect(itemRows.get(i).getText()).toEqual(defaultErrorMessage);
}
});
@@ -49,7 +44,7 @@
elmInput.sendKeys(protractor.Key.TAB);
var elmError = $('.validation-' + formElementNames[i]);
- expect(elmError.getText()).toEqual(errorMessages[i]);
+ expect(elmError.getText()).toEqual(defaultErrorMessage);
}
});
@@ -91,7 +86,7 @@
});
it('Should navigate to home page', function () {
- browser.get('http://localhost/Github/angular-validation/more-examples/customJavascript/');
+ browser.get('http://localhost/Github/angular-validation/more-examples/customValidation/');
// Find the title element
var titleElement = element(by.css('h2'));
@@ -113,7 +108,7 @@
elmInput.sendKeys(protractor.Key.TAB);
var elmError = $('.validation-' + formElementNames[i]);
- expect(elmError.getText()).toEqual(errorMessages[i]);
+ expect(elmError.getText()).toEqual(defaultErrorMessage);
}
});
diff --git a/protractor/mixed_validation_spec.js b/protractor/mixed_validation_spec.js
index 3537517..b96a0a7 100644
--- a/protractor/mixed_validation_spec.js
+++ b/protractor/mixed_validation_spec.js
@@ -145,7 +145,7 @@
elmInput.sendKeys('ab');
elmInput.sendKeys(protractor.Key.TAB);
//$('[for=input1]').click();
- browser.sleep(1500); // sleep because of our data sample having a delay of 1sec internally, we use 1.5sec on this side to be sure
+ browser.sleep(1100); // sleep because of our data sample having a delay of 1sec internally, we use 1.5sec on this side to be sure
var elmError = $('.validation-input1');
expect(elmError.getText()).toEqual('Returned error from promise.');
@@ -157,7 +157,7 @@
elmInput.sendKeys('abc');
elmInput.sendKeys(protractor.Key.TAB);
//$('[for=input1]').click();
- browser.sleep(1500); // sleep because of our data sample having a delay of 1sec internally, we use 1.5sec on this side to be sure
+ browser.sleep(1100); // sleep because of our data sample having a delay of 1sec internally, we use 1.5sec on this side to be sure
var elmError = $('.validation-input1');
expect(elmError.getText()).toEqual('');
diff --git a/protractor/remote_spec.js b/protractor/remote_spec.js
new file mode 100644
index 0000000..a23e3ba
--- /dev/null
+++ b/protractor/remote_spec.js
@@ -0,0 +1,130 @@
+describe('Angular-Validation Remote Validation Tests:', function () {
+ // global variables
+ var formElementNames = ['input1', 'input2', 'input3', 'input4'];
+ var errorFromPromise = 'Must be at least 2 characters. Returned error from promise.';
+ var defaultErrorMessage = 'May only contain letters. Must be at least 2 characters. Field is required.';
+ var errorTooShort = [
+ 'Must be at least 2 characters. Alternate error message.',
+ 'Must be at least 2 characters. Returned error from custom function.',
+ 'Must be at least 2 characters. Alternate error message.',
+ 'Must be at least 2 characters. Returned error from custom function.'
+ ];
+ var oneChar = ['a', 'd', 'a', 'd'];
+ var validInputTexts = ['abc', 'def', 'abc', 'def'];
+
+ describe('When choosing `more-examples` custom remote', function () {
+ it('Should navigate to home page', function () {
+ browser.get('http://localhost/Github/angular-validation/more-examples/customRemote/');
+
+ // Find the title element
+ var titleElement = element(by.css('h2'));
+ expect(titleElement.getText()).toEqual('Example of Angular-Validation with Remote Validation');
+ });
+
+ it('Should have multiple errors in Directive & Service validation summary', function () {
+ var itemRows = element.all(by.binding('message'));
+ var inputName;
+
+ for (var i = 0, j = 0, ln = itemRows.length; i < ln; i++) {
+ expect(itemRows.get(i).getText()).toEqual(defaultErrorMessage);
+ }
+ });
+
+ it('Should check that both submit buttons are disabled', function() {
+ var elmSubmit1 = $('[name=btn_ngDisabled1]');
+ expect(elmSubmit1.isEnabled()).toBe(false);
+
+ var elmSubmit2 = $('[name=btn_ngDisabled2]');
+ expect(elmSubmit2.isEnabled()).toBe(false);
+ });
+
+ it('Should click, blur on each form elements and error message should display on each of them', function () {
+ for (var i = 0, ln = formElementNames.length; i < ln; i++) {
+ var elmInput = $('[name=' + formElementNames[i] + ']');
+ elmInput.click();
+ elmInput.sendKeys(protractor.Key.TAB);
+
+ var elmError = $('.validation-' + formElementNames[i]);
+ expect(elmError.getText()).toEqual(defaultErrorMessage);
+ }
+ });
+
+ it('Should enter 1 character in all inputs and display minChar error message', function() {
+ for (var i = 0, ln = formElementNames.length; i < ln; i++) {
+ var elmInput = $('[name=' + formElementNames[i] + ']');
+ elmInput.click();
+ elmInput.sendKeys('a');
+ browser.sleep(1000);
+
+ var elmError = $('.validation-' + formElementNames[i]);
+ expect(elmError.getText()).toEqual(errorFromPromise);
+ }
+ });
+
+ it('Should enter valid text and make error go away', function () {
+ for (var i = 0, ln = formElementNames.length; i < ln; i++) {
+ var elmInput = $('[name=' + formElementNames[i] + ']');
+ elmInput.click();
+ clearInput(elmInput);
+ elmInput.sendKeys(validInputTexts[i]);
+ elmInput.sendKeys(protractor.Key.TAB);
+ browser.sleep(1000);
+
+ var elmError = $('.validation-' + formElementNames[i]);
+ expect(elmError.getText()).toEqual('');
+ }
+ });
+
+ it('Should have both validation summary empty', function() {
+ var itemRows = element.all(by.binding('message'));
+ expect(itemRows.count()).toBe(0);
+ });
+
+ it('Should check that both submit buttons are now enabled', function() {
+ var elmSubmit1 = $('[name=btn_ngDisabled1]');
+ expect(elmSubmit1.isEnabled()).toBe(true);
+
+ var elmSubmit2 = $('[name=btn_ngDisabled2]');
+ expect(elmSubmit2.isEnabled()).toBe(true);
+ });
+
+ it('Should navigate to home page', function () {
+ browser.get('http://localhost/Github/angular-validation/more-examples/customRemote/');
+
+ // Find the title element
+ var titleElement = element(by.css('h2'));
+ expect(titleElement.getText()).toEqual('Example of Angular-Validation with Remote Validation');
+ });
+
+ it('Should click on both ngSubmit buttons', function() {
+ var btnNgSubmit1 = $('[name=btn_ngSubmit1]');
+ btnNgSubmit1.click();
+
+ var btnNgSubmit2 = $('[name=btn_ngSubmit2]');
+ btnNgSubmit2.click();
+ });
+
+ it('Should show error message on each inputs', function () {
+ for (var i = 0, ln = formElementNames.length; i < ln; i++) {
+ var elmInput = $('[name=' + formElementNames[i] + ']');
+ elmInput.click();
+ elmInput.sendKeys(protractor.Key.TAB);
+
+ var elmError = $('.validation-' + formElementNames[i]);
+ expect(elmError.getText()).toEqual(defaultErrorMessage);
+ }
+ });
+
+ });
+});
+
+/** From a given input name, clear the input
+ * @param string input name
+ */
+function clearInput(elem) {
+ elem.getAttribute('value').then(function (text) {
+ var len = text.length
+ var backspaceSeries = Array(len+1).join(protractor.Key.BACK_SPACE);
+ elem.sendKeys(backspaceSeries);
+ })
+}
\ No newline at end of file
diff --git a/readme.md b/readme.md
index 6dda544..575af8e 100644
--- a/readme.md
+++ b/readme.md
@@ -1,6 +1,6 @@
#Angular Validation (Directive / Service)
-`Version: 1.4.13`
-### Form validation after user inactivity of default 1sec. (customizable timeout)
+`Version: 1.4.14`
+### Form validation after user stop typing (default 1sec).
Forms Validation with Angular made easy! Angular-Validation is an angular directive/service with locales (languages) with a very simple approach of defining your `validation=""` directly within your element to validate (input, textarea, etc) and...that's it!!! The directive/service will take care of the rest!
@@ -76,18 +76,21 @@ All the documentation has been moved to the Wiki section, see the [github wiki](
* [3rd Party Addon Validation](https://github.com/ghiscoding/angular-validation/wiki/3rd-Party-Addons)
* [Directive Examples](https://github.com/ghiscoding/angular-validation/wiki/Working-Directive-Examples)
* [Service Examples](https://github.com/ghiscoding/angular-validation/wiki/Working-Service-Examples)
+* Functionalities
* [Alternate Text on Validators](https://github.com/ghiscoding/angular-validation/wiki/Alternate-Text-on-Validators)
* [DisplayErrorTo](https://github.com/ghiscoding/angular-validation/wiki/Bootstrap-Input-Groups-Wrapping)
* [Isolated Scope](https://github.com/ghiscoding/angular-validation/wiki/Isolated-Scope)
- * [PreValidate Form (on page load)](https://github.com/ghiscoding/angular-validation/wiki/PreValidate-Form-(on-page-load))
- * [Custom Validation function](https://github.com/ghiscoding/angular-validation/wiki/Custom-Validation-functions)
- * [Remote Validation (AJAX)](https://github.com/ghiscoding/angular-validation/wiki/Remote-Validation-(AJAX))
- * [Remove a Validator](https://github.com/ghiscoding/angular-validation/wiki/Remove-Validator-from-Element)
+ * [PreValidate Form (on load)](https://github.com/ghiscoding/angular-validation/wiki/PreValidate-Form-(on-page-load))
* [Reset Form](https://github.com/ghiscoding/angular-validation/wiki/Reset-Form)
* [Submit and Validation](https://github.com/ghiscoding/angular-validation/wiki/Form-Submit-and-Validation)
+ * [Validation Callback](https://github.com/ghiscoding/angular-validation/wiki/Validation-Callback)
+ * [Validator Remove](https://github.com/ghiscoding/angular-validation/wiki/Remove-Validator-from-Element)
* [Validation Summary](https://github.com/ghiscoding/angular-validation/wiki/Validation-Summary)
+* Custom Validations
+ * [Custom Validation (JS)](https://github.com/ghiscoding/angular-validation/wiki/Custom-Validation-functions)
+ * [Remote Validation (AJAX)](https://github.com/ghiscoding/angular-validation/wiki/Remote-Validation-(AJAX))
* Properties & Options
- * [Inputs (all local options)](https://github.com/ghiscoding/angular-validation/wiki/Inputs-(local-options))
+ * [Attributes (all options)](https://github.com/ghiscoding/angular-validation/wiki/Inputs-(local-options))
* [Global Options](https://github.com/ghiscoding/angular-validation/wiki/Global-Options)
* [ControllerAs Syntax](https://github.com/ghiscoding/angular-validation/wiki/ControllerAs-Syntax)
* Validators
diff --git a/src/validation-common.js b/src/validation-common.js
index 3528ee6..7a23080 100644
--- a/src/validation-common.js
+++ b/src/validation-common.js
@@ -24,6 +24,7 @@ angular
if (_globalOptions.resetGlobalOptionsOnRouteChange) {
_globalOptions = {
displayOnlyLastErrorMsg: false, // reset the option of displaying only the last error message
+ hideErrorUnderInputs: false, // reset the option of hiding error under element
preValidateFormElements: false, // reset the option of pre-validate all form elements, false by default
isolatedScope: null, // reset used scope on route change
scope: null, // reset used scope on route change
@@ -79,6 +80,7 @@ angular
validationCommon.prototype.mergeObjects = mergeObjects; // merge 2 javascript objects, Overwrites obj1's values with obj2's (basically Object2 as higher priority over Object1)
validationCommon.prototype.removeFromValidationSummary = removeFromValidationSummary; // remove an element from the $validationSummary
validationCommon.prototype.removeFromFormElementObjectList = removeFromFormElementObjectList; // remove named items from formElements list
+ validationCommon.prototype.runValidationCallbackOnPromise = runValidationCallbackOnPromise; // run a validation callback method when the promise resolve
validationCommon.prototype.setDisplayOnlyLastErrorMsg = setDisplayOnlyLastErrorMsg; // setter on the behaviour of displaying only the last error message
validationCommon.prototype.setGlobalOptions = setGlobalOptions; // set global options used by all validators (usually called by the validationService)
validationCommon.prototype.updateErrorMsg = updateErrorMsg; // update on screen an error message below current form element
@@ -229,7 +231,7 @@ angular
var validations = rules.split('|');
if (validations) {
- self.bFieldRequired = (rules.indexOf("required") >= 0) ? true : false;
+ self.bFieldRequired = (rules.indexOf("required") >= 0);
// loop through all validators of the element
for (var i = 0, ln = validations.length; i < ln; i++) {
@@ -237,7 +239,7 @@ angular
var params = validations[i].split(':');
// check if user provided an alternate text to his validator (validator:alt=Alternate Text)
- var hasAltText = validations[i].indexOf("alt=") >= 0 ? true : false;
+ var hasAltText = validations[i].indexOf("alt=") >= 0;
self.validators[i] = validationRules.getElementValidators({
altText: hasAltText === true ? (params.length === 2 ? params[1] : params[2]) : '',
@@ -369,6 +371,42 @@ angular
return _validationSummary;
}
+ /** Evaluate a function name passed as string and run it from the scope.
+ * The function name could be passed with/without brackets "()", in any case we will run the function
+ * @param object self object
+ * @param string function passed as a string
+ * @param mixed result
+ */
+ function runEvalScopeFunction(self, fnString) {
+ var result;
+
+ // Find if our function has the brackets "()"
+ // if yes then run it through $eval else find it in the scope and then run it
+ if(/\({1}.*\){1}/gi.test(fnString)) {
+ result = self.scope.$eval(fnString);
+ }else {
+ var fct = objectFindById(self.scope, fnString, '.');
+ if(typeof fct === "function") {
+ result = fct();
+ }
+ }
+ return result;
+ }
+
+ /** Run a validation callback function once the promise return
+ * @param object validation promise
+ * @param string callback function name (could be with/without the brackets () )
+ */
+ function runValidationCallbackOnPromise(promise, callbackFct) {
+ var self = this;
+
+ if(typeof promise.then === "function") {
+ promise.then(function() {
+ runEvalScopeFunction(self, callbackFct);
+ });
+ }
+ }
+
/** Setter on the behaviour of displaying only the last error message of each element.
* By default this is false, so the behavior is to display all error messages of each element.
* @param boolean value
@@ -441,7 +479,7 @@ angular
var isSubmitted = (!!attrs && attrs.isSubmitted) ? attrs.isSubmitted : false;
// invalid & isDirty, display the error message... if not exist then create it, else udpate the text
- if (!!attrs && !attrs.isValid && (isSubmitted || self.ctrl.$dirty || self.ctrl.$touched)) {
+ if (!_globalOptions.hideErrorUnderInputs && !!attrs && !attrs.isValid && (isSubmitted || self.ctrl.$dirty || self.ctrl.$touched)) {
(errorElm.length > 0) ? errorElm.html(errorMsg) : elm.after('' + errorMsg + '');
} else {
errorElm.html(''); // element is pristine or no validation applied, error message has to be blank
@@ -703,22 +741,6 @@ angular
return -1;
}
- /** Explode a '.' dot notation string to an object
- * @param string str
- * @parem object
- * @return object
- */
- function explodedDotNotationStringToObject(str, obj) {
- var split = str.split('.');
-
- for (var k = 0, kln = split.length; k < kln; k++) {
- if(!!obj[split[k]]) {
- obj = obj[split[k]];
- }
- }
- return obj;
- }
-
/** Get the element's parent Angular form (if found)
* @param string: element input name
* @param object self
@@ -740,11 +762,11 @@ angular
for (var i = 0; i < forms.length; i++) {
var form = forms[i].form;
- var formName = form.getAttribute("name");
+ var formName = (!!form) ? form.getAttribute("name") : null;
if (!!form && !!formName) {
parentForm = (!!_globalOptions && !!_globalOptions.controllerAs && formName.indexOf('.') >= 0)
- ? explodedDotNotationStringToObject(formName, self.scope)
+ ? objectFindById(self.scope, formName, '.')
: self.scope[formName];
if(!!parentForm) {
@@ -759,7 +781,7 @@ angular
// falling here with a form name but without a form object found in the scope is often due to isolate scope
// we can hack it and define our own form inside this isolate scope, in that way we can still use something like: isolateScope.form1.$validationSummary
if (!!form) {
- var formName = form.getAttribute("name");
+ var formName = (!!form) ? form.getAttribute("name") : null;
if(!!formName) {
var obj = { $name: formName, specialNote: 'Created by Angular-Validation for Isolated Scope usage' };
@@ -781,6 +803,23 @@ angular
return !isNaN(parseFloat(n)) && isFinite(n);
}
+ /** Find a property inside an object.
+ * If a delimiter is passed as argument, we will split the search ID before searching
+ * @param object: source object
+ * @param string: searchId
+ * @return mixed: property found
+ */
+ function objectFindById(sourceObject, searchId, delimiter) {
+ var split = (!!delimiter) ? searchId.split(delimiter) : searchId;
+
+ for (var k = 0, kln = split.length; k < kln; k++) {
+ if(!!sourceObject[split[k]]) {
+ sourceObject = sourceObject[split[k]];
+ }
+ }
+ return sourceObject;
+ }
+
/** Parse a date from a String and return it as a Date Object to be valid for all browsers following ECMA Specs
* Date type ISO (default), US, UK, Europe, etc... Other format could be added in the switch case
* @param String dateStr: date String
@@ -890,16 +929,16 @@ angular
var result = false;
switch (condition) {
- case '<': result = (value1 < value2) ? true : false; break;
- case '<=': result = (value1 <= value2) ? true : false; break;
- case '>': result = (value1 > value2) ? true : false; break;
- case '>=': result = (value1 >= value2) ? true : false; break;
+ case '<': result = (value1 < value2); break;
+ case '<=': result = (value1 <= value2); break;
+ case '>': result = (value1 > value2); break;
+ case '>=': result = (value1 >= value2); break;
case '!=':
- case '<>': result = (value1 != value2) ? true : false; break;
- case '!==': result = (value1 !== value2) ? true : false; break;
+ case '<>': result = (value1 != value2); break;
+ case '!==': result = (value1 !== value2); break;
case '=':
- case '==': result = (value1 == value2) ? true : false; break;
- case '===': result = (value1 === value2) ? true : false; break;
+ case '==': result = (value1 == value2); break;
+ case '===': result = (value1 === value2); break;
default: result = false; break;
}
return result;
@@ -970,7 +1009,7 @@ angular
var timestampParam1 = parseDate(validator.params[1], dateType).getTime();
var isValid1 = testCondition(validator.condition[0], timestampValue, timestampParam0);
var isValid2 = testCondition(validator.condition[1], timestampValue, timestampParam1);
- isValid = (isValid1 && isValid2) ? true : false;
+ isValid = (isValid1 && isValid2);
} else {
// else, 1 param is a simple conditional date check
var timestampParam = parseDate(validator.params[0], dateType).getTime();
@@ -994,7 +1033,7 @@ angular
// this is typically a "between" condition, a range of number >= and <=
var isValid1 = testCondition(validator.condition[0], parseFloat(strValue), parseFloat(validator.params[0]));
var isValid2 = testCondition(validator.condition[1], parseFloat(strValue), parseFloat(validator.params[1]));
- isValid = (isValid1 && isValid2) ? true : false;
+ isValid = (isValid1 && isValid2);
} else {
// else, 1 param is a simple conditional number check
isValid = testCondition(validator.condition, parseFloat(strValue), parseFloat(validator.params[0]));
@@ -1022,25 +1061,14 @@ angular
if (!!strValue) {
var fct = null;
var fname = validator.params[0];
- if (fname.indexOf(".") === -1) {
- fct = self.scope[fname];
- } else {
- // function name might also be declared with the Controller As alias, for example: vm.customJavascript()
- // split the name and flatten it so that we can find it inside the scope
- var split = fname.split('.');
- fct = self.scope;
- for (var k = 0, kln = split.length; k < kln; k++) {
- fct = fct[split[k]];
- }
- }
- var result = (typeof fct === "function") ? fct() : null;
+ var result = runEvalScopeFunction(self, fname);
// analyze the result, could be a boolean or object type, anything else will throw an error
if (typeof result === "boolean") {
- isValid = (!!result) ? true : false;
+ isValid = (!!result);
}
else if (typeof result === "object") {
- isValid = (!!result.isValid) ? true : false;
+ isValid = (!!result.isValid);
}
else {
throw invalidResultErrorMsg;
@@ -1161,18 +1189,7 @@ angular
var fct = null;
var fname = validator.params[0];
- if (fname.indexOf(".") === -1) {
- fct = self.scope[fname];
- } else {
- // function name might also be declared with the Controller As alias, for example: vm.customRemote()
- // split the name and flatten it so that we can find it inside the scope
- var split = fname.split('.');
- fct = self.scope;
- for (var k = 0, kln = split.length; k < kln; k++) {
- fct = fct[split[k]];
- }
- }
- var promise = (typeof fct === "function") ? fct() : null;
+ var promise = runEvalScopeFunction(self, fname);
// if we already have previous promises running, we might want to abort them (if user specified an abort function)
if (_remotePromises.length > 1) {
@@ -1199,10 +1216,10 @@ angular
// analyze the result, could be a boolean or object type, anything else will throw an error
if (typeof result === "boolean") {
- isValid = (!!result) ? true : false;
+ isValid = (!!result);
}
else if (typeof result === "object") {
- isValid = (!!result.isValid) ? true : false;
+ isValid = (!!result.isValid);
}
else {
throw invalidResultErrorMsg;
diff --git a/src/validation-directive.js b/src/validation-directive.js
index fcbe3ca..8d4f719 100644
--- a/src/validation-directive.js
+++ b/src/validation-directive.js
@@ -24,8 +24,9 @@
var _timer;
var _watchers = [];
- // Possible element attributesW
+ // Possible element attributes
var _elmName = attrs.name;
+ var _validationCallback = (attrs.hasOwnProperty('validationCallback')) ? attrs.validationCallback : null;
//-- Possible validation-array attributes
// on a validation array, how many does it require to be valid?
@@ -50,7 +51,11 @@
unbindBlurHandler();
return invalidateBadInputField();
}
- attemptToValidate(newValue);
+ // attempt to validate & run validation callback if user requested it
+ var validationPromise = attemptToValidate(newValue);
+ if(!!_validationCallback) {
+ commonObj.runValidationCallbackOnPromise(validationPromise, _validationCallback);
+ }
}, true);
// save the watcher inside an array in case we want to deregister it when removing a validator
@@ -101,6 +106,7 @@
* and is also customizable through the (typing-limit) for which inactivity this.timer will trigger validation.
* @param string value: value of the input field
* @param int typingLimit: when user stop typing, in how much time it will start validating
+ * @return object validation promise
*/
function attemptToValidate(value, typingLimit) {
var deferred = $q.defer();
@@ -193,7 +199,7 @@
return deferred.promise;
} // attemptToValidate()
- /** Attempt to validate an input value that was previously exploded fromn the input array
+ /** Attempt to validate an input value that was previously exploded from the input array
* Each attempt will return a promise but only after reaching the last index, will we analyze the final validation.
* @param string: input value
* @param int: position index
@@ -251,7 +257,11 @@
var value = (typeof ctrl.$modelValue !== "undefined") ? ctrl.$modelValue : event.target.value;
if (!formElmObj.isValidationCancelled) {
- attemptToValidate(value, 10);
+ // attempt to validate & run validation callback if user requested it
+ var validationPromise = attemptToValidate(value, 10);
+ if(!!_validationCallback) {
+ commonObj.runValidationCallbackOnPromise(validationPromise, _validationCallback);
+ }
}else {
ctrl.$setValidity('validation', true);
}
diff --git a/src/validation-service.js b/src/validation-service.js
index 72e22e8..deafa25 100644
--- a/src/validation-service.js
+++ b/src/validation-service.js
@@ -9,10 +9,11 @@
*/
angular
.module('ghiscoding.validation')
- .service('validationService', ['$interpolate', '$timeout', 'validationCommon', function ($interpolate, $timeout, validationCommon) {
+ .service('validationService', ['$interpolate', '$q', '$timeout', 'validationCommon', function ($interpolate, $q, $timeout, validationCommon) {
// global variables of our object (start with _var)
var _blurHandler;
var _watchers = [];
+ var _validationCallback;
// service constructor
var validationService = function (globalOptions) {
@@ -91,6 +92,8 @@ angular
}
}
+ _validationCallback = (self.validationAttrs.hasOwnProperty('validationCallback')) ? self.validationAttrs.validationCallback : null;
+
// onBlur make validation without waiting
attrs.elm.bind('blur', _blurHandler = function(event) {
// get the form element custom object and use it after
@@ -99,7 +102,12 @@ angular
if (!!formElmObj && !formElmObj.isValidationCancelled) {
// re-initialize to use current element & validate without delay
self.commonObj.initialize(scope, attrs.elm, attrs, attrs.ctrl);
- attemptToValidate(self, event.target.value, 10);
+
+ // attempt to validate & run validation callback if user requested it
+ var validationPromise = attemptToValidate(self, event.target.value, 10);
+ if(!!_validationCallback) {
+ self.commonObj.runValidationCallbackOnPromise(validationPromise, _validationCallback);
+ }
}
});
@@ -151,7 +159,11 @@ angular
self.commonObj.initialize(scope, attrs.elm, attrs, attrs.ctrl);
var waitingTimer = (typeof newValue === "undefined" || (typeof newValue === "number" && isNaN(newValue))) ? 0 : undefined;
- attemptToValidate(self, newValue, waitingTimer);
+ // attempt to validate & run validation callback if user requested it
+ var validationPromise = attemptToValidate(self, newValue, waitingTimer);
+ if(!!_validationCallback) {
+ self.commonObj.runValidationCallbackOnPromise(validationPromise, _validationCallback);
+ }
}, true); // $watch()
// save the watcher inside an array in case we want to deregister it when removing a validator
@@ -329,8 +341,12 @@ angular
* @param object self
* @param string value: value of the input field
* @param int typingLimit: when user stop typing, in how much time it will start validating
+ * @return object validation promise
*/
function attemptToValidate(self, value, typingLimit) {
+ var deferred = $q.defer();
+ var isValid = false;
+
// get the waiting delay time if passed as argument or get it from common Object
var waitingLimit = (typeof typingLimit !== "undefined") ? typingLimit : self.commonObj.typingLimit;
@@ -350,7 +366,8 @@ angular
// if field is not required and his value is empty, cancel validation and exit out
if(!self.commonObj.isFieldRequired() && (value === "" || value === null || typeof value === "undefined")) {
cancelValidation(self, formElmObj);
- return value;
+ deferred.resolve({ isFieldValid: true, formElmObj: formElmObj, value: value });
+ return deferred.promise;
}else {
formElmObj.isValidationCancelled = false;
}
@@ -364,14 +381,17 @@ angular
// we will still call the `.validate()` function so that it shows also the possible other error messages
if((value === "" || typeof value === "undefined") && self.commonObj.elm.prop('type').toUpperCase() === "NUMBER") {
$timeout.cancel(self.timer);
- self.commonObj.ctrl.$setValidity('validation', self.commonObj.validate(value, true));
- return value;
+ isValid = self.commonObj.validate(value, true);
+ deferred.resolve({ isFieldValid: isValid, formElmObj: formElmObj, value: value });
+ return deferred.promise;
}
// select(options) will be validated on the spot
if(self.commonObj.elm.prop('tagName').toUpperCase() === "SELECT") {
- self.commonObj.ctrl.$setValidity('validation', self.commonObj.validate(value, true));
- return value;
+ isValid = self.commonObj.validate(value, true);
+ self.commonObj.ctrl.$setValidity('validation', isValid);
+ deferred.resolve({ isFieldValid: isValid, formElmObj: formElmObj, value: value });
+ return deferred.promise;
}
// onKeyDown event is the default of Angular, no need to even bind it, it will fall under here anyway
@@ -379,19 +399,23 @@ angular
if(typeof value !== "undefined") {
// when no timer, validate right away without a $timeout. This seems quite important on the array input value check
if(typingLimit === 0) {
- self.commonObj.scope.$evalAsync(self.commonObj.ctrl.$setValidity('validation', self.commonObj.validate(value, true) ));
+ isValid = self.commonObj.validate(value, true);
+ self.commonObj.scope.$evalAsync(self.commonObj.ctrl.$setValidity('validation', isValid ));
+ deferred.resolve({ isFieldValid: isValid, formElmObj: formElmObj, value: value });
}else {
// Make the validation only after the user has stopped activity on a field
// everytime a new character is typed, it will cancel/restart the timer & we'll erase any error mmsg
self.commonObj.updateErrorMsg('');
$timeout.cancel(self.timer);
self.timer = $timeout(function() {
- self.commonObj.scope.$evalAsync(self.commonObj.ctrl.$setValidity('validation', self.commonObj.validate(value, true) ));
+ isValid = self.commonObj.validate(value, true);
+ self.commonObj.scope.$evalAsync(self.commonObj.ctrl.$setValidity('validation', isValid ));
+ deferred.resolve({ isFieldValid: isValid, formElmObj: formElmObj, value: value });
}, waitingLimit);
}
}
- return value;
+ return deferred.promise;
} // attemptToValidate()
/** Cancel current validation test and blank any leftover error message
@@ -531,7 +555,11 @@ angular
// re-attach the onBlur handler
attrs.elm.bind('blur', _blurHandler = function(event) {
if (!!formElmObj && !formElmObj.isValidationCancelled) {
- attemptToValidate(self, event.target.value, 10);
+ // attempt to validate & run validation callback if user requested it
+ var validationPromise = attemptToValidate(self, event.target.value, 10);
+ if(!!_validationCallback) {
+ self.commonObj.runValidationCallbackOnPromise(validationPromise, _validationCallback);
+ }
}
});
}
diff --git a/templates/testingFormDirective.html b/templates/testingFormDirective.html
index 442ce0a..342484d 100644
--- a/templates/testingFormDirective.html
+++ b/templates/testingFormDirective.html
@@ -38,7 +38,7 @@