Skip to content

Commit 1379c19

Browse files
committed
Major change - OnTimer Validation added
- Now support on timer validation and to support that behaviour Event support was removed since it is now unecessary. - Added more Regex validation (time, IPV4, IPV6) - Bumped up to Version 1.1
1 parent bd0c016 commit 1379c19

File tree

8 files changed

+160
-116
lines changed

8 files changed

+160
-116
lines changed

angular-validation.js

Lines changed: 85 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
11
/**
2-
* angular-validation - v1.0 - 2014-02-07
2+
* angular-validation - v1.1 - 2014-05-02
33
* https://github.com/ghiscoding/angular-validation
44
* @author: Ghislain B.
55
*
66
* @desc: If a field becomes invalid, the text inside the error <span> or <div> will show up because the error string gets filled
77
* Though when the field becomes valid then the error message becomes an empty string,
88
* it will be transparent to the user even though the <span> still exist but becomes invisible since the text is empty.
9+
*
10+
* Version 1.1: only start validating after user inactivity,
11+
* it could also be passed as an argument for more customization of the inactivity timeout (typing-limit)
912
*/
1013
angular.module('ghiscoding.validation', ['pascalprecht.translate'])
11-
.directive('validation', function($translate) {
14+
.directive('validation', function($translate, $timeout) {
1215
return {
1316
require: "ngModel",
1417
link: function(scope, elm, attrs, ctrl) {
15-
// default validation event that triggers the validation error to be displayed
16-
var DEFAULT_EVENT = "keyup"; // keyup, blur, ...
18+
// constant of default typing limit in ms
19+
var TYPING_LIMIT = 1000;
1720

18-
// get the validation attribute
21+
var timer;
22+
var typingLimit = (attrs.hasOwnProperty('typingLimit')) ? parseInt(attrs.typingLimit, 10) : TYPING_LIMIT;
23+
24+
// get the validation attributes which are the list of validators
1925
var validationAttr = attrs.validation;
2026

2127
// define the variables we'll use
@@ -113,7 +119,7 @@
113119
condition: [">=","<="],
114120
message: "INVALID_BETWEEN_NUM",
115121
params: [range[0], range[1]],
116-
type: "condition"
122+
type: "condition_num"
117123
};
118124
break;
119125
case "creditCard" :
@@ -217,6 +223,27 @@
217223
type: "regex"
218224
};
219225
break;
226+
case "ipv4" :
227+
validators[i] = {
228+
pattern: "^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$",
229+
message: "INVALID_IPV4",
230+
type: "regex"
231+
};
232+
break;
233+
case "ipv6" :
234+
validators[i] = {
235+
pattern: "^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$",
236+
message: "INVALID_IPV6",
237+
type: "regex"
238+
};
239+
break;
240+
case "ipv6_hex" :
241+
validators[i] = {
242+
pattern: "^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$",
243+
message: "INVALID_IPV6_HEX",
244+
type: "regex"
245+
};
246+
break;
220247
case "maxLen" :
221248
case "max_len" :
222249
validators[i] = {
@@ -231,7 +258,7 @@
231258
condition: "<=",
232259
message: "INVALID_MAX_NUM",
233260
params: [params[1]],
234-
type: "condition"
261+
type: "condition_num"
235262
};
236263
break;
237264
case "minLen" :
@@ -248,7 +275,7 @@
248275
condition: ">=",
249276
message: "INVALID_MIN_NUM",
250277
params: [params[1]],
251-
type: "condition"
278+
type: "condition_num"
252279
};
253280
break;
254281
case "numeric" :
@@ -289,9 +316,17 @@
289316
type: "regex"
290317
};
291318
break;
292-
}
293-
}
294-
}
319+
case "time" :
320+
validators[i] = {
321+
pattern: "^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$",
322+
message: "INVALID_TIME",
323+
type: "regex"
324+
};
325+
break;
326+
327+
} // end of switch()
328+
} // end of for()
329+
} // end of if()
295330

296331
/** Validate function, from the input value it will go through all validators (separated by pipe)
297332
* that were passed to the input element and will validate it. If field is invalid it will update
@@ -306,10 +341,10 @@
306341

307342
// loop through all validations (could be multiple)
308343
for(var j = 0, jln = validators.length; j < jln; j++) {
309-
if(validators[j].type === "condition") {
344+
if(validators[j].type === "condition_num") {
310345
// a condition type
311346
if(validators[j].params.length == 2) {
312-
// typically a between condition, a range of number >= and <=
347+
// typically a "between" condition, a range of number >= and <=
313348
var isValid1 = testCondition(validators[j].condition[0], parseFloat(strValue), parseFloat(validators[j].params[0]));
314349
var isValid2 = testCondition(validators[j].condition[1], parseFloat(strValue), parseFloat(validators[j].params[1]));
315350
isValid = (isValid1 && isValid2) ? true : false;
@@ -331,13 +366,11 @@
331366
message = message.replace((':param'), validators[j].params[k]);
332367
}
333368
}
334-
335369
} // end !isValid
336-
} // end for loop
370+
} // end for() loop
337371

338372
// -- Error Display --//
339-
updateErrorMsg(isFieldValid, message);
340-
373+
updateErrorMsg(message, isFieldValid);
341374

342375
return isFieldValid;
343376
}
@@ -375,44 +408,40 @@
375408
* @param bool isFieldValid: is the field valid?
376409
* @param string message: error message to display
377410
*/
378-
var updateErrorMsg = function(isFieldValid, message) {
379-
var errorElm = (typeof attrs.validationErrorTo !== "undefined")
411+
var updateErrorMsg = function(message, isFieldValid) {
412+
var hasValidation = (typeof isFieldValid === "undefined") ? false : true;
413+
var errorElm = (attrs.hasOwnProperty('validationErrorTo'))
380414
? angular.element(document.querySelector('#'+attrs.validationErrorTo))
381415
: elm.next();
382416

383417
// Re-Render Error display element inside the <span> or <div>
384418
if(typeof errorElm !== "undefined") {
385-
if(!isFieldValid && ctrl.$dirty) {
419+
if(hasValidation && !isFieldValid && ctrl.$dirty) {
386420
// Not valid & dirty, display the message
387421
errorElm.text(message);
388422
}else {
389-
// element is prestine, error message has to be blank
423+
// element is prestine or there's no validation applied, error message has to be blank
390424
errorElm.text("");
391425
}
392426
}
393427
}
394428

395429
/** Validator function to attach to the element, this will get call whenever the input field is updated
396-
* and is also customizable through the (validation-event) which can be (onblur).
397-
* If no event is specified, it will validate (onkeyup) as a default action.
430+
* and is also customizable through the (typing-limit) for which inactivity timer will trigger validation.
398431
* @param string value: value of the input field
399432
*/
400433
var validator = function(value) {
401434
// if field is not required and his value is empty
402-
// then no need to validate & return it valid
435+
// then no need to validate & return it has valid
436+
// make sure to unbind any events else it will try to continue validating
403437
if(!isFieldRequired && (value === "" || typeof value === "undefined")) {
404-
var isFieldValid = true;
405-
ctrl.$setValidity('validation', isFieldValid);
406-
updateErrorMsg(isFieldValid, "");
407-
elm.unbind('keyup').unbind('keydown');
438+
$timeout.cancel(timer);
439+
updateErrorMsg("");
440+
ctrl.$setValidity('validation', true);
441+
elm.unbind('keyup').unbind('keydown').unbind('blur');
408442
return value;
409443
}
410444

411-
// analyze which event we'll use, if nothing was defined then use default
412-
// also remove prefix substring of 'on' since we don't need it on the 'on' method
413-
var evnt = (typeof attrs.validationEvent === "undefined") ? DEFAULT_EVENT : attrs.validationEvent;
414-
evnt = evnt.replace('on', ''); // remove possible 'on' prefix
415-
416445
// get some properties of the inspected element
417446
var elmTagName = elm.prop('tagName').toUpperCase();
418447
var elmType = elm.prop('type').toUpperCase();
@@ -437,41 +466,33 @@
437466
});
438467
}
439468

440-
// Also make sure that if user has a select dropdown
441-
// then we'll validate it has if it was a onBlur event
442-
// since onKeyUp would fail has there would never be any keyup
443-
if(elmTagName === "SELECT") {
444-
if(isFieldRequired && (value === "" || typeof value === "undefined")) {
445-
// if select option is null or empty string we already know it's invalid
446-
// but we'll still run validation() to display proper error message
447-
ctrl.$setValidity('validation', validate(value));
448-
elm.unbind('blur');
449-
return value;
450-
}
451-
// else we'll make sure we use an onBlur event to check validation
452-
evnt = "blur";
453-
}
454-
455-
// invalidate field before doing validation
469+
// invalidate field before doing any validation
456470
ctrl.$setValidity('validation', false);
457471

472+
// onBlur make validation without waiting
473+
elm.bind('blur', function() {
474+
// make the regular validation of the field value
475+
var isValid = validate(value);
476+
scope.$apply(ctrl.$setValidity('validation', isValid));
477+
return value;
478+
});
479+
480+
// onKeyDown event is the default of Angular, no need to even bind it, it will fall under here anyway
458481
// in case the field is already pre-filled
459482
// we need to validate it without looking at the event binding
460483
if(value !== "" && typeof value !== "undefined") {
461-
var isValid = validate(value);
462-
ctrl.$setValidity('validation', isValid);
484+
// Make the validation only after the user has stopped activity on a field
485+
// everytime a new character is typed, it will cancel/restart the timer & we<ll erase any error mmsg
486+
updateErrorMsg("");
487+
$timeout.cancel(timer);
488+
timer = $timeout(function() {
489+
var isValid = validate(value);
490+
ctrl.$setValidity('validation', isValid);
491+
}, typingLimit);
463492
}
464493

465-
// run the validate method on the event
466-
// update the validation on both the field & form element
467-
elm.unbind('keyup').unbind(evnt).bind(evnt, function() {
468-
// make the regular validation of the field value
469-
var isValid = validate(value);
470-
scope.$apply(ctrl.$setValidity('validation', isValid));
471-
});
472-
473494
return value;
474-
};
495+
}; // end of validator()
475496

476497
// attach the Validator object to the element
477498
ctrl.$parsers.unshift(validator);
@@ -488,6 +509,7 @@
488509
ctrl.$setValidity('validation', validate(ctrl.$viewValue));
489510
}
490511
});
491-
}
492-
};
493-
});
512+
513+
} // end of link: function()
514+
}; // end of return;
515+
}); // end of directive
File renamed without changes.
File renamed without changes.

index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="utf-8">
55
<title>Angular-Validation</title>
66
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.1.0/css/bootstrap.min.css">
7-
<link rel="stylesheet" href="style.css">
7+
<link rel="stylesheet" href="example-style.css">
88
</head>
99

1010
<body ng-controller="Ctrl">
@@ -34,7 +34,7 @@ <h3 class="text-info">{{'CHANGE_LANGUAGE' | translate}}</h3>
3434
<script type="text/javascript" src="angular-validation.js"></script>
3535

3636
<!-- my application -->
37-
<script type="text/javascript" src="app.js"></script>
37+
<script type="text/javascript" src="example-app.js"></script>
3838
</div>
3939
</body>
4040
</html>

locales/validation/en.json

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,21 @@
99
"INVALID_BETWEEN_NUM": "Needs to be a numeric value, between :param and :param. ",
1010
"INVALID_BOOLEAN": "May only contain a true or false value. ",
1111
"INVALID_CREDIT_CARD": "Must be a valid credit card number. ",
12-
"INVALID_DATE_EURO_LONG": "Must be a valid date following the format of: dd-mm-yyyy OR dd/mm/yyyy. ",
13-
"INVALID_DATE_EURO_SHORT": "Must be a valid date following the format of: dd-mm-yy OR dd/mm/yy. ",
14-
"INVALID_DATE_ISO": "Must be a valid date following the format of: yyyy-mm-dd. ",
15-
"INVALID_DATE_US_LONG": "Must be a valid date following the format of: mm/dd/yyyy OR mm-dd-yyyy. ",
16-
"INVALID_DATE_US_SHORT": "Must be a valid date following the format of: mm/dd/yy OR mm-dd-yy. ",
12+
"INVALID_DATE_EURO_LONG": "Must be a valid date format (dd-mm-yyyy) OR (dd/mm/yyyy). ",
13+
"INVALID_DATE_EURO_SHORT": "Must be a valid date format (dd-mm-yy) OR (dd/mm/yy). ",
14+
"INVALID_DATE_ISO": "Must be a valid date format (yyyy-mm-dd). ",
15+
"INVALID_DATE_US_LONG": "Must be a valid date format (mm/dd/yyyy) OR (mm-dd-yyyy). ",
16+
"INVALID_DATE_US_SHORT": "Must be a valid date format (mm/dd/yy) OR (mm-dd-yy). ",
1717
"INVALID_EMAIL": "Must be a valid email address. ",
1818
"INVALID_EXACT_LEN": "Must have a length of exactly :param characters. ",
1919
"INVALID_FLOAT": "May only contain a float value (integer excluded). ",
2020
"INVALID_FLOAT_SIGNED": "May only contain a positive or negative float value (integer excluded). ",
2121
"INVALID_IBAN": "Must a valid IBAN. ",
2222
"INVALID_INTEGER": "Must be a positive integer. ",
2323
"INVALID_INTEGER_SIGNED": "Must be a positive or negative integer. ",
24+
"INVALID_IPV4": "Must be a valid IP (IPV4). ",
25+
"INVALID_IPV6": "Must be a valid IP (IPV6). ",
26+
"INVALID_IPV6_HEX": "Must be a valid IP (IPV6 Hex). ",
2427
"INVALID_MAX_CHAR": "May not be greater than :param characters. ",
2528
"INVALID_MAX_NUM": "Needs to be a numeric value, equal to, or lower than :param. ",
2629
"INVALID_MIN_CHAR": "Must be at least :param characters. ",
@@ -30,21 +33,24 @@
3033
"INVALID_PATTERN": "Must be following this format: :param. ",
3134
"INVALID_REQUIRED": "Field is required. ",
3235
"INVALID_URL": "Must be a valid URL. ",
36+
"INVALID_TIME": "Must be a valid time format (hh:mm) OR (hh:mm:ss). ",
3337

3438

35-
"INPUT1": "Alphanumeric + Exactly(3) + Required -- EVENT (onblur)",
39+
"AREA1": "TextArea: Alphanumeric + Minimum(15) + Required",
40+
"INPUT1": "Alphanumeric + Exactly(3) + Required -- typing-limit(5sec)",
3641
"INPUT2": "Float (integer excluded) -- input type=\"number\" -- BLOCK non-numeric characters ",
3742
"INPUT3": "Floating number range (integer excluded) -- between_num:x,y OR min_num:x|max_num:y ",
3843
"INPUT4": "Multiple Validations + Custom Regex of Date Code (YYWW)",
3944
"INPUT5": "Email",
4045
"INPUT6": "URL",
41-
"INPUT7": "Credit Card",
42-
"INPUT8": "Between(2,6) Characters",
43-
"INPUT9": "Date ISO (yyyy-mm-dd)",
44-
"INPUT10": "Date US (long)",
45-
"INPUT11": "AlphaNum + Minimum(2) | Maximum(10) characters -- NOT Required",
46-
"INPUT12": "AlphaDashSpaces + Required + Minimum(5) Characters -- MUST USE: validation-error-to=\" \"",
47-
"INPUT13": "Alphanumeric + Required -- NG-DISABLED",
46+
"INPUT7": "IP (IPV4)",
47+
"INPUT8": "Credit Card",
48+
"INPUT9": "Between(2,6) Characters",
49+
"INPUT10": "Date ISO (yyyy-mm-dd)",
50+
"INPUT11": "Date US (long)",
51+
"INPUT12": "Time (hh:mm OR hh:mm:ss) -- NOT Required",
52+
"INPUT13": "AlphaDashSpaces + Required + Minimum(5) Characters -- MUST USE: validation-error-to=\" \"",
53+
"INPUT14": "Alphanumeric + Required -- NG-DISABLED",
4854
"CHANGE_LANGUAGE": "Change language.",
4955
"SAVE": "Save",
5056
"SELECT1": "Required (select) -- NoEVENT default(keyup) -- Directive will validate has EVENT (blur)",

0 commit comments

Comments
 (0)